From 47773232b034c0d7b72bb7419a01e772509c8814 Mon Sep 17 00:00:00 2001 From: Till <2353100+S7evinK@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:25:31 +0200 Subject: [PATCH] Redact membership events if the user requested erasure upon deactivating (#17076) Fixes #15355 by redacting all membership events before leaving rooms. --- changelog.d/17076.bugfix | 1 + synapse/handlers/deactivate_account.py | 13 ++++++- synapse/storage/databases/main/roommember.py | 22 ++++++++++++ tests/handlers/test_deactivate_account.py | 37 ++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 changelog.d/17076.bugfix diff --git a/changelog.d/17076.bugfix b/changelog.d/17076.bugfix new file mode 100644 index 000000000..a111ea2b8 --- /dev/null +++ b/changelog.d/17076.bugfix @@ -0,0 +1 @@ +Redact membership events if the user requested erasure upon deactivating. \ No newline at end of file diff --git a/synapse/handlers/deactivate_account.py b/synapse/handlers/deactivate_account.py index b13c4b6cb..11ac37768 100644 --- a/synapse/handlers/deactivate_account.py +++ b/synapse/handlers/deactivate_account.py @@ -261,11 +261,22 @@ class DeactivateAccountHandler: user = UserID.from_string(user_id) rooms_for_user = await self.store.get_rooms_for_user(user_id) + requester = create_requester(user, authenticated_entity=self._server_name) + should_erase = await self.store.is_user_erased(user_id) + for room_id in rooms_for_user: logger.info("User parter parting %r from %r", user_id, room_id) try: + # Before parting the user, redact all membership events if requested + if should_erase: + event_ids = await self.store.get_membership_event_ids_for_user( + user_id, room_id + ) + for event_id in event_ids: + await self.store.expire_event(event_id) + await self._room_member_handler.update_membership( - create_requester(user, authenticated_entity=self._server_name), + requester, user, room_id, "leave", diff --git a/synapse/storage/databases/main/roommember.py b/synapse/storage/databases/main/roommember.py index 5d5150259..9fddbb2ca 100644 --- a/synapse/storage/databases/main/roommember.py +++ b/synapse/storage/databases/main/roommember.py @@ -1234,6 +1234,28 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore): return set(room_ids) + async def get_membership_event_ids_for_user( + self, user_id: str, room_id: str + ) -> Set[str]: + """Get all event_ids for the given user and room. + + Args: + user_id: The user ID to get the event IDs for. + room_id: The room ID to look up events for. + + Returns: + Set of event IDs + """ + + event_ids = await self.db_pool.simple_select_onecol( + table="room_memberships", + keyvalues={"user_id": user_id, "room_id": room_id}, + retcol="event_id", + desc="get_membership_event_ids_for_user", + ) + + return set(event_ids) + @cached(max_entries=5000) async def _get_membership_from_event_id( self, member_event_id: str diff --git a/tests/handlers/test_deactivate_account.py b/tests/handlers/test_deactivate_account.py index b3f9e50f0..c698771a0 100644 --- a/tests/handlers/test_deactivate_account.py +++ b/tests/handlers/test_deactivate_account.py @@ -424,3 +424,40 @@ class DeactivateAccountTestCase(HomeserverTestCase): self._store.get_knocked_at_rooms_for_local_user(self.user) ) self.assertEqual(len(after_deactivate_knocks), 0) + + def test_membership_is_redacted_upon_deactivation(self) -> None: + """ + Tests that room membership events are redacted if erasure is requested. + """ + # Create a room + room_id = self.helper.create_room_as( + self.user, + is_public=True, + tok=self.token, + ) + + # Change the displayname + membership_event, _ = self.get_success( + self.handler.update_membership( + requester=create_requester(self.user), + target=UserID.from_string(self.user), + room_id=room_id, + action=Membership.JOIN, + content={"displayname": "Hello World!"}, + ) + ) + + # Deactivate the account + self._deactivate_my_account() + + # Get the all membership event IDs + membership_event_ids = self.get_success( + self._store.get_membership_event_ids_for_user(self.user, room_id=room_id) + ) + + # Get the events incl. JSON + events = self.get_success(self._store.get_events_as_list(membership_event_ids)) + + # Validate that there is no displayname in any of the events + for event in events: + self.assertTrue("displayname" not in event.content)