|
|
|
@ -61,21 +61,76 @@ class IdentityHandler(BaseHandler): |
|
|
|
|
return False |
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
def _extract_items_from_creds_dict(self, creds): |
|
|
|
|
""" |
|
|
|
|
Retrieve entries from a "credentials" dictionary |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
creds (dict[str, str]): Dictionary of credentials that contain the following keys: |
|
|
|
|
* client_secret|clientSecret: A unique secret str provided by the client |
|
|
|
|
* id_server|idServer: the domain of the identity server to query |
|
|
|
|
* id_access_token: The access token to authenticate to the identity |
|
|
|
|
server with. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
tuple(str, str, str|None): A tuple containing the client_secret, the id_server, |
|
|
|
|
and the id_access_token value if available. |
|
|
|
|
""" |
|
|
|
|
client_secret = creds.get("client_secret") or creds.get("clientSecret") |
|
|
|
|
if not client_secret: |
|
|
|
|
raise SynapseError( |
|
|
|
|
400, "No client_secret in creds", errcode=Codes.MISSING_PARAM |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
id_server = creds.get("id_server") or creds.get("idServer") |
|
|
|
|
if not id_server: |
|
|
|
|
raise SynapseError( |
|
|
|
|
400, "No id_server in creds", errcode=Codes.MISSING_PARAM |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
id_access_token = creds.get("id_access_token") |
|
|
|
|
return client_secret, id_server, id_access_token |
|
|
|
|
|
|
|
|
|
@defer.inlineCallbacks |
|
|
|
|
def threepid_from_creds(self, creds): |
|
|
|
|
if "id_server" in creds: |
|
|
|
|
id_server = creds["id_server"] |
|
|
|
|
elif "idServer" in creds: |
|
|
|
|
id_server = creds["idServer"] |
|
|
|
|
else: |
|
|
|
|
raise SynapseError(400, "No id_server in creds") |
|
|
|
|
def threepid_from_creds(self, creds, use_v2=True): |
|
|
|
|
""" |
|
|
|
|
Retrieve and validate a threepid identitier from a "credentials" dictionary |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
creds (dict[str, str]): Dictionary of credentials that contain the following keys: |
|
|
|
|
* client_secret|clientSecret: A unique secret str provided by the client |
|
|
|
|
* id_server|idServer: the domain of the identity server to query |
|
|
|
|
* id_access_token: The access token to authenticate to the identity |
|
|
|
|
server with. Required if use_v2 is true |
|
|
|
|
use_v2 (bool): Whether to use v2 Identity Service API endpoints |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
Deferred[dict[str,str|int]|None]: A dictionary consisting of response params to |
|
|
|
|
the /getValidated3pid endpoint of the Identity Service API, or None if the |
|
|
|
|
threepid was not found |
|
|
|
|
""" |
|
|
|
|
client_secret, id_server, id_access_token = self._extract_items_from_creds_dict( |
|
|
|
|
creds |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
if "client_secret" in creds: |
|
|
|
|
client_secret = creds["client_secret"] |
|
|
|
|
elif "clientSecret" in creds: |
|
|
|
|
client_secret = creds["clientSecret"] |
|
|
|
|
# If an id_access_token is not supplied, force usage of v1 |
|
|
|
|
if id_access_token is None: |
|
|
|
|
use_v2 = False |
|
|
|
|
|
|
|
|
|
query_params = {"sid": creds["sid"], "client_secret": client_secret} |
|
|
|
|
|
|
|
|
|
# Decide which API endpoint URLs and query parameters to use |
|
|
|
|
if use_v2: |
|
|
|
|
url = "https://%s%s" % ( |
|
|
|
|
id_server, |
|
|
|
|
"/_matrix/identity/v2/3pid/getValidated3pid", |
|
|
|
|
) |
|
|
|
|
query_params["id_access_token"] = id_access_token |
|
|
|
|
else: |
|
|
|
|
raise SynapseError(400, "No client_secret in creds") |
|
|
|
|
url = "https://%s%s" % ( |
|
|
|
|
id_server, |
|
|
|
|
"/_matrix/identity/api/v1/3pid/getValidated3pid", |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
if not self._should_trust_id_server(id_server): |
|
|
|
|
logger.warn( |
|
|
|
@ -85,43 +140,55 @@ class IdentityHandler(BaseHandler): |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
data = yield self.http_client.get_json( |
|
|
|
|
"https://%s%s" |
|
|
|
|
% (id_server, "/_matrix/identity/api/v1/3pid/getValidated3pid"), |
|
|
|
|
{"sid": creds["sid"], "client_secret": client_secret}, |
|
|
|
|
) |
|
|
|
|
data = yield self.http_client.get_json(url, query_params) |
|
|
|
|
return data if "medium" in data else None |
|
|
|
|
except HttpResponseException as e: |
|
|
|
|
logger.info("getValidated3pid failed with Matrix error: %r", e) |
|
|
|
|
raise e.to_synapse_error() |
|
|
|
|
if e.code != 404 or not use_v2: |
|
|
|
|
# Generic failure |
|
|
|
|
logger.info("getValidated3pid failed with Matrix error: %r", e) |
|
|
|
|
raise e.to_synapse_error() |
|
|
|
|
|
|
|
|
|
if "medium" in data: |
|
|
|
|
return data |
|
|
|
|
return None |
|
|
|
|
# This identity server is too old to understand Identity Service API v2 |
|
|
|
|
# Attempt v1 endpoint |
|
|
|
|
logger.info("Got 404 when POSTing JSON %s, falling back to v1 URL", url) |
|
|
|
|
return (yield self.threepid_from_creds(creds, use_v2=False)) |
|
|
|
|
|
|
|
|
|
@defer.inlineCallbacks |
|
|
|
|
def bind_threepid(self, creds, mxid): |
|
|
|
|
def bind_threepid(self, creds, mxid, use_v2=True): |
|
|
|
|
"""Bind a 3PID to an identity server |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
creds (dict[str, str]): Dictionary of credentials that contain the following keys: |
|
|
|
|
* client_secret|clientSecret: A unique secret str provided by the client |
|
|
|
|
* id_server|idServer: the domain of the identity server to query |
|
|
|
|
* id_access_token: The access token to authenticate to the identity |
|
|
|
|
server with. Required if use_v2 is true |
|
|
|
|
mxid (str): The MXID to bind the 3PID to |
|
|
|
|
use_v2 (bool): Whether to use v2 Identity Service API endpoints |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
Deferred[dict]: The response from the identity server |
|
|
|
|
""" |
|
|
|
|
logger.debug("binding threepid %r to %s", creds, mxid) |
|
|
|
|
data = None |
|
|
|
|
|
|
|
|
|
if "id_server" in creds: |
|
|
|
|
id_server = creds["id_server"] |
|
|
|
|
elif "idServer" in creds: |
|
|
|
|
id_server = creds["idServer"] |
|
|
|
|
else: |
|
|
|
|
raise SynapseError(400, "No id_server in creds") |
|
|
|
|
client_secret, id_server, id_access_token = self._extract_items_from_creds_dict( |
|
|
|
|
creds |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# If an id_access_token is not supplied, force usage of v1 |
|
|
|
|
if id_access_token is None: |
|
|
|
|
use_v2 = False |
|
|
|
|
|
|
|
|
|
if "client_secret" in creds: |
|
|
|
|
client_secret = creds["client_secret"] |
|
|
|
|
elif "clientSecret" in creds: |
|
|
|
|
client_secret = creds["clientSecret"] |
|
|
|
|
# Decide which API endpoint URLs to use |
|
|
|
|
bind_data = {"sid": creds["sid"], "client_secret": client_secret, "mxid": mxid} |
|
|
|
|
if use_v2: |
|
|
|
|
bind_url = "https://%s/_matrix/identity/v2/3pid/bind" % (id_server,) |
|
|
|
|
bind_data["id_access_token"] = id_access_token |
|
|
|
|
else: |
|
|
|
|
raise SynapseError(400, "No client_secret in creds") |
|
|
|
|
bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server,) |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
data = yield self.http_client.post_json_get_json( |
|
|
|
|
"https://%s%s" % (id_server, "/_matrix/identity/api/v1/3pid/bind"), |
|
|
|
|
{"sid": creds["sid"], "client_secret": client_secret, "mxid": mxid}, |
|
|
|
|
) |
|
|
|
|
data = yield self.http_client.post_json_get_json(bind_url, bind_data) |
|
|
|
|
logger.debug("bound threepid %r to %s", creds, mxid) |
|
|
|
|
|
|
|
|
|
# Remember where we bound the threepid |
|
|
|
@ -131,9 +198,18 @@ class IdentityHandler(BaseHandler): |
|
|
|
|
address=data["address"], |
|
|
|
|
id_server=id_server, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
return data |
|
|
|
|
except HttpResponseException as e: |
|
|
|
|
if e.code != 404 or not use_v2: |
|
|
|
|
logger.error("3PID bind failed with Matrix error: %r", e) |
|
|
|
|
raise e.to_synapse_error() |
|
|
|
|
except CodeMessageException as e: |
|
|
|
|
data = json.loads(e.msg) # XXX WAT? |
|
|
|
|
return data |
|
|
|
|
return data |
|
|
|
|
|
|
|
|
|
logger.info("Got 404 when POSTing JSON %s, falling back to v1 URL", bind_url) |
|
|
|
|
return (yield self.bind_threepid(creds, mxid, use_v2=False)) |
|
|
|
|
|
|
|
|
|
@defer.inlineCallbacks |
|
|
|
|
def try_unbind_threepid(self, mxid, threepid): |
|
|
|
@ -189,6 +265,8 @@ class IdentityHandler(BaseHandler): |
|
|
|
|
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"]}, |
|
|
|
@ -200,7 +278,7 @@ class IdentityHandler(BaseHandler): |
|
|
|
|
auth_headers = self.federation_http_client.build_auth_headers( |
|
|
|
|
destination=None, |
|
|
|
|
method="POST", |
|
|
|
|
url_bytes="/_matrix/identity/api/v1/3pid/unbind".encode("ascii"), |
|
|
|
|
url_bytes=url_bytes, |
|
|
|
|
content=content, |
|
|
|
|
destination_is=id_server, |
|
|
|
|
) |
|
|
|
|