summary refs log tree commit diff
path: root/synapse/handlers/identity.py
diff options
context:
space:
mode:
Diffstat (limited to 'synapse/handlers/identity.py')
-rw-r--r--synapse/handlers/identity.py193
1 files changed, 173 insertions, 20 deletions
diff --git a/synapse/handlers/identity.py b/synapse/handlers/identity.py
index 000fbf090f..517e045e5b 100644
--- a/synapse/handlers/identity.py
+++ b/synapse/handlers/identity.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # Copyright 2015, 2016 OpenMarket Ltd
 # Copyright 2017 Vector Creations Ltd
-# Copyright 2018 New Vector Ltd
+# Copyright 2018, 2019 New Vector Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ from synapse.api.errors import (
     CodeMessageException,
     Codes,
     HttpResponseException,
+    ProxiedRequestError,
     SynapseError,
 )
 from synapse.config.emailconfig import ThreepidBehaviour
@@ -51,14 +52,21 @@ class IdentityHandler(BaseHandler):
     def __init__(self, hs):
         super(IdentityHandler, self).__init__(hs)
 
-        self.http_client = SimpleHttpClient(hs)
+        self.hs = hs
+        self.http_client = hs.get_simple_http_client()
         # We create a blacklisting instance of SimpleHttpClient for contacting identity
         # servers specified by clients
         self.blacklisting_http_client = SimpleHttpClient(
             hs, ip_blacklist=hs.config.federation_ip_range_blacklist
         )
         self.federation_http_client = hs.get_http_client()
-        self.hs = hs
+
+        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
 
     @defer.inlineCallbacks
     def threepid_from_creds(self, id_server, creds):
@@ -92,7 +100,15 @@ class IdentityHandler(BaseHandler):
 
         query_params = {"sid": session_id, "client_secret": client_secret}
 
-        url = id_server + "/_matrix/identity/api/v1/3pid/getValidated3pid"
+        # 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]
+
+        url = "https://%s%s" % (
+            id_server,
+            "/_matrix/identity/api/v1/3pid/getValidated3pid",
+        )
 
         try:
             data = yield self.http_client.get_json(url, query_params)
@@ -147,14 +163,24 @@ class IdentityHandler(BaseHandler):
         if id_access_token is None:
             use_v2 = False
 
+        # 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
+
         # 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,)
-            headers["Authorization"] = create_id_access_token_header(id_access_token)
+            bind_url = "https://%s/_matrix/identity/v2/3pid/bind" % (id_server_host,)
+            headers["Authorization"] = create_id_access_token_header(
+                id_access_token
+            )
         else:
-            bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server,)
+            bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server_host,)
 
         try:
             # Use the blacklisting http client as this call is only to identity servers
@@ -261,6 +287,16 @@ class IdentityHandler(BaseHandler):
         )
         headers = {b"Authorization": auth_headers}
 
+        # if we have a rewrite rule set for the identity server,
+        # apply it now.
+        #
+        # 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]
+
+        url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
+
         try:
             # Use the blacklisting http client as this call is only to identity servers
             # provided by a client
@@ -398,6 +434,12 @@ class IdentityHandler(BaseHandler):
             "client_secret": client_secret,
             "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]
+
         if next_link:
             params["next_link"] = next_link
 
@@ -464,6 +506,10 @@ class IdentityHandler(BaseHandler):
                 "details and update your config file."
             )
 
+        # 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]
         try:
             data = yield self.http_client.post_json_get_json(
                 id_server + "/_matrix/identity/api/v1/validate/msisdn/requestToken",
@@ -564,6 +610,89 @@ class IdentityHandler(BaseHandler):
             logger.warning("Error contacting msisdn account_threepid_delegate: %s", e)
             raise SynapseError(400, "Error contacting the identity server")
 
+    # TODO: The following two methods are used for proxying IS requests using
+    # the CS API. They should be consolidated with those in RoomMemberHandler
+    # https://github.com/matrix-org/synapse-dinsic/issues/25
+
+    @defer.inlineCallbacks
+    def proxy_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._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 proxy_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._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},
+            )
+
+        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 lookup_3pid(self, id_server, medium, address, id_access_token=None):
         """Looks up a 3pid in the passed identity server.
@@ -579,6 +708,9 @@ class IdentityHandler(BaseHandler):
         Returns:
             str|None: the matrix ID of the 3pid, or None if it is not recognized.
         """
