@@ -61,21 +61,72 @@ def _should_trust_id_server(self, id_server):
61
61
return False
62
62
return True
63
63
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
+
64
94
@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 }
72
117
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
77
125
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
+ )
79
130
80
131
if not self ._should_trust_id_server (id_server ):
81
132
logger .warn (
@@ -85,43 +136,51 @@ def threepid_from_creds(self, creds):
85
136
return None
86
137
87
138
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
93
141
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 ()
96
146
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 ))
100
151
101
152
@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
+ """
103
168
logger .debug ("binding threepid %r to %s" , creds , mxid )
104
- data = None
105
169
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
+ )
112
173
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
117
179
else :
118
- raise SynapseError ( 400 , "No client_secret in creds" )
180
+ bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % ( id_server , )
119
181
120
182
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 )
125
184
logger .debug ("bound threepid %r to %s" , creds , mxid )
126
185
127
186
# Remember where we bound the threepid
@@ -131,9 +190,18 @@ def bind_threepid(self, creds, mxid):
131
190
address = data ["address" ],
132
191
id_server = id_server ,
133
192
)
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 ()
134
199
except CodeMessageException as e :
135
200
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 ))
137
205
138
206
@defer .inlineCallbacks
139
207
def try_unbind_threepid (self , mxid , threepid ):
@@ -172,13 +240,16 @@ def try_unbind_threepid(self, mxid, threepid):
172
240
return changed
173
241
174
242
@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
+ ):
176
246
"""Removes a binding from an identity server
177
247
178
248
Args:
179
249
mxid (str): Matrix user ID of binding to be removed
180
250
threepid (dict): Dict with medium & address of binding to be removed
181
251
id_server (str): Identity server to unbind from
252
+ use_v2 (bool): Whether to use the v2 identity service unbind API
182
253
183
254
Raises:
184
255
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):
187
258
Deferred[bool]: True on success, otherwise False if the identity
188
259
server doesn't support unbinding
189
260
"""
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
+
191
269
content = {
192
270
"mxid" : mxid ,
193
271
"threepid" : {"medium" : threepid ["medium" ], "address" : threepid ["address" ]},
@@ -199,24 +277,36 @@ def try_unbind_threepid_with_id_server(self, mxid, threepid, id_server):
199
277
auth_headers = self .federation_http_client .build_auth_headers (
200
278
destination = None ,
201
279
method = "POST" ,
202
- url_bytes = "/_matrix/identity/api/v1/3pid/unbind" . encode ( "ascii" ) ,
280
+ url_bytes = url_bytes ,
203
281
content = content ,
204
282
destination_is = id_server ,
205
283
)
206
284
headers = {b"Authorization" : auth_headers }
207
285
286
+ v1_fallback = False
208
287
try :
209
288
yield self .http_client .post_json_get_json (url , content , headers )
210
289
changed = True
211
290
except HttpResponseException as e :
212
291
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 ):
214
296
# The remote server probably doesn't support unbinding (yet)
215
297
logger .warn ("Received %d response while unbinding threepid" , e .code )
216
298
else :
217
299
logger .error ("Failed to unbind threepid on identity server: %s" , e )
218
300
raise SynapseError (502 , "Failed to contact identity server" )
219
301
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
+
220
310
yield self .store .remove_user_bound_threepid (
221
311
user_id = mxid ,
222
312
medium = threepid ["medium" ],
0 commit comments