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.
 
 
 
 
 
 

337 lines
11 KiB

  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from twisted.test.proto_helpers import MemoryReactor
  15. from synapse.api.constants import AccountDataTypes
  16. from synapse.push.rulekinds import PRIORITY_CLASS_MAP
  17. from synapse.rest import admin
  18. from synapse.rest.client import account, login
  19. from synapse.server import HomeServer
  20. from synapse.synapse_rust.push import PushRule
  21. from synapse.util import Clock
  22. from tests.unittest import HomeserverTestCase
  23. class DeactivateAccountTestCase(HomeserverTestCase):
  24. servlets = [
  25. login.register_servlets,
  26. admin.register_servlets,
  27. account.register_servlets,
  28. ]
  29. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  30. self._store = hs.get_datastores().main
  31. self.user = self.register_user("user", "pass")
  32. self.token = self.login("user", "pass")
  33. def _deactivate_my_account(self) -> None:
  34. """
  35. Deactivates the account `self.user` using `self.token` and asserts
  36. that it returns a 200 success code.
  37. """
  38. req = self.make_request(
  39. "POST",
  40. "account/deactivate",
  41. {
  42. "auth": {
  43. "type": "m.login.password",
  44. "user": self.user,
  45. "password": "pass",
  46. },
  47. "erase": True,
  48. },
  49. access_token=self.token,
  50. )
  51. self.assertEqual(req.code, 200, req)
  52. def test_global_account_data_deleted_upon_deactivation(self) -> None:
  53. """
  54. Tests that global account data is removed upon deactivation.
  55. """
  56. # Add some account data
  57. self.get_success(
  58. self._store.add_account_data_for_user(
  59. self.user,
  60. AccountDataTypes.DIRECT,
  61. {"@someone:remote": ["!somewhere:remote"]},
  62. )
  63. )
  64. # Check that we actually added some.
  65. self.assertIsNotNone(
  66. self.get_success(
  67. self._store.get_global_account_data_by_type_for_user(
  68. self.user, AccountDataTypes.DIRECT
  69. )
  70. ),
  71. )
  72. # Request the deactivation of our account
  73. self._deactivate_my_account()
  74. # Check that the account data does not persist.
  75. self.assertIsNone(
  76. self.get_success(
  77. self._store.get_global_account_data_by_type_for_user(
  78. self.user, AccountDataTypes.DIRECT
  79. )
  80. ),
  81. )
  82. def test_room_account_data_deleted_upon_deactivation(self) -> None:
  83. """
  84. Tests that room account data is removed upon deactivation.
  85. """
  86. room_id = "!room:test"
  87. # Add some room account data
  88. self.get_success(
  89. self._store.add_account_data_to_room(
  90. self.user,
  91. room_id,
  92. "m.fully_read",
  93. {"event_id": "$aaaa:test"},
  94. )
  95. )
  96. # Check that we actually added some.
  97. self.assertIsNotNone(
  98. self.get_success(
  99. self._store.get_account_data_for_room_and_type(
  100. self.user, room_id, "m.fully_read"
  101. )
  102. ),
  103. )
  104. # Request the deactivation of our account
  105. self._deactivate_my_account()
  106. # Check that the account data does not persist.
  107. self.assertIsNone(
  108. self.get_success(
  109. self._store.get_account_data_for_room_and_type(
  110. self.user, room_id, "m.fully_read"
  111. )
  112. ),
  113. )
  114. def _is_custom_rule(self, push_rule: PushRule) -> bool:
  115. """
  116. Default rules start with a dot: such as .m.rule and .im.vector.
  117. This function returns true iff a rule is custom (not default).
  118. """
  119. return "/." not in push_rule.rule_id
  120. def test_push_rules_deleted_upon_account_deactivation(self) -> None:
  121. """
  122. Push rules are a special case of account data.
  123. They are stored separately but get sent to the client as account data in /sync.
  124. This tests that deactivating a user deletes push rules along with the rest
  125. of their account data.
  126. """
  127. # Add a push rule
  128. self.get_success(
  129. self._store.add_push_rule(
  130. self.user,
  131. "personal.override.rule1",
  132. PRIORITY_CLASS_MAP["override"],
  133. [],
  134. [],
  135. )
  136. )
  137. # Test the rule exists
  138. filtered_push_rules = self.get_success(
  139. self._store.get_push_rules_for_user(self.user)
  140. )
  141. # Filter out default rules; we don't care
  142. push_rules = [
  143. r for r, _ in filtered_push_rules.rules() if self._is_custom_rule(r)
  144. ]
  145. # Check our rule made it
  146. self.assertEqual(len(push_rules), 1)
  147. self.assertEqual(push_rules[0].rule_id, "personal.override.rule1")
  148. self.assertEqual(push_rules[0].priority_class, 5)
  149. self.assertEqual(push_rules[0].conditions, [])
  150. self.assertEqual(push_rules[0].actions, [])
  151. # Request the deactivation of our account
  152. self._deactivate_my_account()
  153. filtered_push_rules = self.get_success(
  154. self._store.get_push_rules_for_user(self.user)
  155. )
  156. # Filter out default rules; we don't care
  157. push_rules = [
  158. r for r, _ in filtered_push_rules.rules() if self._is_custom_rule(r)
  159. ]
  160. # Check our rule no longer exists
  161. self.assertEqual(push_rules, [], push_rules)
  162. def test_ignored_users_deleted_upon_deactivation(self) -> None:
  163. """
  164. Ignored users are a special case of account data.
  165. They get denormalised into the `ignored_users` table upon being stored as
  166. account data.
  167. Test that a user's list of ignored users is deleted upon deactivation.
  168. """
  169. # Add an ignored user
  170. self.get_success(
  171. self._store.add_account_data_for_user(
  172. self.user,
  173. AccountDataTypes.IGNORED_USER_LIST,
  174. {"ignored_users": {"@sheltie:test": {}}},
  175. )
  176. )
  177. # Test the user is ignored
  178. self.assertEqual(
  179. self.get_success(self._store.ignored_by("@sheltie:test")), {self.user}
  180. )
  181. # Request the deactivation of our account
  182. self._deactivate_my_account()
  183. # Test the user is no longer ignored by the user that was deactivated
  184. self.assertEqual(
  185. self.get_success(self._store.ignored_by("@sheltie:test")), set()
  186. )
  187. def _rerun_retroactive_account_data_deletion_update(self) -> None:
  188. # Reset the 'all done' flag
  189. self._store.db_pool.updates._all_done = False
  190. self.get_success(
  191. self._store.db_pool.simple_insert(
  192. "background_updates",
  193. {
  194. "update_name": "delete_account_data_for_deactivated_users",
  195. "progress_json": "{}",
  196. },
  197. )
  198. )
  199. self.wait_for_background_updates()
  200. def test_account_data_deleted_retroactively_by_background_update_if_deactivated(
  201. self,
  202. ) -> None:
  203. """
  204. Tests that a user, who deactivated their account before account data was
  205. deleted automatically upon deactivation, has their account data retroactively
  206. scrubbed by the background update.
  207. """
  208. # Request the deactivation of our account
  209. self._deactivate_my_account()
  210. # Add some account data
  211. # (we do this after the deactivation so that the act of deactivating doesn't
  212. # clear it out. This emulates a user that was deactivated before this was cleared
  213. # upon deactivation.)
  214. self.get_success(
  215. self._store.add_account_data_for_user(
  216. self.user,
  217. AccountDataTypes.DIRECT,
  218. {"@someone:remote": ["!somewhere:remote"]},
  219. )
  220. )
  221. # Check that the account data is there.
  222. self.assertIsNotNone(
  223. self.get_success(
  224. self._store.get_global_account_data_by_type_for_user(
  225. self.user,
  226. AccountDataTypes.DIRECT,
  227. )
  228. ),
  229. )
  230. # Re-run the retroactive deletion update
  231. self._rerun_retroactive_account_data_deletion_update()
  232. # Check that the account data was cleared.
  233. self.assertIsNone(
  234. self.get_success(
  235. self._store.get_global_account_data_by_type_for_user(
  236. self.user,
  237. AccountDataTypes.DIRECT,
  238. )
  239. ),
  240. )
  241. def test_account_data_preserved_by_background_update_if_not_deactivated(
  242. self,
  243. ) -> None:
  244. """
  245. Tests that the background update does not scrub account data for users that have
  246. not been deactivated.
  247. """
  248. # Add some account data
  249. # (we do this after the deactivation so that the act of deactivating doesn't
  250. # clear it out. This emulates a user that was deactivated before this was cleared
  251. # upon deactivation.)
  252. self.get_success(
  253. self._store.add_account_data_for_user(
  254. self.user,
  255. AccountDataTypes.DIRECT,
  256. {"@someone:remote": ["!somewhere:remote"]},
  257. )
  258. )
  259. # Check that the account data is there.
  260. self.assertIsNotNone(
  261. self.get_success(
  262. self._store.get_global_account_data_by_type_for_user(
  263. self.user,
  264. AccountDataTypes.DIRECT,
  265. )
  266. ),
  267. )
  268. # Re-run the retroactive deletion update
  269. self._rerun_retroactive_account_data_deletion_update()
  270. # Check that the account data was NOT cleared.
  271. self.assertIsNotNone(
  272. self.get_success(
  273. self._store.get_global_account_data_by_type_for_user(
  274. self.user,
  275. AccountDataTypes.DIRECT,
  276. )
  277. ),
  278. )
  279. def test_deactivate_account_needs_auth(self) -> None:
  280. """
  281. Tests that making a request to /deactivate with an empty body
  282. succeeds in starting the user-interactive auth flow.
  283. """
  284. req = self.make_request(
  285. "POST",
  286. "account/deactivate",
  287. {},
  288. access_token=self.token,
  289. )
  290. self.assertEqual(req.code, 401, req)
  291. self.assertEqual(req.json_body["flows"], [{"stages": ["m.login.password"]}])