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

Commit

Permalink
Implement most of the endpoints for MSC3814
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
poljar committed Jul 18, 2023
1 parent 0f49f81 commit 777b305
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 2 deletions.
4 changes: 3 additions & 1 deletion synapse/handlers/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ async def notify_user_signature_update(
async def store_dehydrated_device(
self,
user_id: str,
device_id: Optional[str],
device_data: JsonDict,
initial_device_display_name: Optional[str] = None,
) -> str:
Expand All @@ -661,14 +662,15 @@ async def store_dehydrated_device(
Args:
user_id: the user that we are storing the device for
device_id: device id supplied by client
device_data: the dehydrated device information
initial_device_display_name: The display name to use for the device
Returns:
device id of the dehydrated device
"""
device_id = await self.check_device_registered(
user_id,
None,
device_id,
initial_device_display_name,
)
old_device_id = await self.store.store_dehydrated_device(
Expand Down
168 changes: 167 additions & 1 deletion synapse/rest/client/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
parse_integer,
)
from synapse.http.site import SynapseRequest
from synapse.replication.http.devices import ReplicationUploadKeysForUserRestServlet
from synapse.rest.client._base import client_patterns, interactive_auth_handler
from synapse.rest.client.models import AuthenticationData
from synapse.rest.models import RequestBodyModel
Expand Down Expand Up @@ -301,6 +302,7 @@ async def on_PUT(self, request: SynapseRequest) -> Tuple[int, JsonDict]:

device_id = await self.device_handler.store_dehydrated_device(
requester.user.to_string(),
None,
submission.device_data.dict(),
submission.initial_device_display_name,
)
Expand Down Expand Up @@ -390,6 +392,170 @@ async def on_POST(
return 200, msgs


class DehydratedDeviceV2Servlet(RestServlet):
"""Upload, retrieve, or delete a dehydrated device.
GET /org.matrix.msc3814.v1/dehydrated_device
HTTP/1.1 200 OK
Content-Type: application/json
{
"device_id": "dehydrated_device_id",
"device_data": {
"algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
"account": "dehydrated_device"
}
}
PUT /org.matrix.msc3814.v1/dehydrated_device
Content-Type: application/json
{
"device_id": "dehydrated_device_id",
"device_data": {
"algorithm": "org.matrix.msc2697.v1.dehydration.v1.olm",
"account": "dehydrated_device"
},
"device_keys": {
"user_id": "<user_id>",
"device_id": "<device_id>",
"valid_until_ts": <millisecond_timestamp>,
"algorithms": [
"m.olm.curve25519-aes-sha2",
]
"keys": {
"<algorithm>:<device_id>": "<key_base64>",
},
"signatures:" {
"<user_id>" {
"<algorithm>:<device_id>": "<signature_base64>"
}
}
},
"fallback_keys": {
"<algorithm>:<device_id>": "<key_base64>",
"signed_<algorithm>:<device_id>": {
"fallback": true,
"key": "<key_base64>",
"signatures": {
"<user_id>": {
"<algorithm>:<device_id>": "<key_base64>"
}
}
}
}
"one_time_keys": {
"<algorithm>:<key_id>": "<key_base64>"
},
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"device_id": "dehydrated_device_id"
}
DELETE /org.matrix.msc3814.v1/dehydrated_device
HTTP/1.1 200 OK
Content-Type: application/json
{
"device_id": "dehydrated_device_id",
}
"""

PATTERNS = [
*client_patterns("/org.matrix.msc3814.v1/dehydrated_device$", releases=()),
]

def __init__(self, hs: "HomeServer"):
super().__init__()
self.hs = hs
self.auth = hs.get_auth()
handler = hs.get_device_handler()
assert isinstance(handler, DeviceHandler)
self.e2e_keys_handler = hs.get_e2e_keys_handler()
self.device_handler = handler

if hs.config.worker.worker_app is None:
# if main process
self.key_uploader = self.e2e_keys_handler.upload_keys_for_user
else:
# then a worker
self.key_uploader = ReplicationUploadKeysForUserRestServlet.make_client(hs)

async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
requester = await self.auth.get_user_by_req(request)

dehydrated_device = await self.device_handler.get_dehydrated_device(
requester.user.to_string()
)

if dehydrated_device is not None:
(device_id, device_data) = dehydrated_device
result = {"device_id": device_id, "device_data": device_data}
return 200, result
else:
raise errors.NotFoundError("No dehydrated device available")

async def on_DELETE(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
requester = await self.auth.get_user_by_req(request)

dehydrated_device = await self.device_handler.get_dehydrated_device(
requester.user.to_string()
)

if dehydrated_device is not None:
(device_id, device_data) = dehydrated_device

result = await self.device_handler.rehydrate_device(
requester.user.to_string(),
self.auth.get_access_token_from_request(request),
device_id,
)

result = {"device_id": device_id}

return 200, result
else:
raise errors.NotFoundError("No dehydrated device available")

class PutBody(RequestBodyModel):
device_data: DehydratedDeviceDataModel
device_id: Optional[StrictStr]
initial_device_display_name: Optional[StrictStr]

class Config:
extra = Extra.allow

async def on_PUT(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
submission = parse_and_validate_json_object_from_request(request, self.PutBody)
requester = await self.auth.get_user_by_req(request)
user_id = requester.user.to_string()

# TODO: Those two operations, creating a device and storing the
# device's keys should be atomic.
device_id = await self.device_handler.store_dehydrated_device(
requester.user.to_string(),
submission.device_id,
submission.device_data.dict(),
submission.initial_device_display_name,
)

# TODO: Do we need to do something with the result here?
await self.key_uploader(
user_id=user_id,
device_id=submission.device_id,
keys=submission.dict()
)

return 200, {"device_id": device_id}


def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
if (
hs.config.worker.worker_app is None
Expand All @@ -404,5 +570,5 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
DehydratedDeviceServlet(hs, msc2697=True).register(http_server)
ClaimDehydratedDeviceServlet(hs).register(http_server)
if hs.config.experimental.msc3814_enabled:
DehydratedDeviceServlet(hs, msc2697=False).register(http_server)
DehydratedDeviceV2Servlet(hs).register(http_server)
DehydratedDeviceEventsServlet(hs).register(http_server)

0 comments on commit 777b305

Please sign in to comment.