Add via param to hierarchy enpoint (#18070)

### Pull Request Checklist

Implementation of
[MSC4235](https://github.com/matrix-org/matrix-spec-proposals/pull/4235)
as per suggestion in [pull request
17750](https://github.com/element-hq/synapse/pull/17750#issuecomment-2411248598).

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [x] Pull request is based on the develop branch
* [x] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [x] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct
(run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))

---------

Co-authored-by: Quentin Gliech <quenting@element.io>
This commit is contained in:
Krishan 2025-06-30 22:42:14 +10:00 committed by GitHub
parent 3878699df7
commit a2bee2f255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 89 additions and 3 deletions

View File

@ -0,0 +1 @@
Support for [MSC4235](https://github.com/matrix-org/matrix-spec-proposals/pull/4235): via query param for hierarchy endpoint. Contributed by Krishan (@kfiven).

View File

@ -561,6 +561,9 @@ class ExperimentalConfig(Config):
# MSC4076: Add `disable_badge_count`` to pusher configuration
self.msc4076_enabled: bool = experimental.get("msc4076_enabled", False)
# MSC4235: Add `via` param to hierarchy endpoint
self.msc4235_enabled: bool = experimental.get("msc4235_enabled", False)
# MSC4263: Preventing MXID enumeration via key queries
self.msc4263_limit_key_queries_to_users_who_share_rooms = experimental.get(
"msc4263_limit_key_queries_to_users_who_share_rooms",

View File

@ -111,7 +111,15 @@ class RoomSummaryHandler:
# If a user tries to fetch the same page multiple times in quick succession,
# only process the first attempt and return its result to subsequent requests.
self._pagination_response_cache: ResponseCache[
Tuple[str, str, bool, Optional[int], Optional[int], Optional[str]]
Tuple[
str,
str,
bool,
Optional[int],
Optional[int],
Optional[str],
Optional[Tuple[str, ...]],
]
] = ResponseCache(
hs.get_clock(),
"get_room_hierarchy",
@ -126,6 +134,7 @@ class RoomSummaryHandler:
max_depth: Optional[int] = None,
limit: Optional[int] = None,
from_token: Optional[str] = None,
remote_room_hosts: Optional[Tuple[str, ...]] = None,
) -> JsonDict:
"""
Implementation of the room hierarchy C-S API.
@ -143,6 +152,9 @@ class RoomSummaryHandler:
limit: An optional limit on the number of rooms to return per
page. Must be a positive integer.
from_token: An optional pagination token.
remote_room_hosts: An optional list of remote homeserver server names. If defined,
each host will be used to try and fetch the room hierarchy. Must be a tuple so
that it can be hashed by the `RoomSummaryHandler._pagination_response_cache`.
Returns:
The JSON hierarchy dictionary.
@ -162,6 +174,7 @@ class RoomSummaryHandler:
max_depth,
limit,
from_token,
remote_room_hosts,
),
self._get_room_hierarchy,
requester.user.to_string(),
@ -170,6 +183,7 @@ class RoomSummaryHandler:
max_depth,
limit,
from_token,
remote_room_hosts,
)
async def _get_room_hierarchy(
@ -180,6 +194,7 @@ class RoomSummaryHandler:
max_depth: Optional[int] = None,
limit: Optional[int] = None,
from_token: Optional[str] = None,
remote_room_hosts: Optional[Tuple[str, ...]] = None,
) -> JsonDict:
"""See docstring for SpaceSummaryHandler.get_room_hierarchy."""
@ -199,7 +214,7 @@ class RoomSummaryHandler:
if not local_room:
room_hierarchy = await self._summarize_remote_room_hierarchy(
_RoomQueueEntry(requested_room_id, ()),
_RoomQueueEntry(requested_room_id, remote_room_hosts or ()),
False,
)
root_room_entry = room_hierarchy[0]
@ -240,7 +255,7 @@ class RoomSummaryHandler:
processed_rooms = set(pagination_session["processed_rooms"])
else:
# The queue of rooms to process, the next room is last on the stack.
room_queue = [_RoomQueueEntry(requested_room_id, ())]
room_queue = [_RoomQueueEntry(requested_room_id, remote_room_hosts or ())]
# Rooms we have already processed.
processed_rooms = set()

View File

@ -1538,6 +1538,7 @@ class RoomHierarchyRestServlet(RestServlet):
super().__init__()
self._auth = hs.get_auth()
self._room_summary_handler = hs.get_room_summary_handler()
self.msc4235_enabled = hs.config.experimental.msc4235_enabled
async def on_GET(
self, request: SynapseRequest, room_id: str
@ -1547,6 +1548,15 @@ class RoomHierarchyRestServlet(RestServlet):
max_depth = parse_integer(request, "max_depth")
limit = parse_integer(request, "limit")
# twisted.web.server.Request.args is incorrectly defined as Optional[Any]
remote_room_hosts = None
if self.msc4235_enabled:
args: Dict[bytes, List[bytes]] = request.args # type: ignore
via_param = parse_strings_from_args(
args, "org.matrix.msc4235.via", required=False
)
remote_room_hosts = tuple(via_param or [])
return 200, await self._room_summary_handler.get_room_hierarchy(
requester,
room_id,
@ -1554,6 +1564,7 @@ class RoomHierarchyRestServlet(RestServlet):
max_depth=max_depth,
limit=limit,
from_token=parse_string(request, "from"),
remote_room_hosts=remote_room_hosts,
)

View File

@ -1080,6 +1080,62 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
self.assertEqual(federation_requests, 2)
self._assert_hierarchy(result, expected)
def test_fed_remote_room_hosts(self) -> None:
"""
Test if requested room is available over federation using via's.
"""
fed_hostname = self.hs.hostname + "2"
fed_space = "#fed_space:" + fed_hostname
fed_subroom = "#fed_sub_room:" + fed_hostname
remote_room_hosts = tuple(fed_hostname)
requested_room_entry = _RoomEntry(
fed_space,
{
"room_id": fed_space,
"world_readable": True,
"join_rule": "public",
"room_type": RoomTypes.SPACE,
},
[
{
"type": EventTypes.SpaceChild,
"room_id": fed_space,
"state_key": fed_subroom,
"content": {"via": [fed_hostname]},
}
],
)
child_room = {
"room_id": fed_subroom,
"world_readable": True,
"join_rule": "public",
}
async def summarize_remote_room_hierarchy(
_self: Any, room: Any, suggested_only: bool
) -> Tuple[Optional[_RoomEntry], Dict[str, JsonDict], Set[str]]:
return requested_room_entry, {fed_subroom: child_room}, set()
expected = [
(fed_space, [fed_subroom]),
(fed_subroom, ()),
]
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_hierarchy(
create_requester(self.user),
fed_space,
remote_room_hosts=remote_room_hosts,
)
)
self._assert_hierarchy(result, expected)
class RoomSummaryTestCase(unittest.HomeserverTestCase):
servlets = [