-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Use the v2 Identity Service API for lookups (MSC2134 + MSC2140) #5976
Changes from 19 commits
1954438
24ee3ae
902ef39
3a114fe
5426e13
73fb6f3
2472e2e
7bfccad
75ef0f8
e68d648
38dac27
8f1346d
4dc0849
849d8dc
d9d156b
42b11bd
83021d9
07154ea
f4b7f7f
29c3489
ff5f6a0
a5153af
7f647bc
f8bb859
1c59243
1b20928
db1d161
9f92c3e
07169b1
5b852c2
0d968c0
f18f3f1
18671b0
649dcbe
b4520ea
79f5c4f
cf8dbea
7008c79
ffb284e
317dff6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Switch to the v2 lookup API for 3PID invites. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,15 +20,20 @@ | |
| 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, | ||
| SynapseError, | ||
| ) | ||
| from synapse.util.hash import sha256_and_url_safe_base64 | ||
|
|
||
| from ._base import BaseHandler | ||
|
|
||
|
|
@@ -282,3 +287,192 @@ def requestMsisdnToken( | |
| except HttpResponseException as e: | ||
| logger.info("Proxied requestToken failed: %r", e) | ||
| raise e.to_synapse_error() | ||
|
|
||
| @defer.inlineCallbacks | ||
| def lookup_3pid(self, id_server, medium, address, id_access_token=None): | ||
| """Looks up a 3pid in the passed identity server. | ||
|
|
||
| Args: | ||
| id_server (str): The server name (including protocol and 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. "[email protected]"). | ||
| id_access_token (str|None): The access token to authenticate to the identity | ||
| server with | ||
|
|
||
| Returns: | ||
| str: the matrix ID of the 3pid, or None if it is not recognized. | ||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """ | ||
| # If an access token is present, add it to the query params of the hash_details request | ||
| query_params = {} | ||
| if id_access_token is not None: | ||
| query_params["id_access_token"] = id_access_token | ||
|
|
||
| # Check what hashing details are supported by this identity server | ||
| use_v1 = False | ||
| hash_details = None | ||
| try: | ||
| hash_details = yield self.http_client.get_json( | ||
| "%s/_matrix/identity/v2/hash_details" % id_server, query_params | ||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) | ||
| 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,)) | ||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return None | ||
|
|
||
| if use_v1: | ||
| return (yield self._lookup_3pid_v1(id_server, medium, address)) | ||
|
|
||
| return ( | ||
| yield self._lookup_3pid_v2( | ||
| id_server, id_access_token, medium, address, hash_details | ||
| ) | ||
| ) | ||
|
|
||
| @defer.inlineCallbacks | ||
| 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 protocol and 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. "[email protected]"). | ||
|
|
||
| Returns: | ||
| str: the matrix ID of the 3pid, or None if it is not recognized. | ||
| """ | ||
| try: | ||
| data = yield self.http_client.get_json( | ||
| "%s/_matrix/identity/api/v1/lookup" % (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 IOError as e: | ||
| logger.warn("Error from identity server lookup: %s" % (e,)) | ||
|
|
||
| return None | ||
|
|
||
| @defer.inlineCallbacks | ||
| def _lookup_3pid_v2( | ||
| self, id_server, id_access_token, medium, address, hash_details | ||
| ): | ||
| """Looks up a 3pid in the passed identity server using v2 lookup. | ||
|
|
||
| Args: | ||
| id_server (str): The server name (including protocol and port, if required) | ||
| of the identity server to use. | ||
| 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. "[email protected]"). | ||
| hash_details (dict[str, str|list]): A dictionary containing hashing information | ||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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"] | ||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 are supported: %s", | ||
| 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.http_client.post_json_get_json( | ||
| "%s/_matrix/identity/v2/lookup" % id_server, | ||
| { | ||
| "id_access_token": id_access_token, | ||
| "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 | ||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 _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.http_client.get_json( | ||
| "%s/_matrix/identity/api/v1/pubkey/%s" % (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 | ||
|
|
||
|
|
||
| class LookupAlgorithm: | ||
| """ | ||
| Supported hashing algorithms when performing a 3PID lookup. | ||
|
|
||
| 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 | ||
| """ | ||
|
|
||
| SHA256 = "sha256" | ||
| NONE = "none" | ||
Uh oh!
There was an error while loading. Please reload this page.