diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml index 34bdc3759f..274658bfb2 100644 --- a/docs/sample_config.yaml +++ b/docs/sample_config.yaml @@ -1221,6 +1221,13 @@ account_threepid_delegates: # #autocreate_auto_join_rooms: true +# Rewrite identity server URLs with a map from one URL to another. Applies to URLs +# provided by clients (which have https:// prepended) and those specified +# in `account_threepid_delegates`. URLs should not feature a trailing slash. +# +#rewrite_identity_server_urls: +# "https://somewhere.example.com": "https://somewhereelse.example.com" + ## Metrics ### diff --git a/synapse/config/registration.py b/synapse/config/registration.py index 426c0fecef..4bf5bbaa17 100644 --- a/synapse/config/registration.py +++ b/synapse/config/registration.py @@ -163,8 +163,8 @@ class RegistrationConfig(Config): self.shadow_server = config.get("shadow_server", None) self.rewrite_identity_server_urls = config.get( - "rewrite_identity_server_urls", {} - ) + "rewrite_identity_server_urls" + ) or {} self.disable_msisdn_registration = config.get( "disable_msisdn_registration", False @@ -432,6 +432,13 @@ class RegistrationConfig(Config): # users cannot be auto-joined since they do not exist. # #autocreate_auto_join_rooms: true + + # Rewrite identity server URLs with a map from one URL to another. Applies to URLs + # provided by clients (which have https:// prepended) and those specified + # in `account_threepid_delegates`. URLs should not feature a trailing slash. + # + #rewrite_identity_server_urls: + # "https://somewhere.example.com": "https://somewhereelse.example.com" """ % locals() ) diff --git a/synapse/handlers/identity.py b/synapse/handlers/identity.py index 6c369b78aa..e8a6cf7788 100644 --- a/synapse/handlers/identity.py +++ b/synapse/handlers/identity.py @@ -45,8 +45,6 @@ from ._base import BaseHandler logger = logging.getLogger(__name__) -id_server_scheme = "https://" - class IdentityHandler(BaseHandler): def __init__(self, hs): @@ -69,13 +67,13 @@ class IdentityHandler(BaseHandler): self._enable_lookup = hs.config.enable_3pid_lookup @defer.inlineCallbacks - def threepid_from_creds(self, id_server, creds): + def threepid_from_creds(self, id_server_url, creds): """ Retrieve and validate a threepid identifier from a "credentials" dictionary against a given identity server Args: - id_server (str): The identity server to validate 3PIDs against. Must be a + id_server_url (str): The identity server to validate 3PIDs against. Must be a complete URL including the protocol (http(s)://) creds (dict[str, str]): Dictionary containing the following keys: @@ -104,10 +102,10 @@ class IdentityHandler(BaseHandler): # if we have a rewrite rule set for the identity server, # apply it now. - id_server = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server_url) - url = "https://%s%s" % ( - id_server, + url = "%s%s" % ( + id_server_url, "/_matrix/identity/api/v1/3pid/getValidated3pid", ) @@ -118,7 +116,7 @@ class IdentityHandler(BaseHandler): except HttpResponseException as e: logger.info( "%s returned %i for threepid validation for: %s", - id_server, + id_server_url, e.code, creds, ) @@ -132,7 +130,7 @@ class IdentityHandler(BaseHandler): if "medium" in data: return data - logger.info("%s reported non-validated threepid: %s", id_server, creds) + logger.info("%s reported non-validated threepid: %s", id_server_url, creds) return None @defer.inlineCallbacks @@ -167,18 +165,18 @@ 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). - id_server_host = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) # Decide which API endpoint URLs to use headers = {} bind_data = {"sid": sid, "client_secret": client_secret, "mxid": mxid} if use_v2: - bind_url = "https://%s/_matrix/identity/v2/3pid/bind" % (id_server_host,) + bind_url = "%s/_matrix/identity/v2/3pid/bind" % (id_server_url,) headers["Authorization"] = create_id_access_token_header( id_access_token ) else: - bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server_host,) + bind_url = "%s/_matrix/identity/api/v1/3pid/bind" % (id_server_url,) try: # Use the blacklisting http client as this call is only to identity servers @@ -265,9 +263,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,) - url_bytes = "/_matrix/identity/api/v1/3pid/unbind".encode("ascii") - content = { "mxid": mxid, "threepid": {"medium": threepid["medium"], "address": threepid["address"]}, @@ -276,6 +271,7 @@ class IdentityHandler(BaseHandler): # we abuse the federation http client to sign the request, but we have to send it # using the normal http client since we don't want the SRV lookup and want normal # 'browser-like' HTTPS. + url_bytes = "/_matrix/identity/api/v1/3pid/unbind".encode("ascii") auth_headers = self.federation_http_client.build_auth_headers( destination=None, method="POST", @@ -290,9 +286,9 @@ class IdentityHandler(BaseHandler): # # Note that destination_is has to be the real id_server, not # the server we connect to. - id_server = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) - url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,) + url = "%s/_matrix/identity/api/v1/3pid/unbind" % (id_server_url,) try: # Use the blacklisting http client as this call is only to identity servers @@ -408,33 +404,33 @@ class IdentityHandler(BaseHandler): return session_id - def rewrite_id_server_url(self, url: str) -> str: - """Given an identity server URL, rewrite it according to the - rewrite_identity_server_urls config option + def rewrite_id_server_url(self, url: str, add_https=False) -> str: + """Given an identity server URL, optionally add a protocol scheme + before rewriting it according to the rewrite_identity_server_urls + config option - First removes the protocol scheme from the URL if provided. - Then checks for a rewritten URL. If found, returns the new URL. - Otherwise, returns the original URL. + Adds https:// to the URL if specified, then tries to rewrite the + url. Returns either the rewritten URL or the URL with optional + protocol scheme additions. """ rewritten_url = url - if url.startswith("http://"): - rewritten_url = url[7:] - elif url.startswith("https://"): - rewritten_url = url[8:] - - return self.rewrite_identity_server_urls.get(rewritten_url, url) + if add_https: + rewritten_url = "https://" + rewritten_url + rewritten_url = self.rewrite_identity_server_urls.get(rewritten_url, rewritten_url) + logger.debug("Rewriting identity server rule from %s to %s", url, rewritten_url) + return rewritten_url @defer.inlineCallbacks def requestEmailToken( - self, id_server, email, client_secret, send_attempt, next_link=None + self, id_server_url, email, client_secret, send_attempt, next_link=None ): """ Request an external server send an email on our behalf for the purposes of threepid validation. Args: - id_server (str): The identity server to proxy to + id_server_url (str): The identity server to proxy to email (str): The email to send the message to client_secret (str): The unique client_secret sends by the user send_attempt (int): Which attempt this is @@ -451,7 +447,7 @@ class IdentityHandler(BaseHandler): # if we have a rewrite rule set for the identity server, # apply it now. - id_server = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server_url) if next_link: params["next_link"] = next_link @@ -467,7 +463,7 @@ class IdentityHandler(BaseHandler): try: data = yield self.http_client.post_json_get_json( - id_server + "/_matrix/identity/api/v1/validate/email/requestToken", + "%s/_matrix/identity/api/v1/validate/email/requestToken" % (id_server_url,), params, ) return data @@ -480,7 +476,7 @@ class IdentityHandler(BaseHandler): @defer.inlineCallbacks def requestMsisdnToken( self, - id_server, + id_server_url, country, phone_number, client_secret, @@ -491,7 +487,7 @@ class IdentityHandler(BaseHandler): Request an external server send an SMS message on our behalf for the purposes of threepid validation. Args: - id_server (str): The identity server to proxy to + id_server_url (str): The identity server to proxy to country (str): The country code of the phone number phone_number (str): The number to send the message to client_secret (str): The unique client_secret sends by the user @@ -521,10 +517,10 @@ class IdentityHandler(BaseHandler): # if we have a rewrite rule set for the identity server, # apply it now. - id_server = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server_url) try: data = yield self.http_client.post_json_get_json( - id_server + "/_matrix/identity/api/v1/validate/msisdn/requestToken", + "%s/_matrix/identity/api/v1/validate/msisdn/requestToken" % (id_server_url,), params, ) except HttpResponseException as e: @@ -646,11 +642,11 @@ class IdentityHandler(BaseHandler): 403, "Looking up third-party identifiers is denied from this server" ) - target = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) try: data = yield self.http_client.get_json( - "https://%s/_matrix/identity/api/v1/lookup" % (target,), + "%s/_matrix/identity/api/v1/lookup" % (id_server_url,), {"medium": medium, "address": address}, ) @@ -663,7 +659,7 @@ class IdentityHandler(BaseHandler): 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) + logger.info("Failed to contact %s: %s", id_server, e) raise ProxiedRequestError(503, "Failed to contact identity server") defer.returnValue(data) @@ -688,11 +684,11 @@ class IdentityHandler(BaseHandler): 403, "Looking up third-party identifiers is denied from this server" ) - target = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) try: data = yield self.http_client.post_json_get_json( - "https://%s/_matrix/identity/api/v1/bulk_lookup" % (target,), + "%s/_matrix/identity/api/v1/bulk_lookup" % (id_server_url,), {"threepids": threepids}, ) @@ -700,7 +696,7 @@ class IdentityHandler(BaseHandler): 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) + logger.info("Failed to contact %s: %s", id_server, e) raise ProxiedRequestError(503, "Failed to contact identity server") defer.returnValue(data) @@ -721,12 +717,12 @@ class IdentityHandler(BaseHandler): str|None: the matrix ID of the 3pid, or None if it is not recognized. """ # Rewrite id_server URL if necessary - id_server_url = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) if id_access_token is not None: try: results = yield self._lookup_3pid_v2( - id_server, id_server_url, id_access_token, medium, address + id_server_url, id_access_token, medium, address ) return results @@ -762,7 +758,7 @@ class IdentityHandler(BaseHandler): """ try: data = yield self.http_client.get_json( - "%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, id_server_url), + "%s/_matrix/identity/api/v1/lookup" % (id_server_url,), {"medium": medium, "address": address}, ) @@ -779,13 +775,11 @@ class IdentityHandler(BaseHandler): return None @defer.inlineCallbacks - def _lookup_3pid_v2(self, id_server, id_server_url, id_access_token, medium, address): + def _lookup_3pid_v2(self, id_server_url, id_access_token, medium, address): """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. - id_server_url (str): The actual, reachable domain of the id server + id_server_url (str): The protocol scheme and domain of the id server id_access_token (str): The access token to authenticate to the identity server with medium (str): The type of the third party identifier (e.g. "email"). address (str): The third party identifier (e.g. "foo@example.com"). @@ -796,7 +790,7 @@ class IdentityHandler(BaseHandler): # Check what hashing details are supported by this identity server try: hash_details = yield self.http_client.get_json( - "%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server_url), + "%s/_matrix/identity/v2/hash_details" % (id_server_url,), {"access_token": id_access_token}, ) except TimeoutError: @@ -804,15 +798,14 @@ class IdentityHandler(BaseHandler): if not isinstance(hash_details, dict): logger.warning( - "Got non-dict object when checking hash details of %s%s: %s", - id_server_scheme, - id_server, + "Got non-dict object when checking hash details of %s: %s", + id_server_url, hash_details, ) raise SynapseError( 400, - "Non-dict object from %s%s during v2 hash_details request: %s" - % (id_server_scheme, id_server, hash_details), + "Non-dict object from %s during v2 hash_details request: %s" + % (id_server_url, hash_details), ) # Extract information from hash_details @@ -826,8 +819,8 @@ class IdentityHandler(BaseHandler): ): raise SynapseError( 400, - "Invalid hash details received from identity server %s%s: %s" - % (id_server_scheme, id_server, hash_details), + "Invalid hash details received from identity server %s: %s" + % (id_server_url, hash_details), ) # Check if any of the supported lookup algorithms are present @@ -849,7 +842,7 @@ class IdentityHandler(BaseHandler): else: logger.warning( "None of the provided lookup algorithms of %s are supported: %s", - id_server, + id_server_url, supported_lookup_algorithms, ) raise SynapseError( @@ -863,7 +856,7 @@ class IdentityHandler(BaseHandler): try: lookup_results = yield self.http_client.post_json_get_json( - "%s%s/_matrix/identity/v2/lookup" % (id_server_scheme, id_server_url), + "%s/_matrix/identity/v2/lookup" % (id_server_url,), { "addresses": [lookup_value], "algorithm": lookup_algorithm, @@ -891,30 +884,30 @@ class IdentityHandler(BaseHandler): return mxid @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,)) + def _verify_any_signature(self, data, id_server): + if id_server not in data["signatures"]: + raise AuthError(401, "No signature from server %s" % (id_server,)) - for key_name, signature in data["signatures"][server_hostname].items(): - target = self.rewrite_id_server_url(server_hostname) + for key_name, signature in data["signatures"][id_server].items(): + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) key_data = yield self.http_client.get_json( - "https://%s/_matrix/identity/api/v1/pubkey/%s" % (target, key_name) + "%s/_matrix/identity/api/v1/pubkey/%s" % (id_server_url, key_name) ) if "public_key" not in key_data: raise AuthError( - 401, "No public key named %s from %s" % (key_name, server_hostname) + 401, "No public key named %s from %s" % (key_name, id_server) ) verify_signed_json( data, - server_hostname, + id_server, decode_verify_key_bytes( key_name, decode_base64(key_data["public_key"]) ), ) return - raise AuthError(401, "No signature from server %s" % (server_hostname,)) + raise AuthError(401, "No signature from server %s" % (id_server,)) @defer.inlineCallbacks def ask_id_server_for_third_party_invite( @@ -977,17 +970,16 @@ class IdentityHandler(BaseHandler): } # Rewrite the identity server URL if necessary - id_server = self.rewrite_id_server_url(id_server) + id_server_url = self.rewrite_id_server_url(id_server, add_https=True) # Add the identity service access token to the JSON body and use the v2 # Identity Service endpoints if id_access_token is present data = None - base_url = "%s%s/_matrix/identity" % (id_server_scheme, id_server) + base_url = "%s/_matrix/identity" % (id_server_url,) if id_access_token: - key_validity_url = "%s%s/_matrix/identity/v2/pubkey/isvalid" % ( - id_server_scheme, - id_server, + key_validity_url = "%s/_matrix/identity/v2/pubkey/isvalid" % ( + id_server_url, ) # Attempt a v2 lookup @@ -1006,9 +998,8 @@ class IdentityHandler(BaseHandler): raise e if data is None: - key_validity_url = "%s%s/_matrix/identity/api/v1/pubkey/isvalid" % ( - id_server_scheme, - id_server, + key_validity_url = "%s/_matrix/identity/api/v1/pubkey/isvalid" % ( + id_server_url, ) url = base_url + "/api/v1/store-invite" @@ -1020,9 +1011,8 @@ class IdentityHandler(BaseHandler): raise SynapseError(500, "Timed out contacting identity server") except HttpResponseException as e: logger.warning( - "Error trying to call /store-invite on %s%s: %s", - id_server_scheme, - id_server, + "Error trying to call /store-invite on %s: %s", + id_server_url, e, ) @@ -1036,10 +1026,9 @@ class IdentityHandler(BaseHandler): ) except HttpResponseException as e: logger.warning( - "Error calling /store-invite on %s%s with fallback " + "Error calling /store-invite on %s with fallback " "encoding: %s", - id_server_scheme, - id_server, + id_server_url, e, ) raise e diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index da6050edda..fbc5aa13f3 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -775,7 +775,7 @@ class ThreepidAddRestServlet(RestServlet): "%s/_matrix/client/r0/account/3pid?access_token=%s&user_id=%s" % (shadow_hs_url, as_token, user_id), body, - ) + ) class ThreepidBindRestServlet(RestServlet): diff --git a/tests/handlers/test_identity.py b/tests/handlers/test_identity.py index 34f6bfb422..0ab0356109 100644 --- a/tests/handlers/test_identity.py +++ b/tests/handlers/test_identity.py @@ -35,12 +35,13 @@ class ThreepidISRewrittenURLTestCase(unittest.HomeserverTestCase): def make_homeserver(self, reactor, clock): self.address = "test@test" self.is_server_name = "testis" - self.rewritten_is_url = "int.testis" + self.is_server_url = "https://testis" + self.rewritten_is_url = "https://int.testis" config = self.default_config() config["trusted_third_party_id_servers"] = [self.is_server_name] config["rewrite_identity_server_urls"] = { - self.is_server_name: self.rewritten_is_url + self.is_server_url: self.rewritten_is_url } mock_http_client = Mock(spec=["get_json", "post_json_get_json"]) @@ -98,7 +99,7 @@ class ThreepidISRewrittenURLTestCase(unittest.HomeserverTestCase): # Check that the request was done against the rewritten server name. post_json_get_json.assert_called_once_with( - "https://%s/_matrix/identity/api/v1/3pid/bind" % self.rewritten_is_url, + "%s/_matrix/identity/api/v1/3pid/bind" % (self.rewritten_is_url,), { "sid": creds["sid"], "client_secret": creds["client_secret"],