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

Commit

Permalink
apply patch
Browse files Browse the repository at this point in the history
  • Loading branch information
H-Shay committed Jul 23, 2023
1 parent 0f49f81 commit f7e0933
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 60 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
18 changes: 9 additions & 9 deletions synapse/handlers/devicemessage.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ async def get_events_for_dehydrated_device(
) -> JsonDict:
"""Fetches up to `limit` events sent to `device_id` starting from `since_token`
and returns the new since token. If there are no more messages, returns an empty
array and deletes the dehydrated device associated with the user/device_id.
array.
Args:
requester: the user requesting the messages
Expand Down Expand Up @@ -373,7 +373,11 @@ async def get_events_for_dehydrated_device(
user_id, device_id, since_stream_id
)
logger.debug(
"Deleted %d to-device messages up to %d", deleted, since_stream_id
"Deleted %d to-device messages up to %d for user_id %s device_id %s",
deleted,
since_stream_id,
user_id,
device_id,
)

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

logger.debug(
"Returning %d to-device messages between %d and %d (current token: %d) for dehydrated device %s",
"Returning %d to-device messages between %d and %d (current token: %d) for "
"dehydrated device %s, user_id %s",
len(messages),
since_stream_id,
stream_id,
to_token,
device_id,
user_id,
)

if messages == []:
# we've fetched all the messages, delete the dehydrated device
await self.store.remove_dehydrated_device(
requester.user.to_string(), device_id
)

return {
"events": messages,
"next_batch": f"d{stream_id}",
Expand Down
178 changes: 175 additions & 3 deletions synapse/rest/client/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
# limitations under the License.

import logging
from http import HTTPStatus
from typing import TYPE_CHECKING, List, Optional, Tuple

from pydantic import Extra, StrictStr

from synapse.api import errors
from synapse.api.errors import NotFoundError, UnrecognizedRequestError
from synapse.api.errors import NotFoundError, SynapseError, UnrecognizedRequestError
from synapse.handlers.device import DeviceHandler
from synapse.http.server import HttpServer
from synapse.http.servlet import (
Expand All @@ -28,6 +29,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 @@ -230,7 +232,7 @@ class Config:
class DehydratedDeviceServlet(RestServlet):
"""Retrieve or store a dehydrated device.
Implements either MSC2697 and MSC3814.
Implements either MSC2697 or MSC3814.
GET /org.matrix.msc2697.v2/dehydrated_device
Expand Down Expand Up @@ -301,6 +303,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 +393,175 @@ 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: 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()

device_info = submission.dict()
if "device_keys" not in device_info.keys():
raise SynapseError(
HTTPStatus.BAD_REQUEST,
"Device key(s) not found, these must be provided.",
)

# 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 +576,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)
25 changes: 5 additions & 20 deletions tests/handlers/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ def test_dehydrate_and_rehydrate_device(self) -> None:
stored_dehydrated_device_id = self.get_success(
self.handler.store_dehydrated_device(
user_id=user_id,
device_id=None,
device_data={"device_data": {"foo": "bar"}},
initial_device_display_name="dehydrated device",
)
Expand Down Expand Up @@ -506,6 +507,7 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:
stored_dehydrated_device_id = self.get_success(
self.handler.store_dehydrated_device(
user_id=user_id,
device_id=None,
device_data={"device_data": {"foo": "bar"}},
initial_device_display_name="dehydrated device",
)
Expand All @@ -530,7 +532,7 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:

requester = create_requester(user_id, device_id=device_id)

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

# Fetch the message of the dehydrated device again, which should return nothing and delete the old messages
# Fetch the message of the dehydrated device again, which should return nothing
# and delete the old messages
res = self.get_success(
self.message_handler.get_events_for_dehydrated_device(
requester=requester,
Expand All @@ -576,21 +579,3 @@ def test_dehydrate_v2_and_fetch_events(self) -> None:
)
self.assertTrue(len(res["next_batch"]) > 1)
self.assertEqual(len(res["events"]), 0)

# Fetching messages again should fail, since the messages and dehydrated device
# were deleted
self.get_failure(
self.message_handler.get_events_for_dehydrated_device(
requester=requester,
device_id=stored_dehydrated_device_id,
since_token=None,
limit=10,
),
SynapseError,
)

# make sure that the dehydrated device ID is deleted after fetching messages
res2 = self.get_success(
self.handler.get_dehydrated_device(requester.user.to_string()),
)
self.assertEqual(res2, None)
Loading

0 comments on commit f7e0933

Please sign in to comment.