Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 admin get devices endpoint.
11 changes: 11 additions & 0 deletions synapse/rest/admin/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ async def on_GET(
raise NotFoundError("Unknown user")

devices = await self.device_handler.get_devices_by_user(target_user.to_string())

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

@anoadragon453 anoadragon453 Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be nicer from an Admin API standpoint if the dehydrated field was always present?

Suggested change
if device["device_id"] == dehydrated_device_id:
device["dehydrated"] = True
is_dehydrated = device["device_id"] == dehydrated_device_id
device["dehydrated"] = is_dehydrated


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

async def on_POST(
Expand Down
63 changes: 60 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,21 @@ 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.assertEqual(True, d.get("dehydrated"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertEqual(True, d.get("dehydrated"))
self.assertTrue(d.get("dehydrated"))

found_dehydrated = True
else:
self.assertNotIn("dehydrated", d)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To align with the suggestion above.

Suggested change
self.assertNotIn("dehydrated", d)
# Either the field is not present, or set to False
self.assertFalse(d["dehydrated])


self.assertEqual(True, found_dehydrated)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertEqual(True, found_dehydrated)
self.assertTrue(found_dehydrated)



class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
Expand Down
Loading