diff --git a/changelog.d/18823.bugfix b/changelog.d/18823.bugfix new file mode 100644 index 000000000..473c865aa --- /dev/null +++ b/changelog.d/18823.bugfix @@ -0,0 +1 @@ +Fix bug where we did not send invite revocations over federation. diff --git a/synapse/federation/sender/__init__.py b/synapse/federation/sender/__init__.py index 278a95733..6baa23314 100644 --- a/synapse/federation/sender/__init__.py +++ b/synapse/federation/sender/__init__.py @@ -150,6 +150,7 @@ from prometheus_client import Counter from twisted.internet import defer import synapse.metrics +from synapse.api.constants import EventTypes, Membership from synapse.api.presence import UserPresenceState from synapse.events import EventBase from synapse.federation.sender.per_destination_queue import ( @@ -655,6 +656,31 @@ class FederationSender(AbstractFederationSender): ) return + # If we've rescinded an invite then we want to tell the + # other server. + if ( + event.type == EventTypes.Member + and event.membership == Membership.LEAVE + and event.sender != event.state_key + ): + # We check if this leave event is rescinding an invite + # by looking if there is an invite event for the user in + # the auth events. It could otherwise be a kick or + # unban, which we don't want to send (if the user wasn't + # already in the room). + auth_events = await self.store.get_events_as_list( + event.auth_event_ids() + ) + for auth_event in auth_events: + if ( + auth_event.type == EventTypes.Member + and auth_event.state_key == event.state_key + and auth_event.membership == Membership.INVITE + ): + destinations = set(destinations) + destinations.add(get_domain_from_id(event.state_key)) + break + sharded_destinations = { d for d in destinations diff --git a/synapse/handlers/federation_event.py b/synapse/handlers/federation_event.py index 04ee774aa..1e47b4ef4 100644 --- a/synapse/handlers/federation_event.py +++ b/synapse/handlers/federation_event.py @@ -248,9 +248,10 @@ class FederationEventHandler: self.room_queues[room_id].append((pdu, origin)) return - # If we're not in the room just ditch the event entirely. This is - # probably an old server that has come back and thinks we're still in - # the room (or we've been rejoined to the room by a state reset). + # If we're not in the room just ditch the event entirely (and not + # invited). This is probably an old server that has come back and thinks + # we're still in the room (or we've been rejoined to the room by a state + # reset). # # Note that if we were never in the room then we would have already # dropped the event, since we wouldn't know the room version. @@ -258,6 +259,43 @@ class FederationEventHandler: room_id, self.server_name ) if not is_in_room: + # Check if this is a leave event rescinding an invite + if ( + pdu.type == EventTypes.Member + and pdu.membership == Membership.LEAVE + and pdu.state_key != pdu.sender + and self._is_mine_id(pdu.state_key) + ): + ( + membership, + membership_event_id, + ) = await self._store.get_local_current_membership_for_user_in_room( + pdu.state_key, pdu.room_id + ) + if ( + membership == Membership.INVITE + and membership_event_id + and membership_event_id + in pdu.auth_event_ids() # The invite should be in the auth events of the rescission. + ): + invite_event = await self._store.get_event( + membership_event_id, allow_none=True + ) + + # We cannot fully auth the rescission event, but we can + # check if the sender of the leave event is the same as the + # invite. + # + # Technically, a room admin could rescind the invite, but we + # have no way of knowing who is and isn't a room admin. + if invite_event and pdu.sender == invite_event.sender: + # Handle the rescission event + pdu.internal_metadata.outlier = True + pdu.internal_metadata.out_of_band_membership = True + context = EventContext.for_outlier(self._storage_controllers) + await self.persist_events_and_notify(room_id, [(pdu, context)]) + return + logger.info( "Ignoring PDU from %s as we're not in the room", origin,