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

Commit 777b305

Browse files
committed
Implement most of the endpoints for MSC3814
There's one important difference to the MSC, the PUT `/dehydrated_device` accepts the upload of one-time and device keys. This helps to ensure that a dehydrated devices is immediately E2EE enabled and has device and one-time keys. A DELETE `/dehydrated_device` endpoint was also added.
1 parent 0f49f81 commit 777b305

File tree

2 files changed

+170
-2
lines changed

2 files changed

+170
-2
lines changed

synapse/handlers/device.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ async def notify_user_signature_update(
653653
async def store_dehydrated_device(
654654
self,
655655
user_id: str,
656+
device_id: Optional[str],
656657
device_data: JsonDict,
657658
initial_device_display_name: Optional[str] = None,
658659
) -> str:
@@ -661,14 +662,15 @@ async def store_dehydrated_device(
661662
662663
Args:
663664
user_id: the user that we are storing the device for
665+
device_id: device id supplied by client
664666
device_data: the dehydrated device information
665667
initial_device_display_name: The display name to use for the device
666668
Returns:
667669
device id of the dehydrated device
668670
"""
669671
device_id = await self.check_device_registered(
670672
user_id,
671-
None,
673+
device_id,
672674
initial_device_display_name,
673675
)
674676
old_device_id = await self.store.store_dehydrated_device(

synapse/rest/client/devices.py

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
parse_integer,
2929
)
3030
from synapse.http.site import SynapseRequest
31+
from synapse.replication.http.devices import ReplicationUploadKeysForUserRestServlet
3132
from synapse.rest.client._base import client_patterns, interactive_auth_handler
3233
from synapse.rest.client.models import AuthenticationData
3334
from synapse.rest.models import RequestBodyModel
@@ -301,6 +302,7 @@ async def on_PUT(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
301302

302303
device_id = await self.device_handler.store_dehydrated_device(
303304
requester.user.to_string(),
305+
None,
304306
submission.device_data.dict(),
305307
submission.initial_device_display_name,
306308
)
@@ -390,6 +392,170 @@ async def on_POST(
390392
return 200, msgs
391393

392394

395+
class DehydratedDeviceV2Servlet(RestServlet):
396+
"""Upload, retrieve, or delete a dehydrated device.
397+
398+
GET /org.matrix.msc3814.v1/dehydrated_device
399+
400+
HTTP/1.1 200 OK
401+
Content-Type: application/json
402+
403+
{
404+
"device_id": "dehydrated_device_id",
405+
"device_data": {
406+
"algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
407+
"account": "dehydrated_device"
408+
}
409+
}
410+
411+
PUT /org.matrix.msc3814.v1/dehydrated_device
412+
Content-Type: application/json
413+
414+
{
415+
"device_id": "dehydrated_device_id",
416+
"device_data": {
417+
"algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
418+
"account": "dehydrated_device"
419+
},
420+
"device_keys": {
421+
"user_id": "<user_id>",
422+
"device_id": "<device_id>",
423+
"valid_until_ts": <millisecond_timestamp>,
424+
"algorithms": [
425+
"m.olm.curve25519-aes-sha2",
426+
]
427+
"keys": {
428+
"<algorithm>:<device_id>": "<key_base64>",
429+
},
430+
"signatures:" {
431+
"<user_id>" {
432+
"<algorithm>:<device_id>": "<signature_base64>"
433+
}
434+
}
435+
},
436+
"fallback_keys": {
437+
"<algorithm>:<device_id>": "<key_base64>",
438+
"signed_<algorithm>:<device_id>": {
439+
"fallback": true,
440+
"key": "<key_base64>",
441+
"signatures": {
442+
"<user_id>": {
443+
"<algorithm>:<device_id>": "<key_base64>"
444+
}
445+
}
446+
}
447+
}
448+
"one_time_keys": {
449+
"<algorithm>:<key_id>": "<key_base64>"
450+
},
451+
452+
}
453+
454+
HTTP/1.1 200 OK
455+
Content-Type: application/json
456+
457+
{
458+
"device_id": "dehydrated_device_id"
459+
}
460+
461+
DELETE /org.matrix.msc3814.v1/dehydrated_device
462+
463+
HTTP/1.1 200 OK
464+
Content-Type: application/json
465+
466+
{
467+
"device_id": "dehydrated_device_id",
468+
}
469+
"""
470+
471+
PATTERNS = [
472+
*client_patterns("/org.matrix.msc3814.v1/dehydrated_device$", releases=()),
473+
]
474+
475+
def __init__(self, hs: "HomeServer"):
476+
super().__init__()
477+
self.hs = hs
478+
self.auth = hs.get_auth()
479+
handler = hs.get_device_handler()
480+
assert isinstance(handler, DeviceHandler)
481+
self.e2e_keys_handler = hs.get_e2e_keys_handler()
482+
self.device_handler = handler
483+
484+
if hs.config.worker.worker_app is None:
485+
# if main process
486+
self.key_uploader = self.e2e_keys_handler.upload_keys_for_user
487+
else:
488+
# then a worker
489+
self.key_uploader = ReplicationUploadKeysForUserRestServlet.make_client(hs)
490+
491+
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
492+
requester = await self.auth.get_user_by_req(request)
493+
494+
dehydrated_device = await self.device_handler.get_dehydrated_device(
495+
requester.user.to_string()
496+
)
497+
498+
if dehydrated_device is not None:
499+
(device_id, device_data) = dehydrated_device
500+
result = {"device_id": device_id, "device_data": device_data}
501+
return 200, result
502+
else:
503+
raise errors.NotFoundError("No dehydrated device available")
504+
505+
async def on_DELETE(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
506+
requester = await self.auth.get_user_by_req(request)
507+
508+
dehydrated_device = await self.device_handler.get_dehydrated_device(
509+
requester.user.to_string()
510+
)
511+
512+
if dehydrated_device is not None:
513+
(device_id, device_data) = dehydrated_device
514+
515+
result = await self.device_handler.rehydrate_device(
516+
requester.user.to_string(),
517+
self.auth.get_access_token_from_request(request),
518+
device_id,
519+
)
520+
521+
result = {"device_id": device_id}
522+
523+
return 200, result
524+
else:
525+
raise errors.NotFoundError("No dehydrated device available")
526+
527+
class PutBody(RequestBodyModel):
528+
device_data: DehydratedDeviceDataModel
529+
device_id: Optional[StrictStr]
530+
initial_device_display_name: Optional[StrictStr]
531+
532+
class Config:
533+
extra = Extra.allow
534+
535+
async def on_PUT(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
536+
submission = parse_and_validate_json_object_from_request(request, self.PutBody)
537+
requester = await self.auth.get_user_by_req(request)
538+
user_id = requester.user.to_string()
539+
540+
# TODO: Those two operations, creating a device and storing the
541+
# device's keys should be atomic.
542+
device_id = await self.device_handler.store_dehydrated_device(
543+
requester.user.to_string(),
544+
submission.device_id,
545+
submission.device_data.dict(),
546+
submission.initial_device_display_name,
547+
)
548+
549+
# TODO: Do we need to do something with the result here?
550+
await self.key_uploader(
551+
user_id=user_id,
552+
device_id=submission.device_id,
553+
keys=submission.dict()
554+
)
555+
556+
return 200, {"device_id": device_id}
557+
558+
393559
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
394560
if (
395561
hs.config.worker.worker_app is None
@@ -404,5 +570,5 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
404570
DehydratedDeviceServlet(hs, msc2697=True).register(http_server)
405571
ClaimDehydratedDeviceServlet(hs).register(http_server)
406572
if hs.config.experimental.msc3814_enabled:
407-
DehydratedDeviceServlet(hs, msc2697=False).register(http_server)
573+
DehydratedDeviceV2Servlet(hs).register(http_server)
408574
DehydratedDeviceEventsServlet(hs).register(http_server)

0 commit comments

Comments
 (0)