test_client_ips.py 14 KB

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