+        # Rewrite id_server URL if necessary
+        id_server = self._get_id_server_target(id_server)
+
         if id_access_token is not None:
             try:
                 results = yield self._lookup_3pid_v2(
@@ -616,7 +748,7 @@ class IdentityHandler(BaseHandler):
             str: the matrix ID of the 3pid, or None if it is not recognized.
         """
         try:
-            data = yield self.blacklisting_http_client.get_json(
+            data = yield self.http_client.get_json(
                 "%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, id_server),
                 {"medium": medium, "address": address},
             )
@@ -649,7 +781,7 @@ class IdentityHandler(BaseHandler):
         """
         # Check what hashing details are supported by this identity server
         try:
-            hash_details = yield self.blacklisting_http_client.get_json(
+            hash_details = yield self.http_client.get_json(
                 "%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server),
                 {"access_token": id_access_token},
             )
@@ -667,7 +799,7 @@ class IdentityHandler(BaseHandler):
                 400,
                 "Non-dict object from %s%s during v2 hash_details request: %s"
                 % (id_server_scheme, id_server, hash_details),
-            )
+                )
 
         # Extract information from hash_details
         supported_lookup_algorithms = hash_details.get("algorithms")
@@ -682,7 +814,7 @@ class IdentityHandler(BaseHandler):
                 400,
                 "Invalid hash details received from identity server %s%s: %s"
                 % (id_server_scheme, id_server, hash_details),
-            )
+                )
 
         # Check if any of the supported lookup algorithms are present
         if LookupAlgorithm.SHA256 in supported_lookup_algorithms:
@@ -716,7 +848,7 @@ class IdentityHandler(BaseHandler):
         headers = {"Authorization": create_id_access_token_header(id_access_token)}
 
         try:
-            lookup_results = yield self.blacklisting_http_client.post_json_get_json(
+            lookup_results = yield self.http_client.post_json_get_json(
                 "%s%s/_matrix/identity/v2/lookup" % (id_server_scheme, id_server),
                 {
                     "addresses": [lookup_value],
@@ -724,7 +856,7 @@ class IdentityHandler(BaseHandler):
                     "pepper": lookup_pepper,
                 },
                 headers=headers,
-            )
+                )
         except TimeoutError:
             raise SynapseError(500, "Timed out contacting identity server")
         except Exception as e:
@@ -748,14 +880,15 @@ class IdentityHandler(BaseHandler):
     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():
-            try:
-                key_data = yield self.blacklisting_http_client.get_json(
-                    "%s%s/_matrix/identity/api/v1/pubkey/%s"
-                    % (id_server_scheme, server_hostname, key_name)
-                )
-            except TimeoutError:
-                raise SynapseError(500, "Timed out contacting identity server")
+            target = self.rewrite_identity_server_urls.get(
+                server_hostname, server_hostname
+            )
+
+            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)
@@ -769,6 +902,23 @@ class IdentityHandler(BaseHandler):
             )
             return
 
+        raise AuthError(401, "No signature from server %s" % (server_hostname,))
+
+    def _get_id_server_target(self, id_server):
+        """Looks up an id_server's actual http endpoint
+
+        Args:
+            id_server (str): the server name to lookup.
+
+        Returns:
+            the http endpoint to connect to.
+        """
+        if id_server in self.rewrite_identity_server_urls:
+            return self.rewrite_identity_server_urls[id_server]
+
+        return id_server
+
+
     @defer.inlineCallbacks
     def ask_id_server_for_third_party_invite(
         self,
@@ -829,6 +979,9 @@ class IdentityHandler(BaseHandler):
             "sender_avatar_url": inviter_avatar_url,
         }
 
+        # Rewrite the identity server URL if necessary
+        id_server = self._get_id_server_target(id_server)
+
         # 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