|
@@ -15,7 +15,6 @@
|
|
|
import itertools
|
|
|
import logging
|
|
|
import re
|
|
|
-from collections import deque
|
|
|
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Sequence, Set, Tuple
|
|
|
|
|
|
import attr
|
|
@@ -107,153 +106,6 @@ class RoomSummaryHandler:
|
|
|
"get_room_hierarchy",
|
|
|
)
|
|
|
|
|
|
- async def get_space_summary(
|
|
|
- self,
|
|
|
- requester: str,
|
|
|
- room_id: str,
|
|
|
- suggested_only: bool = False,
|
|
|
- max_rooms_per_space: Optional[int] = None,
|
|
|
- ) -> JsonDict:
|
|
|
- """
|
|
|
- Implementation of the space summary C-S API
|
|
|
-
|
|
|
- Args:
|
|
|
- requester: user id of the user making this request
|
|
|
-
|
|
|
- room_id: room id to start the summary at
|
|
|
-
|
|
|
- suggested_only: whether we should only return children with the "suggested"
|
|
|
- flag set.
|
|
|
-
|
|
|
- max_rooms_per_space: an optional limit on the number of child rooms we will
|
|
|
- return. This does not apply to the root room (ie, room_id), and
|
|
|
- is overridden by MAX_ROOMS_PER_SPACE.
|
|
|
-
|
|
|
- Returns:
|
|
|
- summary dict to return
|
|
|
- """
|
|
|
- # First of all, check that the room is accessible.
|
|
|
- if not await self._is_local_room_accessible(room_id, requester):
|
|
|
- raise AuthError(
|
|
|
- 403,
|
|
|
- "User %s not in room %s, and room previews are disabled"
|
|
|
- % (requester, room_id),
|
|
|
- )
|
|
|
-
|
|
|
- # the queue of rooms to process
|
|
|
- room_queue = deque((_RoomQueueEntry(room_id, ()),))
|
|
|
-
|
|
|
- # rooms we have already processed
|
|
|
- processed_rooms: Set[str] = set()
|
|
|
-
|
|
|
- # events we have already processed. We don't necessarily have their event ids,
|
|
|
- # so instead we key on (room id, state key)
|
|
|
- processed_events: Set[Tuple[str, str]] = set()
|
|
|
-
|
|
|
- rooms_result: List[JsonDict] = []
|
|
|
- events_result: List[JsonDict] = []
|
|
|
-
|
|
|
- if max_rooms_per_space is None or max_rooms_per_space > MAX_ROOMS_PER_SPACE:
|
|
|
- max_rooms_per_space = MAX_ROOMS_PER_SPACE
|
|
|
-
|
|
|
- while room_queue and len(rooms_result) < MAX_ROOMS:
|
|
|
- queue_entry = room_queue.popleft()
|
|
|
- room_id = queue_entry.room_id
|
|
|
- if room_id in processed_rooms:
|
|
|
- # already done this room
|
|
|
- continue
|
|
|
-
|
|
|
- logger.debug("Processing room %s", room_id)
|
|
|
-
|
|
|
- is_in_room = await self._store.is_host_joined(room_id, self._server_name)
|
|
|
-
|
|
|
- # The client-specified max_rooms_per_space limit doesn't apply to the
|
|
|
- # room_id specified in the request, so we ignore it if this is the
|
|
|
- # first room we are processing.
|
|
|
- max_children = max_rooms_per_space if processed_rooms else MAX_ROOMS
|
|
|
-
|
|
|
- if is_in_room:
|
|
|
- room_entry = await self._summarize_local_room(
|
|
|
- requester, None, room_id, suggested_only, max_children
|
|
|
- )
|
|
|
-
|
|
|
- events: Sequence[JsonDict] = []
|
|
|
- if room_entry:
|
|
|
- rooms_result.append(room_entry.room)
|
|
|
- events = room_entry.children_state_events
|
|
|
-
|
|
|
- logger.debug(
|
|
|
- "Query of local room %s returned events %s",
|
|
|
- room_id,
|
|
|
- ["%s->%s" % (ev["room_id"], ev["state_key"]) for ev in events],
|
|
|
- )
|
|
|
- else:
|
|
|
- fed_rooms = await self._summarize_remote_room(
|
|
|
- queue_entry,
|
|
|
- suggested_only,
|
|
|
- max_children,
|
|
|
- exclude_rooms=processed_rooms,
|
|
|
- )
|
|
|
-
|
|
|
- # The results over federation might include rooms that the we,
|
|
|
- # as the requesting server, are allowed to see, but the requesting
|
|
|
- # user is not permitted see.
|
|
|
- #
|
|
|
- # Filter the returned results to only what is accessible to the user.
|
|
|
- events = []
|
|
|
- for room_entry in fed_rooms:
|
|
|
- room = room_entry.room
|
|
|
- fed_room_id = room_entry.room_id
|
|
|
-
|
|
|
- # The user can see the room, include it!
|
|
|
- if await self._is_remote_room_accessible(
|
|
|
- requester, fed_room_id, room
|
|
|
- ):
|
|
|
- # Before returning to the client, remove the allowed_room_ids
|
|
|
- # and allowed_spaces keys.
|
|
|
- room.pop("allowed_room_ids", None)
|
|
|
- room.pop("allowed_spaces", None) # historical
|
|
|
-
|
|
|
- rooms_result.append(room)
|
|
|
- events.extend(room_entry.children_state_events)
|
|
|
-
|
|
|
- # All rooms returned don't need visiting again (even if the user
|
|
|
- # didn't have access to them).
|
|
|
- processed_rooms.add(fed_room_id)
|
|
|
-
|
|
|
- logger.debug(
|
|
|
- "Query of %s returned rooms %s, events %s",
|
|
|
- room_id,
|
|
|
- [room_entry.room.get("room_id") for room_entry in fed_rooms],
|
|
|
- ["%s->%s" % (ev["room_id"], ev["state_key"]) for ev in events],
|
|
|
- )
|
|
|
-
|
|
|
- # the room we queried may or may not have been returned, but don't process
|
|
|
- # it again, anyway.
|
|
|
- processed_rooms.add(room_id)
|
|
|
-
|
|
|
- # XXX: is it ok that we blindly iterate through any events returned by
|
|
|
- # a remote server, whether or not they actually link to any rooms in our
|
|
|
- # tree?
|
|
|
- for ev in events:
|
|
|
- # remote servers might return events we have already processed
|
|
|
- # (eg, Dendrite returns inward pointers as well as outward ones), so
|
|
|
- # we need to filter them out, to avoid returning duplicate links to the
|
|
|
- # client.
|
|
|
- ev_key = (ev["room_id"], ev["state_key"])
|
|
|
- if ev_key in processed_events:
|
|
|
- continue
|
|
|
- events_result.append(ev)
|
|
|
-
|
|
|
- # add the child to the queue. we have already validated
|
|
|
- # that the vias are a list of server names.
|
|
|
- room_queue.append(
|
|
|
- _RoomQueueEntry(ev["state_key"], ev["content"]["via"])
|
|
|
- )
|
|
|
- processed_events.add(ev_key)
|
|
|
-
|
|
|
- return {"rooms": rooms_result, "events": events_result}
|
|
|
-
|
|
|
async def get_room_hierarchy(
|
|
|
self,
|
|
|
requester: Requester,
|
|
@@ -398,8 +250,6 @@ class RoomSummaryHandler:
|
|
|
None,
|
|
|
room_id,
|
|
|
suggested_only,
|
|
|
- # Do not limit the maximum children.
|
|
|
- max_children=None,
|
|
|
)
|
|
|
|
|
|
# Otherwise, attempt to use information for federation.
|
|
@@ -488,74 +338,6 @@ class RoomSummaryHandler:
|
|
|
|
|
|
return result
|
|
|
|
|
|
- async def federation_space_summary(
|
|
|
- self,
|
|
|
- origin: str,
|
|
|
- room_id: str,
|
|
|
- suggested_only: bool,
|
|
|
- max_rooms_per_space: Optional[int],
|
|
|
- exclude_rooms: Iterable[str],
|
|
|
- ) -> JsonDict:
|
|
|
- """
|
|
|
- Implementation of the space summary Federation API
|
|
|
-
|
|
|
- Args:
|
|
|
- origin: The server requesting the spaces summary.
|
|
|
-
|
|
|
- room_id: room id to start the summary at
|
|
|
-
|
|
|
- suggested_only: whether we should only return children with the "suggested"
|
|
|
- flag set.
|
|
|
-
|
|
|
- max_rooms_per_space: an optional limit on the number of child rooms we will
|
|
|
- return. Unlike the C-S API, this applies to the root room (room_id).
|
|
|
- It is clipped to MAX_ROOMS_PER_SPACE.
|
|
|
-
|
|
|
- exclude_rooms: a list of rooms to skip over (presumably because the
|
|
|
- calling server has already seen them).
|
|
|
-
|
|
|
- Returns:
|
|
|
- summary dict to return
|
|
|
- """
|
|
|
- # the queue of rooms to process
|
|
|
- room_queue = deque((room_id,))
|
|
|
-
|
|
|
- # the set of rooms that we should not walk further. Initialise it with the
|
|
|
- # excluded-rooms list; we will add other rooms as we process them so that
|
|
|
- # we do not loop.
|
|
|
- processed_rooms: Set[str] = set(exclude_rooms)
|
|
|
-
|
|
|
- rooms_result: List[JsonDict] = []
|
|
|
- events_result: List[JsonDict] = []
|
|
|
-
|
|
|
- # Set a limit on the number of rooms to return.
|
|
|
- if max_rooms_per_space is None or max_rooms_per_space > MAX_ROOMS_PER_SPACE:
|
|
|
- max_rooms_per_space = MAX_ROOMS_PER_SPACE
|
|
|
-
|
|
|
- while room_queue and len(rooms_result) < MAX_ROOMS:
|
|
|
- room_id = room_queue.popleft()
|
|
|
- if room_id in processed_rooms:
|
|
|
- # already done this room
|
|
|
- continue
|
|
|
-
|
|
|
- room_entry = await self._summarize_local_room(
|
|
|
- None, origin, room_id, suggested_only, max_rooms_per_space
|
|
|
- )
|
|
|
-
|
|
|
- processed_rooms.add(room_id)
|
|
|
-
|
|
|
- if room_entry:
|
|
|
- rooms_result.append(room_entry.room)
|
|
|
- events_result.extend(room_entry.children_state_events)
|
|
|
-
|
|
|
- # add any children to the queue
|
|
|
- room_queue.extend(
|
|
|
- edge_event["state_key"]
|
|
|
- for edge_event in room_entry.children_state_events
|
|
|
- )
|
|
|
-
|
|
|
- return {"rooms": rooms_result, "events": events_result}
|
|
|
-
|
|
|
async def get_federation_hierarchy(
|
|
|
self,
|
|
|
origin: str,
|
|
@@ -579,7 +361,7 @@ class RoomSummaryHandler:
|
|
|
The JSON hierarchy dictionary.
|
|
|
"""
|
|
|
root_room_entry = await self._summarize_local_room(
|
|
|
- None, origin, requested_room_id, suggested_only, max_children=None
|
|
|
+ None, origin, requested_room_id, suggested_only
|
|
|
)
|
|
|
if root_room_entry is None:
|
|
|
# Room is inaccessible to the requesting server.
|
|
@@ -600,7 +382,7 @@ class RoomSummaryHandler:
|
|
|
continue
|
|
|
|
|
|
room_entry = await self._summarize_local_room(
|
|
|
- None, origin, room_id, suggested_only, max_children=0
|
|
|
+ None, origin, room_id, suggested_only, include_children=False
|
|
|
)
|
|
|
# If the room is accessible, include it in the results.
|
|
|
#
|
|
@@ -626,7 +408,7 @@ class RoomSummaryHandler:
|
|
|
origin: Optional[str],
|
|
|
room_id: str,
|
|
|
suggested_only: bool,
|
|
|
- max_children: Optional[int],
|
|
|
+ include_children: bool = True,
|
|
|
) -> Optional["_RoomEntry"]:
|
|
|
"""
|
|
|
Generate a room entry and a list of event entries for a given room.
|
|
@@ -641,9 +423,8 @@ class RoomSummaryHandler:
|
|
|
room_id: The room ID to summarize.
|
|
|
suggested_only: True if only suggested children should be returned.
|
|
|
Otherwise, all children are returned.
|
|
|
- max_children:
|
|
|
- The maximum number of children rooms to include. A value of None
|
|
|
- means no limit.
|
|
|
+ include_children:
|
|
|
+ Whether to include the events of any children.
|
|
|
|
|
|
Returns:
|
|
|
A room entry if the room should be returned. None, otherwise.
|
|
@@ -653,9 +434,8 @@ class RoomSummaryHandler:
|
|
|
|
|
|
room_entry = await self._build_room_entry(room_id, for_federation=bool(origin))
|
|
|
|
|
|
- # If the room is not a space or the children don't matter, return just
|
|
|
- # the room information.
|
|
|
- if room_entry.get("room_type") != RoomTypes.SPACE or max_children == 0:
|
|
|
+ # If the room is not a space return just the room information.
|
|
|
+ if room_entry.get("room_type") != RoomTypes.SPACE or not include_children:
|
|
|
return _RoomEntry(room_id, room_entry)
|
|
|
|
|
|
# Otherwise, look for child rooms/spaces.
|
|
@@ -665,14 +445,6 @@ class RoomSummaryHandler:
|
|
|
# we only care about suggested children
|
|
|
child_events = filter(_is_suggested_child_event, child_events)
|
|
|
|
|
|
- # TODO max_children is legacy code for the /spaces endpoint.
|
|
|
- if max_children is not None:
|
|
|
- child_iter: Iterable[EventBase] = itertools.islice(
|
|
|
- child_events, max_children
|
|
|
- )
|
|
|
- else:
|
|
|
- child_iter = child_events
|
|
|
-
|
|
|
stripped_events: List[JsonDict] = [
|
|
|
{
|
|
|
"type": e.type,
|
|
@@ -682,80 +454,10 @@ class RoomSummaryHandler:
|
|
|
"sender": e.sender,
|
|
|
"origin_server_ts": e.origin_server_ts,
|
|
|
}
|
|
|
- for e in child_iter
|
|
|
+ for e in child_events
|
|
|
]
|
|
|
return _RoomEntry(room_id, room_entry, stripped_events)
|
|
|
|
|
|
- async def _summarize_remote_room(
|
|
|
- self,
|
|
|
- room: "_RoomQueueEntry",
|
|
|
- suggested_only: bool,
|
|
|
- max_children: Optional[int],
|
|
|
- exclude_rooms: Iterable[str],
|
|
|
- ) -> Iterable["_RoomEntry"]:
|
|
|
- """
|
|
|
- Request room entries and a list of event entries for a given room by querying a remote server.
|
|
|
-
|
|
|
- Args:
|
|
|
- room: The room to summarize.
|
|
|
- suggested_only: True if only suggested children should be returned.
|
|
|
- Otherwise, all children are returned.
|
|
|
- max_children:
|
|
|
- The maximum number of children rooms to include. This is capped
|
|
|
- to a server-set limit.
|
|
|
- exclude_rooms:
|
|
|
- Rooms IDs which do not need to be summarized.
|
|
|
-
|
|
|
- Returns:
|
|
|
- An iterable of room entries.
|
|
|
- """
|
|
|
- room_id = room.room_id
|
|
|
- logger.info("Requesting summary for %s via %s", room_id, room.via)
|
|
|
-
|
|
|
- # we need to make the exclusion list json-serialisable
|
|
|
- exclude_rooms = list(exclude_rooms)
|
|
|
-
|
|
|
- via = itertools.islice(room.via, MAX_SERVERS_PER_SPACE)
|
|
|
- try:
|
|
|
- res = await self._federation_client.get_space_summary(
|
|
|
- via,
|
|
|
- room_id,
|
|
|
- suggested_only=suggested_only,
|
|
|
- max_rooms_per_space=max_children,
|
|
|
- exclude_rooms=exclude_rooms,
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- logger.warning(
|
|
|
- "Unable to get summary of %s via federation: %s",
|
|
|
- room_id,
|
|
|
- e,
|
|
|
- exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
|
- )
|
|
|
- return ()
|
|
|
-
|
|
|
- # Group the events by their room.
|
|
|
- children_by_room: Dict[str, List[JsonDict]] = {}
|
|
|
- for ev in res.events:
|
|
|
- if ev.event_type == EventTypes.SpaceChild:
|
|
|
- children_by_room.setdefault(ev.room_id, []).append(ev.data)
|
|
|
-
|
|
|
- # Generate the final results.
|
|
|
- results = []
|
|
|
- for fed_room in res.rooms:
|
|
|
- fed_room_id = fed_room.get("room_id")
|
|
|
- if not fed_room_id or not isinstance(fed_room_id, str):
|
|
|
- continue
|
|
|
-
|
|
|
- results.append(
|
|
|
- _RoomEntry(
|
|
|
- fed_room_id,
|
|
|
- fed_room,
|
|
|
- children_by_room.get(fed_room_id, []),
|
|
|
- )
|
|
|
- )
|
|
|
-
|
|
|
- return results
|
|
|
-
|
|
|
async def _summarize_remote_room_hierarchy(
|
|
|
self, room: "_RoomQueueEntry", suggested_only: bool
|
|
|
) -> Tuple[Optional["_RoomEntry"], Dict[str, JsonDict], Set[str]]:
|
|
@@ -958,9 +660,8 @@ class RoomSummaryHandler:
|
|
|
):
|
|
|
return True
|
|
|
|
|
|
- # Check if the user is a member of any of the allowed spaces
|
|
|
- # from the response.
|
|
|
- allowed_rooms = room.get("allowed_room_ids") or room.get("allowed_spaces")
|
|
|
+ # Check if the user is a member of any of the allowed rooms from the response.
|
|
|
+ allowed_rooms = room.get("allowed_room_ids")
|
|
|
if allowed_rooms and isinstance(allowed_rooms, list):
|
|
|
if await self._event_auth_handler.is_user_in_rooms(
|
|
|
allowed_rooms, requester
|
|
@@ -1028,8 +729,6 @@ class RoomSummaryHandler:
|
|
|
)
|
|
|
if allowed_rooms:
|
|
|
entry["allowed_room_ids"] = allowed_rooms
|
|
|
- # TODO Remove this key once the API is stable.
|
|
|
- entry["allowed_spaces"] = 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}
|
|
@@ -1094,7 +793,7 @@ class RoomSummaryHandler:
|
|
|
room_id,
|
|
|
# Suggested-only doesn't matter since no children are requested.
|
|
|
suggested_only=False,
|
|
|
- max_children=0,
|
|
|
+ include_children=False,
|
|
|
)
|
|
|
|
|
|
if not room_entry:
|