Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Support MSC3266 room summaries over federation #11507

Merged
merged 19 commits into from
May 5, 2022
Merged
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/11507.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support [MSC3266](https://github.com/matrix-org/matrix-doc/pull/3266) room summaries over federation.
2 changes: 2 additions & 0 deletions synapse/federation/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,8 @@ async def send_request(
room = res.get("room")
if not isinstance(room, dict):
raise InvalidResponseError("'room' must be a dict")
if room.get("room_id") != room_id:
raise InvalidResponseError("wrong room returned in hierarchy response")

# Validate children_state of the room.
children_state = room.pop("children_state", [])
Expand Down
54 changes: 49 additions & 5 deletions synapse/handlers/room_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def __init__(self, hs: "HomeServer"):
hs.get_clock(),
"get_room_hierarchy",
)
self._msc3266_enabled = hs.config.experimental.msc3266_enabled

async def get_room_hierarchy(
self,
Expand Down Expand Up @@ -630,7 +631,7 @@ async def _is_local_room_accessible(
return False

async def _is_remote_room_accessible(
self, requester: str, room_id: str, room: JsonDict
self, requester: Optional[str], room_id: str, room: JsonDict
) -> bool:
"""
Calculate whether the room received over federation should be shown to the requester.
Expand All @@ -645,7 +646,8 @@ async def _is_remote_room_accessible(
due to an invite, etc.

Args:
requester: The user requesting the summary.
requester: The user requesting the summary. If not passed only world
readability is checked.
room_id: The room ID returned over federation.
room: The summary of the room returned over federation.

Expand All @@ -659,6 +661,8 @@ async def _is_remote_room_accessible(
or room.get("world_readable") is True
):
return True
elif not requester:
return False

# Check if the user is a member of any of the allowed rooms from the response.
allowed_rooms = room.get("allowed_room_ids")
Expand Down Expand Up @@ -715,6 +719,10 @@ async def _build_room_entry(self, room_id: str, for_federation: bool) -> JsonDic
"room_type": create_event.content.get(EventContentFields.ROOM_TYPE),
}

if self._msc3266_enabled:
entry["im.nheko.summary.version"] = stats["version"]
entry["im.nheko.summary.encryption"] = stats["encryption"]

# Federation requests need to provide additional information so the
# requested server is able to filter the response appropriately.
if for_federation:
Expand Down Expand Up @@ -812,9 +820,45 @@ async def get_room_summary(

room_summary["membership"] = membership or "leave"
else:
# TODO federation API, descoped from initial unstable implementation
# as MSC needs more maturing on that side.
raise SynapseError(400, "Federation is not currently supported.")
# Reuse the hierarchy query over federation
if remote_room_hosts is None:
raise SynapseError(400, "Missing via to query remote room")

(
room_entry,
children_room_entries,
inaccessible_children,
) = await self._summarize_remote_room_hierarchy(
_RoomQueueEntry(room_id, remote_room_hosts),
suggested_only=True,
)

# The results over federation might include rooms that we, as the
# requesting server, are allowed to see, but the requesting user is
# not permitted to see.
#
# Filter the returned results to only what is accessible to the user.
if not room_entry or not await self._is_remote_room_accessible(
requester, room_entry.room_id, room_entry.room
):
raise NotFoundError("Room not found or is not accessible")

room = dict(room_entry.room)
room.pop("allowed_room_ids", None)

# If there was a requester, add their membership.
# We keep the membership in the local membership table unless the
# room is purged even for remote rooms.
if requester:
(
membership,
_,
) = await self._store.get_local_current_membership_for_user_in_room(
requester, room_id
)
room["membership"] = membership or "leave"

return room

return room_summary

Expand Down
26 changes: 26 additions & 0 deletions tests/handlers/test_room_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,3 +1092,29 @@ def test_visibility(self):
)
result = self.get_success(self.handler.get_room_summary(user2, self.room))
self.assertEqual(result.get("room_id"), self.room)

def test_fed(self):
"""
Return data over federation and ensure that it is handled properly.
"""
fed_hostname = self.hs.hostname + "2"
fed_room = "#fed_room:" + fed_hostname

requested_room_entry = _RoomEntry(
fed_room,
{"room_id": fed_room, "world_readable": True},
)

async def summarize_remote_room_hierarchy(_self, room, suggested_only):
return requested_room_entry, {}, set()

with mock.patch(
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
new=summarize_remote_room_hierarchy,
):
result = self.get_success(
self.handler.get_room_summary(
self.user, fed_room, remote_room_hosts=[fed_hostname]
)
)
self.assertEqual(result.get("room_id"), fed_room)