Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/18252.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Mark dehydrated devices in the [List All User Devices Admin API](https://element-hq.github.io/synapse/latest/admin_api/user_admin_api.html#list-all-devices).
7 changes: 5 additions & 2 deletions docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -954,15 +954,17 @@ A response body like the following is returned:
"last_seen_ip": "1.2.3.4",
"last_seen_user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0",
"last_seen_ts": 1474491775024,
"user_id": "<user_id>"
"user_id": "<user_id>",
"dehydrated": false
},
{
"device_id": "AUIECTSRND",
"display_name": "ios",
"last_seen_ip": "1.2.3.5",
"last_seen_user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0",
"last_seen_ts": 1474491775025,
"user_id": "<user_id>"
"user_id": "<user_id>",
"dehydrated": false
}
],
"total": 2
Expand Down Expand Up @@ -992,6 +994,7 @@ The following fields are returned in the JSON response body:
- `last_seen_ts` - The timestamp (in milliseconds since the unix epoch) when this
devices was last seen. (May be a few minutes out of date, for efficiency reasons).
- `user_id` - Owner of device.
- `dehydrated` - Whether the device is a dehydrated device.

- `total` - Total number of user's devices.

Expand Down
11 changes: 11 additions & 0 deletions synapse/rest/admin/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ async def on_GET(
devices = await self.device_worker_handler.get_devices_by_user(
target_user.to_string()
)

# mark the dehydrated device by adding a "dehydrated" flag
dehydrated_device_info = await self.device_worker_handler.get_dehydrated_device(
target_user.to_string()
)
if dehydrated_device_info:
dehydrated_device_id = dehydrated_device_info[0]
for device in devices:
is_dehydrated = device["device_id"] == dehydrated_device_id
device["dehydrated"] = is_dehydrated

return HTTPStatus.OK, {"devices": devices, "total": len(devices)}


Expand Down
64 changes: 61 additions & 3 deletions tests/rest/admin/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import synapse.rest.admin
from synapse.api.errors import Codes
from synapse.handlers.device import DeviceHandler
from synapse.rest.client import login
from synapse.rest.client import devices, login
from synapse.server import HomeServer
from synapse.util import Clock

Expand Down Expand Up @@ -299,6 +299,7 @@ def test_delete_device(self) -> None:
class DevicesRestTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
devices.register_servlets,
login.register_servlets,
]

Expand Down Expand Up @@ -390,15 +391,63 @@ def test_user_has_no_devices(self) -> None:
self.assertEqual(0, channel.json_body["total"])
self.assertEqual(0, len(channel.json_body["devices"]))

@unittest.override_config(
{"experimental_features": {"msc2697_enabled": False, "msc3814_enabled": True}}
)
def test_get_devices(self) -> None:
"""
Tests that a normal lookup for devices is successfully
"""
# Create devices
number_devices = 5
for _ in range(number_devices):
# we create 2 fewer devices in the loop, because we will create another
# login after the loop, and we will create a dehydrated device
for _ in range(number_devices - 2):
self.login("user", "pass")

other_user_token = self.login("user", "pass")
dehydrated_device_url = (
"/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device"
)
content = {
"device_data": {
"algorithm": "m.dehydration.v1.olm",
},
"device_id": "dehydrated_device",
"initial_device_display_name": "foo bar",
"device_keys": {
"user_id": "@user:test",
"device_id": "dehydrated_device",
"valid_until_ts": "80",
"algorithms": [
"m.olm.curve25519-aes-sha2",
],
"keys": {
"<algorithm>:<device_id>": "<key_base64>",
},
"signatures": {
"@user:test": {"<algorithm>:<device_id>": "<signature_base64>"}
},
},
"fallback_keys": {
"alg1:device1": "f4llb4ckk3y",
"signed_<algorithm>:<device_id>": {
"fallback": "true",
"key": "f4llb4ckk3y",
"signatures": {
"@user:test": {"<algorithm>:<device_id>": "<key_base64>"}
},
},
},
"one_time_keys": {"alg1:k1": "0net1m3k3y"},
}
self.make_request(
"PUT",
dehydrated_device_url,
access_token=other_user_token,
content=content,
)

# Get devices
channel = self.make_request(
"GET",
Expand All @@ -410,13 +459,22 @@ def test_get_devices(self) -> None:
self.assertEqual(number_devices, channel.json_body["total"])
self.assertEqual(number_devices, len(channel.json_body["devices"]))
self.assertEqual(self.other_user, channel.json_body["devices"][0]["user_id"])
# Check that all fields are available
# Check that all fields are available, and that the dehydrated device is marked as dehydrated
found_dehydrated = False
for d in channel.json_body["devices"]:
self.assertIn("user_id", d)
self.assertIn("device_id", d)
self.assertIn("display_name", d)
self.assertIn("last_seen_ip", d)
self.assertIn("last_seen_ts", d)
if d["device_id"] == "dehydrated_device":
self.assertTrue(d.get("dehydrated"))
found_dehydrated = True
else:
# Either the field is not present, or set to False
self.assertFalse(d.get("dehydrated"))

self.assertTrue(found_dehydrated)


class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
Expand Down
Loading