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

Remove account data (including client config, push rules and ignored users) upon user deactivation. #11621

Merged
merged 24 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
68b663d
Remove account data upon user deactivation
reivilibre Dec 21, 2021
13fec0d
Document that account data is removed upon user deactivation
reivilibre Dec 21, 2021
c1fed49
Newsfile
reivilibre Dec 21, 2021
4da869e
Remove account data upon user deactivation
reivilibre Dec 21, 2021
b132bba
Test the removal of account data upon deactivation
reivilibre Dec 29, 2021
ab97003
Clarify and document what is included in account data
reivilibre Jan 4, 2022
5d4c6ca
Clarify why we purge ignored users and push rules
reivilibre Jan 13, 2022
6d7e9ac
Remove needless super call
reivilibre Jan 13, 2022
d73be6c
Also delete room account data
reivilibre Jan 13, 2022
e58ec5d
Ensure the test actually does something
reivilibre Jan 13, 2022
00f9033
Add a test for room account data
reivilibre Jan 13, 2022
2123435
Add a test for push rules
reivilibre Jan 17, 2022
a1a8c68
Add a test for ignored users
reivilibre Jan 17, 2022
3edab88
Update synapse/storage/databases/main/account_data.py
reivilibre Jan 20, 2022
249b05b
Update synapse/storage/databases/main/account_data.py
reivilibre Jan 20, 2022
37dc2e1
Extract account deactivation test helper
reivilibre Jan 20, 2022
81a2863
Antilint
reivilibre Jan 20, 2022
87473f4
Don't invalidate caches just for tests
reivilibre Jan 20, 2022
e13780e
Handle change in arg order for get_global_account_data_by_type_for_user
reivilibre Jan 20, 2022
cbe543b
Merge branch 'develop' into rei/account_data_deactivation
reivilibre Jan 21, 2022
75f1e2f
Invalidate caches properly when purging account data
reivilibre Jan 21, 2022
1b28e2f
Drive-by type annotations
reivilibre Jan 21, 2022
a68607c
Make `get_account_data_for_room` a tree cache (missed one)
reivilibre Jan 21, 2022
97ceb01
Revert "Drive-by type annotations"
reivilibre Jan 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/11621.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove account data (including client config, push rules and ignored users) upon user deactivation.
6 changes: 5 additions & 1 deletion docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ The following actions are performed when deactivating an user:
- Remove the user from the user directory
- Reject all pending invites
- Remove all account validity information related to the user
- Remove the arbitrary data store known as *account data*. For example, this includes:
- list of ignored users;
- push rules;
- secret storage keys; and
- cross-signing keys.

The following additional actions are performed during deactivation if `erase`
is set to `true`:
Expand All @@ -365,7 +370,6 @@ The following actions are **NOT** performed. The list may be incomplete.
- Remove mappings of SSO IDs
- [Delete media uploaded](#delete-media-uploaded-by-a-user) by user (included avatar images)
- Delete sent and received messages
- Delete E2E cross-signing keys
- Remove the user's creation (registration) timestamp
- [Remove rate limit overrides](#override-ratelimiting-for-users)
- Remove from monthly active users
Expand Down
3 changes: 3 additions & 0 deletions synapse/handlers/deactivate_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ async def deactivate_account(
# Mark the user as deactivated.
await self.store.set_user_deactivated_status(user_id, True)

# Remove account data (including ignored users and push rules).
await self.store.purge_account_data_for_user(user_id)

return identity_server_supports_unbinding

async def _reject_pending_invites_for_user(self, user_id: str) -> None:
Expand Down
39 changes: 39 additions & 0 deletions synapse/storage/databases/main/account_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,45 @@ def _add_account_data_for_user(
for ignored_user_id in previously_ignored_users ^ currently_ignored_users:
self._invalidate_cache_and_stream(txn, self.ignored_by, (ignored_user_id,))

async def purge_account_data_for_user(self, user_id: str) -> None:
"""
Removes ALL the account data for a user.
Intended to be used upon user deactivation.

Also purges the user from the ignored_users cache table
and the push_rules cache tables.
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
"""

def purge_account_data_for_user_txn(txn: LoggingTransaction) -> None:
# Purge from the primary account_data table.
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
self.db_pool.simple_delete_txn(
txn, table="account_data", keyvalues={"user_id": user_id}
)

# Purge from ignored_users where this user is the ignorer.
# N.B. We don't purge where this user is the ignoree, because that
# interferes with other users' account data.
# It's also not this user's data to delete!
self.db_pool.simple_delete_txn(
txn, table="ignored_users", keyvalues={"ignorer_user_id": user_id}
)

# Remove the push rules
self.db_pool.simple_delete_txn(
txn, table="push_rules", keyvalues={"user_name": user_id}
)
self.db_pool.simple_delete_txn(
txn, table="push_rules_enable", keyvalues={"user_name": user_id}
)
self.db_pool.simple_delete_txn(
txn, table="push_rules_stream", keyvalues={"user_id": user_id}
)

await self.db_pool.runInteraction(
"purge_account_data_for_user_txn",
purge_account_data_for_user_txn,
)


class AccountDataStore(AccountDataWorkerStore):
pass
78 changes: 78 additions & 0 deletions tests/handlers/test_deactivate_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from twisted.test.proto_helpers import MemoryReactor

from synapse.api.constants import AccountDataTypes
from synapse.rest import admin
from synapse.rest.client import account, login
from synapse.server import HomeServer
from synapse.util import Clock

from tests.unittest import HomeserverTestCase


class DeactivateAccountTestCase(HomeserverTestCase):
servlets = [
login.register_servlets,
admin.register_servlets,
account.register_servlets,
]

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
super(DeactivateAccountTestCase, self).prepare(reactor, clock, hs)
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
self._store = hs.get_datastore()

self.user = self.register_user("user", "pass")
self.token = self.login("user", "pass")

def test_account_data_deleted_upon_deactivation(self) -> None:
"""
Tests that account data is removed upon deactivation.
"""
# Add some account data
clokep marked this conversation as resolved.
Show resolved Hide resolved
self.get_success(
self._store.add_account_data_for_user(
self.user,
AccountDataTypes.DIRECT,
{"@someone:remote": ["!somewhere:remote"]},
)
)

# Request the deactivation of our account
req = self.get_success(
self.make_request(
"POST",
"account/deactivate",
{
"auth": {
"type": "m.login.password",
"user": self.user,
"password": "pass",
},
"erase": True,
},
access_token=self.token,
)
)
self.assertEqual(req.code, 200, req)

# Check that the account data does not persist.
self.assertIsNone(
self.get_success(
self._store.get_global_account_data_by_type_for_user(
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
AccountDataTypes.DIRECT,
self.user,
)
),
)