diff --git a/synapse/storage/databases/main/registration.py b/synapse/storage/databases/main/registration.py index c99faa29ca..291bb8d01e 100644 --- a/synapse/storage/databases/main/registration.py +++ b/synapse/storage/databases/main/registration.py @@ -2589,7 +2589,6 @@ def user_delete_access_tokens_for_devices_txn( """ txn.execute(sql, args) tokens_and_devices = txn.fetchall() - self._invalidate_cache_and_stream_bulk( txn, self.get_user_by_access_token, @@ -2605,7 +2604,10 @@ def user_delete_access_tokens_for_devices_txn( batch_device_ids, ) results.extend(tokens_and_devices) - + # Print after transaction completes (cache invalidation has happened) + if tokens_and_devices: + print(tokens_and_devices) + print(await self.get_user_by_access_token(tokens_and_devices[0][0])) return results async def delete_access_token(self, access_token: str) -> None: diff --git a/tests/rest/client/test_read_marker.py b/tests/rest/client/test_read_marker.py index c8bb0da5e6..f5fd40d77b 100644 --- a/tests/rest/client/test_read_marker.py +++ b/tests/rest/client/test_read_marker.py @@ -18,27 +18,31 @@ # [This file includes modifications made by New Vector Limited] # # + from twisted.internet.testing import MemoryReactor import synapse.rest.admin from synapse.api.constants import EventTypes from synapse.rest import admin -from synapse.rest.client import login, read_marker, register, room +from synapse.rest.client import devices, login, read_marker, register, room from synapse.server import HomeServer from synapse.util.clock import Clock from tests import unittest +from tests.replication._base import BaseMultiWorkerStreamTestCase +from tests.server import make_request ONE_HOUR_MS = 3600000 ONE_DAY_MS = ONE_HOUR_MS * 24 -class ReadMarkerTestCase(unittest.HomeserverTestCase): +class ReadMarkerTestCase(BaseMultiWorkerStreamTestCase): servlets = [ login.register_servlets, register.register_servlets, read_marker.register_servlets, room.register_servlets, + devices.register_servlets, synapse.rest.admin.register_servlets, admin.register_servlets, ] @@ -149,3 +153,134 @@ def send_message() -> str: access_token=self.owner_tok, ) self.assertEqual(channel.code, 200, channel.result) + + def test_deleted_device_cannot_set_read_marker_or_send_message(self) -> None: + """ + Test that a deleted device cannot set a read marker or send a message. + This test removes the first device on one worker and tries the next requests on another worker. + It also verifies that a second device (not deleted) can still make requests successfully. + """ + # Register a user and login with a specific device_id + user_id = self.register_user("testuser", "password") + device_id = "TEST_DEVICE_123" + access_token = self.login("testuser", "password", device_id=device_id) + + # Login with a second device to get a second access token + device_id_2 = "TEST_DEVICE_456" + access_token_2 = self.login("testuser", "password", device_id=device_id_2) + + # Create a room + room_id = self.helper.create_room_as(user_id, tok=access_token) + + # Send a message + message_result = self.helper.send( + room_id=room_id, body="Hello", tok=access_token + ) + event_id = message_result["event_id"] + + # Create two workers + worker1 = self.make_worker_hs( + "synapse.app.generic_worker", + extra_config={"worker_name": "worker1"}, + ) + worker2 = self.make_worker_hs( + "synapse.app.generic_worker", + extra_config={"worker_name": "worker2"}, + ) + + # Get the sites for the workers + worker1_site = self._hs_to_site[worker1] + worker2_site = self._hs_to_site[worker2] + + # Delete the first device on worker1 + channel = make_request( + self.reactor, + worker1_site, + "DELETE", + f"devices/{device_id}", + access_token=access_token, + ) + + # If UI auth is required, provide it + if channel.code == 401: + session = channel.json_body["session"] + channel = make_request( + self.reactor, + worker1_site, + "DELETE", + f"devices/{device_id}", + access_token=access_token, + content={ + "auth": { + "type": "m.login.password", + "user": "testuser", + "password": "password", + "session": session, + }, + }, + ) + + self.assertEqual(channel.code, 200, channel.result) + + # Replicate to ensure the deletion propagates to other workers + # self.replicate() + + # Try to set a read marker with the deleted device's token on worker2 + channel = make_request( + self.reactor, + worker2_site, + "POST", + f"/rooms/{room_id}/read_markers", + content={ + "m.fully_read": event_id, + }, + access_token=access_token, + ) + # Should fail with 401 (unauthorized) since the device is deleted + self.assertEqual(channel.code, 401, channel.result) + self.assertEqual(channel.json_body["errcode"], "M_UNKNOWN_TOKEN") + + # Try to send a message with the deleted device's token on worker2 + channel = make_request( + self.reactor, + worker2_site, + "PUT", + f"/rooms/{room_id}/send/m.room.message/test_msg", + content={ + "msgtype": "m.text", + "body": "This should fail", + }, + access_token=access_token, + ) + # Should fail with 401 (unauthorized) since the device is deleted + self.assertEqual(channel.code, 401, channel.result) + self.assertEqual(channel.json_body["errcode"], "M_UNKNOWN_TOKEN") + + # Verify that the second device's token still works - it should be able to set a read marker + channel = make_request( + self.reactor, + worker2_site, + "POST", + f"/rooms/{room_id}/read_markers", + content={ + "m.fully_read": event_id, + }, + access_token=access_token_2, + ) + # Should succeed since the second device is not deleted + self.assertEqual(channel.code, 200, channel.result) + + # Verify that the second device's token can still send a message + channel = make_request( + self.reactor, + worker2_site, + "PUT", + f"/rooms/{room_id}/send/m.room.message/test_msg_2", + content={ + "msgtype": "m.text", + "body": "This should work", + }, + access_token=access_token_2, + ) + # Should succeed since the second device is not deleted + self.assertEqual(channel.code, 200, channel.result)