Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/19959.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Don't 500 when calling /context on an out-of-band membership event.
19 changes: 19 additions & 0 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,25 @@ async def filter_evts(events: list[EventBase]) -> list[FilteredEvent]:
if not filtered:
raise AuthError(403, "You don't have permission to access that event.")

if event.internal_metadata.outlier:
# The only outliers that pass the visibility filter are the user's own
# out-of-band membership events (e.g. an invite or knock received over
# federation). They have no stream position or state group, so we cannot
# compute surrounding events or state — return the event with an empty
# context rather than failing. Clients legitimately fetch such events,
# e.g. to render a push notification for an invite after the room has
# already been joined.
dummy_token = await StreamToken.START.to_string(self.store)
return EventContext(
events_before=[],
event=filtered[0],
events_after=[],
state=[],
aggregations={},
start=dummy_token,
end=dummy_token,
)

results = await self.store.get_events_around(
room_id, event_id, before_limit, after_limit, event_filter
)
Expand Down
53 changes: 53 additions & 0 deletions tests/handlers/test_federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,59 @@ def create_invite() -> EventBase:
exc=LimitExceededError,
)

def test_context_on_out_of_band_invite(self) -> None:
"""/context on the user's own out-of-band invite returns the event with an
empty context instead of a 500: the outlier has no stream position or state
group, so surrounding events and state cannot be computed. Clients fetch such
events e.g. to render a push notification for an invite after the server has
already joined the user (auto-accepted knock)."""
other_server = "otherserver"
other_user = "@otheruser:" + other_server

user_id = self.register_user("kermit", "test")
tok = self.login("kermit", "test")
room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
room_version = self.get_success(self.store.get_room_version(room_id))

invited_user = self.register_user("invitee", "test")
invited_tok = self.login("invitee", "test")

invite = make_test_pdu_event(
{
"type": EventTypes.Member,
"content": {"membership": "invite"},
"room_id": room_id,
"sender": other_user,
"state_key": invited_user,
"depth": 32,
"prev_events": [],
"auth_events": [],
"origin_server_ts": self.clock.time_msec(),
},
room_version,
)
self.get_success(
self.handler.on_invite_request(other_server, invite, invite.room_version)
)

channel = self.make_request(
"GET",
f"/rooms/{room_id}/context/{invite.event_id}",
access_token=invited_tok,
)
self.assertEqual(channel.code, 200, channel.result)
self.assertEqual(channel.json_body["event"]["event_id"], invite.event_id)
self.assertEqual(channel.json_body["events_before"], [])
self.assertEqual(channel.json_body["events_after"], [])

# Another user must NOT be able to fetch it.
channel = self.make_request(
"GET",
f"/rooms/{room_id}/context/{invite.event_id}",
access_token=tok,
)
self.assertEqual(channel.code, 403, channel.result)

def _build_and_send_join_event(
self, other_server: str, other_user: str, room_id: str
) -> EventBase:
Expand Down
Loading