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

Commit f7e0933

Browse files
committed
apply patch
1 parent 0f49f81 commit f7e0933

File tree

5 files changed

+233
-60
lines changed

5 files changed

+233
-60
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/handlers/devicemessage.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ async def get_events_for_dehydrated_device(
317317
) -> JsonDict:
318318
"""Fetches up to `limit` events sent to `device_id` starting from `since_token`
319319
and returns the new since token. If there are no more messages, returns an empty
320-
array and deletes the dehydrated device associated with the user/device_id.
320+
array.
321321
322322
Args:
323323
requester: the user requesting the messages
@@ -373,7 +373,11 @@ async def get_events_for_dehydrated_device(
373373
user_id, device_id, since_stream_id
374374
)
375375
logger.debug(
376-
"Deleted %d to-device messages up to %d", deleted, since_stream_id
376+
"Deleted %d to-device messages up to %d for user_id %s device_id %s",
377+
deleted,
378+
since_stream_id,
379+
user_id,
380+
device_id,
377381
)
378382

379383
to_token = self.event_sources.get_current_token().to_device_key
@@ -389,20 +393,16 @@ async def get_events_for_dehydrated_device(
389393
set_tag(SynapseTags.TO_DEVICE_EDU_ID, message_id)
390394

391395
logger.debug(
392-
"Returning %d to-device messages between %d and %d (current token: %d) for dehydrated device %s",
396+
"Returning %d to-device messages between %d and %d (current token: %d) for "
397+
"dehydrated device %s, user_id %s",
393398
len(messages),
394399
since_stream_id,
395400
stream_id,
396401
to_token,
397402
device_id,
403+
user_id,
398404
)
399405

400-
if messages == []:
401-
# we've fetched all the messages, delete the dehydrated device
402-
await self.store.remove_dehydrated_device(
403-
requester.user.to_string(), device_id
404-
)
405-
406406
return {
407407
"events": messages,
408408
"next_batch": f"d{stream_id}",

synapse/rest/client/devices.py

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
# limitations under the License.
1515

1616
import logging
17+
from http import HTTPStatus
1718
from typing import TYPE_CHECKING, List, Optional, Tuple
1819

1920
from pydantic import Extra, StrictStr
2021

2122
from synapse.api import errors
22-
from synapse.api.errors import NotFoundError, UnrecognizedRequestError
23+
from synapse.api.errors import NotFoundError, SynapseError, UnrecognizedRequestError
2324
from synapse.handlers.device import DeviceHandler
2425
from synapse.http.server import HttpServer
2526
from synapse.http.servlet import (
@@ -28,6 +29,7 @@
2829
parse_integer,
2930
)
3031
from synapse.http.site import SynapseRequest
32+
from synapse.replication.http.devices import ReplicationUploadKeysForUserRestServlet
3133
from synapse.rest.client._base import client_patterns, interactive_auth_handler
3234
from synapse.rest.client.models import AuthenticationData
3335
from synapse.rest.models import RequestBodyModel
@@ -230,7 +232,7 @@ class Config:
230232
class DehydratedDeviceServlet(RestServlet):
231233
"""Retrieve or store a dehydrated device.
232234
233-
Implements either MSC2697 and MSC3814.
235+
Implements either MSC2697 or MSC3814.
234236
235237
GET /org.matrix.msc2697.v2/dehydrated_device
236238
@@ -301,6 +303,7 @@ async def on_PUT(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
301303

302304
device_id = await self.device_handler.store_dehydrated_device(
303305
requester.user.to_string(),
306+
None,
304307
submission.device_data.dict(),
305308
submission.initial_device_display_name,
306309
)
@@ -390,6 +393,175 @@ async def on_POST(
390393
return 200, msgs
391394

392395

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+
393565
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
394566
if (
395567
hs.config.worker.worker_app is None
@@ -404,5 +576,5 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
404576
DehydratedDeviceServlet(hs, msc2697=True).register(http_server)
405577
ClaimDehydratedDeviceServlet(hs).register(http_server)
406578
if hs.config.experimental.msc3814_enabled:
407-
DehydratedDeviceServlet(hs, msc2697=False).register(http_server)
579+
DehydratedDeviceV2Servlet(hs).register(http_server)
408580
DehydratedDeviceEventsServlet(hs).register(http_server)

tests/handlers/test_device.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ def test_dehydrate_and_rehydrate_device(self) -> None:
430430
stored_dehydrated_device_id = self.get_success(
431431
self.handler.store_dehydrated_device(
432432
user_id=user_id,
433+
device_id=None,
433434
device_data={"device_data": {"foo": "bar"}},
434435
initial_device_display_name="dehydrated device",
435436
)
@@ -506,6 +507,7 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:
506507
stored_dehydrated_device_id = self.get_success(
507508
self.handler.store_dehydrated_device(
508509
user_id=user_id,
510+
device_id=None,
509511
device_data={"device_data": {"foo": "bar"}},
510512
initial_device_display_name="dehydrated device",
511513
)
@@ -530,7 +532,7 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:
530532

531533
requester = create_requester(user_id, device_id=device_id)
532534

533-
# Fetching messages for a non existing device should return an error
535+
# Fetching messages for a non-existing device should return an error
534536
self.get_failure(
535537
self.message_handler.get_events_for_dehydrated_device(
536538
requester=requester,
@@ -565,7 +567,8 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:
565567
self.assertEqual(len(res["events"]), 1)
566568
self.assertEqual(res["events"][0]["content"]["body"], "foo")
567569

568-
# Fetch the message of the dehydrated device again, which should return nothing and delete the old messages
570+
# Fetch the message of the dehydrated device again, which should return nothing
571+
# and delete the old messages
569572
res = self.get_success(
570573
self.message_handler.get_events_for_dehydrated_device(
571574
requester=requester,
@@ -576,21 +579,3 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:
576579
)
577580
self.assertTrue(len(res["next_batch"]) > 1)
578581
self.assertEqual(len(res["events"]), 0)
579-
580-
# Fetching messages again should fail, since the messages and dehydrated device
581-
# were deleted
582-
self.get_failure(
583-
self.message_handler.get_events_for_dehydrated_device(
584-
requester=requester,
585-
device_id=stored_dehydrated_device_id,
586-
since_token=None,
587-
limit=10,
588-
),
589-
SynapseError,
590-
)
591-
592-
# make sure that the dehydrated device ID is deleted after fetching messages
593-
res2 = self.get_success(
594-
self.handler.get_dehydrated_device(requester.user.to_string()),
595-
)
596-
self.assertEqual(res2, None)

0 commit comments

Comments
 (0)