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

Add a background database update to purge account data for deactivated users. #11655

Merged
merged 22 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 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
1589d6a
Pull out the account data transaction so it can be reused in other tr…
reivilibre Dec 29, 2021
5344446
Add a background job to retroactively purge account data for deactiva…
reivilibre Dec 29, 2021
da884c0
Add a schema delta to trigger the clean-up job
reivilibre Dec 29, 2021
288b0e8
Newsfile
reivilibre Dec 29, 2021
2690bd6
Add tests for the background update job
reivilibre Jan 17, 2022
9413a46
Fix port_db script not being able to run the background job
reivilibre Jan 17, 2022
3b48c9d
Merge branch 'develop' into rei/add_job
reivilibre Jan 24, 2022
2337b28
Reformat after merge
reivilibre Jan 24, 2022
2ee3d8f
Simplify test after merge
reivilibre Jan 24, 2022
d362e31
Fix order of args after merge
reivilibre Jan 24, 2022
3ecc6bc
Move schema delta to correct version
reivilibre Jan 24, 2022
9a4ec65
Fix database port script's Method Resolution Order
reivilibre Jan 24, 2022
0543946
Fix abstract method error in synapse_port_db
reivilibre Jan 28, 2022
d18e2dd
job → update
reivilibre Jan 28, 2022
99353f3
Remove obsolete manual cache invalidations in tests
reivilibre Feb 2, 2022
2587e51
Merge branch 'develop' into rei/add_job
reivilibre Feb 2, 2022
2f79559
Fix up background update delta number
reivilibre Feb 2, 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
Prev Previous commit
Next Next commit
Merge branch 'develop' into rei/add_job
  • Loading branch information
reivilibre committed Jan 24, 2022
commit 3b48c9d8a0881bb6243dec76e4f98bf458c0f781
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 @@ -353,7 +353,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* (list of ignored users, push rules, etc.)
- 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 Down
29 changes: 28 additions & 1 deletion synapse/storage/databases/main/account_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,15 @@ def _purge_account_data_for_user_txn(
"""
See `purge_account_data_for_user`.
"""
# Purge from the primary account_data table.
# Purge from the primary account_data tables.
self.db_pool.simple_delete_txn(
txn, table="account_data", keyvalues={"user_id": user_id}
)

self.db_pool.simple_delete_txn(
txn, table="room_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.
Expand All @@ -597,6 +601,29 @@ def _purge_account_data_for_user_txn(
txn, table="push_rules_stream", keyvalues={"user_id": user_id}
)

# Invalidate caches as appropriate
self._invalidate_cache_and_stream(
txn, self.get_account_data_for_room_and_type, (user_id,)
)
self._invalidate_cache_and_stream(
txn, self.get_account_data_for_user, (user_id,)
)
self._invalidate_cache_and_stream(
txn, self.get_global_account_data_by_type_for_user, (user_id,)
)
self._invalidate_cache_and_stream(
txn, self.get_account_data_for_room, (user_id,)
)
self._invalidate_cache_and_stream(
txn, self.get_push_rules_for_user, (user_id,)
)
self._invalidate_cache_and_stream(
txn, self.get_push_rules_enabled_for_user, (user_id,)
)
# This user might be contained in the ignored_by cache for other users,
# so we have to invalidate it all.
self._invalidate_all_cache_and_stream(txn, self.ignored_by)

async def _delete_account_data_for_deactivated_users(
self, progress: dict, batch_size: int
) -> int:
Expand Down
176 changes: 160 additions & 16 deletions tests/handlers/test_deactivate_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
# 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 http import HTTPStatus
from typing import Any, Dict

from twisted.test.proto_helpers import MemoryReactor

from synapse.api.constants import AccountDataTypes
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
from synapse.rest import admin
from synapse.rest.client import account, login
from synapse.server import HomeServer
Expand All @@ -30,26 +34,16 @@ class DeactivateAccountTestCase(HomeserverTestCase):
]

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
super(DeactivateAccountTestCase, self).prepare(reactor, clock, hs)
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:
def _deactivate_my_account(self):
"""
Tests that account data is removed upon deactivation.
Deactivates the account `self.user` using `self.token` and asserts
that it returns a 200 success code.
"""
# Add some account data
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",
Expand All @@ -65,18 +59,166 @@ def test_account_data_deleted_upon_deactivation(self) -> None:
access_token=self.token,
)
)
self.assertEqual(req.code, 200, req)
self.assertEqual(req.code, HTTPStatus.OK, req)

