test_client_ips.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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 unittest.mock import Mock
  16. import synapse.rest.admin
  17. from synapse.http.site import XForwardedForRequest
  18. from synapse.rest.client.v1 import login
  19. from tests import unittest
  20. from tests.server import make_request
  21. from tests.test_utils import make_awaitable
  22. from tests.unittest import override_config
  23. class ClientIpStoreTestCase(unittest.HomeserverTestCase):
  24. def make_homeserver(self, reactor, clock):
  25. hs = self.setup_test_homeserver()
  26. return hs
  27. def prepare(self, hs, reactor, clock):
  28. self.store = self.hs.get_datastore()
  29. def test_insert_new_client_ip(self):
  30. self.reactor.advance(12345678)
  31. user_id = "@user:id"
  32. device_id = "MY_DEVICE"
  33. # Insert a user IP
  34. self.get_success(
  35. self.store.store_device(
  36. user_id,
  37. device_id,
  38. "display name",
  39. )
  40. )
  41. self.get_success(
  42. self.store.insert_client_ip(
  43. user_id, "access_token", "ip", "user_agent", device_id
  44. )
  45. )
  46. # Trigger the storage loop
  47. self.reactor.advance(10)
  48. result = self.get_success(
  49. self.store.get_last_client_ip_by_device(user_id, device_id)
  50. )
  51. r = result[(user_id, device_id)]
  52. self.assertDictContainsSubset(
  53. {
  54. "user_id": user_id,
  55. "device_id": device_id,
  56. "ip": "ip",
  57. "user_agent": "user_agent",
  58. "last_seen": 12345678000,
  59. },
  60. r,
  61. )
  62. def test_insert_new_client_ip_none_device_id(self):
  63. """
  64. An insert with a device ID of NULL will not create a new entry, but
  65. update an existing entry in the user_ips table.
  66. """
  67. self.reactor.advance(12345678)
  68. user_id = "@user:id"
  69. # Add & trigger the storage loop
  70. self.get_success(
  71. self.store.insert_client_ip(
  72. user_id, "access_token", "ip", "user_agent", None
  73. )
  74. )
  75. self.reactor.advance(200)
  76. self.pump(0)
  77. result = self.get_success(
  78. self.store.db_pool.simple_select_list(
  79. table="user_ips",
  80. keyvalues={"user_id": user_id},
  81. retcols=["access_token", "ip", "user_agent", "device_id", "last_seen"],
  82. desc="get_user_ip_and_agents",
  83. )
  84. )
  85. self.assertEqual(
  86. result,
  87. [
  88. {
  89. "access_token": "access_token",
  90. "ip": "ip",
  91. "user_agent": "user_agent",
  92. "device_id": None,
  93. "last_seen": 12345678000,
  94. }
  95. ],
  96. )
  97. # Add another & trigger the storage loop
  98. self.get_success(
  99. self.store.insert_client_ip(
  100. user_id, "access_token", "ip", "user_agent", None
  101. )
  102. )
  103. self.reactor.advance(10)
  104. self.pump(0)
  105. result = self.get_success(
  106. self.store.db_pool.simple_select_list(
  107. table="user_ips",
  108. keyvalues={"user_id": user_id},
  109. retcols=["access_token", "ip", "user_agent", "device_id", "last_seen"],
  110. desc="get_user_ip_and_agents",
  111. )
  112. )
  113. # Only one result, has been upserted.
  114. self.assertEqual(
  115. result,
  116. [
  117. {
  118. "access_token": "access_token",
  119. "ip": "ip",
  120. "user_agent": "user_agent",
  121. "device_id": None,
  122. "last_seen": 12345878000,
  123. }
  124. ],
  125. )
  126. @override_config({"limit_usage_by_mau": False, "max_mau_value": 50})
  127. def test_disabled_monthly_active_user(self):
  128. user_id = "@user:server"
  129. self.get_success(
  130. self.store.insert_client_ip(
  131. user_id, "access_token", "ip", "user_agent", "device_id"
  132. )
  133. )
  134. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  135. self.assertFalse(active)
  136. @override_config({"limit_usage_by_mau": True, "max_mau_value": 50})
  137. def test_adding_monthly_active_user_when_full(self):
  138. lots_of_users = 100
  139. user_id = "@user:server"
  140. self.store.get_monthly_active_count = Mock(
  141. return_value=make_awaitable(lots_of_users)
  142. )
  143. self.get_success(
  144. self.store.insert_client_ip(
  145. user_id, "access_token", "ip", "user_agent", "device_id"
  146. )
  147. )
  148. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  149. self.assertFalse(active)
  150. @override_config({"limit_usage_by_mau": True, "max_mau_value": 50})
  151. def test_adding_monthly_active_user_when_space(self):
  152. user_id = "@user:server"
  153. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  154. self.assertFalse(active)
  155. # Trigger the saving loop
  156. self.reactor.advance(10)
  157. self.get_success(
  158. self.store.insert_client_ip(
  159. user_id, "access_token", "ip", "user_agent", "device_id"
  160. )
  161. )
  162. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  163. self.assertTrue(active)
  164. @override_config({"limit_usage_by_mau": True, "max_mau_value": 50})
  165. def test_updating_monthly_active_user_when_space(self):
  166. user_id = "@user:server"
  167. self.get_success(self.store.register_user(user_id=user_id, password_hash=None))
  168. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  169. self.assertFalse(active)
  170. # Trigger the saving loop
  171. self.reactor.advance(10)
  172. self.get_success(
  173. self.store.insert_client_ip(
  174. user_id, "access_token", "ip", "user_agent", "device_id"
  175. )
  176. )
  177. active = self.get_success(self.store.user_last_seen_monthly_active(user_id))
  178. self.assertTrue(active)
  179. def test_devices_last_seen_bg_update(self):
  180. # First make sure we have completed all updates.
  181. while not self.get_success(
  182. self.store.db_pool.updates.has_completed_background_updates()
  183. ):
  184. self.get_success(
  185. self.store.db_pool.updates.do_next_background_update(100), by=0.1
  186. )
  187. user_id = "@user:id"
  188. device_id = "MY_DEVICE"
  189. # Insert a user IP
  190. self.get_success(
  191. self.store.store_device(
  192. user_id,
  193. device_id,
  194. "display name",
  195. )
  196. )
  197. self.get_success(
  198. self.store.insert_client_ip(
  199. user_id, "access_token", "ip", "user_agent", device_id
  200. )
  201. )
  202. # Force persisting to disk
  203. self.reactor.advance(200)
  204. # But clear the associated entry in devices table
  205. self.get_success(
  206. self.store.db_pool.simple_update(
  207. table="devices",
  208. keyvalues={"user_id": user_id, "device_id": device_id},
  209. updatevalues={"last_seen": None, "ip": None, "user_agent": None},
  210. desc="test_devices_last_seen_bg_update",
  211. )
  212. )
  213. # We should now get nulls when querying
  214. result = self.get_success(
  215. self.store.get_last_client_ip_by_device(user_id, device_id)
  216. )
  217. r = result[(user_id, device_id)]
  218. self.assertDictContainsSubset(
  219. {
  220. "user_id": user_id,
  221. "device_id": device_id,
  222. "ip": None,
  223. "user_agent": None,
  224. "last_seen": None,
  225. },
  226. r,
  227. )
  228. # Register the background update to run again.
  229. self.get_success(
  230. self.store.db_pool.simple_insert(
  231. table="background_updates",
  232. values={
  233. "update_name": "devices_last_seen",
  234. "progress_json": "{}",
  235. "depends_on": None,
  236. },
  237. )
  238. )
  239. # ... and tell the DataStore that it hasn't finished all updates yet
  240. self.store.db_pool.updates._all_done = False
  241. # Now let's actually drive the updates to completion
  242. while not self.get_success(
  243. self.store.db_pool.updates.has_completed_background_updates()
  244. ):
  245. self.get_success(
  246. self.store.db_pool.updates.do_next_background_update(100), by=0.1
  247. )
  248. # We should now get the correct result again
  249. result = self.get_success(
  250. self.store.get_last_client_ip_by_device(user_id, device_id)
  251. )
  252. r = result[(user_id, device_id)]
  253. self.assertDictContainsSubset(
  254. {
  255. "user_id": user_id,
  256. "device_id": device_id,
  257. "ip": "ip",
  258. "user_agent": "user_agent",
  259. "last_seen": 0,
  260. },
  261. r,
  262. )
  263. def test_old_user_ips_pruned(self):
  264. # First make sure we have completed all updates.
  265. while not self.get_success(
  266. self.store.db_pool.updates.has_completed_background_updates()
  267. ):
  268. self.get_success(
  269. self.store.db_pool.updates.do_next_background_update(100), by=0.1
  270. )
  271. user_id = "@user:id"
  272. device_id = "MY_DEVICE"
  273. # Insert a user IP
  274. self.get_success(
  275. self.store.store_device(
  276. user_id,
  277. device_id,
  278. "display name",
  279. )
  280. )
  281. self.get_success(
  282. self.store.insert_client_ip(
  283. user_id, "access_token", "ip", "user_agent", device_id
  284. )
  285. )
  286. # Force persisting to disk
  287. self.reactor.advance(200)
  288. # We should see that in the DB
  289. result = self.get_success(
  290. self.store.db_pool.simple_select_list(
  291. table="user_ips",
  292. keyvalues={"user_id": user_id},
  293. retcols=["access_token", "ip", "user_agent", "device_id", "last_seen"],
  294. desc="get_user_ip_and_agents",
  295. )
  296. )
  297. self.assertEqual(
  298. result,
  299. [
  300. {
  301. "access_token": "access_token",
  302. "ip": "ip",
  303. "user_agent": "user_agent",
  304. "device_id": device_id,
  305. "last_seen": 0,
  306. }
  307. ],
  308. )
  309. # Now advance by a couple of months
  310. self.reactor.advance(60 * 24 * 60 * 60)
  311. # We should get no results.
  312. result = self.get_success(
  313. self.store.db_pool.simple_select_list(
  314. table="user_ips",
  315. keyvalues={"user_id": user_id},
  316. retcols=["access_token", "ip", "user_agent", "device_id", "last_seen"],
  317. desc="get_user_ip_and_agents",
  318. )
  319. )
  320. self.assertEqual(result, [])
  321. # But we should still get the correct values for the device
  322. result = self.get_success(
  323. self.store.get_last_client_ip_by_device(user_id, device_id)
  324. )
  325. r = result[(user_id, device_id)]
  326. self.assertDictContainsSubset(
  327. {
  328. "user_id": user_id,
  329. "device_id": device_id,
  330. "ip": "ip",
  331. "user_agent": "user_agent",
  332. "last_seen": 0,
  333. },
  334. r,
  335. )
  336. class ClientIpAuthTestCase(unittest.HomeserverTestCase):
  337. servlets = [
  338. synapse.rest.admin.register_servlets,
  339. login.register_servlets,
  340. ]
  341. def make_homeserver(self, reactor, clock):
  342. hs = self.setup_test_homeserver()
  343. return hs
  344. def prepare(self, hs, reactor, clock):
  345. self.store = self.hs.get_datastore()
  346. self.user_id = self.register_user("bob", "abc123", True)
  347. def test_request_with_xforwarded(self):
  348. """
  349. The IP in X-Forwarded-For is entered into the client IPs table.
  350. """
  351. self._runtest(
  352. {b"X-Forwarded-For": b"127.9.0.1"},
  353. "127.9.0.1",
  354. {"request": XForwardedForRequest},
  355. )
  356. def test_request_from_getPeer(self):
  357. """
  358. The IP returned by getPeer is entered into the client IPs table, if
  359. there's no X-Forwarded-For header.
  360. """
  361. self._runtest({}, "127.0.0.1", {})
  362. def _runtest(self, headers, expected_ip, make_request_args):
  363. device_id = "bleb"
  364. access_token = self.login("bob", "abc123", device_id=device_id)
  365. # Advance to a known time
  366. self.reactor.advance(123456 - self.reactor.seconds())
  367. headers1 = {b"User-Agent": b"Mozzila pizza"}
  368. headers1.update(headers)
  369. make_request(
  370. self.reactor,
  371. self.site,
  372. "GET",
  373. "/_synapse/admin/v2/users/" + self.user_id,
  374. access_token=access_token,
  375. custom_headers=headers1.items(),
  376. **make_request_args,
  377. )
  378. # Advance so the save loop occurs
  379. self.reactor.advance(100)
  380. result = self.get_success(
  381. self.store.get_last_client_ip_by_device(self.user_id, device_id)
  382. )
  383. r = result[(self.user_id, device_id)]
  384. self.assertDictContainsSubset(
  385. {
  386. "user_id": self.user_id,
  387. "device_id": device_id,
  388. "ip": expected_ip,
  389. "user_agent": "Mozzila pizza",
  390. "last_seen": 123456100,
  391. },
  392. r,
  393. )