Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit af79517

Browse files
authored
Add a background database update to purge account data for deactivated users. (#11655)
1 parent 513913c commit af79517

File tree

5 files changed

+240
-55
lines changed

5 files changed

+240
-55
lines changed

changelog.d/11655.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove account data (including client config, push rules and ignored users) upon user deactivation.

scripts/synapse_port_db

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ from synapse.logging.context import (
3636
run_in_background,
3737
)
3838
from synapse.storage.database import DatabasePool, make_conn
39+
from synapse.storage.databases.main import PushRuleStore
40+
from synapse.storage.databases.main.account_data import AccountDataWorkerStore
3941
from synapse.storage.databases.main.client_ips import ClientIpBackgroundUpdateStore
4042
from synapse.storage.databases.main.deviceinbox import DeviceInboxBackgroundUpdateStore
4143
from synapse.storage.databases.main.devices import DeviceBackgroundUpdateStore
@@ -180,6 +182,8 @@ class Store(
180182
UserDirectoryBackgroundUpdateStore,
181183
EndToEndKeyBackgroundStore,
182184
StatsStore,
185+
AccountDataWorkerStore,
186+
PushRuleStore,
183187
PusherWorkerStore,
184188
PresenceBackgroundUpdateStore,
185189
GroupServerWorkerStore,

synapse/storage/databases/main/account_data.py

Lines changed: 109 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ def __init__(
106106
"AccountDataAndTagsChangeCache", account_max
107107
)
108108

109+
self.db_pool.updates.register_background_update_handler(
110+
"delete_account_data_for_deactivated_users",
111+
self._delete_account_data_for_deactivated_users,
112+
)
113+
109114
def get_max_account_data_stream_id(self) -> int:
110115
"""Get the current max stream ID for account data stream
111116
@@ -549,72 +554,121 @@ def _add_account_data_for_user(
549554

550555
async def purge_account_data_for_user(self, user_id: str) -> None:
551556
"""
552-
Removes the account data for a user.
557+
Removes ALL the account data for a user.
558+
Intended to be used upon user deactivation.
553559
554-
This is intended to be used upon user deactivation and also removes any
555-
derived information from account data (e.g. push rules and ignored users).
560+
Also purges the user from the ignored_users cache table
561+
and the push_rules cache tables.
562+
"""
556563

557-
Args:
558-
user_id: The user ID to remove data for.
564+
await self.db_pool.runInteraction(
565+
"purge_account_data_for_user_txn",
566+
self._purge_account_data_for_user_txn,
567+
user_id,
568+
)
569+
570+
def _purge_account_data_for_user_txn(
571+
self, txn: LoggingTransaction, user_id: str
572+
) -> None:
559573
"""
574+
See `purge_account_data_for_user`.
575+
"""
576+
# Purge from the primary account_data tables.
577+
self.db_pool.simple_delete_txn(
578+
txn, table="account_data", keyvalues={"user_id": user_id}
579+
)
560580

561-
def purge_account_data_for_user_txn(txn: LoggingTransaction) -> None:
562-
# Purge from the primary account_data tables.
563-
self.db_pool.simple_delete_txn(
564-
txn, table="account_data", keyvalues={"user_id": user_id}
565-
)
581+
self.db_pool.simple_delete_txn(
582+
txn, table="room_account_data", keyvalues={"user_id": user_id}
583+
)
566584

567-
self.db_pool.simple_delete_txn(
568-
txn, table="room_account_data", keyvalues={"user_id": user_id}
569-
)
585+
# Purge from ignored_users where this user is the ignorer.
586+
# N.B. We don't purge where this user is the ignoree, because that
587+
# interferes with other users' account data.
588+
# It's also not this user's data to delete!
589+
self.db_pool.simple_delete_txn(
590+
txn, table="ignored_users", keyvalues={"ignorer_user_id": user_id}
591+
)
570592

571-
# Purge from ignored_users where this user is the ignorer.
572-
# N.B. We don't purge where this user is the ignoree, because that
573-
# interferes with other users' account data.
574-
# It's also not this user's data to delete!
575-
self.db_pool.simple_delete_txn(
576-
txn, table="ignored_users", keyvalues={"ignorer_user_id": user_id}
577-
)
593+
# Remove the push rules
594+
self.db_pool.simple_delete_txn(
595+
txn, table="push_rules", keyvalues={"user_name": user_id}
596+
)
597+
self.db_pool.simple_delete_txn(
598+
txn, table="push_rules_enable", keyvalues={"user_name": user_id}
599+
)
600+
self.db_pool.simple_delete_txn(
601+
txn, table="push_rules_stream", keyvalues={"user_id": user_id}
602+
)
578603

579-
# Remove the push rules
580-
self.db_pool.simple_delete_txn(
581-
txn, table="push_rules", keyvalues={"user_name": user_id}
582-
)
583-
self.db_pool.simple_delete_txn(
584-
txn, table="push_rules_enable", keyvalues={"user_name": user_id}
585-
)
586-
self.db_pool.simple_delete_txn(
587-
txn, table="push_rules_stream", keyvalues={"user_id": user_id}
588-
)
604+
# Invalidate caches as appropriate
605+
self._invalidate_cache_and_stream(
606+
txn, self.get_account_data_for_room_and_type, (user_id,)
607+
)
608+
self._invalidate_cache_and_stream(
609+
txn, self.get_account_data_for_user, (user_id,)
610+
)
611+
self._invalidate_cache_and_stream(
612+
txn, self.get_global_account_data_by_type_for_user, (user_id,)
613+
)
614+
self._invalidate_cache_and_stream(
615+
txn, self.get_account_data_for_room, (user_id,)
616+
)
617+
self._invalidate_cache_and_stream(txn, self.get_push_rules_for_user, (user_id,))
618+
self._invalidate_cache_and_stream(
619+
txn, self.get_push_rules_enabled_for_user, (user_id,)
620+
)
621+
# This user might be contained in the ignored_by cache for other users,
622+
# so we have to invalidate it all.
623+
self._invalidate_all_cache_and_stream(txn, self.ignored_by)
589624

590-
# Invalidate caches as appropriate
591-
self._invalidate_cache_and_stream(
592-
txn, self.get_account_data_for_room_and_type, (user_id,)
593-
)
594-
self._invalidate_cache_and_stream(
595-
txn, self.get_account_data_for_user, (user_id,)
596-
)
597-
self._invalidate_cache_and_stream(
598-
txn, self.get_global_account_data_by_type_for_user, (user_id,)
599-
)
600-
self._invalidate_cache_and_stream(
601-
txn, self.get_account_data_for_room, (user_id,)
602-
)
603-
self._invalidate_cache_and_stream(
604-
txn, self.get_push_rules_for_user, (user_id,)
605-
)
606-
self._invalidate_cache_and_stream(
607-
txn, self.get_push_rules_enabled_for_user, (user_id,)
608-
)
609-
# This user might be contained in the ignored_by cache for other users,
610-
# so we have to invalidate it all.
611-
self._invalidate_all_cache_and_stream(txn, self.ignored_by)
625+
async def _delete_account_data_for_deactivated_users(
626+
self, progress: dict, batch_size: int
627+
) -> int:
628+
"""
629+
Retroactively purges account data for users that have already been deactivated.
630+
Gets run as a background update caused by a schema delta.
631+
"""
612632

613-
await self.db_pool.runInteraction(
614-
"purge_account_data_for_user_txn",
615-
purge_account_data_for_user_txn,
633+
last_user: str = progress.get("last_user", "")
634+
635+
def _delete_account_data_for_deactivated_users_txn(
636+
txn: LoggingTransaction,
637+
) -> int:
638+
sql = """
639+
SELECT name FROM users
640+
WHERE deactivated = ? and name > ?
641+
ORDER BY name ASC
642+
LIMIT ?
643+
"""
644+
645+
txn.execute(sql, (1, last_user, batch_size))
646+
users = [row[0] for row in txn]
647+
648+
for user in users:
649+
self._purge_account_data_for_user_txn(txn, user_id=user)
650+
651+
if users:
652+
self.db_pool.updates._background_update_progress_txn(
653+
txn,
654+
"delete_account_data_for_deactivated_users",
655+
{"last_user": users[-1]},
656+
)
657+
658+
return len(users)
659+
660+
number_deleted = await self.db_pool.runInteraction(
661+
"_delete_account_data_for_deactivated_users",
662+
_delete_account_data_for_deactivated_users_txn,
616663
)
617664

665+
if number_deleted < batch_size:
666+
await self.db_pool.updates._end_background_update(
667+
"delete_account_data_for_deactivated_users"
668+
)
669+
670+
return number_deleted
671+
618672

619673
class AccountDataStore(AccountDataWorkerStore):
620674
pass
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
*/
15+
16+
17+
-- We want to retroactively delete account data for users that were already
18+
-- deactivated.
19+
INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
20+
(6803, 'delete_account_data_for_deactivated_users', '{}');

tests/handlers/test_deactivate_account.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,109 @@ def test_ignored_users_deleted_upon_deactivation(self) -> None:
217217
self.assertEqual(
218218
self.get_success(self._store.ignored_by("@sheltie:test")), set()
219219
)
220+
221+
def _rerun_retroactive_account_data_deletion_update(self) -> None:
222+
# Reset the 'all done' flag
223+
self._store.db_pool.updates._all_done = False
224+
225+
self.get_success(
226+
self._store.db_pool.simple_insert(
227+
"background_updates",
228+
{
229+
"update_name": "delete_account_data_for_deactivated_users",
230+
"progress_json": "{}",
231+
},
232+
)
233+
)
234+
235+
self.wait_for_background_updates()
236+
237+
def test_account_data_deleted_retroactively_by_background_update_if_deactivated(
238+
self,
239+
) -> None:
240+
"""
241+
Tests that a user, who deactivated their account before account data was
242+
deleted automatically upon deactivation, has their account data retroactively
243+
scrubbed by the background update.
244+
"""
245+
246+
# Request the deactivation of our account
247+
self._deactivate_my_account()
248+
249+
# Add some account data
250+
# (we do this after the deactivation so that the act of deactivating doesn't
251+
# clear it out. This emulates a user that was deactivated before this was cleared
252+
# upon deactivation.)
253+
self.get_success(
254+
self._store.add_account_data_for_user(
255+
self.user,
256+
AccountDataTypes.DIRECT,
257+
{"@someone:remote": ["!somewhere:remote"]},
258+
)
259+
)
260+
261+
# Check that the account data is there.
262+
self.assertIsNotNone(
263+
self.get_success(
264+
self._store.get_global_account_data_by_type_for_user(
265+
self.user,
266+
AccountDataTypes.DIRECT,
267+
)
268+
),
269+
)
270+
271+
# Re-run the retroactive deletion update
272+
self._rerun_retroactive_account_data_deletion_update()
273+
274+
# Check that the account data was cleared.
275+
self.assertIsNone(
276+
self.get_success(
277+
self._store.get_global_account_data_by_type_for_user(
278+
self.user,
279+
AccountDataTypes.DIRECT,
280+
)
281+
),
282+
)
283+
284+
def test_account_data_preserved_by_background_update_if_not_deactivated(
285+
self,
286+
) -> None:
287+
"""
288+
Tests that the background update does not scrub account data for users that have
289+
not been deactivated.
290+
"""
291+
292+
# Add some account data
293+
# (we do this after the deactivation so that the act of deactivating doesn't
294+
# clear it out. This emulates a user that was deactivated before this was cleared
295+
# upon deactivation.)
296+
self.get_success(
297+
self._store.add_account_data_for_user(
298+
self.user,
299+
AccountDataTypes.DIRECT,
300+
{"@someone:remote": ["!somewhere:remote"]},
301+
)
302+
)
303+
304+
# Check that the account data is there.
305+
self.assertIsNotNone(
306+
self.get_success(
307+
self._store.get_global_account_data_by_type_for_user(
308+
self.user,
309+
AccountDataTypes.DIRECT,
310+
)
311+
),
312+
)
313+
314+
# Re-run the retroactive deletion update
315+
self._rerun_retroactive_account_data_deletion_update()
316+
317+
# Check that the account data was NOT cleared.
318+
self.assertIsNotNone(
319+
self.get_success(
320+
self._store.get_global_account_data_by_type_for_user(
321+
self.user,
322+
AccountDataTypes.DIRECT,
323+
)
324+
),
325+
)

0 commit comments

Comments
 (0)