|
15 | 15 | # limitations under the License. |
16 | 16 |
|
17 | 17 | from typing import Optional |
| 18 | +from unittest import mock |
18 | 19 |
|
19 | 20 | from twisted.test.proto_helpers import MemoryReactor |
20 | 21 |
|
| 22 | +from synapse.api.constants import RoomEncryptionAlgorithms |
21 | 23 | from synapse.api.errors import NotFoundError, SynapseError |
| 24 | +from synapse.appservice import ApplicationService |
22 | 25 | from synapse.handlers.device import MAX_DEVICE_DISPLAY_NAME_LEN, DeviceHandler |
23 | 26 | from synapse.server import HomeServer |
| 27 | +from synapse.storage.databases.main.appservice import _make_exclusive_regex |
| 28 | +from synapse.types import JsonDict |
24 | 29 | from synapse.util import Clock |
25 | 30 |
|
26 | 31 | from tests import unittest |
| 32 | +from tests.test_utils import make_awaitable |
| 33 | +from tests.unittest import override_config |
27 | 34 |
|
28 | 35 | user1 = "@boris:aaa" |
29 | 36 | user2 = "@theresa:bbb" |
30 | 37 |
|
31 | 38 |
|
32 | 39 | class DeviceTestCase(unittest.HomeserverTestCase): |
33 | 40 | def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer: |
34 | | - hs = self.setup_test_homeserver("server", federation_http_client=None) |
| 41 | + self.appservice_api = mock.Mock() |
| 42 | + hs = self.setup_test_homeserver( |
| 43 | + "server", |
| 44 | + federation_http_client=None, |
| 45 | + application_service_api=self.appservice_api, |
| 46 | + ) |
35 | 47 | handler = hs.get_device_handler() |
36 | 48 | assert isinstance(handler, DeviceHandler) |
37 | 49 | self.handler = handler |
@@ -265,6 +277,127 @@ def _record_user( |
265 | 277 | ) |
266 | 278 | self.reactor.advance(1000) |
267 | 279 |
|
| 280 | + @override_config({"experimental_features": {"msc3984_appservice_key_query": True}}) |
| 281 | + def test_on_federation_query_user_devices_appservice(self) -> None: |
| 282 | + """Test that querying of appservices for keys overrides responses from the database.""" |
| 283 | + local_user = "@boris:" + self.hs.hostname |
| 284 | + device_1 = "abc" |
| 285 | + device_2 = "def" |
| 286 | + device_3 = "ghi" |
| 287 | + |
| 288 | + # There are 3 devices: |
| 289 | + # |
| 290 | + # 1. One which is uploaded to the homeserver. |
| 291 | + # 2. One which is uploaded to the homeserver, but a newer copy is returned |
| 292 | + # by the appservice. |
| 293 | + # 3. One which is only returned by the appservice. |
| 294 | + device_key_1: JsonDict = { |
| 295 | + "user_id": local_user, |
| 296 | + "device_id": device_1, |
| 297 | + "algorithms": [ |
| 298 | + "m.olm.curve25519-aes-sha2", |
| 299 | + RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2, |
| 300 | + ], |
| 301 | + "keys": { |
| 302 | + "ed25519:abc": "base64+ed25519+key", |
| 303 | + "curve25519:abc": "base64+curve25519+key", |
| 304 | + }, |
| 305 | + "signatures": {local_user: {"ed25519:abc": "base64+signature"}}, |
| 306 | + } |
| 307 | + device_key_2a: JsonDict = { |
| 308 | + "user_id": local_user, |
| 309 | + "device_id": device_2, |
| 310 | + "algorithms": [ |
| 311 | + "m.olm.curve25519-aes-sha2", |
| 312 | + RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2, |
| 313 | + ], |
| 314 | + "keys": { |
| 315 | + "ed25519:def": "base64+ed25519+key", |
| 316 | + "curve25519:def": "base64+curve25519+key", |
| 317 | + }, |
| 318 | + "signatures": {local_user: {"ed25519:def": "base64+signature"}}, |
| 319 | + } |
| 320 | + |
| 321 | + device_key_2b: JsonDict = { |
| 322 | + "user_id": local_user, |
| 323 | + "device_id": device_2, |
| 324 | + "algorithms": [ |
| 325 | + "m.olm.curve25519-aes-sha2", |
| 326 | + RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2, |
| 327 | + ], |
| 328 | + # The device ID is the same (above), but the keys are different. |
| 329 | + "keys": { |
| 330 | + "ed25519:xyz": "base64+ed25519+key", |
| 331 | + "curve25519:xyz": "base64+curve25519+key", |
| 332 | + }, |
| 333 | + "signatures": {local_user: {"ed25519:xyz": "base64+signature"}}, |
| 334 | + } |
| 335 | + device_key_3: JsonDict = { |
| 336 | + "user_id": local_user, |
| 337 | + "device_id": device_3, |
| 338 | + "algorithms": [ |
| 339 | + "m.olm.curve25519-aes-sha2", |
| 340 | + RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2, |
| 341 | + ], |
| 342 | + "keys": { |
| 343 | + "ed25519:jkl": "base64+ed25519+key", |
| 344 | + "curve25519:jkl": "base64+curve25519+key", |
| 345 | + }, |
| 346 | + "signatures": {local_user: {"ed25519:jkl": "base64+signature"}}, |
| 347 | + } |
| 348 | + |
| 349 | + # Upload keys for devices 1 & 2a. |
| 350 | + e2e_keys_handler = self.hs.get_e2e_keys_handler() |
| 351 | + self.get_success( |
| 352 | + e2e_keys_handler.upload_keys_for_user( |
| 353 | + local_user, device_1, {"device_keys": device_key_1} |
| 354 | + ) |
| 355 | + ) |
| 356 | + self.get_success( |
| 357 | + e2e_keys_handler.upload_keys_for_user( |
| 358 | + local_user, device_2, {"device_keys": device_key_2a} |
| 359 | + ) |
| 360 | + ) |
| 361 | + |
| 362 | + # Inject an appservice interested in this user. |
| 363 | + appservice = ApplicationService( |
| 364 | + token="i_am_an_app_service", |
| 365 | + id="1234", |
| 366 | + namespaces={"users": [{"regex": r"@boris:.+", "exclusive": True}]}, |
| 367 | + # Note: this user does not have to match the regex above |
| 368 | + sender="@as_main:test", |
| 369 | + ) |
| 370 | + self.hs.get_datastores().main.services_cache = [appservice] |
| 371 | + self.hs.get_datastores().main.exclusive_user_regex = _make_exclusive_regex( |
| 372 | + [appservice] |
| 373 | + ) |
| 374 | + |
| 375 | + # Setup a response. |
| 376 | + self.appservice_api.query_keys.return_value = make_awaitable( |
| 377 | + { |
| 378 | + "device_keys": { |
| 379 | + local_user: {device_2: device_key_2b, device_3: device_key_3} |
| 380 | + } |
| 381 | + } |
| 382 | + ) |
| 383 | + |
| 384 | + # Request all devices. |
| 385 | + res = self.get_success( |
| 386 | + self.handler.on_federation_query_user_devices(local_user) |
| 387 | + ) |
| 388 | + self.assertIn("devices", res) |
| 389 | + res_devices = res["devices"] |
| 390 | + for device in res_devices: |
| 391 | + device["keys"].pop("unsigned", None) |
| 392 | + self.assertEqual( |
| 393 | + res_devices, |
| 394 | + [ |
| 395 | + {"device_id": device_1, "keys": device_key_1}, |
| 396 | + {"device_id": device_2, "keys": device_key_2b}, |
| 397 | + {"device_id": device_3, "keys": device_key_3}, |
| 398 | + ], |
| 399 | + ) |
| 400 | + |
268 | 401 |
|
269 | 402 | class DehydrationTestCase(unittest.HomeserverTestCase): |
270 | 403 | def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer: |
|
0 commit comments