diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 55dd5ffb59..5aabd4448c 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -119,6 +119,7 @@ from synapse.types import ( StreamKeyType, UserID, get_domain_from_id, + is_compliant_user_id, ) from synapse.util.async_helpers import Linearizer from synapse.util.duration import Duration @@ -1511,6 +1512,14 @@ class PresenceHandler(BasePresenceHandler): ) continue + if not is_compliant_user_id(user_id): + logger.info( + "Dropping presence update from %r for non-compliant user ID %r", + origin, + user_id, + ) + continue + presence_state = push.get("presence", None) if not presence_state: logger.info( diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 61cf78730f..d8f96556b2 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -591,6 +591,35 @@ class PresenceUpdateTestCase(unittest.HomeserverTestCase): # Assert the user is now unavailable. self.assertEqual(state.state, PresenceState.UNAVAILABLE) + def test_non_compliant_user_id_federation_presence_dropped(self) -> None: + """A remote server pushes presence for a compliant and a non-compliant + (grandfathered historical) user ID. Only the compliant one is applied. + """ + compliant_user_id = UserID.from_string("@john:remote") + non_compliant_user_id = UserID.from_string("@héllo:remote") + + presence_handler = self.hs.get_presence_handler() + assert isinstance(presence_handler, PresenceHandler) + self.get_success( + presence_handler.incoming_presence( + "remote", + { + "push": [ + { + "user_id": user_id.to_string(), + "presence": PresenceState.ONLINE, + } + for user_id in (compliant_user_id, non_compliant_user_id) + ] + }, + ) + ) + + state = self.get_success(presence_handler.get_state(compliant_user_id)) + self.assertEqual(state.state, PresenceState.ONLINE) + state = self.get_success(presence_handler.get_state(non_compliant_user_id)) + self.assertEqual(state.state, PresenceState.OFFLINE) + class PresenceTimeoutTestCase(unittest.TestCase): """Tests different timers and that the timer does not change `status_msg` of user."""