test_device.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. import synapse.api.errors
  17. import synapse.handlers.device
  18. import synapse.storage
  19. from tests import unittest
  20. user1 = "@boris:aaa"
  21. user2 = "@theresa:bbb"
  22. class DeviceTestCase(unittest.HomeserverTestCase):
  23. def make_homeserver(self, reactor, clock):
  24. hs = self.setup_test_homeserver("server", federation_http_client=None)
  25. self.handler = hs.get_device_handler()
  26. self.store = hs.get_datastores().main
  27. return hs
  28. def prepare(self, reactor, clock, hs):
  29. # These tests assume that it starts 1000 seconds in.
  30. self.reactor.advance(1000)
  31. def test_device_is_created_with_invalid_name(self):
  32. self.get_failure(
  33. self.handler.check_device_registered(
  34. user_id="@boris:foo",
  35. device_id="foo",
  36. initial_device_display_name="a"
  37. * (synapse.handlers.device.MAX_DEVICE_DISPLAY_NAME_LEN + 1),
  38. ),
  39. synapse.api.errors.SynapseError,
  40. )
  41. def test_device_is_created_if_doesnt_exist(self):
  42. res = self.get_success(
  43. self.handler.check_device_registered(
  44. user_id="@boris:foo",
  45. device_id="fco",
  46. initial_device_display_name="display name",
  47. )
  48. )
  49. self.assertEqual(res, "fco")
  50. dev = self.get_success(self.handler.store.get_device("@boris:foo", "fco"))
  51. self.assertEqual(dev["display_name"], "display name")
  52. def test_device_is_preserved_if_exists(self):
  53. res1 = self.get_success(
  54. self.handler.check_device_registered(
  55. user_id="@boris:foo",
  56. device_id="fco",
  57. initial_device_display_name="display name",
  58. )
  59. )
  60. self.assertEqual(res1, "fco")
  61. res2 = self.get_success(
  62. self.handler.check_device_registered(
  63. user_id="@boris:foo",
  64. device_id="fco",
  65. initial_device_display_name="new display name",
  66. )
  67. )
  68. self.assertEqual(res2, "fco")
  69. dev = self.get_success(self.handler.store.get_device("@boris:foo", "fco"))
  70. self.assertEqual(dev["display_name"], "display name")
  71. def test_device_id_is_made_up_if_unspecified(self):
  72. device_id = self.get_success(
  73. self.handler.check_device_registered(
  74. user_id="@theresa:foo",
  75. device_id=None,
  76. initial_device_display_name="display",
  77. )
  78. )
  79. dev = self.get_success(self.handler.store.get_device("@theresa:foo", device_id))
  80. self.assertEqual(dev["display_name"], "display")
  81. def test_get_devices_by_user(self):
  82. self._record_users()
  83. res = self.get_success(self.handler.get_devices_by_user(user1))
  84. self.assertEqual(3, len(res))
  85. device_map = {d["device_id"]: d for d in res}
  86. self.assertDictContainsSubset(
  87. {
  88. "user_id": user1,
  89. "device_id": "xyz",
  90. "display_name": "display 0",
  91. "last_seen_ip": None,
  92. "last_seen_ts": None,
  93. },
  94. device_map["xyz"],
  95. )
  96. self.assertDictContainsSubset(
  97. {
  98. "user_id": user1,
  99. "device_id": "fco",
  100. "display_name": "display 1",
  101. "last_seen_ip": "ip1",
  102. "last_seen_ts": 1000000,
  103. },
  104. device_map["fco"],
  105. )
  106. self.assertDictContainsSubset(
  107. {
  108. "user_id": user1,
  109. "device_id": "abc",
  110. "display_name": "display 2",
  111. "last_seen_ip": "ip3",
  112. "last_seen_ts": 3000000,
  113. },
  114. device_map["abc"],
  115. )
  116. def test_get_device(self):
  117. self._record_users()
  118. res = self.get_success(self.handler.get_device(user1, "abc"))
  119. self.assertDictContainsSubset(
  120. {
  121. "user_id": user1,
  122. "device_id": "abc",
  123. "display_name": "display 2",
  124. "last_seen_ip": "ip3",
  125. "last_seen_ts": 3000000,
  126. },
  127. res,
  128. )
  129. def test_delete_device(self):
  130. self._record_users()
  131. # delete the device
  132. self.get_success(self.handler.delete_device(user1, "abc"))
  133. # check the device was deleted
  134. self.get_failure(
  135. self.handler.get_device(user1, "abc"), synapse.api.errors.NotFoundError
  136. )
  137. # we'd like to check the access token was invalidated, but that's a
  138. # bit of a PITA.
  139. def test_delete_device_and_device_inbox(self):
  140. self._record_users()
  141. # add an device_inbox
  142. self.get_success(
  143. self.store.db_pool.simple_insert(
  144. "device_inbox",
  145. {
  146. "user_id": user1,
  147. "device_id": "abc",
  148. "stream_id": 1,
  149. "message_json": "{}",
  150. },
  151. )
  152. )
  153. # delete the device
  154. self.get_success(self.handler.delete_device(user1, "abc"))
  155. # check that the device_inbox was deleted
  156. res = self.get_success(
  157. self.store.db_pool.simple_select_one(
  158. table="device_inbox",
  159. keyvalues={"user_id": user1, "device_id": "abc"},
  160. retcols=("user_id", "device_id"),
  161. allow_none=True,
  162. desc="get_device_id_from_device_inbox",
  163. )
  164. )
  165. self.assertIsNone(res)
  166. def test_update_device(self):
  167. self._record_users()
  168. update = {"display_name": "new display"}
  169. self.get_success(self.handler.update_device(user1, "abc", update))
  170. res = self.get_success(self.handler.get_device(user1, "abc"))
  171. self.assertEqual(res["display_name"], "new display")
  172. def test_update_device_too_long_display_name(self):
  173. """Update a device with a display name that is invalid (too long)."""
  174. self._record_users()
  175. # Request to update a device display name with a new value that is longer than allowed.
  176. update = {
  177. "display_name": "a"
  178. * (synapse.handlers.device.MAX_DEVICE_DISPLAY_NAME_LEN + 1)
  179. }
  180. self.get_failure(
  181. self.handler.update_device(user1, "abc", update),
  182. synapse.api.errors.SynapseError,
  183. )
  184. # Ensure the display name was not updated.
  185. res = self.get_success(self.handler.get_device(user1, "abc"))
  186. self.assertEqual(res["display_name"], "display 2")
  187. def test_update_unknown_device(self):
  188. update = {"display_name": "new_display"}
  189. self.get_failure(
  190. self.handler.update_device("user_id", "unknown_device_id", update),
  191. synapse.api.errors.NotFoundError,
  192. )
  193. def _record_users(self):
  194. # check this works for both devices which have a recorded client_ip,
  195. # and those which don't.
  196. self._record_user(user1, "xyz", "display 0")
  197. self._record_user(user1, "fco", "display 1", "token1", "ip1")
  198. self._record_user(user1, "abc", "display 2", "token2", "ip2")
  199. self._record_user(user1, "abc", "display 2", "token3", "ip3")
  200. self._record_user(user2, "def", "dispkay", "token4", "ip4")
  201. self.reactor.advance(10000)
  202. def _record_user(
  203. self, user_id, device_id, display_name, access_token=None, ip=None
  204. ):
  205. device_id = self.get_success(
  206. self.handler.check_device_registered(
  207. user_id=user_id,
  208. device_id=device_id,
  209. initial_device_display_name=display_name,
  210. )
  211. )
  212. if ip is not None:
  213. self.get_success(
  214. self.store.insert_client_ip(
  215. user_id, access_token, ip, "user_agent", device_id
  216. )
  217. )
  218. self.reactor.advance(1000)
  219. class DehydrationTestCase(unittest.HomeserverTestCase):
  220. def make_homeserver(self, reactor, clock):
  221. hs = self.setup_test_homeserver("server", federation_http_client=None)
  222. self.handler = hs.get_device_handler()
  223. self.registration = hs.get_registration_handler()
  224. self.auth = hs.get_auth()
  225. self.store = hs.get_datastores().main
  226. return hs
  227. def test_dehydrate_and_rehydrate_device(self):
  228. user_id = "@boris:dehydration"
  229. self.get_success(self.store.register_user(user_id, "foobar"))
  230. # First check if we can store and fetch a dehydrated device
  231. stored_dehydrated_device_id = self.get_success(
  232. self.handler.store_dehydrated_device(
  233. user_id=user_id,
  234. device_data={"device_data": {"foo": "bar"}},
  235. initial_device_display_name="dehydrated device",
  236. )
  237. )
  238. retrieved_device_id, device_data = self.get_success(
  239. self.handler.get_dehydrated_device(user_id=user_id)
  240. )
  241. self.assertEqual(retrieved_device_id, stored_dehydrated_device_id)
  242. self.assertEqual(device_data, {"device_data": {"foo": "bar"}})
  243. # Create a new login for the user and dehydrated the device
  244. device_id, access_token, _expiration_time, _refresh_token = self.get_success(
  245. self.registration.register_device(
  246. user_id=user_id,
  247. device_id=None,
  248. initial_display_name="new device",
  249. )
  250. )
  251. # Trying to claim a nonexistent device should throw an error
  252. self.get_failure(
  253. self.handler.rehydrate_device(
  254. user_id=user_id,
  255. access_token=access_token,
  256. device_id="not the right device ID",
  257. ),
  258. synapse.api.errors.NotFoundError,
  259. )
  260. # dehydrating the right devices should succeed and change our device ID
  261. # to the dehydrated device's ID
  262. res = self.get_success(
  263. self.handler.rehydrate_device(
  264. user_id=user_id,
  265. access_token=access_token,
  266. device_id=retrieved_device_id,
  267. )
  268. )
  269. self.assertEqual(res, {"success": True})
  270. # make sure that our device ID has changed
  271. user_info = self.get_success(self.auth.get_user_by_access_token(access_token))
  272. self.assertEqual(user_info.device_id, retrieved_device_id)
  273. # make sure the device has the display name that was set from the login
  274. res = self.get_success(self.handler.get_device(user_id, retrieved_device_id))
  275. self.assertEqual(res["display_name"], "new device")
  276. # make sure that the device ID that we were initially assigned no longer exists
  277. self.get_failure(
  278. self.handler.get_device(user_id, device_id),
  279. synapse.api.errors.NotFoundError,
  280. )
  281. # make sure that there's no device available for dehydrating now
  282. ret = self.get_success(self.handler.get_dehydrated_device(user_id=user_id))
  283. self.assertIsNone(ret)