14
14
# limitations under the License.
15
15
16
16
import logging
17
+ from http import HTTPStatus
17
18
from typing import TYPE_CHECKING , List , Optional , Tuple
18
19
19
20
from pydantic import Extra , StrictStr
20
21
21
22
from synapse .api import errors
22
- from synapse .api .errors import NotFoundError , UnrecognizedRequestError
23
+ from synapse .api .errors import NotFoundError , SynapseError , UnrecognizedRequestError
23
24
from synapse .handlers .device import DeviceHandler
24
25
from synapse .http .server import HttpServer
25
26
from synapse .http .servlet import (
28
29
parse_integer ,
29
30
)
30
31
from synapse .http .site import SynapseRequest
32
+ from synapse .replication .http .devices import ReplicationUploadKeysForUserRestServlet
31
33
from synapse .rest .client ._base import client_patterns , interactive_auth_handler
32
34
from synapse .rest .client .models import AuthenticationData
33
35
from synapse .rest .models import RequestBodyModel
@@ -230,7 +232,7 @@ class Config:
230
232
class DehydratedDeviceServlet (RestServlet ):
231
233
"""Retrieve or store a dehydrated device.
232
234
233
- Implements either MSC2697 and MSC3814.
235
+ Implements either MSC2697 or MSC3814.
234
236
235
237
GET /org.matrix.msc2697.v2/dehydrated_device
236
238
@@ -301,6 +303,7 @@ async def on_PUT(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
301
303
302
304
device_id = await self .device_handler .store_dehydrated_device (
303
305
requester .user .to_string (),
306
+ None ,
304
307
submission .device_data .dict (),
305
308
submission .initial_device_display_name ,
306
309
)
@@ -390,6 +393,175 @@ async def on_POST(
390
393
return 200 , msgs
391
394
392
395
396
+ class DehydratedDeviceV2Servlet (RestServlet ):
397
+ """Upload, retrieve, or delete a dehydrated device.
398
+
399
+ GET /org.matrix.msc3814.v1/dehydrated_device
400
+
401
+ HTTP/1.1 200 OK
402
+ Content-Type: application/json
403
+
404
+ {
405
+ "device_id": "dehydrated_device_id",
406
+ "device_data": {
407
+ "algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
408
+ "account": "dehydrated_device"
409
+ }
410
+ }
411
+
412
+ PUT /org.matrix.msc3814.v1/dehydrated_device
413
+ Content-Type: application/json
414
+
415
+ {
416
+ "device_id": "dehydrated_device_id",
417
+ "device_data": {
418
+ "algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
419
+ "account": "dehydrated_device"
420
+ },
421
+ "device_keys": {
422
+ "user_id": "<user_id>",
423
+ "device_id": "<device_id>",
424
+ "valid_until_ts": <millisecond_timestamp>,
425
+ "algorithms": [
426
+ "m.olm.curve25519-aes-sha2",
427
+ ]
428
+ "keys": {
429
+ "<algorithm>:<device_id>": "<key_base64>",
430
+ },
431
+ "signatures:" {
432
+ "<user_id>" {
433
+ "<algorithm>:<device_id>": "<signature_base64>"
434
+ }
435
+ }
436
+ },
437
+ "fallback_keys": {
438
+ "<algorithm>:<device_id>": "<key_base64>",
439
+ "signed_<algorithm>:<device_id>": {
440
+ "fallback": true,
441
+ "key": "<key_base64>",
442
+ "signatures": {
443
+ "<user_id>": {
444
+ "<algorithm>:<device_id>": "<key_base64>"
445
+ }
446
+ }
447
+ }
448
+ }
449
+ "one_time_keys": {
450
+ "<algorithm>:<key_id>": "<key_base64>"
451
+ },
452
+
453
+ }
454
+
455
+ HTTP/1.1 200 OK
456
+ Content-Type: application/json
457
+
458
+ {
459
+ "device_id": "dehydrated_device_id"
460
+ }
461
+
462
+ DELETE /org.matrix.msc3814.v1/dehydrated_device
463
+
464
+ HTTP/1.1 200 OK
465
+ Content-Type: application/json
466
+
467
+ {
468
+ "device_id": "dehydrated_device_id",
469
+ }
470
+ """
471
+
472
+ PATTERNS = [
473
+ * client_patterns ("/org.matrix.msc3814.v1/dehydrated_device$" , releases = ()),
474
+ ]
475
+
476
+ def __init__ (self , hs : "HomeServer" ):
477
+ super ().__init__ ()
478
+ self .hs = hs
479
+ self .auth = hs .get_auth ()
480
+ handler = hs .get_device_handler ()
481
+ assert isinstance (handler , DeviceHandler )
482
+ self .e2e_keys_handler = hs .get_e2e_keys_handler ()
483
+ self .device_handler = handler
484
+
485
+ if hs .config .worker .worker_app is None :
486
+ # if main process
487
+ self .key_uploader = self .e2e_keys_handler .upload_keys_for_user
488
+ else :
489
+ # then a worker
490
+ self .key_uploader = ReplicationUploadKeysForUserRestServlet .make_client (hs )
491
+
492
+ async def on_GET (self , request : SynapseRequest ) -> Tuple [int , JsonDict ]:
493
+ requester = await self .auth .get_user_by_req (request )
494
+
495
+ dehydrated_device = await self .device_handler .get_dehydrated_device (
496
+ requester .user .to_string ()
497
+ )
498
+
499
+ if dehydrated_device is not None :
500
+ (device_id , device_data ) = dehydrated_device
501
+ result = {"device_id" : device_id , "device_data" : device_data }
502
+ return 200 , result
503
+ else :
504
+ raise errors .NotFoundError ("No dehydrated device available" )
505
+
506
+ async def on_DELETE (self , request : SynapseRequest ) -> Tuple [int , JsonDict ]:
507
+ requester = await self .auth .get_user_by_req (request )
508
+
509
+ dehydrated_device = await self .device_handler .get_dehydrated_device (
510
+ requester .user .to_string ()
511
+ )
512
+
513
+ if dehydrated_device is not None :
514
+ (device_id , device_data ) = dehydrated_device
515
+
516
+ result = await self .device_handler .rehydrate_device (
517
+ requester .user .to_string (),
518
+ self .auth .get_access_token_from_request (request ),
519
+ device_id ,
520
+ )
521
+
522
+ result = {"device_id" : device_id }
523
+
524
+ return 200 , result
525
+ else :
526
+ raise errors .NotFoundError ("No dehydrated device available" )
527
+
528
+ class PutBody (RequestBodyModel ):
529
+ device_data : DehydratedDeviceDataModel
530
+ device_id : StrictStr
531
+ initial_device_display_name : Optional [StrictStr ]
532
+
533
+ class Config :
534
+ extra = Extra .allow
535
+
536
+ async def on_PUT (self , request : SynapseRequest ) -> Tuple [int , JsonDict ]:
537
+ submission = parse_and_validate_json_object_from_request (request , self .PutBody )
538
+ requester = await self .auth .get_user_by_req (request )
539
+ user_id = requester .user .to_string ()
540
+
541
+ device_info = submission .dict ()
542
+ if "device_keys" not in device_info .keys ():
543
+ raise SynapseError (
544
+ HTTPStatus .BAD_REQUEST ,
545
+ "Device key(s) not found, these must be provided." ,
546
+ )
547
+
548
+ # TODO: Those two operations, creating a device and storing the
549
+ # device's keys should be atomic.
550
+ device_id = await self .device_handler .store_dehydrated_device (
551
+ requester .user .to_string (),
552
+ submission .device_id ,
553
+ submission .device_data .dict (),
554
+ submission .initial_device_display_name ,
555
+ )
556
+
557
+ # TODO: Do we need to do something with the result here?
558
+ await self .key_uploader (
559
+ user_id = user_id , device_id = submission .device_id , keys = submission .dict ()
560
+ )
561
+
562
+ return 200 , {"device_id" : device_id }
563
+
564
+
393
565
def register_servlets (hs : "HomeServer" , http_server : HttpServer ) -> None :
394
566
if (
395
567
hs .config .worker .worker_app is None
@@ -404,5 +576,5 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
404
576
DehydratedDeviceServlet (hs , msc2697 = True ).register (http_server )
405
577
ClaimDehydratedDeviceServlet (hs ).register (http_server )
406
578
if hs .config .experimental .msc3814_enabled :
407
- DehydratedDeviceServlet (hs , msc2697 = False ).register (http_server )
579
+ DehydratedDeviceV2Servlet (hs ).register (http_server )
408
580
DehydratedDeviceEventsServlet (hs ).register (http_server )
0 commit comments