14
14
# limitations under the License.
15
15
16
16
import logging
17
- from typing import TYPE_CHECKING , Tuple
17
+ from typing import TYPE_CHECKING , List , Optional , Tuple
18
+
19
+ from pydantic import Extra , StrictStr
18
20
19
21
from synapse .api import errors
20
22
from synapse .api .errors import NotFoundError
21
23
from synapse .http .server import HttpServer
22
24
from synapse .http .servlet import (
23
25
RestServlet ,
24
- assert_params_in_dict ,
25
- parse_json_object_from_request ,
26
+ parse_and_validate_json_object_from_request ,
26
27
)
27
28
from synapse .http .site import SynapseRequest
28
29
from synapse .rest .client ._base import client_patterns , interactive_auth_handler
30
+ from synapse .rest .client .models import AuthenticationData
31
+ from synapse .rest .models import RequestBodyModel
29
32
from synapse .types import JsonDict
30
33
31
34
if TYPE_CHECKING :
@@ -80,35 +83,37 @@ def __init__(self, hs: "HomeServer"):
80
83
self .device_handler = hs .get_device_handler ()
81
84
self .auth_handler = hs .get_auth_handler ()
82
85
86
+ class PostBody (RequestBodyModel ):
87
+ auth : Optional [AuthenticationData ]
88
+ devices : List [StrictStr ]
89
+
83
90
@interactive_auth_handler
84
91
async def on_POST (self , request : SynapseRequest ) -> Tuple [int , JsonDict ]:
85
92
requester = await self .auth .get_user_by_req (request )
86
93
87
94
try :
88
- body = parse_json_object_from_request (request )
95
+ body = parse_and_validate_json_object_from_request (request , self . PostBody )
89
96
except errors .SynapseError as e :
90
97
if e .errcode == errors .Codes .NOT_JSON :
91
- # DELETE
98
+ # TODO: Can/should we remove this fallback now?
92
99
# deal with older clients which didn't pass a JSON dict
93
100
# the same as those that pass an empty dict
94
- body = {}
101
+ body = self . PostBody . parse_obj ({})
95
102
else :
96
103
raise e
97
104
98
- assert_params_in_dict (body , ["devices" ])
99
-
100
105
await self .auth_handler .validate_user_via_ui_auth (
101
106
requester ,
102
107
request ,
103
- body ,
108
+ body . dict ( exclude_unset = True ) ,
104
109
"remove device(s) from your account" ,
105
110
# Users might call this multiple times in a row while cleaning up
106
111
# devices, allow a single UI auth session to be re-used.
107
112
can_skip_ui_auth = True ,
108
113
)
109
114
110
115
await self .device_handler .delete_devices (
111
- requester .user .to_string (), body [ " devices" ]
116
+ requester .user .to_string (), body . devices
112
117
)
113
118
return 200 , {}
114
119
@@ -147,27 +152,31 @@ async def on_GET(
147
152
148
153
return 200 , device
149
154
155
+ class DeleteBody (RequestBodyModel ):
156
+ auth : Optional [AuthenticationData ]
157
+
150
158
@interactive_auth_handler
151
159
async def on_DELETE (
152
160
self , request : SynapseRequest , device_id : str
153
161
) -> Tuple [int , JsonDict ]:
154
162
requester = await self .auth .get_user_by_req (request )
155
163
156
164
try :
157
- body = parse_json_object_from_request (request )
165
+ body = parse_and_validate_json_object_from_request (request , self . DeleteBody )
158
166
159
167
except errors .SynapseError as e :
160
168
if e .errcode == errors .Codes .NOT_JSON :
169
+ # TODO: can/should we remove this fallback now?
161
170
# deal with older clients which didn't pass a JSON dict
162
171
# the same as those that pass an empty dict
163
- body = {}
172
+ body = self . DeleteBody . parse_obj ({})
164
173
else :
165
174
raise
166
175
167
176
await self .auth_handler .validate_user_via_ui_auth (
168
177
requester ,
169
178
request ,
170
- body ,
179
+ body . dict ( exclude_unset = True ) ,
171
180
"remove a device from your account" ,
172
181
# Users might call this multiple times in a row while cleaning up
173
182
# devices, allow a single UI auth session to be re-used.
@@ -179,18 +188,33 @@ async def on_DELETE(
179
188
)
180
189
return 200 , {}
181
190
191
+ class PutBody (RequestBodyModel ):
192
+ display_name : Optional [StrictStr ]
193
+
182
194
async def on_PUT (
183
195
self , request : SynapseRequest , device_id : str
184
196
) -> Tuple [int , JsonDict ]:
185
197
requester = await self .auth .get_user_by_req (request , allow_guest = True )
186
198
187
- body = parse_json_object_from_request (request )
199
+ body = parse_and_validate_json_object_from_request (request , self . PutBody )
188
200
await self .device_handler .update_device (
189
- requester .user .to_string (), device_id , body
201
+ requester .user .to_string (), device_id , body . dict ()
190
202
)
191
203
return 200 , {}
192
204
193
205
206
+ class DehydratedDeviceDataModel (RequestBodyModel ):
207
+ """JSON blob describing a dehydrated device to be stored.
208
+
209
+ Expects other freeform fields. Use .dict() to access them.
210
+ """
211
+
212
+ class Config :
213
+ extra = Extra .allow
214
+
215
+ algorithm : StrictStr
216
+
217
+
194
218
class DehydratedDeviceServlet (RestServlet ):
195
219
"""Retrieve or store a dehydrated device.
196
220
@@ -246,27 +270,19 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
246
270
else :
247
271
raise errors .NotFoundError ("No dehydrated device available" )
248
272
273
+ class PutBody (RequestBodyModel ):
274
+ device_id : StrictStr
275
+ device_data : DehydratedDeviceDataModel
276
+ initial_device_display_name : Optional [StrictStr ]
277
+
249
278
async def on_PUT (self , request : SynapseRequest ) -> Tuple [int , JsonDict ]:
250
- submission = parse_json_object_from_request (request )
279
+ submission = parse_and_validate_json_object_from_request (request , self . PutBody )
251
280
requester = await self .auth .get_user_by_req (request )
252
281
253
- if "device_data" not in submission :
254
- raise errors .SynapseError (
255
- 400 ,
256
- "device_data missing" ,
257
- errcode = errors .Codes .MISSING_PARAM ,
258
- )
259
- elif not isinstance (submission ["device_data" ], dict ):
260
- raise errors .SynapseError (
261
- 400 ,
262
- "device_data must be an object" ,
263
- errcode = errors .Codes .INVALID_PARAM ,
264
- )
265
-
266
282
device_id = await self .device_handler .store_dehydrated_device (
267
283
requester .user .to_string (),
268
- submission [ " device_data" ] ,
269
- submission .get ( " initial_device_display_name" , None ) ,
284
+ submission . device_data ,
285
+ submission .initial_device_display_name ,
270
286
)
271
287
return 200 , {"device_id" : device_id }
272
288
@@ -300,28 +316,18 @@ def __init__(self, hs: "HomeServer"):
300
316
self .auth = hs .get_auth ()
301
317
self .device_handler = hs .get_device_handler ()
302
318
319
+ class PostBody (RequestBodyModel ):
320
+ device_id : StrictStr
321
+
303
322
async def on_POST (self , request : SynapseRequest ) -> Tuple [int , JsonDict ]:
304
323
requester = await self .auth .get_user_by_req (request )
305
324
306
- submission = parse_json_object_from_request (request )
307
-
308
- if "device_id" not in submission :
309
- raise errors .SynapseError (
310
- 400 ,
311
- "device_id missing" ,
312
- errcode = errors .Codes .MISSING_PARAM ,
313
- )
314
- elif not isinstance (submission ["device_id" ], str ):
315
- raise errors .SynapseError (
316
- 400 ,
317
- "device_id must be a string" ,
318
- errcode = errors .Codes .INVALID_PARAM ,
319
- )
325
+ submission = parse_and_validate_json_object_from_request (request , self .PostBody )
320
326
321
327
result = await self .device_handler .rehydrate_device (
322
328
requester .user .to_string (),
323
329
self .auth .get_access_token_from_request (request ),
324
- submission [ " device_id" ] ,
330
+ submission . device_id ,
325
331
)
326
332
327
333
return 200 , result
0 commit comments