summary refs log tree commit diff
diff options
context:
space:
mode:
authorAndrew Morgan <andrew@amorgan.xyz>2020-02-24 17:15:16 +0000
committerAndrew Morgan <andrew@amorgan.xyz>2020-02-24 17:15:16 +0000
commit978f263e7c5d1eb440efaf07abc5009408ade25d (patch)
tree87593d9987e171ebe2f2b8966e12da35c0ec7465
parentConfig templating (#5900) (diff)
parentUse the v2 lookup API for 3PID invites (#5897) (diff)
downloadsynapse-978f263e7c5d1eb440efaf07abc5009408ade25d.tar.xz
Use the v2 lookup API for 3PID invites (#5897)
-rw-r--r--changelog.d/5897.feature1
-rw-r--r--synapse/events/spamcheck.py1
-rw-r--r--synapse/handlers/identity.py182
-rw-r--r--synapse/handlers/room_member.py250
-rw-r--r--synapse/rest/client/v2_alpha/account.py6
-rw-r--r--synapse/util/hash.py33
-rw-r--r--tests/rest/client/test_identity.py18
-rw-r--r--tests/rest/client/test_room_access_rules.py6
-rw-r--r--tests/rest/client/v2_alpha/test_register.py1
-rw-r--r--tests/rulecheck/test_domainrulecheck.py5
10 files changed, 320 insertions, 183 deletions
diff --git a/changelog.d/5897.feature b/changelog.d/5897.feature
new file mode 100644

index 0000000000..7b10774c96 --- /dev/null +++ b/changelog.d/5897.feature
@@ -0,0 +1 @@ +Switch to the v2 lookup API for 3PID invites. \ No newline at end of file diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py
index f0de4d961f..c194ba53f4 100644 --- a/synapse/events/spamcheck.py +++ b/synapse/events/spamcheck.py
@@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - class SpamChecker(object): def __init__(self, hs): self.spam_checker = None diff --git a/synapse/handlers/identity.py b/synapse/handlers/identity.py
index 339e0dd04d..4cea75723b 100644 --- a/synapse/handlers/identity.py +++ b/synapse/handlers/identity.py
@@ -20,18 +20,13 @@ import logging from canonicaljson import json -from signedjson.key import decode_verify_key_bytes -from signedjson.sign import verify_signed_json -from unpaddedbase64 import decode_base64 from twisted.internet import defer from synapse.api.errors import ( - AuthError, CodeMessageException, Codes, HttpResponseException, - ProxiedRequestError, SynapseError, ) @@ -48,26 +43,9 @@ class IdentityHandler(BaseHandler): self.federation_http_client = hs.get_http_client() self.trusted_id_servers = set(hs.config.trusted_third_party_id_servers) - self.trust_any_id_server_just_for_testing_do_not_use = ( - hs.config.use_insecure_ssl_client_just_for_testing_do_not_use - ) self.rewrite_identity_server_urls = hs.config.rewrite_identity_server_urls self._enable_lookup = hs.config.enable_3pid_lookup - def _should_trust_id_server(self, id_server): - if id_server not in self.trusted_id_servers: - if self.trust_any_id_server_just_for_testing_do_not_use: - logger.warn( - "Trusting untrustworthy ID server %r even though it isn't" - " in the trusted id list for testing because" - " 'use_insecure_ssl_client_just_for_testing_do_not_use'" - " is set in the config", - id_server, - ) - else: - return False - return True - @defer.inlineCallbacks def threepid_from_creds(self, creds): if "id_server" in creds: @@ -84,16 +62,17 @@ class IdentityHandler(BaseHandler): else: raise SynapseError(400, "No client_secret in creds") - if not self._should_trust_id_server(id_server): + if not should_trust_id_server(self.hs, id_server): logger.warn( "%s is not a trusted ID server: rejecting 3pid " + "credentials", id_server, ) return None + # if we have a rewrite rule set for the identity server, # apply it now. - if id_server in self.rewrite_identity_server_urls: - id_server = self.rewrite_identity_server_urls[id_server] + id_server = self.rewrite_identity_server_urls.get(id_server, id_server) + try: data = yield self.http_client.get_json( "https://%s%s" @@ -130,10 +109,7 @@ class IdentityHandler(BaseHandler): # if we have a rewrite rule set for the identity server, # apply it now, but only for sending the request (not # storing in the database). - if id_server in self.rewrite_identity_server_urls: - id_server_host = self.rewrite_identity_server_urls[id_server] - else: - id_server_host = id_server + id_server_host = self.rewrite_identity_server_urls.get(id_server, id_server) try: data = yield self.http_client.post_json_get_json( @@ -205,7 +181,6 @@ class IdentityHandler(BaseHandler): Deferred[bool]: True on success, otherwise False if the identity server doesn't support unbinding """ - url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,) content = { "mxid": mxid, "threepid": {"medium": threepid["medium"], "address": threepid["address"]}, @@ -228,8 +203,7 @@ class IdentityHandler(BaseHandler): # # Note that destination_is has to be the real id_server, not # the server we connect to. - if id_server in self.rewrite_identity_server_urls: - id_server = self.rewrite_identity_server_urls[id_server] + id_server = self.rewrite_identity_server_urls.get(id_server, id_server) url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,) @@ -258,7 +232,7 @@ class IdentityHandler(BaseHandler): def requestEmailToken( self, id_server, email, client_secret, send_attempt, next_link=None ): - if not self._should_trust_id_server(id_server): + if not should_trust_id_server(self.hs, id_server): raise SynapseError( 400, "Untrusted ID server '%s'" % id_server, Codes.SERVER_NOT_TRUSTED ) @@ -269,10 +243,8 @@ class IdentityHandler(BaseHandler): "send_attempt": send_attempt, } - # if we have a rewrite rule set for the identity server, - # apply it now. - if id_server in self.rewrite_identity_server_urls: - id_server = self.rewrite_identity_server_urls[id_server] + # Rewrite id_server URL if necessary + id_server = self.rewrite_identity_server_urls.get(id_server, id_server) if next_link: params.update({"next_link": next_link}) @@ -292,11 +264,14 @@ class IdentityHandler(BaseHandler): def requestMsisdnToken( self, id_server, country, phone_number, client_secret, send_attempt, **kwargs ): - if not self._should_trust_id_server(id_server): + if not should_trust_id_server(self.hs, id_server): raise SynapseError( 400, "Untrusted ID server '%s'" % id_server, Codes.SERVER_NOT_TRUSTED ) + # Rewrite id_server URL if necessary + id_server = self.rewrite_identity_server_urls.get(id_server, id_server) + params = { "country": country, "phone_number": phone_number, @@ -319,119 +294,30 @@ class IdentityHandler(BaseHandler): logger.info("Proxied requestToken failed: %r", e) raise e.to_synapse_error() - @defer.inlineCallbacks - def lookup_3pid(self, id_server, medium, address): - """Looks up a 3pid in the passed identity server. - - Args: - id_server (str): The server name (including port, if required) - of the identity server to use. - medium (str): The type of the third party identifier (e.g. "email"). - address (str): The third party identifier (e.g. "foo@example.com"). - - Returns: - Deferred[dict]: The result of the lookup. See - https://matrix.org/docs/spec/identity_service/r0.1.0.html#association-lookup - for details - """ - if not self._should_trust_id_server(id_server): - raise SynapseError( - 400, "Untrusted ID server '%s'" % id_server, Codes.SERVER_NOT_TRUSTED - ) - - if not self._enable_lookup: - raise AuthError( - 403, "Looking up third-party identifiers is denied from this server" - ) - - target = self.rewrite_identity_server_urls.get(id_server, id_server) - - try: - data = yield self.http_client.get_json( - "https://%s/_matrix/identity/api/v1/lookup" % (target,), - {"medium": medium, "address": address}, - ) - if "mxid" in data: - if "signatures" not in data: - raise AuthError(401, "No signatures on 3pid binding") - yield self._verify_any_signature(data, id_server) - - except HttpResponseException as e: - logger.info("Proxied lookup failed: %r", e) - raise e.to_synapse_error() - except IOError as e: - logger.info("Failed to contact %r: %s", id_server, e) - raise ProxiedRequestError(503, "Failed to contact identity server") - - defer.returnValue(data) - - @defer.inlineCallbacks - def bulk_lookup_3pid(self, id_server, threepids): - """Looks up given 3pids in the passed identity server. - - Args: - id_server (str): The server name (including port, if required) - of the identity server to use. - threepids ([[str, str]]): The third party identifiers to lookup, as - a list of 2-string sized lists ([medium, address]). - - Returns: - Deferred[dict]: The result of the lookup. See - https://matrix.org/docs/spec/identity_service/r0.1.0.html#association-lookup - for details - """ - if not self._should_trust_id_server(id_server): - raise SynapseError( - 400, "Untrusted ID server '%s'" % id_server, Codes.SERVER_NOT_TRUSTED - ) - - if not self._enable_lookup: - raise AuthError( - 403, "Looking up third-party identifiers is denied from this server" - ) - - target = self.rewrite_identity_server_urls.get(id_server, id_server) - - try: - data = yield self.http_client.post_json_get_json( - "https://%s/_matrix/identity/api/v1/bulk_lookup" % (target,), - {"threepids": threepids}, +def should_trust_id_server(hs, id_server): + if id_server not in hs.config.trusted_third_party_id_servers: + if hs.trust_any_id_server_just_for_testing_do_not_use: + logger.warn( + "Trusting untrustworthy ID server %r even though it isn't" + " in the trusted id list for testing because" + " 'use_insecure_ssl_client_just_for_testing_do_not_use'" + " is set in the config", + id_server, ) + else: + return False + return True - except HttpResponseException as e: - logger.info("Proxied lookup failed: %r", e) - raise e.to_synapse_error() - except IOError as e: - logger.info("Failed to contact %r: %s", id_server, e) - raise ProxiedRequestError(503, "Failed to contact identity server") - - defer.returnValue(data) - - @defer.inlineCallbacks - def _verify_any_signature(self, data, server_hostname): - if server_hostname not in data["signatures"]: - raise AuthError(401, "No signature from server %s" % (server_hostname,)) - for key_name, signature in data["signatures"][server_hostname].items(): - target = self.rewrite_identity_server_urls.get( - server_hostname, server_hostname - ) +class LookupAlgorithm: + """ + Supported hashing algorithms when performing a 3PID lookup. - key_data = yield self.http_client.get_json( - "https://%s/_matrix/identity/api/v1/pubkey/%s" % (target, key_name) - ) - if "public_key" not in key_data: - raise AuthError( - 401, "No public key named %s from %s" % (key_name, server_hostname) - ) - verify_signed_json( - data, - server_hostname, - decode_verify_key_bytes( - key_name, decode_base64(key_data["public_key"]) - ), - ) - return + SHA256 - Hashing an (address, medium, pepper) combo with sha256, then url-safe base64 + encoding + NONE - Not performing any hashing. Simply sending an (address, medium) combo in plaintext + """ - raise AuthError(401, "No signature from server %s" % (server_hostname,)) + SHA256 = "sha256" + NONE = "none" diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py
index 3c00151042..398c377216 100644 --- a/synapse/handlers/room_member.py +++ b/synapse/handlers/room_member.py
@@ -20,11 +20,14 @@ import logging from six.moves import http_client +from signedjson.key import decode_verify_key_bytes +from signedjson.sign import verify_signed_json +from unpaddedbase64 import decode_base64 + from twisted.internet import defer from synapse import types from synapse.api.constants import EventTypes, Membership -from synapse.api.ratelimiting import Ratelimiter from synapse.api.errors import ( AuthError, Codes, @@ -32,9 +35,13 @@ from synapse.api.errors import ( HttpResponseException, SynapseError, ) +from synapse.handlers.identity import LookupAlgorithm, should_trust_id_server from synapse.types import RoomID, UserID from synapse.util.async_helpers import Linearizer from synapse.util.distributor import user_joined_room, user_left_room +from synapse.util.hash import sha256_and_url_safe_base64 + +from ._base import BaseHandler logger = logging.getLogger(__name__) @@ -78,7 +85,11 @@ class RoomMemberHandler(object): self.rewrite_identity_server_urls = self.config.rewrite_identity_server_urls self._enable_lookup = hs.config.enable_3pid_lookup self.allow_per_room_profiles = self.config.allow_per_room_profiles - self.ratelimiter = Ratelimiter() + + # This is only used to get at ratelimit function, and + # maybe_kick_guest_users. It's fine there are multiple of these as + # it doesn't store state. + self.base_handler = BaseHandler(hs) @abc.abstractmethod def _remote_join(self, requester, remote_room_hosts, room_id, user, content): @@ -572,7 +583,7 @@ class RoomMemberHandler(object): event (SynapseEvent): The membership event. context: The context of the event. is_guest (bool): Whether the sender is a guest. - room_hosts ([str]): Homeservers which are likely to already be in + remote_room_hosts (list[str]|None): Homeservers which are likely to already be in the room, and could be danced with in order to join this homeserver for the first time. ratelimit (bool): Whether to rate limit this request. @@ -683,7 +694,7 @@ class RoomMemberHandler(object): servers.remove(room_alias.domain) servers.insert(0, room_alias.domain) - return (RoomID.from_string(room_id), servers) + return RoomID.from_string(room_id), servers @defer.inlineCallbacks def _get_inviter(self, user_id, room_id): @@ -714,7 +725,7 @@ class RoomMemberHandler(object): # We need to rate limit *before* we send out any 3PID invites, so we # can't just rely on the standard ratelimiting of events. - self.ratelimiter.ratelimit( + self.base_handler.ratelimiter.ratelimit( requester.user.to_string(), time_now_s=self.hs.clock.time(), rate_hz=self.hs.config.rc_third_party_invite.per_second, @@ -742,7 +753,7 @@ class RoomMemberHandler(object): Codes.FORBIDDEN, ) - invitee = yield self._lookup_3pid(id_server, medium, address) + invitee = yield self.lookup_3pid(id_server, medium, address) is_published = yield self.store.is_room_published(room_id) @@ -766,23 +777,60 @@ class RoomMemberHandler(object): requester, id_server, medium, address, room_id, inviter, txn_id=txn_id ) - def _get_id_server_target(self, id_server): - """Looks up an id_server's actual http endpoint + @defer.inlineCallbacks + def lookup_3pid(self, id_server, medium, address): + """Looks up a 3pid in the passed identity server. Args: - id_server (str): the server name to lookup. + id_server (str): The server name (including port, if required) + of the identity server to use. + medium (str): The type of the third party identifier (e.g. "email"). + address (str): The third party identifier (e.g. "foo@example.com"). Returns: - the http endpoint to connect to. + str: the matrix ID of the 3pid, or None if it is not recognized. """ - if id_server in self.rewrite_identity_server_urls: - return self.rewrite_identity_server_urls[id_server] + if not should_trust_id_server(self.hs, id_server): + raise SynapseError( + 400, "Untrusted ID server '%s'" % id_server, + Codes.SERVER_NOT_TRUSTED + ) + + if not self._enable_lookup: + raise SynapseError( + 403, "Looking up third-party identifiers is denied from this server" + ) - return id_server + # Rewrite id_server URL if necessary + id_server = self.rewrite_identity_server_urls.get(id_server, id_server) + + # Check what hashing details are supported by this identity server + use_v1 = False + hash_details = None + try: + hash_details = yield self.simple_http_client.get_json( + "%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server) + ) + except (HttpResponseException, ValueError) as e: + # Catch HttpResponseExcept for a non-200 response code + # Catch ValueError for non-JSON response body + + # Check if this identity server does not know about v2 lookups + if e.code == 404: + # This is an old identity server that does not yet support v2 lookups + use_v1 = True + else: + logger.warn("Error when looking up hashing details: %s" % (e,)) + return None + + if use_v1: + return (yield self._lookup_3pid_v1(id_server, medium, address)) + + return (yield self._lookup_3pid_v2(id_server, medium, address, hash_details)) @defer.inlineCallbacks - def _lookup_3pid(self, id_server, medium, address): - """Looks up a 3pid in the passed identity server. + def _lookup_3pid_v1(self, id_server, medium, address): + """Looks up a 3pid in the passed identity server using v1 lookup. Args: id_server (str): The server name (including port, if required) @@ -794,12 +842,164 @@ class RoomMemberHandler(object): str: the matrix ID of the 3pid, or None if it is not recognized. """ try: - data = yield self.identity_handler.lookup_3pid(id_server, medium, address) - return data.get("mxid") + data = yield self.simple_http_client.get_json( + "%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, id_server), + {"medium": medium, "address": address}, + ) + + if "mxid" in data: + if "signatures" not in data: + raise AuthError(401, "No signatures on 3pid binding") + yield self._verify_any_signature(data, id_server) + return data["mxid"] + except ProxiedRequestError as e: logger.warn("Error from identity server lookup: %s" % (e,)) + + except IOError as e: + logger.warn("Error from identity server lookup: %s" % (e,)) + + return None + + @defer.inlineCallbacks + def _lookup_3pid_v2(self, id_server, medium, address, hash_details): + """Looks up a 3pid in the passed identity server using v2 lookup. + + Args: + id_server (str): The server name (including port, if required) + of the identity server to use. + medium (str): The type of the third party identifier (e.g. "email"). + address (str): The third party identifier (e.g. "foo@example.com"). + hash_details (dict[str, str|list]): A dictionary containing hashing information + provided by an identity server. + + Returns: + Deferred[str|None]: the matrix ID of the 3pid, or None if it is not recognised. + """ + # Extract information from hash_details + supported_lookup_algorithms = hash_details["algorithms"] + lookup_pepper = hash_details["lookup_pepper"] + + # Check if any of the supported lookup algorithms are present + if LookupAlgorithm.SHA256 in supported_lookup_algorithms: + # Perform a hashed lookup + lookup_algorithm = LookupAlgorithm.SHA256 + + # Hash address, medium and the pepper with sha256 + to_hash = "%s %s %s" % (address, medium, lookup_pepper) + lookup_value = sha256_and_url_safe_base64(to_hash) + + elif LookupAlgorithm.NONE in supported_lookup_algorithms: + # Perform a non-hashed lookup + lookup_algorithm = LookupAlgorithm.NONE + + # Combine together plaintext address and medium + lookup_value = "%s %s" % (address, medium) + + else: + logger.warn( + "None of the provided lookup algorithms of %s%s are supported: %s", + id_server_scheme, + id_server, + hash_details["algorithms"], + ) + raise SynapseError( + 400, + "Provided identity server does not support any v2 lookup " + "algorithms that this homeserver supports.", + ) + + try: + lookup_results = yield self.simple_http_client.post_json_get_json( + "%s%s/_matrix/identity/v2/lookup" % (id_server_scheme, id_server), + { + "addresses": [lookup_value], + "algorithm": lookup_algorithm, + "pepper": lookup_pepper, + }, + ) + except (HttpResponseException, ValueError) as e: + # Catch HttpResponseExcept for a non-200 response code + # Catch ValueError for non-JSON response body + logger.warn("Error when performing a 3pid lookup: %s" % (e,)) + return None + + # Check for a mapping from what we looked up to an MXID + if "mappings" not in lookup_results or not isinstance( + lookup_results["mappings"], dict + ): + logger.debug("No results from 3pid lookup") return None + # Return the MXID if it's available, or None otherwise + mxid = lookup_results["mappings"].get(lookup_value) + return mxid + + @defer.inlineCallbacks + def bulk_lookup_3pid(self, id_server, threepids): + """Looks up given 3pids in the passed identity server. + Args: + id_server (str): The server name (including port, if required) + of the identity server to use. + threepids ([[str, str]]): The third party identifiers to lookup, as + a list of 2-string sized lists ([medium, address]). + Returns: + Deferred[dict]: The result of the lookup. See + https://matrix.org/docs/spec/identity_service/r0.1.0.html#association-lookup + for details + """ + if not should_trust_id_server(self.hs, id_server): + raise SynapseError( + 400, "Untrusted ID server '%s'" % id_server, + Codes.SERVER_NOT_TRUSTED + ) + + if not self._enable_lookup: + raise AuthError( + 403, "Looking up third-party identifiers is denied from this server", + ) + + target = self.rewrite_identity_server_urls.get(id_server, id_server) + + try: + data = yield self.simple_http_client.post_json_get_json( + "https://%s/_matrix/identity/api/v1/bulk_lookup" % (target,), + { + "threepids": threepids, + } + ) + + except HttpResponseException as e: + logger.info("Proxied lookup failed: %r", e) + raise e.to_synapse_error() + except IOError as e: + logger.info("Failed to contact %r: %s", id_server, e) + raise ProxiedRequestError(503, "Failed to contact identity server") + + defer.returnValue(data) + + @defer.inlineCallbacks + def _verify_any_signature(self, data, server_hostname): + if server_hostname not in data["signatures"]: + raise AuthError(401, "No signature from server %s" % (server_hostname,)) + for key_name, signature in data["signatures"][server_hostname].items(): + key_data = yield self.simple_http_client.get_json( + "%s%s/_matrix/identity/api/v1/pubkey/%s" + % (id_server_scheme, server_hostname, key_name) + ) + if "public_key" not in key_data: + raise AuthError( + 401, "No public key named %s from %s" % (key_name, server_hostname) + ) + verify_signed_json( + data, + server_hostname, + decode_verify_key_bytes( + key_name, decode_base64(key_data["public_key"]) + ), + ) + return + @defer.inlineCallbacks def _make_and_store_3pid_invite( self, requester, id_server, medium, address, room_id, user, txn_id @@ -917,11 +1117,10 @@ class RoomMemberHandler(object): display_name (str): A user-friendly name to represent the invited user. """ - - target = self._get_id_server_target(id_server) + id_server = self.rewrite_identity_server_urls.get(id_server, id_server) is_url = "%s%s/_matrix/identity/api/v1/store-invite" % ( id_server_scheme, - target, + id_server, ) invite_config = { @@ -961,7 +1160,7 @@ class RoomMemberHandler(object): fallback_public_key = { "public_key": data["public_key"], "key_validity_url": "%s%s/_matrix/identity/api/v1/pubkey/isvalid" - % (id_server_scheme, target), + % (id_server_scheme, id_server), } else: fallback_public_key = public_keys[0] @@ -1028,9 +1227,7 @@ class RoomMemberMasterHandler(RoomMemberHandler): ) if complexity: - if complexity["v1"] > max_complexity: - return True - return False + return complexity["v1"] > max_complexity return None @defer.inlineCallbacks @@ -1046,10 +1243,7 @@ class RoomMemberMasterHandler(RoomMemberHandler): max_complexity = self.hs.config.limit_remote_rooms.complexity complexity = yield self.store.get_room_complexity(room_id) - if complexity["v1"] > max_complexity: - return True - - return False + return complexity["v1"] > max_complexity @defer.inlineCallbacks def _remote_join(self, requester, remote_room_hosts, room_id, user, content): diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py
index b83704fed4..986f56d3d7 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py
@@ -706,6 +706,7 @@ class ThreepidLookupRestServlet(RestServlet): super(ThreepidLookupRestServlet, self).__init__() self.auth = hs.get_auth() self.identity_handler = hs.get_handlers().identity_handler + self.room_member_handler = hs.get_room_member_handler() @defer.inlineCallbacks def on_GET(self, request): @@ -725,7 +726,7 @@ class ThreepidLookupRestServlet(RestServlet): # Proxy the request to the identity server. lookup_3pid handles checking # if the lookup is allowed so we don't need to do it here. - ret = yield self.identity_handler.lookup_3pid(id_server, medium, address) + ret = yield self.room_member_handler.lookup_3pid(id_server, medium, address) defer.returnValue((200, ret)) @@ -737,6 +738,7 @@ class ThreepidBulkLookupRestServlet(RestServlet): super(ThreepidBulkLookupRestServlet, self).__init__() self.auth = hs.get_auth() self.identity_handler = hs.get_handlers().identity_handler + self.room_member_handler = hs.get_room_member_handler() @defer.inlineCallbacks def on_POST(self, request): @@ -751,7 +753,7 @@ class ThreepidBulkLookupRestServlet(RestServlet): # Proxy the request to the identity server. lookup_3pid handles checking # if the lookup is allowed so we don't need to do it here. - ret = yield self.identity_handler.bulk_lookup_3pid( + ret = yield self.room_member_handler.bulk_lookup_3pid( body["id_server"], body["threepids"] ) diff --git a/synapse/util/hash.py b/synapse/util/hash.py new file mode 100644
index 0000000000..359168704e --- /dev/null +++ b/synapse/util/hash.py
@@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import hashlib + +import unpaddedbase64 + + +def sha256_and_url_safe_base64(input_text): + """SHA256 hash an input string, encode the digest as url-safe base64, and + return + + :param input_text: string to hash + :type input_text: str + + :returns a sha256 hashed and url-safe base64 encoded digest + :rtype: str + """ + digest = hashlib.sha256(input_text.encode()).digest() + return unpaddedbase64.encode_base64(digest, urlsafe=True) diff --git a/tests/rest/client/test_identity.py b/tests/rest/client/test_identity.py
index f81f81602e..a7f08a3f19 100644 --- a/tests/rest/client/test_identity.py +++ b/tests/rest/client/test_identity.py
@@ -20,6 +20,7 @@ from mock import Mock from twisted.internet import defer import synapse.rest.admin +from synapse.api.errors import HttpResponseException from synapse.rest.client.v1 import login, room from synapse.rest.client.v2_alpha import account @@ -109,8 +110,15 @@ class IdentityEnabledTestCase(unittest.HomeserverTestCase): config["enable_3pid_lookup"] = True config["trusted_third_party_id_servers"] = ["testis"] + def get_json(uri, args={}, headers=None): + # TODO: Mock v2 hash_details endpoints and don't just run the v1 code + if "/hash_details" in uri: + raise HttpResponseException(404, "I am a happy v1 server", b"{}") + + return defer.succeed((200, "{}")) + mock_http_client = Mock(spec=["get_json", "post_json_get_json"]) - mock_http_client.get_json.return_value = defer.succeed((200, "{}")) + mock_http_client.get_json.side_effect = get_json mock_http_client.post_json_get_json.return_value = defer.succeed((200, "{}")) self.hs = self.setup_test_homeserver( @@ -143,8 +151,10 @@ class IdentityEnabledTestCase(unittest.HomeserverTestCase): ) self.render(request) + # There will be a call to hash_details, which will 404 + # then /lookup. Thus we don't use `assert_called_with_once` here get_json = self.hs.get_simple_http_client().get_json - get_json.assert_called_once_with( + get_json.assert_called_with( "https://testis/_matrix/identity/api/v1/lookup", {"address": "test@example.com", "medium": "email"}, ) @@ -157,8 +167,10 @@ class IdentityEnabledTestCase(unittest.HomeserverTestCase): request, channel = self.make_request("GET", url, access_token=self.tok) self.render(request) + # There will be a call to hash_details, which will 404 + # then /lookup. Thus we don't use `assert_called_with_once` here get_json = self.hs.get_simple_http_client().get_json - get_json.assert_called_once_with( + get_json.assert_called_with( "https://testis/_matrix/identity/api/v1/lookup", {"address": "foo@bar.baz", "medium": "email"}, ) diff --git a/tests/rest/client/test_room_access_rules.py b/tests/rest/client/test_room_access_rules.py
index d44f5c2c8c..69920853e6 100644 --- a/tests/rest/client/test_room_access_rules.py +++ b/tests/rest/client/test_room_access_rules.py
@@ -22,6 +22,7 @@ from mock import Mock from twisted.internet import defer from synapse.api.constants import EventTypes, JoinRules, RoomCreationPreset +from synapse.api.errors import HttpResponseException from synapse.rest import admin from synapse.rest.client.v1 import login, room from synapse.third_party_rules.access_rules import ( @@ -58,6 +59,10 @@ class RoomAccessTestCase(unittest.HomeserverTestCase): return defer.succeed(pdu) def get_json(uri, args={}, headers=None): + # TODO: Mock v2 hash_details endpoints and don't just run the v1 code + if "/hash_details" in uri: + raise HttpResponseException(404, "I am a happy v1 server", b"{}") + address_domain = args["address"].split("@")[1] return defer.succeed({"hs": address_domain}) @@ -332,6 +337,7 @@ class RoomAccessTestCase(unittest.HomeserverTestCase): self.hs.config.rc_third_party_invite.burst_count = 10 self.hs.config.rc_third_party_invite.per_second = 0.1 + # Note: These calls trigger the mocked get_json method above # We can't send a 3PID invite to a room that already has two members. self.send_threepid_invite( address="test@allowed_domain", diff --git a/tests/rest/client/v2_alpha/test_register.py b/tests/rest/client/v2_alpha/test_register.py
index 9e72cae62a..9b7eb1e856 100644 --- a/tests/rest/client/v2_alpha/test_register.py +++ b/tests/rest/client/v2_alpha/test_register.py
@@ -52,7 +52,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase): return self.hs - @unittest.DEBUG def test_POST_appservice_registration_valid(self): user_id = "@as_user_kermit:test" as_token = "i_am_an_app_service" diff --git a/tests/rulecheck/test_domainrulecheck.py b/tests/rulecheck/test_domainrulecheck.py
index 1accc70dc9..145d6cba5b 100644 --- a/tests/rulecheck/test_domainrulecheck.py +++ b/tests/rulecheck/test_domainrulecheck.py
@@ -15,6 +15,8 @@ import json +from mock import Mock +from twisted.internet import defer import synapse.rest.admin from synapse.config._base import ConfigError @@ -287,6 +289,9 @@ class DomainRuleCheckerRoomTestCase(unittest.HomeserverTestCase): def test_cannot_3pid_invite(self): """Test that unbound 3pid invites get rejected. """ + self.hs.get_room_member_handler().lookup_3pid = Mock() + self.hs.get_room_member_handler().lookup_3pid.return_value = defer.succeed(None) + channel = self._create_room(self.admin_access_token) assert channel.result["code"] == b"200", channel.result