def test_global_account_data_deleted_upon_deactivation(self) -> None:
"""
Tests that global account data is removed upon deactivation.
"""
# Add some account data
self.get_success(
self._store.add_account_data_for_user(
self.user,
AccountDataTypes.DIRECT,
{"@someone:remote": ["!somewhere:remote"]},
)
)

# Check that we actually added some.
self.assertIsNotNone(
self.get_success(
self._store.get_global_account_data_by_type_for_user(
self.user, AccountDataTypes.DIRECT
)
),
)

# Request the deactivation of our account
self._deactivate_my_account()

# Check that the account data does not persist.
self.assertIsNone(
self.get_success(
self._store.get_global_account_data_by_type_for_user(
AccountDataTypes.DIRECT,
self.user,
self.user, AccountDataTypes.DIRECT
)
),
)

def test_room_account_data_deleted_upon_deactivation(self) -> None:
"""
Tests that room account data is removed upon deactivation.
"""
room_id = "!room:test"

# Add some room account data
self.get_success(
self._store.add_account_data_to_room(
self.user,
room_id,
"m.fully_read",
{"event_id": "$aaaa:test"},
)
)

# Check that we actually added some.
self.assertIsNotNone(
self.get_success(
self._store.get_account_data_for_room_and_type(
self.user, room_id, "m.fully_read"
)
),
)

# Request the deactivation of our account
self._deactivate_my_account()

# Check that the account data does not persist.
self.assertIsNone(
self.get_success(
self._store.get_account_data_for_room_and_type(
self.user, room_id, "m.fully_read"
)
),
)

def _is_custom_rule(self, push_rule: Dict[str, Any]) -> bool:
"""
Default rules start with a dot: such as .m.rule and .im.vector.
This function returns true iff a rule is custom (not default).
"""
return "/." not in push_rule["rule_id"]

def test_push_rules_deleted_upon_account_deactivation(self) -> None:
"""
Push rules are a special case of account data.
They are stored separately but get sent to the client as account data in /sync.
This tests that deactivating a user deletes push rules along with the rest
of their account data.
"""

# Add a push rule
self.get_success(
self._store.add_push_rule(
self.user,
"personal.override.rule1",
PRIORITY_CLASS_MAP["override"],
[],
[],
)
)

# Test the rule exists
push_rules = self.get_success(self._store.get_push_rules_for_user(self.user))
# Filter out default rules; we don't care
push_rules = list(filter(self._is_custom_rule, push_rules))
# Check our rule made it
self.assertEqual(
push_rules,
[
{
"user_name": "@user:test",
"rule_id": "personal.override.rule1",
"priority_class": 5,
"priority": 0,
"conditions": [],
"actions": [],
"default": False,
}
],
push_rules,
)

# Request the deactivation of our account
self._deactivate_my_account()

push_rules = self.get_success(self._store.get_push_rules_for_user(self.user))
# Filter out default rules; we don't care
push_rules = list(filter(self._is_custom_rule, push_rules))
# Check our rule no longer exists
self.assertEqual(push_rules, [], push_rules)

def test_ignored_users_deleted_upon_deactivation(self) -> None:
"""
Ignored users are a special case of account data.
They get denormalised into the `ignored_users` table upon being stored as
account data.
Test that a user's list of ignored users is deleted upon deactivation.
"""

# Add an ignored user
self.get_success(
self._store.add_account_data_for_user(
self.user,
AccountDataTypes.IGNORED_USER_LIST,
{"ignored_users": {"@sheltie:test": {}}},
)
)

# Test the user is ignored
self.assertEqual(
self.get_success(self._store.ignored_by("@sheltie:test")), {self.user}
)

# Request the deactivation of our account
self._deactivate_my_account()

# Test the user is no longer ignored by the user that was deactivated
self.assertEqual(
self.get_success(self._store.ignored_by("@sheltie:test")), set()
)


def _rerun_retroactive_account_data_deletion_job(self) -> None:
# Reset the 'all done' flag
self._store.db_pool.updates._all_done = False
Expand All @@ -93,6 +235,7 @@ def _rerun_retroactive_account_data_deletion_job(self) -> None:

self.wait_for_background_updates()


def test_account_data_deleted_retroactively_by_background_job_if_deactivated(
self,
) -> None:
Expand Down Expand Up @@ -156,6 +299,7 @@ def test_account_data_deleted_retroactively_by_background_job_if_deactivated(
),
)


def test_account_data_preserved_by_background_job_if_not_deactivated(self) -> None:
"""
Tests that the background job does not scrub account data for users that have
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.