Switch to using v2 Identity Service APIs other than lookup (MSC 2140) (#5892)

This commit is contained in:
Andrew Morgan 2019-09-05 14:31:22 +01:00 committed by GitHub
parent b9cfd3c375
commit a0d294c306
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 48 deletions

1
changelog.d/5892.misc Normal file
View file

@ -0,0 +1 @@
Compatibility with v2 Identity Service APIs other than /lookup.

View file

@ -268,6 +268,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks @defer.inlineCallbacks
def _do_emailrequest(self, args): def _do_emailrequest(self, args):
# TODO: Update to use v2 Identity Service API endpoint
url = ( url = (
self._identityServerUrl() self._identityServerUrl()
+ "/_matrix/identity/api/v1/validate/email/requestToken" + "/_matrix/identity/api/v1/validate/email/requestToken"
@ -302,6 +303,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks @defer.inlineCallbacks
def _do_emailvalidate(self, args): def _do_emailvalidate(self, args):
# TODO: Update to use v2 Identity Service API endpoint
url = ( url = (
self._identityServerUrl() self._identityServerUrl()
+ "/_matrix/identity/api/v1/validate/email/submitToken" + "/_matrix/identity/api/v1/validate/email/submitToken"
@ -330,6 +332,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks @defer.inlineCallbacks
def _do_3pidbind(self, args): def _do_3pidbind(self, args):
# TODO: Update to use v2 Identity Service API endpoint
url = self._identityServerUrl() + "/_matrix/identity/api/v1/3pid/bind" url = self._identityServerUrl() + "/_matrix/identity/api/v1/3pid/bind"
json_res = yield self.http_client.do_request( json_res = yield self.http_client.do_request(
@ -398,6 +401,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks @defer.inlineCallbacks
def _do_invite(self, roomid, userstring): def _do_invite(self, roomid, userstring):
if not userstring.startswith("@") and self._is_on("complete_usernames"): if not userstring.startswith("@") and self._is_on("complete_usernames"):
# TODO: Update to use v2 Identity Service API endpoint
url = self._identityServerUrl() + "/_matrix/identity/api/v1/lookup" url = self._identityServerUrl() + "/_matrix/identity/api/v1/lookup"
json_res = yield self.http_client.do_request( json_res = yield self.http_client.do_request(
@ -407,6 +411,7 @@ class SynapseCmd(cmd.Cmd):
mxid = None mxid = None
if "mxid" in json_res and "signatures" in json_res: if "mxid" in json_res and "signatures" in json_res:
# TODO: Update to use v2 Identity Service API endpoint
url = ( url = (
self._identityServerUrl() self._identityServerUrl()
+ "/_matrix/identity/api/v1/pubkey/ed25519" + "/_matrix/identity/api/v1/pubkey/ed25519"

View file

@ -61,21 +61,76 @@ class IdentityHandler(BaseHandler):
return False return False
return True return True
@defer.inlineCallbacks def _extract_items_from_creds_dict(self, creds):
def threepid_from_creds(self, creds): """
if "id_server" in creds: Retrieve entries from a "credentials" dictionary
id_server = creds["id_server"]
elif "idServer" in creds:
id_server = creds["idServer"]
else:
raise SynapseError(400, "No id_server in creds")
if "client_secret" in creds: Args:
client_secret = creds["client_secret"] creds (dict[str, str]): Dictionary of credentials that contain the following keys:
elif "clientSecret" in creds: * client_secret|clientSecret: A unique secret str provided by the client
client_secret = creds["clientSecret"] * 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, 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 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: 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): if not self._should_trust_id_server(id_server):
logger.warn( logger.warn(
@ -85,43 +140,55 @@ class IdentityHandler(BaseHandler):
return None return None
try: try:
data = yield self.http_client.get_json( data = yield self.http_client.get_json(url, query_params)
"https://%s%s" return data if "medium" in data else None
% (id_server, "/_matrix/identity/api/v1/3pid/getValidated3pid"),
{"sid": creds["sid"], "client_secret": client_secret},
)
except HttpResponseException as e: except HttpResponseException as e:
logger.info("getValidated3pid failed with Matrix error: %r", e) if e.code != 404 or not use_v2:
raise e.to_synapse_error() # Generic failure
logger.info("getValidated3pid failed with Matrix error: %r", e)
raise e.to_synapse_error()
if "medium" in data: # This identity server is too old to understand Identity Service API v2
return data # Attempt v1 endpoint
return None 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 @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) logger.debug("binding threepid %r to %s", creds, mxid)
data = None
if "id_server" in creds: client_secret, id_server, id_access_token = self._extract_items_from_creds_dict(
id_server = creds["id_server"] creds
elif "idServer" in creds: )
id_server = creds["idServer"]
else:
raise SynapseError(400, "No id_server in creds")
if "client_secret" in creds: # If an id_access_token is not supplied, force usage of v1
client_secret = creds["client_secret"] if id_access_token is None:
elif "clientSecret" in creds: use_v2 = False
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: else:
raise SynapseError(400, "No client_secret in creds") bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server,)
try: try:
data = yield self.http_client.post_json_get_json( data = yield self.http_client.post_json_get_json(bind_url, bind_data)
"https://%s%s" % (id_server, "/_matrix/identity/api/v1/3pid/bind"),
{"sid": creds["sid"], "client_secret": client_secret, "mxid": mxid},
)
logger.debug("bound threepid %r to %s", creds, mxid) logger.debug("bound threepid %r to %s", creds, mxid)
# Remember where we bound the threepid # Remember where we bound the threepid
@ -131,9 +198,18 @@ class IdentityHandler(BaseHandler):
address=data["address"], address=data["address"],
id_server=id_server, 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: except CodeMessageException as e:
data = json.loads(e.msg) # XXX WAT? 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 @defer.inlineCallbacks
def try_unbind_threepid(self, mxid, threepid): def try_unbind_threepid(self, mxid, threepid):
@ -189,6 +265,8 @@ class IdentityHandler(BaseHandler):
server doesn't support unbinding server doesn't support unbinding
""" """
url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,) url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
url_bytes = "/_matrix/identity/api/v1/3pid/unbind".encode("ascii")
content = { content = {
"mxid": mxid, "mxid": mxid,
"threepid": {"medium": threepid["medium"], "address": threepid["address"]}, "threepid": {"medium": threepid["medium"], "address": threepid["address"]},
@ -200,7 +278,7 @@ class IdentityHandler(BaseHandler):
auth_headers = self.federation_http_client.build_auth_headers( auth_headers = self.federation_http_client.build_auth_headers(
destination=None, destination=None,
method="POST", method="POST",
url_bytes="/_matrix/identity/api/v1/3pid/unbind".encode("ascii"), url_bytes=url_bytes,
content=content, content=content,
destination_is=id_server, destination_is=id_server,
) )

View file

@ -542,15 +542,16 @@ class ThreepidRestServlet(RestServlet):
def on_POST(self, request): def on_POST(self, request):
body = parse_json_object_from_request(request) body = parse_json_object_from_request(request)
threePidCreds = body.get("threePidCreds") threepid_creds = body.get("threePidCreds") or body.get("three_pid_creds")
threePidCreds = body.get("three_pid_creds", threePidCreds) if threepid_creds is None:
if threePidCreds is None: raise SynapseError(
raise SynapseError(400, "Missing param", Codes.MISSING_PARAM) 400, "Missing param three_pid_creds", Codes.MISSING_PARAM
)
requester = yield self.auth.get_user_by_req(request) requester = yield self.auth.get_user_by_req(request)
user_id = requester.user.to_string() user_id = requester.user.to_string()
threepid = yield self.identity_handler.threepid_from_creds(threePidCreds) threepid = yield self.identity_handler.threepid_from_creds(threepid_creds)
if not threepid: if not threepid:
raise SynapseError(400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED) raise SynapseError(400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED)
@ -566,7 +567,7 @@ class ThreepidRestServlet(RestServlet):
if "bind" in body and body["bind"]: if "bind" in body and body["bind"]:
logger.debug("Binding threepid %s to %s", threepid, user_id) logger.debug("Binding threepid %s to %s", threepid, user_id)
yield self.identity_handler.bind_threepid(threePidCreds, user_id) yield self.identity_handler.bind_threepid(threepid_creds, user_id)
return 200, {} return 200, {}