test_client_ips.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. # Copyright 2016 OpenMarket Ltd
  2. # Copyright 2018 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from typing import Any, Dict, List, Optional, Tuple, cast
  16. from unittest.mock import AsyncMock
  17. from parameterized import parameterized
  18. from twisted.test.proto_helpers import MemoryReactor
  19. import synapse.rest.admin
  20. from synapse.http.site import XForwardedForRequest
  21. from synapse.rest.client import login
  22. from synapse.server import HomeServer
  23. from synapse.storage.databases.main.client_ips import (
  24. LAST_SEEN_GRANULARITY,
  25. DeviceLastConnectionInfo,
  26. )
  27. from synapse.types import UserID
  28. from synapse.util import Clock
  29. from tests import unittest
  30. from tests.server import make_request
  31. from tests.unittest import override_config
  32. class ClientIpStoreTestCase(unittest.HomeserverTestCase):
  33. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  34. self.store = hs.get_datastores().main
  35. def test_insert_new_client_ip(self) -> None:
  36. self.reactor.advance(12345678)
  37. user_id = "@user:id"
  38. device_id = "MY_DEVICE"
  39. # Insert a user IP
  40. self.get_success(
  41. self.store.store_device(
  42. user_id,
  43. device_id,
  44. "display name",
  45. )
  46. )
  47. self.get_success(
  48. self.store.insert_client_ip(
  49. user_id, "access_token", "ip", "user_agent", device_id
  50. )
  51. )
  52. # Trigger the storage loop
  53. self.reactor.advance(10)
  54. result = self.get_success(
  55. self.store.get_last_client_ip_by_device(user_id, device_id)
  56. )
  57. r = result[(user_id, device_id)]
  58. self.assertEqual(
  59. DeviceLastConnectionInfo(
  60. user_id=user_id,
  61. device_id=device_id,
  62. ip="ip",
  63. user_agent="user_agent",
  64. last_seen=12345678000,
  65. ),
  66. r,
  67. )
  68. def test_insert_new_client_ip_none_device_id(self) -> None:
  69. """
  70. An insert with a device ID of NULL will not create a new entry, but
  71. update an existing entry in the user_ips table.
  72. """
  73. self.reactor.advance(12345678)
  74. user_id = "@user:id"
  75. # Add & trigger the storage loop
  76. self.get_success(
  77. self.store.insert_client_ip(
  78. user_id, "access_token", "ip", "user_agent", None
  79. )
  80. )
  81. self.reactor.advance(200)
  82. self.pump(0)
  83. result = cast(
  84. List[Tuple[str, str, str, Optional[str], int]],
  85. self.get_success(
  86. self.store.db_pool.simple_select_list(
  87. table="user_ips",
  88. keyvalues={"user_id": user_id},
  89. retcols=[
  90. "access_token",
  91. "ip",
  92. "user_agent",
  93. "device_id",
  94. "last_seen",
  95. ],
  96. desc="get_user_ip_and_agents",
  97. )
  98. ),
  99. )
  100. self.assertEqual(
  101. result, [("access_token", "ip", "user_agent", None, 12345678000)]
  102. )
  103. # Add another & trigger the storage loop
  104. self.get_success(
  105. self.store.insert_client_ip(
  106. user_id, "access_token", "ip", "user_agent", None
  107. )
  108. )
  109. self.reactor.advance(10)
  110. self.pump(0)
  111. result = cast(
  112. List[Tuple[str, str, str, Optional[str], int]],
  113. self.get_success(
  114. self.store.db_pool.simple_select_list(
  115. table="user_ips",
  116. keyvalues={"user_id": user_id},
  117. retcols=[
  118. "access_token",
  119. "ip",
  120. "user_agent",
  121. "device_id",
  122. "last_seen",
  123. ],
  124. desc="get_user_ip_and_agents",
  125. )
  126. ),
  127. )
  128. # Only one result, has been upserted.
  129. self.assertEqual(
  130. result, [("access_token", "ip", "user_agent", None, 12345878000)]
  131. )
  132. @parameterized.expand([(False,), (True,)])
  133. def test_get_last_client_ip_by_device(self, after_persisting: bool) -> None:
  134. """Test `get_last_client_ip_by_device` for persisted and unpersisted data"""
  135. self.reactor.advance(12345678)
  136. user_id = "@user:id"
  137. device_id = "MY_DEVICE"
  138. # Insert a user IP
  139. self.get_success(
  140. self.store.store_device(
  141. user_id,
  142. device_id,
  143. "display name",
  144. )
  145. )
  146. self.get_success(
  147. self.store.insert_client_ip(
  148. user_id, "access_token", "ip", "user_agent", device_id
  149. )
  150. )
  151. if after_persisting:
  152. # Trigger the storage loop
  153. self.reactor.advance(10)
  154. else:
  155. # Check that the new IP and user agent has not been stored yet
  156. db_result = cast(
  157. List[Tuple[str, Optional[str], Optional[str], str, Optional[int]]],
  158. self.get_success(
  159. self.store.db_pool.simple_select_list(
  160. table="devices",
  161. keyvalues={},
  162. retcols=(
  163. "user_id",
  164. "ip",
  165. "user_agent",
  166. "device_id",
  167. "last_seen",
  168. ),
  169. ),
  170. ),
  171. )
  172. self.assertEqual(db_result, [(user_id, None, None, device_id, None)])
  173. result = self.get_success(
  174. self.store.get_last_client_ip_by_device(user_id, device_id)
  175. )
  176. self.assertEqual(
  177. result,
  178. {
  179. (user_id, device_id): DeviceLastConnectionInfo(
  180. user_id=user_id,
  181. device_id=device_id,
  182. ip="ip",
  183. user_agent="user_agent",
  184. last_seen=12345678000,
  185. ),
  186. },
  187. )
  188. def test_get_last_client_ip_by_device_combined_data(self) -> None:
  189. """Test that `get_last_client_ip_by_device` combines persisted and unpersisted
  190. data together correctly
  191. """
  192. self.reactor.advance(12345678)
  193. user_id = "@user:id"
  194. device_id_1 = "MY_DEVICE_1"
  195. device_id_2 = "MY_DEVICE_2"
  196. # Insert user IPs
  197. self.get_success(
  198. self.store.store_device(
  199. user_id,
  200. device_id_1,
  201. "display name",
  202. )
  203. )
  204. self.get_success(
  205. self.store.store_device(
  206. user_id,
  207. device_id_2,
  208. "display name",
  209. )
  210. )
  211. self.get_success(
  212. self.store.insert_client_ip(
  213. user_id, "access_token_1", "ip_1", "user_agent_1", device_id_1
  214. )
  215. )
  216. self.get_success(
  217. self.store.insert_client_ip(
  218. user_id, "access_token_2", "ip_2", "user_agent_2", device_id_2
  219. )
  220. )
  221. # Trigger the storage loop and wait for the rate limiting period to be over
  222. self.reactor.advance(10 + LAST_SEEN_GRANULARITY / 1000)
  223. # Update the user agent for the second device, without running the storage loop
  224. self.get_success(
  225. self.store.insert_client_ip(
  226. user_id, "access_token_2", "ip_2", "user_agent_3", device_id_2
  227. )
  228. )
  229. # Check that the new IP and user agent has not been stored yet
  230. db_result = cast(
  231. List[Tuple[str, Optional[str], Optional[str], str, Optional[int]]],
  232. self.get_success(
  233. self.store.db_pool.simple_select_list(
  234. table="devices",
  235. keyvalues={},
  236. retcols=("user_id", "ip", "user_agent", "device_id", "last_seen"),
  237. ),
  238. ),
  239. )
  240. self.assertCountEqual(
  241. db_result,
  242. [
  243. (user_id, "ip_1", "user_agent_1", device_id_1, 12345678000),
  244. (user_id, "ip_2", "user_agent_2", device_id_2, 12345678000),
  245. ],
  246. )
  247. # Check that data from the database and memory are combined together correctly
  248. result = self.get_success(
  249. self.store.get_last_client_ip_by_device(user_id, None)
  250. )
  251. self.assertEqual(
  252. result,
  253. {
  254. (user_id, device_id_1): DeviceLastConnectionInfo(
  255. user_id=user_id,
  256. device_id=device_id_1,
  257. ip="ip_1",
  258. user_agent="user_agent_1",
  259. last_seen=12345678000,
  260. ),
  261. (user_id, device_id_2): DeviceLastConnectionInfo(
  262. user_id=user_id,
  263. device_id=device_id_2,
  264. ip="ip_2",
  265. user_agent="user_agent_3",
  266. last_seen=12345688000 + LAST_SEEN_GRANULARITY,
  267. ),
  268. },
  269. )
  270. @parameterized.expand([(False,), (True,)])
  271. def test_get_user_ip_and_agents(self, after_persisting: bool) -> None:
  272. """Test `get_user_ip_and_agents` for persisted and unpersisted data"""
  273. self.reactor.advance(12345678)
  274. user_id = "@user:id"
  275. user = UserID.from_string(user_id)
  276. # Insert a user IP
  277. self.get_success(
  278. self.store.insert_client_ip(
  279. user_id, "access_token", "ip", "user_agent", "MY_DEVICE"
  280. )
  281. )
  282. if after_persisting:
  283. # Trigger the storage loop
  284. self.reactor.advance(10)
  285. else:
  286. # Check that the new IP and user agent has not been stored yet
  287. db_result = self.get_success(
  288. self.store.db_pool.simple_select_list(
  289. table="user_ips",
  290. keyvalues={},
  291. retcols=("access_token", "ip", "user_agent", "last_seen"),
  292. ),
  293. )
  294. self.assertEqual(db_result, [])
  295. self.assertEqual(
  296. self.get_success(self.store.get_user_ip_and_agents(user)),
  297. [
  298. {
  299. "access_token": "access_token",
  300. "ip": "ip",
  301. "user_agent": "user_agent",
  302. "last_seen": 12345678000,
  303. },
  304. ],
  305. )
  306. def test_get_user_ip_and_agents_combined_data(self) -> None:
  307. """Test that `get_user_ip_and_agents` combines persisted and unpersisted data
  308. together correctly
  309. """
  310. self.reactor.advance(12345678)
  311. user_id = "@user:id"
  312. user = UserID.from_string(user_id)
  313. # Insert user IPs
  314. self.get_success(
  315. self.store.insert_client_ip(
  316. user_id, "access_token", "ip_1", "user_agent_1", "MY_DEVICE_1"
  317. )
  318. )
  319. self.get_success(
  320. self.store.insert_client_ip(
  321. user_id, "access_token", "ip_2", "user_agent_2", "MY_DEVICE_2"
  322. )
  323. )
  324. # Trigger the storage loop and wait for the rate limiting period to be over
  325. self.reactor.advance(10 + LAST_SEEN_GRANULARITY / 1000)
  326. # Update the user agent for the second device, without running the storage loop
  327. self.get_success(
  328. self.store.insert_client_ip(
  329. user_id, "access_token", "ip_2", "user_agent_3", "MY_DEVICE_2"
  330. )
  331. )
  332. # Check that the new IP and user agent has not been stored yet
  333. db_result = cast(
  334. List[Tuple[str, str, str, int]],
  335. self.get_success(
  336. self.store.db_pool.simple_select_list(
  337. table="user_ips",
  338. keyvalues={},
  339. retcols=("access_token", "ip", "user_agent", "last_seen"),
  340. ),
  341. ),
  342. )
  343. self.assertEqual(
  344. db_result,
  345. [
  346. ("access_token", "ip_1", "user_agent_1", 12345678000),
  347. ("access_token", "ip_2", "user_agent_2", 12345678000),
  348. ],
  349. )
  350. # Check that data from the database and memory are combined together correctly
  351. self.assertCountEqual(
  352. self.get_success(self.store.get_user_ip_and_agents(user)),
  353. [
  354. {
  355. "access_token": "access_token",
  356. "ip": "ip_1",
  357. "user_agent": "user_agent_1",
  358. "last_seen": 12345678000,
  359. },
  360. {
  361. "access_token": "access_token",
  362. "ip": "ip_2",
  363. "user_agent": "user_agent_3",
  364. "last_seen": 12345688000 + LAST_SEEN_GRANULARITY,
  365. },
  366. ],
  367. )
  368. @override_config({"limit_usage_by_mau": False, "max_mau_value": 50})
  369. def test_disabled_monthly_active_user(self) -> None:
  370. user_id = "@user:server"
  371. self.get_success(
  372. self.store.insert_client_ip(
  373. user_id, "access_token", "ip", "user_agent", "device_id"
  374. )
  375. )
  376. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  377. self.assertFalse(active)
  378. @override_config({"limit_usage_by_mau": True, "max_mau_value": 50})
  379. def test_adding_monthly_active_user_when_full(self) -> None:
  380. lots_of_users = 100
  381. user_id = "@user:server"
  382. self.store.get_monthly_active_count = AsyncMock(return_value=lots_of_users)
  383. self.get_success(
  384. self.store.insert_client_ip(
  385. user_id, "access_token", "ip", "user_agent", "device_id"
  386. )
  387. )
  388. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  389. self.assertFalse(active)
  390. @override_config({"limit_usage_by_mau": True, "max_mau_value": 50})
  391. def test_adding_monthly_active_user_when_space(self) -> None:
  392. user_id = "@user:server"
  393. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  394. self.assertFalse(active)
  395. # Trigger the saving loop
  396. self.reactor.advance(10)
  397. self.get_success(
  398. self.store.insert_client_ip(
  399. user_id, "access_token", "ip", "user_agent", "device_id"
  400. )
  401. )
  402. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  403. self.assertTrue(active)
  404. @override_config({"limit_usage_by_mau": True, "max_mau_value": 50})
  405. def test_updating_monthly_active_user_when_space(self) -> None:
  406. user_id = "@user:server"
  407. self.get_success(self.store.register_user(user_id=user_id, password_hash=None))
  408. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  409. self.assertFalse(active)
  410. # Trigger the saving loop
  411. self.reactor.advance(10)
  412. self.get_success(
  413. self.store.insert_client_ip(
  414. user_id, "access_token", "ip", "user_agent", "device_id"
  415. )
  416. )
  417. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  418. self.assertTrue(active)
  419. def test_devices_last_seen_bg_update(self) -> None:
  420. # First make sure we have completed all updates.
  421. self.wait_for_background_updates()
  422. user_id = "@user:id"
  423. device_id = "MY_DEVICE"
  424. # Insert a user IP
  425. self.get_success(
  426. self.store.store_device(
  427. user_id,
  428. device_id,
  429. "display name",
  430. )
  431. )
  432. self.get_success(
  433. self.store.insert_client_ip(
  434. user_id, "access_token", "ip", "user_agent", device_id
  435. )
  436. )
  437. # Force persisting to disk
  438. self.reactor.advance(200)
  439. # But clear the associated entry in devices table
  440. self.get_success(
  441. self.store.db_pool.simple_update(
  442. table="devices",
  443. keyvalues={"user_id": user_id, "device_id": device_id},
  444. updatevalues={"last_seen": None, "ip": None, "user_agent": None},
  445. desc="test_devices_last_seen_bg_update",
  446. )
  447. )
  448. # We should now get nulls when querying
  449. result = self.get_success(
  450. self.store.get_last_client_ip_by_device(user_id, device_id)
  451. )
  452. r = result[(user_id, device_id)]
  453. self.assertEqual(
  454. DeviceLastConnectionInfo(
  455. user_id=user_id,
  456. device_id=device_id,
  457. ip=None,
  458. user_agent=None,
  459. last_seen=None,
  460. ),
  461. r,
  462. )
  463. # Register the background update to run again.
  464. self.get_success(
  465. self.store.db_pool.simple_insert(
  466. table="background_updates",
  467. values={
  468. "update_name": "devices_last_seen",
  469. "progress_json": "{}",
  470. "depends_on": None,
  471. },
  472. )
  473. )
  474. # ... and tell the DataStore that it hasn't finished all updates yet
  475. self.store.db_pool.updates._all_done = False
  476. # Now let's actually drive the updates to completion
  477. self.wait_for_background_updates()
  478. # We should now get the correct result again
  479. result = self.get_success(
  480. self.store.get_last_client_ip_by_device(user_id, device_id)
  481. )
  482. r = result[(user_id, device_id)]
  483. self.assertEqual(
  484. DeviceLastConnectionInfo(
  485. user_id=user_id,
  486. device_id=device_id,
  487. ip="ip",
  488. user_agent="user_agent",
  489. last_seen=0,
  490. ),
  491. r,
  492. )
  493. def test_old_user_ips_pruned(self) -> None:
  494. # First make sure we have completed all updates.
  495. self.wait_for_background_updates()
  496. user_id = "@user:id"
  497. device_id = "MY_DEVICE"
  498. # Insert a user IP
  499. self.get_success(
  500. self.store.store_device(
  501. user_id,
  502. device_id,
  503. "display name",
  504. )
  505. )
  506. self.get_success(
  507. self.store.insert_client_ip(
  508. user_id, "access_token", "ip", "user_agent", device_id
  509. )
  510. )
  511. # Force persisting to disk
  512. self.reactor.advance(200)
  513. # We should see that in the DB
  514. result = cast(
  515. List[Tuple[str, str, str, Optional[str], int]],
  516. self.get_success(
  517. self.store.db_pool.simple_select_list(
  518. table="user_ips",
  519. keyvalues={"user_id": user_id},
  520. retcols=[
  521. "access_token",
  522. "ip",
  523. "user_agent",
  524. "device_id",
  525. "last_seen",
  526. ],
  527. desc="get_user_ip_and_agents",
  528. )
  529. ),
  530. )
  531. self.assertEqual(
  532. result,
  533. [("access_token", "ip", "user_agent", device_id, 0)],
  534. )
  535. # Now advance by a couple of months
  536. self.reactor.advance(60 * 24 * 60 * 60)
  537. # We should get no results.
  538. result = cast(
  539. List[Tuple[str, str, str, Optional[str], int]],
  540. self.get_success(
  541. self.store.db_pool.simple_select_list(
  542. table="user_ips",
  543. keyvalues={"user_id": user_id},
  544. retcols=[
  545. "access_token",
  546. "ip",
  547. "user_agent",
  548. "device_id",
  549. "last_seen",
  550. ],
  551. desc="get_user_ip_and_agents",
  552. )
  553. ),
  554. )
  555. self.assertEqual(result, [])
  556. # But we should still get the correct values for the device
  557. result2 = self.get_success(
  558. self.store.get_last_client_ip_by_device(user_id, device_id)
  559. )
  560. r = result2[(user_id, device_id)]
  561. self.assertEqual(
  562. DeviceLastConnectionInfo(
  563. user_id=user_id,
  564. device_id=device_id,
  565. ip="ip",
  566. user_agent="user_agent",
  567. last_seen=0,
  568. ),
  569. r,
  570. )
  571. def test_invalid_user_agents_are_ignored(self) -> None:
  572. # First make sure we have completed all updates.
  573. self.wait_for_background_updates()
  574. user_id1 = "@user1:id"
  575. user_id2 = "@user2:id"
  576. device_id1 = "MY_DEVICE1"
  577. device_id2 = "MY_DEVICE2"
  578. access_token1 = "access_token1"
  579. access_token2 = "access_token2"
  580. # Insert a user IP 1
  581. self.get_success(
  582. self.store.store_device(
  583. user_id1,
  584. device_id1,
  585. "display name1",
  586. )
  587. )
  588. # Insert a user IP 2
  589. self.get_success(
  590. self.store.store_device(
  591. user_id2,
  592. device_id2,
  593. "display name2",
  594. )
  595. )
  596. self.get_success(
  597. self.store.insert_client_ip(
  598. user_id1, access_token1, "ip", "sync-v3-proxy-", device_id1
  599. )
  600. )
  601. self.get_success(
  602. self.store.insert_client_ip(
  603. user_id2, access_token2, "ip", "user_agent", device_id2
  604. )
  605. )
  606. # Force persisting to disk
  607. self.reactor.advance(200)
  608. # We should see that in the DB
  609. result = cast(
  610. List[Tuple[str, str, str, Optional[str], int]],
  611. self.get_success(
  612. self.store.db_pool.simple_select_list(
  613. table="user_ips",
  614. keyvalues={},
  615. retcols=[
  616. "access_token",
  617. "ip",
  618. "user_agent",
  619. "device_id",
  620. "last_seen",
  621. ],
  622. desc="get_user_ip_and_agents",
  623. )
  624. ),
  625. )
  626. # ensure user1 is filtered out
  627. self.assertEqual(result, [(access_token2, "ip", "user_agent", device_id2, 0)])
  628. class ClientIpAuthTestCase(unittest.HomeserverTestCase):
  629. servlets = [
  630. synapse.rest.admin.register_servlets,
  631. login.register_servlets,
  632. ]
  633. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  634. self.store = self.hs.get_datastores().main
  635. self.user_id = self.register_user("bob", "abc123", True)
  636. def test_request_with_xforwarded(self) -> None:
  637. """
  638. The IP in X-Forwarded-For is entered into the client IPs table.
  639. """
  640. self._runtest(
  641. {b"X-Forwarded-For": b"127.9.0.1"},
  642. "127.9.0.1",
  643. {"request": XForwardedForRequest},
  644. )
  645. def test_request_from_getPeer(self) -> None:
  646. """
  647. The IP returned by getPeer is entered into the client IPs table, if
  648. there's no X-Forwarded-For header.
  649. """
  650. self._runtest({}, "127.0.0.1", {})
  651. def _runtest(
  652. self,
  653. headers: Dict[bytes, bytes],
  654. expected_ip: str,
  655. make_request_args: Dict[str, Any],
  656. ) -> None:
  657. device_id = "bleb"
  658. access_token = self.login("bob", "abc123", device_id=device_id)
  659. # Advance to a known time
  660. self.reactor.advance(123456 - self.reactor.seconds())
  661. headers1 = {b"User-Agent": b"Mozzila pizza"}
  662. headers1.update(headers)
  663. make_request(
  664. self.reactor,
  665. self.site,
  666. "GET",
  667. "/_synapse/admin/v2/users/" + self.user_id,
  668. access_token=access_token,
  669. custom_headers=headers1.items(),
  670. **make_request_args,
  671. )
  672. # Advance so the save loop occurs
  673. self.reactor.advance(100)
  674. result = self.get_success(
  675. self.store.get_last_client_ip_by_device(self.user_id, device_id)
  676. )
  677. r = result[(self.user_id, device_id)]
  678. self.assertEqual(
  679. DeviceLastConnectionInfo(
  680. user_id=self.user_id,
  681. device_id=device_id,
  682. ip=expected_ip,
  683. user_agent="Mozzila pizza",
  684. last_seen=123456100,
  685. ),
  686. r,
  687. )