Files
synapse/x.patch
T

89 lines
4.6 KiB
Diff

diff --git a/synapse/handlers/federation_event.py b/synapse/handlers/federation_event.py
index 52e5435793..26d74305f8 100644
--- a/synapse/handlers/federation_event.py
+++ b/synapse/handlers/federation_event.py
@@ -270,23 +270,22 @@ class FederationEventHandler:
) = 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.
- ):
+ if membership == Membership.INVITE and membership_event_id:
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.
+ # invite, and that it references the invite it rescinds.
#
# 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:
+ if (
+ invite_event
+ and pdu.sender == invite_event.sender
+ and rescinds_invite(pdu, invite_event)
+ ):
# Handle the rescission event
pdu.internal_metadata.outlier = True
pdu.internal_metadata.out_of_band_membership = True
@@ -3172,6 +3171,23 @@ class FederationEventHandler:
raise SynapseError(HTTPStatus.BAD_REQUEST, "Too many auth_events")
+def rescinds_invite(pdu: EventBase, invite_event: EventBase) -> bool:
+ """Returns True if the given leave event references the given invite.
+
+ We are not in the room, so we cannot authorise the leave event: the most we can do is
+ check that it names the invite we are holding. Without this an old leave event could
+ be replayed by anyone to cancel a newer invite.
+ """
+ if supports_msc4242_state_dag(pdu):
+ # MSC4242 state DAG events don't list their auth events: they are calculated from
+ # the state DAG, which we cannot do as we are not in the room. Instead, a leave
+ # event which rescinds an invite must name the invite in its prev_state_events.
+ return invite_event.event_id in pdu.prev_state_events
+
+ # The invite should be in the auth events of the rescission.
+ return invite_event.event_id in pdu.auth_event_ids()
+
+
def is_state_dag_connected(state_dag: Collection[MSC4242Event]) -> bool:
"""Returns True if the given events form a connected state DAG (MSC4242).
diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py
index b73e7114c0..faa8aab7ea 100644
--- a/synapse/handlers/room_member.py
+++ b/synapse/handlers/room_member.py
@@ -1040,6 +1040,24 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
"You cannot reject this invite",
errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
)
+
+ # If we are rescinding an invite to a user on a server which is not in the
+ # room, that server cannot authorise the leave event: it has no state DAG,
+ # so it cannot calculate the auth events which would otherwise tell it that
+ # the leave supersedes the invite it is holding. Name the invite in
+ # prev_state_events so that it can, see MSC4242.
+ if (
+ is_state_dags
+ and prev_state_events is None
+ and old_membership == Membership.INVITE
+ and effective_membership_state == Membership.LEAVE
+ and requester.user.to_string() != target.to_string()
+ and not self.hs.is_mine(target)
+ ):
+ # `latest_event_ids` are the state DAG extremities for these rooms.
+ prev_state_events = list(latest_event_ids)
+ if old_state_id not in prev_state_events:
+ prev_state_events.append(old_state_id)
else:
if action == "kick":
raise AuthError(403, "The target user is not in the room")