Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

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

Merged
merged 5 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5892.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Compatibility with v2 Identity Service APIs other than /lookup.
5 changes: 5 additions & 0 deletions contrib/cmdclient/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ def do_emailrequest(self, line):

@defer.inlineCallbacks
def _do_emailrequest(self, args):
# TODO: Update to use v2 Identity Service API endpoint
url = (
self._identityServerUrl()
+ "/_matrix/identity/api/v1/validate/email/requestToken"
Expand Down Expand Up @@ -302,6 +303,7 @@ def do_emailvalidate(self, line):

@defer.inlineCallbacks
def _do_emailvalidate(self, args):
# TODO: Update to use v2 Identity Service API endpoint
url = (
self._identityServerUrl()
+ "/_matrix/identity/api/v1/validate/email/submitToken"
Expand Down Expand Up @@ -330,6 +332,7 @@ def do_3pidbind(self, line):

@defer.inlineCallbacks
def _do_3pidbind(self, args):
# TODO: Update to use v2 Identity Service API endpoint
url = self._identityServerUrl() + "/_matrix/identity/api/v1/3pid/bind"

json_res = yield self.http_client.do_request(
Expand Down Expand Up @@ -398,6 +401,7 @@ def do_invite(self, line):
@defer.inlineCallbacks
def _do_invite(self, roomid, userstring):
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"

json_res = yield self.http_client.do_request(
Expand All @@ -407,6 +411,7 @@ def _do_invite(self, roomid, userstring):
mxid = None

if "mxid" in json_res and "signatures" in json_res:
# TODO: Update to use v2 Identity Service API endpoint
url = (
self._identityServerUrl()
+ "/_matrix/identity/api/v1/pubkey/ed25519"
Expand Down
160 changes: 119 additions & 41 deletions synapse/handlers/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,76 @@ def _should_trust_id_server(self, id_server):
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")
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
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:
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
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(
Expand All @@ -85,43 +140,55 @@ def threepid_from_creds(self, creds):
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)
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
logger.debug("bound threepid %r to %s", creds, mxid)

# Remember where we bound the threepid
Expand All @@ -131,9 +198,18 @@ def bind_threepid(self, creds, mxid):
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):
Expand Down Expand Up @@ -188,6 +264,8 @@ def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
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"]},
Expand All @@ -199,7 +277,7 @@ def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
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,
)
Expand Down
13 changes: 7 additions & 6 deletions synapse/rest/client/v2_alpha/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,15 +542,16 @@ def on_GET(self, request):
def on_POST(self, request):
body = parse_json_object_from_request(request)

threePidCreds = body.get("threePidCreds")
threePidCreds = body.get("three_pid_creds", threePidCreds)
if threePidCreds is None:
raise SynapseError(400, "Missing param", Codes.MISSING_PARAM)
threepid_creds = body.get("threePidCreds") or body.get("three_pid_creds")
if threepid_creds is None:
raise SynapseError(
400, "Missing param three_pid_creds", Codes.MISSING_PARAM
)

requester = yield self.auth.get_user_by_req(request)
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:
raise SynapseError(400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED)
Expand All @@ -566,7 +567,7 @@ def on_POST(self, request):

if "bind" in body and body["bind"]:
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, {}

Expand Down