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

Commit 3750d83

Browse files
committed
Switch to v2 identity server api endpoints.
1 parent 478840d commit 3750d83

File tree

4 files changed

+147
-51
lines changed

4 files changed

+147
-51
lines changed

changelog.d/5892.misc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Compatibility with v2 Identity Service APIs.
1+
Compatibility with v2 Identity Service APIs other than /lookup.

contrib/cmdclient/console.py

+5
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ def do_emailrequest(self, line):
268268

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

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

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

335338
json_res = yield self.http_client.do_request(
@@ -398,6 +401,7 @@ def do_invite(self, line):
398401
@defer.inlineCallbacks
399402
def _do_invite(self, roomid, userstring):
400403
if not userstring.startswith("@") and self._is_on("complete_usernames"):
404+
# TODO: Update to use v2 Identity Service API endpoint
401405
url = self._identityServerUrl() + "/_matrix/identity/api/v1/lookup"
402406

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

409413
if "mxid" in json_res and "signatures" in json_res:
414+
# TODO: Update to use v2 Identity Service API endpoint
410415
url = (
411416
self._identityServerUrl()
412417
+ "/_matrix/identity/api/v1/pubkey/ed25519"

synapse/handlers/identity.py

+134-44
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,72 @@ def _should_trust_id_server(self, id_server):
6161
return False
6262
return True
6363

64+
def _extract_items_from_creds_dict(self, creds):
65+
"""
66+
Retrieve entries from a "credentials" dictionary
67+
68+
Args:
69+
creds (dict[str, str]): Dictionary of credentials that contain the following keys:
70+
* client_secret|clientSecret: A unique secret str provided by the client
71+
* id_server|idServer: the domain of the identity server to query
72+
* id_access_token: The access token to authenticate to the identity
73+
server with.
74+
75+
Returns:
76+
tuple(str, str, str|None): A tuple containing the client_secret, the id_server,
77+
and the id_access_token value if available.
78+
"""
79+
client_secret = creds.get("client_secret") or creds.get("clientSecret")
80+
if not client_secret:
81+
raise SynapseError(
82+
400, "No client_secret in creds", errcode=Codes.MISSING_PARAM
83+
)
84+
85+
id_server = creds.get("id_server") or creds.get("idServer")
86+
if not id_server:
87+
raise SynapseError(
88+
400, "No id_server in creds", errcode=Codes.MISSING_PARAM
89+
)
90+
91+
id_access_token = creds.get("id_access_token")
92+
return client_secret, id_server, id_access_token
93+
6494
@defer.inlineCallbacks
65-
def threepid_from_creds(self, creds):
66-
if "id_server" in creds:
67-
id_server = creds["id_server"]
68-
elif "idServer" in creds:
69-
id_server = creds["idServer"]
70-
else:
71-
raise SynapseError(400, "No id_server in creds")
95+
def threepid_from_creds(self, creds, use_v2=True):
96+
"""
97+
Retrieve and validate a threepid identitier from a "credentials" dictionary
98+
99+
Args:
100+
creds (dict[str, str]): Dictionary of credentials that contain the following keys:
101+
* client_secret|clientSecret: A unique secret str provided by the client
102+
* id_server|idServer: the domain of the identity server to query
103+
* id_access_token: The access token to authenticate to the identity
104+
server with. Required if use_v2 is true
105+
use_v2 (bool): Whether to use v2 Identity Service API endpoints
106+
107+
Returns:
108+
Deferred[dict[str,str|int]|None]: A dictionary consisting of response params to
109+
the /getValidated3pid endpoint of the Identity Service API, or None if the
110+
threepid was not found
111+
"""
112+
client_secret, id_server, id_access_token = self._extract_items_from_creds_dict(
113+
creds
114+
)
115+
116+
query_params = {"sid": creds["sid"], "client_secret": client_secret}
72117

73-
if "client_secret" in creds:
74-
client_secret = creds["client_secret"]
75-
elif "clientSecret" in creds:
76-
client_secret = creds["clientSecret"]
118+
# Decide which API endpoint URLs and query parameters to use
119+
if use_v2:
120+
url = "https://%s%s" % (
121+
id_server,
122+
"/_matrix/identity/v2/3pid/getValidated3pid",
123+
)
124+
query_params["id_access_token"] = id_access_token
77125
else:
78-
raise SynapseError(400, "No client_secret in creds")
126+
url = "https://%s%s" % (
127+
id_server,
128+
"/_matrix/identity/api/v1/3pid/getValidated3pid",
129+
)
79130

80131
if not self._should_trust_id_server(id_server):
81132
logger.warn(
@@ -85,43 +136,51 @@ def threepid_from_creds(self, creds):
85136
return None
86137

87138
try:
88-
data = yield self.http_client.get_json(
89-
"https://%s%s"
90-
% (id_server, "/_matrix/identity/api/v1/3pid/getValidated3pid"),
91-
{"sid": creds["sid"], "client_secret": client_secret},
92-
)
139+
data = yield self.http_client.get_json(url, query_params)
140+
return data if "medium" in data else None
93141
except HttpResponseException as e:
94-
logger.info("getValidated3pid failed with Matrix error: %r", e)
95-
raise e.to_synapse_error()
142+
if e.code != 404 or not use_v2:
143+
# Generic failure
144+
logger.info("getValidated3pid failed with Matrix error: %r", e)
145+
raise e.to_synapse_error()
96146

97-
if "medium" in data:
98-
return data
99-
return None
147+
# This identity server is too old to understand Identity Service API v2
148+
# Attempt v1 endpoint
149+
logger.warn("Got 404 when POSTing JSON %s, falling back to v1 URL", url)
150+
return (yield self.threepid_from_creds(creds, use_v2=False))
100151

101152
@defer.inlineCallbacks
102-
def bind_threepid(self, creds, mxid):
153+
def bind_threepid(self, creds, mxid, use_v2=True):
154+
"""Bind a 3PID to an identity server
155+
156+
Args:
157+
creds (dict[str, str]): Dictionary of credentials that contain the following keys:
158+
* client_secret|clientSecret: A unique secret str provided by the client
159+
* id_server|idServer: the domain of the identity server to query
160+
* id_access_token: The access token to authenticate to the identity
161+
server with. Required if use_v2 is true
162+
mxid (str): The MXID to bind the 3PID to
163+
use_v2 (bool): Whether to use v2 Identity Service API endpoints
164+
165+
Returns:
166+
Deferred[dict]: The response from the identity server
167+
"""
103168
logger.debug("binding threepid %r to %s", creds, mxid)
104-
data = None
105169

106-
if "id_server" in creds:
107-
id_server = creds["id_server"]
108-
elif "idServer" in creds:
109-
id_server = creds["idServer"]
110-
else:
111-
raise SynapseError(400, "No id_server in creds")
170+
client_secret, id_server, id_access_token = self._extract_items_from_creds_dict(
171+
creds
172+
)
112173

113-
if "client_secret" in creds:
114-
client_secret = creds["client_secret"]
115-
elif "clientSecret" in creds:
116-
client_secret = creds["clientSecret"]
174+
# Decide which API endpoint URLs to use
175+
bind_data = {"sid": creds["sid"], "client_secret": client_secret, "mxid": mxid}
176+
if use_v2:
177+
bind_url = "https://%s/_matrix/identity/v2/3pid/bind" % (id_server,)
178+
bind_data["id_access_token"] = id_access_token
117179
else:
118-
raise SynapseError(400, "No client_secret in creds")
180+
bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server,)
119181

120182
try:
121-
data = yield self.http_client.post_json_get_json(
122-
"https://%s%s" % (id_server, "/_matrix/identity/api/v1/3pid/bind"),
123-
{"sid": creds["sid"], "client_secret": client_secret, "mxid": mxid},
124-
)
183+
data = yield self.http_client.post_json_get_json(bind_url, bind_data)
125184
logger.debug("bound threepid %r to %s", creds, mxid)
126185

127186
# Remember where we bound the threepid
@@ -131,9 +190,18 @@ def bind_threepid(self, creds, mxid):
131190
address=data["address"],
132191
id_server=id_server,
133192
)
193+
194+
return data
195+
except HttpResponseException as e:
196+
if e.code != 404 or not use_v2:
197+
logger.error("3PID bind failed with Matrix error: %r", e)
198+
raise e.to_synapse_error()
134199
except CodeMessageException as e:
135200
data = json.loads(e.msg) # XXX WAT?
136-
return data
201+
return data
202+
203+
logger.warn("Got 404 when POSTing JSON %s, falling back to v1 URL", bind_url)
204+
return (yield self.bind_threepid(creds, mxid, use_v2=False))
137205

138206
@defer.inlineCallbacks
139207
def try_unbind_threepid(self, mxid, threepid):
@@ -172,13 +240,16 @@ def try_unbind_threepid(self, mxid, threepid):
172240
return changed
173241

174242
@defer.inlineCallbacks
175-
def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
243+
def try_unbind_threepid_with_id_server(
244+
self, mxid, threepid, id_server, use_v2=True
245+
):
176246
"""Removes a binding from an identity server
177247
178248
Args:
179249
mxid (str): Matrix user ID of binding to be removed
180250
threepid (dict): Dict with medium & address of binding to be removed
181251
id_server (str): Identity server to unbind from
252+
use_v2 (bool): Whether to use the v2 identity service unbind API
182253
183254
Raises:
184255
SynapseError: If we failed to contact the identity server
@@ -187,7 +258,14 @@ def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
187258
Deferred[bool]: True on success, otherwise False if the identity
188259
server doesn't support unbinding
189260
"""
190-
url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
261+
# First attempt the v2 endpoint
262+
if use_v2:
263+
url = "https://%s/_matrix/identity/v2/3pid/unbind" % (id_server,)
264+
url_bytes = "/_matrix/identity/v2/3pid/unbind".encode("ascii")
265+
else:
266+
url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
267+
url_bytes = "/_matrix/identity/api/v1/3pid/unbind".encode("ascii")
268+
191269
content = {
192270
"mxid": mxid,
193271
"threepid": {"medium": threepid["medium"], "address": threepid["address"]},
@@ -199,24 +277,36 @@ def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
199277
auth_headers = self.federation_http_client.build_auth_headers(
200278
destination=None,
201279
method="POST",
202-
url_bytes="/_matrix/identity/api/v1/3pid/unbind".encode("ascii"),
280+
url_bytes=url_bytes,
203281
content=content,
204282
destination_is=id_server,
205283
)
206284
headers = {b"Authorization": auth_headers}
207285

286+
v1_fallback = False
208287
try:
209288
yield self.http_client.post_json_get_json(url, content, headers)
210289
changed = True
211290
except HttpResponseException as e:
212291
changed = False
213-
if e.code in (400, 404, 501):
292+
if e.code == 404 and use_v2:
293+
# v2 is not supported yet, try again with v1
294+
v1_fallback = True
295+
elif e.code in (400, 404, 501):
214296
# The remote server probably doesn't support unbinding (yet)
215297
logger.warn("Received %d response while unbinding threepid", e.code)
216298
else:
217299
logger.error("Failed to unbind threepid on identity server: %s", e)
218300
raise SynapseError(502, "Failed to contact identity server")
219301

302+
if v1_fallback:
303+
logger.warn("Got 404 when POSTing JSON %s, falling back to v1 URL", url)
304+
return (
305+
yield self.try_unbind_threepid_with_id_server(
306+
mxid, threepid, id_server, use_v2=False
307+
)
308+
)
309+
220310
yield self.store.remove_user_bound_threepid(
221311
user_id=mxid,
222312
medium=threepid["medium"],

synapse/rest/client/v2_alpha/account.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -542,15 +542,16 @@ def on_GET(self, request):
542542
def on_POST(self, request):
543543
body = parse_json_object_from_request(request)
544544

545-
threePidCreds = body.get("threePidCreds")
546-
threePidCreds = body.get("three_pid_creds", threePidCreds)
547-
if threePidCreds is None:
548-
raise SynapseError(400, "Missing param", Codes.MISSING_PARAM)
545+
threepid_creds = body.get("threePidCreds") or body.get("three_pid_creds")
546+
if threepid_creds is None:
547+
raise SynapseError(
548+
400, "Missing param three_pid_creds", Codes.MISSING_PARAM
549+
)
549550

550551
requester = yield self.auth.get_user_by_req(request)
551552
user_id = requester.user.to_string()
552553

553-
threepid = yield self.identity_handler.threepid_from_creds(threePidCreds)
554+
threepid = yield self.identity_handler.threepid_from_creds(threepid_creds)
554555

555556
if not threepid:
556557
raise SynapseError(400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED)
@@ -566,7 +567,7 @@ def on_POST(self, request):
566567

567568
if "bind" in body and body["bind"]:
568569
logger.debug("Binding threepid %s to %s", threepid, user_id)
569-
yield self.identity_handler.bind_threepid(threePidCreds, user_id)
570+
yield self.identity_handler.bind_threepid(threepid_creds, user_id)
570571

571572
return 200, {}
572573

0 commit comments

Comments
 (0)