Mark dehydrated devices in admin get devices endpoint (#18252)

Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
This commit is contained in:
Hubert Chathi 2025-05-28 07:20:27 -04:00 committed by GitHub
parent d82ad6e554
commit 2436512a25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 5 deletions

1
changelog.d/18252.misc Normal file
View File

@ -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).

View File

@ -954,7 +954,8 @@ A response body like the following is returned:
"last_seen_ip": "1.2.3.4", "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_user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0",
"last_seen_ts": 1474491775024, "last_seen_ts": 1474491775024,
"user_id": "<user_id>" "user_id": "<user_id>",
"dehydrated": false
}, },
{ {
"device_id": "AUIECTSRND", "device_id": "AUIECTSRND",
@ -962,7 +963,8 @@ A response body like the following is returned:
"last_seen_ip": "1.2.3.5", "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_user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0",
"last_seen_ts": 1474491775025, "last_seen_ts": 1474491775025,
"user_id": "<user_id>" "user_id": "<user_id>",
"dehydrated": false
} }
], ],
"total": 2 "total": 2
@ -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 - `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). devices was last seen. (May be a few minutes out of date, for efficiency reasons).
- `user_id` - Owner of device. - `user_id` - Owner of device.
- `dehydrated` - Whether the device is a dehydrated device.
- `total` - Total number of user's devices. - `total` - Total number of user's devices.

View File

@ -145,6 +145,17 @@ class DevicesGetRestServlet(RestServlet):
devices = await self.device_worker_handler.get_devices_by_user( devices = await self.device_worker_handler.get_devices_by_user(
target_user.to_string() 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)} return HTTPStatus.OK, {"devices": devices, "total": len(devices)}

View File

@ -27,7 +27,7 @@ from twisted.test.proto_helpers import MemoryReactor
import synapse.rest.admin import synapse.rest.admin
from synapse.api.errors import Codes from synapse.api.errors import Codes
from synapse.handlers.device import DeviceHandler 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.server import HomeServer
from synapse.util import Clock from synapse.util import Clock
@ -299,6 +299,7 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
class DevicesRestTestCase(unittest.HomeserverTestCase): class DevicesRestTestCase(unittest.HomeserverTestCase):
servlets = [ servlets = [
synapse.rest.admin.register_servlets, synapse.rest.admin.register_servlets,
devices.register_servlets,
login.register_servlets, login.register_servlets,
] ]
@ -390,15 +391,63 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
self.assertEqual(0, channel.json_body["total"]) self.assertEqual(0, channel.json_body["total"])
self.assertEqual(0, len(channel.json_body["devices"])) 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: def test_get_devices(self) -> None:
""" """
Tests that a normal lookup for devices is successfully Tests that a normal lookup for devices is successfully
""" """
# Create devices # Create devices
number_devices = 5 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") 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 # Get devices
channel = self.make_request( channel = self.make_request(
"GET", "GET",
@ -410,13 +459,22 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
self.assertEqual(number_devices, channel.json_body["total"]) self.assertEqual(number_devices, channel.json_body["total"])
self.assertEqual(number_devices, len(channel.json_body["devices"])) self.assertEqual(number_devices, len(channel.json_body["devices"]))
self.assertEqual(self.other_user, channel.json_body["devices"][0]["user_id"]) 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"]: for d in channel.json_body["devices"]:
self.assertIn("user_id", d) self.assertIn("user_id", d)
self.assertIn("device_id", d) self.assertIn("device_id", d)
self.assertIn("display_name", d) self.assertIn("display_name", d)
self.assertIn("last_seen_ip", d) self.assertIn("last_seen_ip", d)
self.assertIn("last_seen_ts", 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): class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):