feat: Include allowed_room_ids in the /summary client-server API response for rooms with restricted join rules, as required by Matrix 1.15. (#19762)

Currently the `/summary` endpoint on the client and federation APIs does
not include the allowed_room_ids field, that is present on the
/hierarchy API. This is required by the 1.15 spec and this is what this
PR does.

Complement test: https://github.com/matrix-org/complement/pull/873

Part of https://github.com/element-hq/synapse/issues/18731
This commit is contained in:
FrenchGithubUser
2026-06-18 14:12:51 +01:00
committed by GitHub
parent 0640a3e838
commit 75ee23a16b
3 changed files with 80 additions and 21 deletions
+2
View File
@@ -0,0 +1,2 @@
Include `allowed_room_ids` in the `/summary` client-server API response for rooms with restricted join rules, as required by Matrix 1.15.
Contributed by @FrenchGithubUser @Famedly.
+24 -21
View File
@@ -513,7 +513,7 @@ class RoomSummaryHandler:
):
return None
room_entry = await self._build_room_entry(room_id, for_federation=bool(origin))
room_entry = await self._build_room_entry(room_id)
# If the room is not a space return just the room information.
if room_entry.get("room_type") != RoomTypes.SPACE or not include_children:
@@ -769,14 +769,12 @@ class RoomSummaryHandler:
# pending invite, etc.
return await self._is_local_room_accessible(room_id, requester)
async def _build_room_entry(self, room_id: str, for_federation: bool) -> JsonDict:
async def _build_room_entry(self, room_id: str) -> JsonDict:
"""
Generate en entry summarising a single room.
Args:
room_id: The room ID to summarize.
for_federation: True if this is a summary requested over federation
(which includes additional fields).
Returns:
The JSON dictionary for the room.
@@ -805,24 +803,30 @@ class RoomSummaryHandler:
"encryption": stats.encryption,
}
# Federation requests need to provide additional information so the
# requested server is able to filter the response appropriately.
if for_federation:
current_state_ids = (
await self._storage_controllers.state.get_current_state_ids(room_id)
# Include allowed_room_ids for rooms with restricted join rules so that
# clients can determine which memberships grant access.
# Only the join rules event is needed for both has_restricted_join_rules
# and get_rooms_that_allow_join, so avoid fetching full state.
join_rules_state_ids = (
await self._storage_controllers.state.get_current_state_ids(
room_id,
state_filter=StateFilter.from_types([(EventTypes.JoinRules, "")]),
)
room_version = await self._store.get_room_version(room_id)
)
if await self._event_auth_handler.has_restricted_join_rules(
current_state_ids, room_version
):
allowed_rooms = (
await self._event_auth_handler.get_rooms_that_allow_join(
current_state_ids
)
)
if allowed_rooms:
entry["allowed_room_ids"] = allowed_rooms
try:
room_version = await self._store.get_room_version(room_id)
except UnsupportedRoomVersionError:
room_version = None
if room_version and await self._event_auth_handler.has_restricted_join_rules(
join_rules_state_ids, room_version
):
allowed_rooms = await self._event_auth_handler.get_rooms_that_allow_join(
join_rules_state_ids
)
if allowed_rooms:
entry["allowed_room_ids"] = allowed_rooms
# Filter out Nones rather omit the field altogether
room_entry = {k: v for k, v in entry.items() if v is not None}
@@ -932,7 +936,6 @@ class RoomSummaryHandler:
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
+54
View File
@@ -1219,6 +1219,60 @@ class RoomSummaryTestCase(unittest.HomeserverTestCase):
result = self.get_success(self.handler.get_room_summary(user2, self.room))
self.assertEqual(result.get("room_id"), self.room)
def test_allowed_room_ids_local(self) -> None:
"""allowed_room_ids is returned for a local room with restricted join rules."""
# Create a space that the restricted room will allow membership from.
space = self.helper.create_room_as(
self.user,
tok=self.token,
extra_content={
"creation_content": {"type": RoomTypes.SPACE},
"initial_state": [
{
"type": EventTypes.JoinRules,
"state_key": "",
"content": {"join_rule": JoinRules.PUBLIC},
}
],
},
)
# Create a room version 8 room with join_rule=restricted allowing members
# of the space above.
restricted_room = self.helper.create_room_as(
self.user,
room_version=RoomVersions.V8.identifier,
tok=self.token,
extra_content={
"initial_state": [
{
"type": EventTypes.JoinRules,
"state_key": "",
"content": {
"join_rule": JoinRules.RESTRICTED,
"allow": [
{
"type": RestrictedJoinRuleTypes.ROOM_MEMBERSHIP,
"room_id": space,
}
],
},
}
]
},
)
result = self.get_success(
self.handler.get_room_summary(self.user, restricted_room)
)
self.assertEqual(result.get("room_id"), restricted_room)
self.assertEqual(result.get("allowed_room_ids"), [space])
def test_allowed_room_ids_absent_without_restricted_join_rules(self) -> None:
"""allowed_room_ids is absent for rooms that do not use restricted join rules."""
result = self.get_success(self.handler.get_room_summary(self.user, self.room))
self.assertNotIn("allowed_room_ids", result)
def test_fed(self) -> None:
"""
Return data over federation and ensure that it is handled properly.