You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

626 lines
22 KiB

  1. # Copyright 2016 OpenMarket Ltd
  2. # Copyright 2018 New Vector Ltd
  3. # Copyright 2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. from typing import Optional
  17. from unittest import mock
  18. from twisted.internet.defer import ensureDeferred
  19. from twisted.test.proto_helpers import MemoryReactor
  20. from synapse.api.constants import RoomEncryptionAlgorithms
  21. from synapse.api.errors import NotFoundError, SynapseError
  22. from synapse.appservice import ApplicationService
  23. from synapse.handlers.device import MAX_DEVICE_DISPLAY_NAME_LEN, DeviceHandler
  24. from synapse.rest import admin
  25. from synapse.rest.client import devices, login, register
  26. from synapse.server import HomeServer
  27. from synapse.storage.databases.main.appservice import _make_exclusive_regex
  28. from synapse.types import JsonDict, create_requester
  29. from synapse.util import Clock
  30. from synapse.util.task_scheduler import TaskScheduler
  31. from tests import unittest
  32. from tests.unittest import override_config
  33. user1 = "@boris:aaa"
  34. user2 = "@theresa:bbb"
  35. class DeviceTestCase(unittest.HomeserverTestCase):
  36. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  37. self.appservice_api = mock.AsyncMock()
  38. hs = self.setup_test_homeserver(
  39. "server",
  40. application_service_api=self.appservice_api,
  41. )
  42. handler = hs.get_device_handler()
  43. assert isinstance(handler, DeviceHandler)
  44. self.handler = handler
  45. self.store = hs.get_datastores().main
  46. self.device_message_handler = hs.get_device_message_handler()
  47. return hs
  48. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  49. # These tests assume that it starts 1000 seconds in.
  50. self.reactor.advance(1000)
  51. def test_device_is_created_with_invalid_name(self) -> None:
  52. self.get_failure(
  53. self.handler.check_device_registered(
  54. user_id="@boris:foo",
  55. device_id="foo",
  56. initial_device_display_name="a" * (MAX_DEVICE_DISPLAY_NAME_LEN + 1),
  57. ),
  58. SynapseError,
  59. )
  60. def test_device_is_created_if_doesnt_exist(self) -> None:
  61. res = self.get_success(
  62. self.handler.check_device_registered(
  63. user_id="@boris:foo",
  64. device_id="fco",
  65. initial_device_display_name="display name",
  66. )
  67. )
  68. self.assertEqual(res, "fco")
  69. dev = self.get_success(self.handler.store.get_device("@boris:foo", "fco"))
  70. assert dev is not None
  71. self.assertEqual(dev["display_name"], "display name")
  72. def test_device_is_preserved_if_exists(self) -> None:
  73. res1 = self.get_success(
  74. self.handler.check_device_registered(
  75. user_id="@boris:foo",
  76. device_id="fco",
  77. initial_device_display_name="display name",
  78. )
  79. )
  80. self.assertEqual(res1, "fco")
  81. res2 = self.get_success(
  82. self.handler.check_device_registered(
  83. user_id="@boris:foo",
  84. device_id="fco",
  85. initial_device_display_name="new display name",
  86. )
  87. )
  88. self.assertEqual(res2, "fco")
  89. dev = self.get_success(self.handler.store.get_device("@boris:foo", "fco"))
  90. assert dev is not None
  91. self.assertEqual(dev["display_name"], "display name")
  92. def test_device_id_is_made_up_if_unspecified(self) -> None:
  93. device_id = self.get_success(
  94. self.handler.check_device_registered(
  95. user_id="@theresa:foo",
  96. device_id=None,
  97. initial_device_display_name="display",
  98. )
  99. )
  100. dev = self.get_success(self.handler.store.get_device("@theresa:foo", device_id))
  101. assert dev is not None
  102. self.assertEqual(dev["display_name"], "display")
  103. def test_get_devices_by_user(self) -> None:
  104. self._record_users()
  105. res = self.get_success(self.handler.get_devices_by_user(user1))
  106. self.assertEqual(3, len(res))
  107. device_map = {d["device_id"]: d for d in res}
  108. self.assertLessEqual(
  109. {
  110. "user_id": user1,
  111. "device_id": "xyz",
  112. "display_name": "display 0",
  113. "last_seen_ip": None,
  114. "last_seen_ts": None,
  115. }.items(),
  116. device_map["xyz"].items(),
  117. )
  118. self.assertLessEqual(
  119. {
  120. "user_id": user1,
  121. "device_id": "fco",
  122. "display_name": "display 1",
  123. "last_seen_ip": "ip1",
  124. "last_seen_ts": 1000000,
  125. }.items(),
  126. device_map["fco"].items(),
  127. )
  128. self.assertLessEqual(
  129. {
  130. "user_id": user1,
  131. "device_id": "abc",
  132. "display_name": "display 2",
  133. "last_seen_ip": "ip3",
  134. "last_seen_ts": 3000000,
  135. }.items(),
  136. device_map["abc"].items(),
  137. )
  138. def test_get_device(self) -> None:
  139. self._record_users()
  140. res = self.get_success(self.handler.get_device(user1, "abc"))
  141. self.assertLessEqual(
  142. {
  143. "user_id": user1,
  144. "device_id": "abc",
  145. "display_name": "display 2",
  146. "last_seen_ip": "ip3",
  147. "last_seen_ts": 3000000,
  148. }.items(),
  149. res.items(),
  150. )
  151. def test_delete_device(self) -> None:
  152. self._record_users()
  153. # delete the device
  154. self.get_success(self.handler.delete_devices(user1, ["abc"]))
  155. # check the device was deleted
  156. self.get_failure(self.handler.get_device(user1, "abc"), NotFoundError)
  157. # we'd like to check the access token was invalidated, but that's a
  158. # bit of a PITA.
  159. def test_delete_device_and_device_inbox(self) -> None:
  160. self._record_users()
  161. # add an device_inbox
  162. self.get_success(
  163. self.store.db_pool.simple_insert(
  164. "device_inbox",
  165. {
  166. "user_id": user1,
  167. "device_id": "abc",
  168. "stream_id": 1,
  169. "message_json": "{}",
  170. },
  171. )
  172. )
  173. # delete the device
  174. self.get_success(self.handler.delete_devices(user1, ["abc"]))
  175. # check that the device_inbox was deleted
  176. res = self.get_success(
  177. self.store.db_pool.simple_select_one(
  178. table="device_inbox",
  179. keyvalues={"user_id": user1, "device_id": "abc"},
  180. retcols=("user_id", "device_id"),
  181. allow_none=True,
  182. desc="get_device_id_from_device_inbox",
  183. )
  184. )
  185. self.assertIsNone(res)
  186. def test_delete_device_and_big_device_inbox(self) -> None:
  187. """Check that deleting a big device inbox is staged and batched asynchronously."""
  188. DEVICE_ID = "abc"
  189. sender = "@sender:" + self.hs.hostname
  190. receiver = "@receiver:" + self.hs.hostname
  191. self._record_user(sender, DEVICE_ID, DEVICE_ID)
  192. self._record_user(receiver, DEVICE_ID, DEVICE_ID)
  193. # queue a bunch of messages in the inbox
  194. requester = create_requester(sender, device_id=DEVICE_ID)
  195. for i in range(DeviceHandler.DEVICE_MSGS_DELETE_BATCH_LIMIT + 10):
  196. self.get_success(
  197. self.device_message_handler.send_device_message(
  198. requester, "message_type", {receiver: {"*": {"val": i}}}
  199. )
  200. )
  201. # delete the device
  202. self.get_success(self.handler.delete_devices(receiver, [DEVICE_ID]))
  203. # messages should be deleted up to DEVICE_MSGS_DELETE_BATCH_LIMIT straight away
  204. res = self.get_success(
  205. self.store.db_pool.simple_select_list(
  206. table="device_inbox",
  207. keyvalues={"user_id": receiver},
  208. retcols=("user_id", "device_id", "stream_id"),
  209. desc="get_device_id_from_device_inbox",
  210. )
  211. )
  212. self.assertEqual(10, len(res))
  213. # wait for the task scheduler to do a second delete pass
  214. self.reactor.advance(TaskScheduler.SCHEDULE_INTERVAL_MS / 1000)
  215. # remaining messages should now be deleted
  216. res = self.get_success(
  217. self.store.db_pool.simple_select_list(
  218. table="device_inbox",
  219. keyvalues={"user_id": receiver},
  220. retcols=("user_id", "device_id", "stream_id"),
  221. desc="get_device_id_from_device_inbox",
  222. )
  223. )
  224. self.assertEqual(0, len(res))
  225. def test_update_device(self) -> None:
  226. self._record_users()
  227. update = {"display_name": "new display"}
  228. self.get_success(self.handler.update_device(user1, "abc", update))
  229. res = self.get_success(self.handler.get_device(user1, "abc"))
  230. self.assertEqual(res["display_name"], "new display")
  231. def test_update_device_too_long_display_name(self) -> None:
  232. """Update a device with a display name that is invalid (too long)."""
  233. self._record_users()
  234. # Request to update a device display name with a new value that is longer than allowed.
  235. update = {"display_name": "a" * (MAX_DEVICE_DISPLAY_NAME_LEN + 1)}
  236. self.get_failure(
  237. self.handler.update_device(user1, "abc", update),
  238. SynapseError,
  239. )
  240. # Ensure the display name was not updated.
  241. res = self.get_success(self.handler.get_device(user1, "abc"))
  242. self.assertEqual(res["display_name"], "display 2")
  243. def test_update_unknown_device(self) -> None:
  244. update = {"display_name": "new_display"}
  245. self.get_failure(
  246. self.handler.update_device("user_id", "unknown_device_id", update),
  247. NotFoundError,
  248. )
  249. def _record_users(self) -> None:
  250. # check this works for both devices which have a recorded client_ip,
  251. # and those which don't.
  252. self._record_user(user1, "xyz", "display 0")
  253. self._record_user(user1, "fco", "display 1", "token1", "ip1")
  254. self._record_user(user1, "abc", "display 2", "token2", "ip2")
  255. self._record_user(user1, "abc", "display 2", "token3", "ip3")
  256. self._record_user(user2, "def", "dispkay", "token4", "ip4")
  257. self.reactor.advance(10000)
  258. def _record_user(
  259. self,
  260. user_id: str,
  261. device_id: str,
  262. display_name: str,
  263. access_token: Optional[str] = None,
  264. ip: Optional[str] = None,
  265. ) -> None:
  266. device_id = self.get_success(
  267. self.handler.check_device_registered(
  268. user_id=user_id,
  269. device_id=device_id,
  270. initial_device_display_name=display_name,
  271. )
  272. )
  273. if access_token is not None and ip is not None:
  274. self.get_success(
  275. self.store.insert_client_ip(
  276. user_id, access_token, ip, "user_agent", device_id
  277. )
  278. )
  279. self.reactor.advance(1000)
  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. # There are 3 devices:
  288. #
  289. # 1. One which is uploaded to the homeserver.
  290. # 2. One which is uploaded to the homeserver, but a newer copy is returned
  291. # by the appservice.
  292. # 3. One which is only returned by the appservice.
  293. device_key_1: JsonDict = {
  294. "user_id": local_user,
  295. "device_id": device_1,
  296. "algorithms": [
  297. "m.olm.curve25519-aes-sha2",
  298. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  299. ],
  300. "keys": {
  301. "ed25519:abc": "base64+ed25519+key",
  302. "curve25519:abc": "base64+curve25519+key",
  303. },
  304. "signatures": {local_user: {"ed25519:abc": "base64+signature"}},
  305. }
  306. device_key_2a: JsonDict = {
  307. "user_id": local_user,
  308. "device_id": device_2,
  309. "algorithms": [
  310. "m.olm.curve25519-aes-sha2",
  311. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  312. ],
  313. "keys": {
  314. "ed25519:def": "base64+ed25519+key",
  315. "curve25519:def": "base64+curve25519+key",
  316. },
  317. "signatures": {local_user: {"ed25519:def": "base64+signature"}},
  318. }
  319. device_key_2b: JsonDict = {
  320. "user_id": local_user,
  321. "device_id": device_2,
  322. "algorithms": [
  323. "m.olm.curve25519-aes-sha2",
  324. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  325. ],
  326. # The device ID is the same (above), but the keys are different.
  327. "keys": {
  328. "ed25519:xyz": "base64+ed25519+key",
  329. "curve25519:xyz": "base64+curve25519+key",
  330. },
  331. "signatures": {local_user: {"ed25519:xyz": "base64+signature"}},
  332. }
  333. device_key_3: JsonDict = {
  334. "user_id": local_user,
  335. "device_id": device_3,
  336. "algorithms": [
  337. "m.olm.curve25519-aes-sha2",
  338. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  339. ],
  340. "keys": {
  341. "ed25519:jkl": "base64+ed25519+key",
  342. "curve25519:jkl": "base64+curve25519+key",
  343. },
  344. "signatures": {local_user: {"ed25519:jkl": "base64+signature"}},
  345. }
  346. # Upload keys for devices 1 & 2a.
  347. e2e_keys_handler = self.hs.get_e2e_keys_handler()
  348. self.get_success(
  349. e2e_keys_handler.upload_keys_for_user(
  350. local_user, device_1, {"device_keys": device_key_1}
  351. )
  352. )
  353. self.get_success(
  354. e2e_keys_handler.upload_keys_for_user(
  355. local_user, device_2, {"device_keys": device_key_2a}
  356. )
  357. )
  358. # Inject an appservice interested in this user.
  359. appservice = ApplicationService(
  360. token="i_am_an_app_service",
  361. id="1234",
  362. namespaces={"users": [{"regex": r"@boris:.+", "exclusive": True}]},
  363. # Note: this user does not have to match the regex above
  364. sender="@as_main:test",
  365. )
  366. self.hs.get_datastores().main.services_cache = [appservice]
  367. self.hs.get_datastores().main.exclusive_user_regex = _make_exclusive_regex(
  368. [appservice]
  369. )
  370. # Setup a response.
  371. self.appservice_api.query_keys.return_value = {
  372. "device_keys": {
  373. local_user: {device_2: device_key_2b, device_3: device_key_3}
  374. }
  375. }
  376. # Request all devices.
  377. res = self.get_success(
  378. self.handler.on_federation_query_user_devices(local_user)
  379. )
  380. self.assertIn("devices", res)
  381. res_devices = res["devices"]
  382. for device in res_devices:
  383. device["keys"].pop("unsigned", None)
  384. self.assertEqual(
  385. res_devices,
  386. [
  387. {"device_id": device_1, "keys": device_key_1},
  388. {"device_id": device_2, "keys": device_key_2b},
  389. {"device_id": device_3, "keys": device_key_3},
  390. ],
  391. )
  392. class DehydrationTestCase(unittest.HomeserverTestCase):
  393. servlets = [
  394. admin.register_servlets_for_client_rest_resource,
  395. login.register_servlets,
  396. register.register_servlets,
  397. devices.register_servlets,
  398. ]
  399. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  400. hs = self.setup_test_homeserver("server")
  401. handler = hs.get_device_handler()
  402. assert isinstance(handler, DeviceHandler)
  403. self.handler = handler
  404. self.message_handler = hs.get_device_message_handler()
  405. self.registration = hs.get_registration_handler()
  406. self.auth = hs.get_auth()
  407. self.store = hs.get_datastores().main
  408. return hs
  409. def test_dehydrate_and_rehydrate_device(self) -> None:
  410. user_id = "@boris:dehydration"
  411. self.get_success(self.store.register_user(user_id, "foobar"))
  412. # First check if we can store and fetch a dehydrated device
  413. stored_dehydrated_device_id = self.get_success(
  414. self.handler.store_dehydrated_device(
  415. user_id=user_id,
  416. device_id=None,
  417. device_data={"device_data": {"foo": "bar"}},
  418. initial_device_display_name="dehydrated device",
  419. )
  420. )
  421. result = self.get_success(self.handler.get_dehydrated_device(user_id=user_id))
  422. assert result is not None
  423. retrieved_device_id, device_data = result
  424. self.assertEqual(retrieved_device_id, stored_dehydrated_device_id)
  425. self.assertEqual(device_data, {"device_data": {"foo": "bar"}})
  426. # Create a new login for the user and dehydrated the device
  427. device_id, access_token, _expiration_time, _refresh_token = self.get_success(
  428. self.registration.register_device(
  429. user_id=user_id,
  430. device_id=None,
  431. initial_display_name="new device",
  432. )
  433. )
  434. # Trying to claim a nonexistent device should throw an error
  435. self.get_failure(
  436. self.handler.rehydrate_device(
  437. user_id=user_id,
  438. access_token=access_token,
  439. device_id="not the right device ID",
  440. ),
  441. NotFoundError,
  442. )
  443. # dehydrating the right devices should succeed and change our device ID
  444. # to the dehydrated device's ID
  445. res = self.get_success(
  446. self.handler.rehydrate_device(
  447. user_id=user_id,
  448. access_token=access_token,
  449. device_id=retrieved_device_id,
  450. )
  451. )
  452. self.assertEqual(res, {"success": True})
  453. # make sure that our device ID has changed
  454. user_info = self.get_success(self.auth.get_user_by_access_token(access_token))
  455. self.assertEqual(user_info.device_id, retrieved_device_id)
  456. # make sure the device has the display name that was set from the login
  457. res = self.get_success(self.handler.get_device(user_id, retrieved_device_id))
  458. self.assertEqual(res["display_name"], "new device")
  459. # make sure that the device ID that we were initially assigned no longer exists
  460. self.get_failure(
  461. self.handler.get_device(user_id, device_id),
  462. NotFoundError,
  463. )
  464. # make sure that there's no device available for dehydrating now
  465. ret = self.get_success(self.handler.get_dehydrated_device(user_id=user_id))
  466. self.assertIsNone(ret)
  467. @unittest.override_config(
  468. {"experimental_features": {"msc2697_enabled": False, "msc3814_enabled": True}}
  469. )
  470. def test_dehydrate_v2_and_fetch_events(self) -> None:
  471. user_id = "@boris:server"
  472. self.get_success(self.store.register_user(user_id, "foobar"))
  473. # First check if we can store and fetch a dehydrated device
  474. stored_dehydrated_device_id = self.get_success(
  475. self.handler.store_dehydrated_device(
  476. user_id=user_id,
  477. device_id=None,
  478. device_data={"device_data": {"foo": "bar"}},
  479. initial_device_display_name="dehydrated device",
  480. )
  481. )
  482. device_info = self.get_success(
  483. self.handler.get_dehydrated_device(user_id=user_id)
  484. )
  485. assert device_info is not None
  486. retrieved_device_id, device_data = device_info
  487. self.assertEqual(retrieved_device_id, stored_dehydrated_device_id)
  488. self.assertEqual(device_data, {"device_data": {"foo": "bar"}})
  489. # Create a new login for the user
  490. device_id, access_token, _expiration_time, _refresh_token = self.get_success(
  491. self.registration.register_device(
  492. user_id=user_id,
  493. device_id=None,
  494. initial_display_name="new device",
  495. )
  496. )
  497. requester = create_requester(user_id, device_id=device_id)
  498. # Fetching messages for a non-existing device should return an error
  499. self.get_failure(
  500. self.message_handler.get_events_for_dehydrated_device(
  501. requester=requester,
  502. device_id="not the right device ID",
  503. since_token=None,
  504. limit=10,
  505. ),
  506. SynapseError,
  507. )
  508. # Send a message to the dehydrated device
  509. ensureDeferred(
  510. self.message_handler.send_device_message(
  511. requester=requester,
  512. message_type="test.message",
  513. messages={user_id: {stored_dehydrated_device_id: {"body": "foo"}}},
  514. )
  515. )
  516. self.pump()
  517. # Fetch the message of the dehydrated device
  518. res = self.get_success(
  519. self.message_handler.get_events_for_dehydrated_device(
  520. requester=requester,
  521. device_id=stored_dehydrated_device_id,
  522. since_token=None,
  523. limit=10,
  524. )
  525. )
  526. self.assertTrue(len(res["next_batch"]) > 1)
  527. self.assertEqual(len(res["events"]), 1)
  528. self.assertEqual(res["events"][0]["content"]["body"], "foo")
  529. # Fetch the message of the dehydrated device again, which should return
  530. # the same message as it has not been deleted
  531. res = self.get_success(
  532. self.message_handler.get_events_for_dehydrated_device(
  533. requester=requester,
  534. device_id=stored_dehydrated_device_id,
  535. since_token=None,
  536. limit=10,
  537. )
  538. )
  539. self.assertTrue(len(res["next_batch"]) > 1)
  540. self.assertEqual(len(res["events"]), 1)
  541. self.assertEqual(res["events"][0]["content"]["body"], "foo")