Experimental support for MSC3266 Room Summary API. (#10394)
parent
87b62f8bb2
commit
0ace38b7b3
|
@ -0,0 +1 @@
|
||||||
|
Initial local support for [MSC3266](https://github.com/matrix-org/synapse/pull/10394), Room Summary over the unstable `/rooms/{roomIdOrAlias}/summary` API.
|
2
mypy.ini
2
mypy.ini
|
@ -86,7 +86,7 @@ files =
|
||||||
tests/test_event_auth.py,
|
tests/test_event_auth.py,
|
||||||
tests/test_utils,
|
tests/test_utils,
|
||||||
tests/handlers/test_password_providers.py,
|
tests/handlers/test_password_providers.py,
|
||||||
tests/handlers/test_space_summary.py,
|
tests/handlers/test_room_summary.py,
|
||||||
tests/rest/client/v1/test_login.py,
|
tests/rest/client/v1/test_login.py,
|
||||||
tests/rest/client/v2_alpha/test_auth.py,
|
tests/rest/client/v2_alpha/test_auth.py,
|
||||||
tests/util/test_itertools.py,
|
tests/util/test_itertools.py,
|
||||||
|
|
|
@ -38,3 +38,6 @@ class ExperimentalConfig(Config):
|
||||||
|
|
||||||
# MSC3244 (room version capabilities)
|
# MSC3244 (room version capabilities)
|
||||||
self.msc3244_enabled: bool = experimental.get("msc3244_enabled", False)
|
self.msc3244_enabled: bool = experimental.get("msc3244_enabled", False)
|
||||||
|
|
||||||
|
# MSC3266 (room summary api)
|
||||||
|
self.msc3266_enabled: bool = experimental.get("msc3266_enabled", False)
|
||||||
|
|
|
@ -547,7 +547,7 @@ class FederationSpaceSummaryServlet(BaseFederationServlet):
|
||||||
server_name: str,
|
server_name: str,
|
||||||
):
|
):
|
||||||
super().__init__(hs, authenticator, ratelimiter, server_name)
|
super().__init__(hs, authenticator, ratelimiter, server_name)
|
||||||
self.handler = hs.get_space_summary_handler()
|
self.handler = hs.get_room_summary_handler()
|
||||||
|
|
||||||
async def on_GET(
|
async def on_GET(
|
||||||
self,
|
self,
|
||||||
|
@ -608,7 +608,7 @@ class FederationRoomHierarchyServlet(BaseFederationServlet):
|
||||||
server_name: str,
|
server_name: str,
|
||||||
):
|
):
|
||||||
super().__init__(hs, authenticator, ratelimiter, server_name)
|
super().__init__(hs, authenticator, ratelimiter, server_name)
|
||||||
self.handler = hs.get_space_summary_handler()
|
self.handler = hs.get_room_summary_handler()
|
||||||
|
|
||||||
async def on_GET(
|
async def on_GET(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -28,7 +28,7 @@ from synapse.api.constants import (
|
||||||
Membership,
|
Membership,
|
||||||
RoomTypes,
|
RoomTypes,
|
||||||
)
|
)
|
||||||
from synapse.api.errors import AuthError, Codes, SynapseError
|
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase
|
||||||
from synapse.events.utils import format_event_for_client_v2
|
from synapse.events.utils import format_event_for_client_v2
|
||||||
from synapse.types import JsonDict
|
from synapse.types import JsonDict
|
||||||
|
@ -75,7 +75,7 @@ class _PaginationSession:
|
||||||
processed_rooms: Set[str]
|
processed_rooms: Set[str]
|
||||||
|
|
||||||
|
|
||||||
class SpaceSummaryHandler:
|
class RoomSummaryHandler:
|
||||||
# The time a pagination session remains valid for.
|
# The time a pagination session remains valid for.
|
||||||
_PAGINATION_SESSION_VALIDITY_PERIOD_MS = 5 * 60 * 1000
|
_PAGINATION_SESSION_VALIDITY_PERIOD_MS = 5 * 60 * 1000
|
||||||
|
|
||||||
|
@ -412,7 +412,7 @@ class SpaceSummaryHandler:
|
||||||
room_entry,
|
room_entry,
|
||||||
children_room_entries,
|
children_room_entries,
|
||||||
inaccessible_children,
|
inaccessible_children,
|
||||||
) = await self._summarize_remote_room_hiearchy(
|
) = await self._summarize_remote_room_hierarchy(
|
||||||
queue_entry,
|
queue_entry,
|
||||||
suggested_only,
|
suggested_only,
|
||||||
)
|
)
|
||||||
|
@ -724,7 +724,7 @@ class SpaceSummaryHandler:
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
async def _summarize_remote_room_hiearchy(
|
async def _summarize_remote_room_hierarchy(
|
||||||
self, room: "_RoomQueueEntry", suggested_only: bool
|
self, room: "_RoomQueueEntry", suggested_only: bool
|
||||||
) -> Tuple[Optional["_RoomEntry"], Dict[str, JsonDict], Set[str]]:
|
) -> Tuple[Optional["_RoomEntry"], Dict[str, JsonDict], Set[str]]:
|
||||||
"""
|
"""
|
||||||
|
@ -781,25 +781,25 @@ class SpaceSummaryHandler:
|
||||||
self, room_id: str, requester: Optional[str], origin: Optional[str] = None
|
self, room_id: str, requester: Optional[str], origin: Optional[str] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Calculate whether the room should be shown in the spaces summary.
|
Calculate whether the room should be shown to the requester.
|
||||||
|
|
||||||
It should be included if:
|
It should return true if:
|
||||||
|
|
||||||
* The requester is joined or can join the room (per MSC3173).
|
* The requester is joined or can join the room (per MSC3173).
|
||||||
* The origin server has any user that is joined or can join the room.
|
* The origin server has any user that is joined or can join the room.
|
||||||
* The history visibility is set to world readable.
|
* The history visibility is set to world readable.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
room_id: The room ID to summarize.
|
room_id: The room ID to check accessibility of.
|
||||||
requester:
|
requester:
|
||||||
The user requesting the summary, if it is a local request. None
|
The user making the request, if it is a local request.
|
||||||
if this is a federation request.
|
None if this is a federation request.
|
||||||
origin:
|
origin:
|
||||||
The server requesting the summary, if it is a federation request.
|
The server making the request, if it is a federation request.
|
||||||
None if this is a local request.
|
None if this is a local request.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the room should be included in the spaces summary.
|
True if the room is accessible to the requesting user or server.
|
||||||
"""
|
"""
|
||||||
state_ids = await self._store.get_current_state_ids(room_id)
|
state_ids = await self._store.get_current_state_ids(room_id)
|
||||||
|
|
||||||
|
@ -893,9 +893,9 @@ class SpaceSummaryHandler:
|
||||||
self, requester: str, room_id: str, room: JsonDict
|
self, requester: str, room_id: str, room: JsonDict
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Calculate whether the room received over federation should be shown in the spaces summary.
|
Calculate whether the room received over federation should be shown to the requester.
|
||||||
|
|
||||||
It should be included if:
|
It should return true if:
|
||||||
|
|
||||||
* The requester is joined or can join the room (per MSC3173).
|
* The requester is joined or can join the room (per MSC3173).
|
||||||
* The history visibility is set to world readable.
|
* The history visibility is set to world readable.
|
||||||
|
@ -907,10 +907,10 @@ class SpaceSummaryHandler:
|
||||||
Args:
|
Args:
|
||||||
requester: The user requesting the summary.
|
requester: The user requesting the summary.
|
||||||
room_id: The room ID returned over federation.
|
room_id: The room ID returned over federation.
|
||||||
room: The summary of the child room returned over federation.
|
room: The summary of the room returned over federation.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the room should be included in the spaces summary.
|
True if the room is accessible to the requesting user.
|
||||||
"""
|
"""
|
||||||
# The API doesn't return the room version so assume that a
|
# The API doesn't return the room version so assume that a
|
||||||
# join rule of knock is valid.
|
# join rule of knock is valid.
|
||||||
|
@ -936,7 +936,7 @@ class SpaceSummaryHandler:
|
||||||
|
|
||||||
async def _build_room_entry(self, room_id: str, for_federation: bool) -> JsonDict:
|
async def _build_room_entry(self, room_id: str, for_federation: bool) -> JsonDict:
|
||||||
"""
|
"""
|
||||||
Generate en entry suitable for the 'rooms' list in the summary response.
|
Generate en entry summarising a single room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
room_id: The room ID to summarize.
|
room_id: The room ID to summarize.
|
||||||
|
@ -1024,6 +1024,61 @@ class SpaceSummaryHandler:
|
||||||
# and order to ensure we return stable results.
|
# and order to ensure we return stable results.
|
||||||
return sorted(filter(_has_valid_via, events), key=_child_events_comparison_key)
|
return sorted(filter(_has_valid_via, events), key=_child_events_comparison_key)
|
||||||
|
|
||||||
|
async def get_room_summary(
|
||||||
|
self,
|
||||||
|
requester: Optional[str],
|
||||||
|
room_id: str,
|
||||||
|
remote_room_hosts: Optional[List[str]] = None,
|
||||||
|
) -> JsonDict:
|
||||||
|
"""
|
||||||
|
Implementation of the room summary C-S API from MSC3266
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: user id of the user making this request, will be None
|
||||||
|
for unauthenticated requests
|
||||||
|
|
||||||
|
room_id: room id to summarise.
|
||||||
|
|
||||||
|
remote_room_hosts: a list of homeservers to try fetching data through
|
||||||
|
if we don't know it ourselves
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
summary dict to return
|
||||||
|
"""
|
||||||
|
is_in_room = await self._store.is_host_joined(room_id, self._server_name)
|
||||||
|
|
||||||
|
if is_in_room:
|
||||||
|
room_entry = await self._summarize_local_room(
|
||||||
|
requester,
|
||||||
|
None,
|
||||||
|
room_id,
|
||||||
|
# Suggested-only doesn't matter since no children are requested.
|
||||||
|
suggested_only=False,
|
||||||
|
max_children=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not room_entry:
|
||||||
|
raise NotFoundError("Room not found or is not accessible")
|
||||||
|
|
||||||
|
room_summary = room_entry.room
|
||||||
|
|
||||||
|
# If there was a requester, add their membership.
|
||||||
|
if requester:
|
||||||
|
(
|
||||||
|
membership,
|
||||||
|
_,
|
||||||
|
) = await self._store.get_local_current_membership_for_user_in_room(
|
||||||
|
requester, room_id
|
||||||
|
)
|
||||||
|
|
||||||
|
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.")
|
||||||
|
|
||||||
|
return room_summary
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, slots=True, auto_attribs=True)
|
@attr.s(frozen=True, slots=True, auto_attribs=True)
|
||||||
class _RoomQueueEntry:
|
class _RoomQueueEntry:
|
|
@ -14,16 +14,28 @@
|
||||||
|
|
||||||
""" This module contains base REST classes for constructing REST servlets. """
|
""" This module contains base REST classes for constructing REST servlets. """
|
||||||
import logging
|
import logging
|
||||||
from typing import Iterable, List, Mapping, Optional, Sequence, overload
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
|
|
||||||
from twisted.web.server import Request
|
from twisted.web.server import Request
|
||||||
|
|
||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, SynapseError
|
||||||
from synapse.types import JsonDict
|
from synapse.types import JsonDict, RoomAlias, RoomID
|
||||||
from synapse.util import json_decoder
|
from synapse.util import json_decoder
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -663,3 +675,45 @@ class RestServlet:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("RestServlet must register something.")
|
raise NotImplementedError("RestServlet must register something.")
|
||||||
|
|
||||||
|
|
||||||
|
class ResolveRoomIdMixin:
|
||||||
|
def __init__(self, hs: "HomeServer"):
|
||||||
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
|
|
||||||
|
async def resolve_room_id(
|
||||||
|
self, room_identifier: str, remote_room_hosts: Optional[List[str]] = None
|
||||||
|
) -> Tuple[str, Optional[List[str]]]:
|
||||||
|
"""
|
||||||
|
Resolve a room identifier to a room ID, if necessary.
|
||||||
|
|
||||||
|
This also performanes checks to ensure the room ID is of the proper form.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_identifier: The room ID or alias.
|
||||||
|
remote_room_hosts: The potential remote room hosts to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The resolved room ID.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
SynapseError if the room ID is of the wrong form.
|
||||||
|
"""
|
||||||
|
if RoomID.is_valid(room_identifier):
|
||||||
|
resolved_room_id = room_identifier
|
||||||
|
elif RoomAlias.is_valid(room_identifier):
|
||||||
|
room_alias = RoomAlias.from_string(room_identifier)
|
||||||
|
(
|
||||||
|
room_id,
|
||||||
|
remote_room_hosts,
|
||||||
|
) = await self.room_member_handler.lookup_room_alias(room_alias)
|
||||||
|
resolved_room_id = room_id.to_string()
|
||||||
|
else:
|
||||||
|
raise SynapseError(
|
||||||
|
400, "%s was not legal room ID or room alias" % (room_identifier,)
|
||||||
|
)
|
||||||
|
if not resolved_room_id:
|
||||||
|
raise SynapseError(
|
||||||
|
400, "Unknown room ID or room alias %s" % room_identifier
|
||||||
|
)
|
||||||
|
return resolved_room_id, remote_room_hosts
|
||||||
|
|
|
@ -20,6 +20,7 @@ from synapse.api.constants import EventTypes, JoinRules, Membership
|
||||||
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
|
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
|
||||||
from synapse.api.filtering import Filter
|
from synapse.api.filtering import Filter
|
||||||
from synapse.http.servlet import (
|
from synapse.http.servlet import (
|
||||||
|
ResolveRoomIdMixin,
|
||||||
RestServlet,
|
RestServlet,
|
||||||
assert_params_in_dict,
|
assert_params_in_dict,
|
||||||
parse_integer,
|
parse_integer,
|
||||||
|
@ -33,7 +34,7 @@ from synapse.rest.admin._base import (
|
||||||
assert_user_is_admin,
|
assert_user_is_admin,
|
||||||
)
|
)
|
||||||
from synapse.storage.databases.main.room import RoomSortOrder
|
from synapse.storage.databases.main.room import RoomSortOrder
|
||||||
from synapse.types import JsonDict, RoomAlias, RoomID, UserID, create_requester
|
from synapse.types import JsonDict, UserID, create_requester
|
||||||
from synapse.util import json_decoder
|
from synapse.util import json_decoder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -45,48 +46,6 @@ if TYPE_CHECKING:
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ResolveRoomIdMixin:
|
|
||||||
def __init__(self, hs: "HomeServer"):
|
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
|
||||||
|
|
||||||
async def resolve_room_id(
|
|
||||||
self, room_identifier: str, remote_room_hosts: Optional[List[str]] = None
|
|
||||||
) -> Tuple[str, Optional[List[str]]]:
|
|
||||||
"""
|
|
||||||
Resolve a room identifier to a room ID, if necessary.
|
|
||||||
|
|
||||||
This also performanes checks to ensure the room ID is of the proper form.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room_identifier: The room ID or alias.
|
|
||||||
remote_room_hosts: The potential remote room hosts to use.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The resolved room ID.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
SynapseError if the room ID is of the wrong form.
|
|
||||||
"""
|
|
||||||
if RoomID.is_valid(room_identifier):
|
|
||||||
resolved_room_id = room_identifier
|
|
||||||
elif RoomAlias.is_valid(room_identifier):
|
|
||||||
room_alias = RoomAlias.from_string(room_identifier)
|
|
||||||
(
|
|
||||||
room_id,
|
|
||||||
remote_room_hosts,
|
|
||||||
) = await self.room_member_handler.lookup_room_alias(room_alias)
|
|
||||||
resolved_room_id = room_id.to_string()
|
|
||||||
else:
|
|
||||||
raise SynapseError(
|
|
||||||
400, "%s was not legal room ID or room alias" % (room_identifier,)
|
|
||||||
)
|
|
||||||
if not resolved_room_id:
|
|
||||||
raise SynapseError(
|
|
||||||
400, "Unknown room ID or room alias %s" % room_identifier
|
|
||||||
)
|
|
||||||
return resolved_room_id, remote_room_hosts
|
|
||||||
|
|
||||||
|
|
||||||
class ShutdownRoomRestServlet(RestServlet):
|
class ShutdownRoomRestServlet(RestServlet):
|
||||||
"""Shuts down a room by removing all local users from the room and blocking
|
"""Shuts down a room by removing all local users from the room and blocking
|
||||||
all future invites and joins to the room. Any local aliases will be repointed
|
all future invites and joins to the room. Any local aliases will be repointed
|
||||||
|
|
|
@ -24,12 +24,14 @@ from synapse.api.errors import (
|
||||||
AuthError,
|
AuthError,
|
||||||
Codes,
|
Codes,
|
||||||
InvalidClientCredentialsError,
|
InvalidClientCredentialsError,
|
||||||
|
MissingClientTokenError,
|
||||||
ShadowBanError,
|
ShadowBanError,
|
||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
from synapse.api.filtering import Filter
|
from synapse.api.filtering import Filter
|
||||||
from synapse.events.utils import format_event_for_client_v2
|
from synapse.events.utils import format_event_for_client_v2
|
||||||
from synapse.http.servlet import (
|
from synapse.http.servlet import (
|
||||||
|
ResolveRoomIdMixin,
|
||||||
RestServlet,
|
RestServlet,
|
||||||
assert_params_in_dict,
|
assert_params_in_dict,
|
||||||
parse_boolean,
|
parse_boolean,
|
||||||
|
@ -44,14 +46,7 @@ from synapse.rest.client.transactions import HttpTransactionCache
|
||||||
from synapse.rest.client.v2_alpha._base import client_patterns
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.storage.state import StateFilter
|
from synapse.storage.state import StateFilter
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
from synapse.types import (
|
from synapse.types import JsonDict, StreamToken, ThirdPartyInstanceID, UserID
|
||||||
JsonDict,
|
|
||||||
RoomAlias,
|
|
||||||
RoomID,
|
|
||||||
StreamToken,
|
|
||||||
ThirdPartyInstanceID,
|
|
||||||
UserID,
|
|
||||||
)
|
|
||||||
from synapse.util import json_decoder
|
from synapse.util import json_decoder
|
||||||
from synapse.util.stringutils import parse_and_validate_server_name, random_string
|
from synapse.util.stringutils import parse_and_validate_server_name, random_string
|
||||||
|
|
||||||
|
@ -266,10 +261,10 @@ class RoomSendEventRestServlet(TransactionRestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for room ID + alias joins
|
# TODO: Needs unit testing for room ID + alias joins
|
||||||
class JoinRoomAliasServlet(TransactionRestServlet):
|
class JoinRoomAliasServlet(ResolveRoomIdMixin, TransactionRestServlet):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super().__init__(hs)
|
super().__init__(hs)
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
super(ResolveRoomIdMixin, self).__init__(hs) # ensure the Mixin is set up
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
|
@ -292,24 +287,13 @@ class JoinRoomAliasServlet(TransactionRestServlet):
|
||||||
# cheekily send invalid bodies.
|
# cheekily send invalid bodies.
|
||||||
content = {}
|
content = {}
|
||||||
|
|
||||||
if RoomID.is_valid(room_identifier):
|
# twisted.web.server.Request.args is incorrectly defined as Optional[Any]
|
||||||
room_id = room_identifier
|
args: Dict[bytes, List[bytes]] = request.args # type: ignore
|
||||||
|
remote_room_hosts = parse_strings_from_args(args, "server_name", required=False)
|
||||||
# twisted.web.server.Request.args is incorrectly defined as Optional[Any]
|
room_id, remote_room_hosts = await self.resolve_room_id(
|
||||||
args: Dict[bytes, List[bytes]] = request.args # type: ignore
|
room_identifier,
|
||||||
|
remote_room_hosts,
|
||||||
remote_room_hosts = parse_strings_from_args(
|
)
|
||||||
args, "server_name", required=False
|
|
||||||
)
|
|
||||||
elif RoomAlias.is_valid(room_identifier):
|
|
||||||
handler = self.room_member_handler
|
|
||||||
room_alias = RoomAlias.from_string(room_identifier)
|
|
||||||
room_id_obj, remote_room_hosts = await handler.lookup_room_alias(room_alias)
|
|
||||||
room_id = room_id_obj.to_string()
|
|
||||||
else:
|
|
||||||
raise SynapseError(
|
|
||||||
400, "%s was not legal room ID or room alias" % (room_identifier,)
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.room_member_handler.update_membership(
|
await self.room_member_handler.update_membership(
|
||||||
requester=requester,
|
requester=requester,
|
||||||
|
@ -1002,14 +986,14 @@ class RoomSpaceSummaryRestServlet(RestServlet):
|
||||||
def __init__(self, hs: "HomeServer"):
|
def __init__(self, hs: "HomeServer"):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._auth = hs.get_auth()
|
self._auth = hs.get_auth()
|
||||||
self._space_summary_handler = hs.get_space_summary_handler()
|
self._room_summary_handler = hs.get_room_summary_handler()
|
||||||
|
|
||||||
async def on_GET(
|
async def on_GET(
|
||||||
self, request: SynapseRequest, room_id: str
|
self, request: SynapseRequest, room_id: str
|
||||||
) -> Tuple[int, JsonDict]:
|
) -> Tuple[int, JsonDict]:
|
||||||
requester = await self._auth.get_user_by_req(request, allow_guest=True)
|
requester = await self._auth.get_user_by_req(request, allow_guest=True)
|
||||||
|
|
||||||
return 200, await self._space_summary_handler.get_space_summary(
|
return 200, await self._room_summary_handler.get_space_summary(
|
||||||
requester.user.to_string(),
|
requester.user.to_string(),
|
||||||
room_id,
|
room_id,
|
||||||
suggested_only=parse_boolean(request, "suggested_only", default=False),
|
suggested_only=parse_boolean(request, "suggested_only", default=False),
|
||||||
|
@ -1035,7 +1019,7 @@ class RoomSpaceSummaryRestServlet(RestServlet):
|
||||||
400, "'max_rooms_per_space' must be an integer", Codes.BAD_JSON
|
400, "'max_rooms_per_space' must be an integer", Codes.BAD_JSON
|
||||||
)
|
)
|
||||||
|
|
||||||
return 200, await self._space_summary_handler.get_space_summary(
|
return 200, await self._room_summary_handler.get_space_summary(
|
||||||
requester.user.to_string(),
|
requester.user.to_string(),
|
||||||
room_id,
|
room_id,
|
||||||
suggested_only=suggested_only,
|
suggested_only=suggested_only,
|
||||||
|
@ -1054,7 +1038,7 @@ class RoomHierarchyRestServlet(RestServlet):
|
||||||
def __init__(self, hs: "HomeServer"):
|
def __init__(self, hs: "HomeServer"):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._auth = hs.get_auth()
|
self._auth = hs.get_auth()
|
||||||
self._space_summary_handler = hs.get_space_summary_handler()
|
self._room_summary_handler = hs.get_room_summary_handler()
|
||||||
|
|
||||||
async def on_GET(
|
async def on_GET(
|
||||||
self, request: SynapseRequest, room_id: str
|
self, request: SynapseRequest, room_id: str
|
||||||
|
@ -1073,7 +1057,7 @@ class RoomHierarchyRestServlet(RestServlet):
|
||||||
400, "'limit' must be a positive integer", Codes.BAD_JSON
|
400, "'limit' must be a positive integer", Codes.BAD_JSON
|
||||||
)
|
)
|
||||||
|
|
||||||
return 200, await self._space_summary_handler.get_room_hierarchy(
|
return 200, await self._room_summary_handler.get_room_hierarchy(
|
||||||
requester.user.to_string(),
|
requester.user.to_string(),
|
||||||
room_id,
|
room_id,
|
||||||
suggested_only=parse_boolean(request, "suggested_only", default=False),
|
suggested_only=parse_boolean(request, "suggested_only", default=False),
|
||||||
|
@ -1083,6 +1067,44 @@ class RoomHierarchyRestServlet(RestServlet):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomSummaryRestServlet(ResolveRoomIdMixin, RestServlet):
|
||||||
|
PATTERNS = (
|
||||||
|
re.compile(
|
||||||
|
"^/_matrix/client/unstable/im.nheko.summary"
|
||||||
|
"/rooms/(?P<room_identifier>[^/]*)/summary$"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, hs: "HomeServer"):
|
||||||
|
super().__init__(hs)
|
||||||
|
self._auth = hs.get_auth()
|
||||||
|
self._room_summary_handler = hs.get_room_summary_handler()
|
||||||
|
|
||||||
|
async def on_GET(
|
||||||
|
self, request: SynapseRequest, room_identifier: str
|
||||||
|
) -> Tuple[int, JsonDict]:
|
||||||
|
try:
|
||||||
|
requester = await self._auth.get_user_by_req(request, allow_guest=True)
|
||||||
|
requester_user_id: Optional[str] = requester.user.to_string()
|
||||||
|
except MissingClientTokenError:
|
||||||
|
# auth is optional
|
||||||
|
requester_user_id = None
|
||||||
|
|
||||||
|
# twisted.web.server.Request.args is incorrectly defined as Optional[Any]
|
||||||
|
args: Dict[bytes, List[bytes]] = request.args # type: ignore
|
||||||
|
remote_room_hosts = parse_strings_from_args(args, "via", required=False)
|
||||||
|
room_id, remote_room_hosts = await self.resolve_room_id(
|
||||||
|
room_identifier,
|
||||||
|
remote_room_hosts,
|
||||||
|
)
|
||||||
|
|
||||||
|
return 200, await self._room_summary_handler.get_room_summary(
|
||||||
|
requester_user_id,
|
||||||
|
room_id,
|
||||||
|
remote_room_hosts,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_servlets(hs: "HomeServer", http_server, is_worker=False):
|
def register_servlets(hs: "HomeServer", http_server, is_worker=False):
|
||||||
RoomStateEventRestServlet(hs).register(http_server)
|
RoomStateEventRestServlet(hs).register(http_server)
|
||||||
RoomMemberListRestServlet(hs).register(http_server)
|
RoomMemberListRestServlet(hs).register(http_server)
|
||||||
|
@ -1098,6 +1120,8 @@ def register_servlets(hs: "HomeServer", http_server, is_worker=False):
|
||||||
RoomEventContextServlet(hs).register(http_server)
|
RoomEventContextServlet(hs).register(http_server)
|
||||||
RoomSpaceSummaryRestServlet(hs).register(http_server)
|
RoomSpaceSummaryRestServlet(hs).register(http_server)
|
||||||
RoomHierarchyRestServlet(hs).register(http_server)
|
RoomHierarchyRestServlet(hs).register(http_server)
|
||||||
|
if hs.config.experimental.msc3266_enabled:
|
||||||
|
RoomSummaryRestServlet(hs).register(http_server)
|
||||||
RoomEventServlet(hs).register(http_server)
|
RoomEventServlet(hs).register(http_server)
|
||||||
JoinedRoomsRestServlet(hs).register(http_server)
|
JoinedRoomsRestServlet(hs).register(http_server)
|
||||||
RoomAliasListServlet(hs).register(http_server)
|
RoomAliasListServlet(hs).register(http_server)
|
||||||
|
|
|
@ -99,10 +99,10 @@ from synapse.handlers.room import (
|
||||||
from synapse.handlers.room_list import RoomListHandler
|
from synapse.handlers.room_list import RoomListHandler
|
||||||
from synapse.handlers.room_member import RoomMemberHandler, RoomMemberMasterHandler
|
from synapse.handlers.room_member import RoomMemberHandler, RoomMemberMasterHandler
|
||||||
from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
|
from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
|
||||||
|
from synapse.handlers.room_summary import RoomSummaryHandler
|
||||||
from synapse.handlers.search import SearchHandler
|
from synapse.handlers.search import SearchHandler
|
||||||
from synapse.handlers.send_email import SendEmailHandler
|
from synapse.handlers.send_email import SendEmailHandler
|
||||||
from synapse.handlers.set_password import SetPasswordHandler
|
from synapse.handlers.set_password import SetPasswordHandler
|
||||||
from synapse.handlers.space_summary import SpaceSummaryHandler
|
|
||||||
from synapse.handlers.sso import SsoHandler
|
from synapse.handlers.sso import SsoHandler
|
||||||
from synapse.handlers.stats import StatsHandler
|
from synapse.handlers.stats import StatsHandler
|
||||||
from synapse.handlers.sync import SyncHandler
|
from synapse.handlers.sync import SyncHandler
|
||||||
|
@ -772,8 +772,8 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||||
return AccountDataHandler(self)
|
return AccountDataHandler(self)
|
||||||
|
|
||||||
@cache_in_self
|
@cache_in_self
|
||||||
def get_space_summary_handler(self) -> SpaceSummaryHandler:
|
def get_room_summary_handler(self) -> RoomSummaryHandler:
|
||||||
return SpaceSummaryHandler(self)
|
return RoomSummaryHandler(self)
|
||||||
|
|
||||||
@cache_in_self
|
@cache_in_self
|
||||||
def get_event_auth_handler(self) -> EventAuthHandler:
|
def get_event_auth_handler(self) -> EventAuthHandler:
|
||||||
|
|
|
@ -23,10 +23,10 @@ from synapse.api.constants import (
|
||||||
RestrictedJoinRuleTypes,
|
RestrictedJoinRuleTypes,
|
||||||
RoomTypes,
|
RoomTypes,
|
||||||
)
|
)
|
||||||
from synapse.api.errors import AuthError, SynapseError
|
from synapse.api.errors import AuthError, NotFoundError, SynapseError
|
||||||
from synapse.api.room_versions import RoomVersions
|
from synapse.api.room_versions import RoomVersions
|
||||||
from synapse.events import make_event_from_dict
|
from synapse.events import make_event_from_dict
|
||||||
from synapse.handlers.space_summary import _child_events_comparison_key, _RoomEntry
|
from synapse.handlers.room_summary import _child_events_comparison_key, _RoomEntry
|
||||||
from synapse.rest import admin
|
from synapse.rest import admin
|
||||||
from synapse.rest.client.v1 import login, room
|
from synapse.rest.client.v1 import login, room
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
@ -106,7 +106,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
def prepare(self, reactor, clock, hs: HomeServer):
|
def prepare(self, reactor, clock, hs: HomeServer):
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.handler = self.hs.get_space_summary_handler()
|
self.handler = self.hs.get_room_summary_handler()
|
||||||
|
|
||||||
# Create a user.
|
# Create a user.
|
||||||
self.user = self.register_user("user", "pass")
|
self.user = self.register_user("user", "pass")
|
||||||
|
@ -624,14 +624,14 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
async def summarize_remote_room_hiearchy(_self, room, suggested_only):
|
async def summarize_remote_room_hierarchy(_self, room, suggested_only):
|
||||||
return requested_room_entry, {subroom: child_room}, set()
|
return requested_room_entry, {subroom: child_room}, set()
|
||||||
|
|
||||||
# Add a room to the space which is on another server.
|
# Add a room to the space which is on another server.
|
||||||
self._add_child(self.space, subspace, self.token)
|
self._add_child(self.space, subspace, self.token)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
|
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room",
|
||||||
new=summarize_remote_room,
|
new=summarize_remote_room,
|
||||||
):
|
):
|
||||||
result = self.get_success(
|
result = self.get_success(
|
||||||
|
@ -647,8 +647,8 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
self._assert_rooms(result, expected)
|
self._assert_rooms(result, expected)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room_hiearchy",
|
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
|
||||||
new=summarize_remote_room_hiearchy,
|
new=summarize_remote_room_hierarchy,
|
||||||
):
|
):
|
||||||
result = self.get_success(
|
result = self.get_success(
|
||||||
self.handler.get_room_hierarchy(self.user, self.space)
|
self.handler.get_room_hierarchy(self.user, self.space)
|
||||||
|
@ -774,14 +774,14 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
for child_room in children_rooms
|
for child_room in children_rooms
|
||||||
]
|
]
|
||||||
|
|
||||||
async def summarize_remote_room_hiearchy(_self, room, suggested_only):
|
async def summarize_remote_room_hierarchy(_self, room, suggested_only):
|
||||||
return subspace_room_entry, dict(children_rooms), set()
|
return subspace_room_entry, dict(children_rooms), set()
|
||||||
|
|
||||||
# Add a room to the space which is on another server.
|
# Add a room to the space which is on another server.
|
||||||
self._add_child(self.space, subspace, self.token)
|
self._add_child(self.space, subspace, self.token)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
|
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room",
|
||||||
new=summarize_remote_room,
|
new=summarize_remote_room,
|
||||||
):
|
):
|
||||||
result = self.get_success(
|
result = self.get_success(
|
||||||
|
@ -814,8 +814,8 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
self._assert_rooms(result, expected)
|
self._assert_rooms(result, expected)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room_hiearchy",
|
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
|
||||||
new=summarize_remote_room_hiearchy,
|
new=summarize_remote_room_hierarchy,
|
||||||
):
|
):
|
||||||
result = self.get_success(
|
result = self.get_success(
|
||||||
self.handler.get_room_hierarchy(self.user, self.space)
|
self.handler.get_room_hierarchy(self.user, self.space)
|
||||||
|
@ -850,14 +850,14 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
):
|
):
|
||||||
return [fed_room_entry]
|
return [fed_room_entry]
|
||||||
|
|
||||||
async def summarize_remote_room_hiearchy(_self, room, suggested_only):
|
async def summarize_remote_room_hierarchy(_self, room, suggested_only):
|
||||||
return fed_room_entry, {}, set()
|
return fed_room_entry, {}, set()
|
||||||
|
|
||||||
# Add a room to the space which is on another server.
|
# Add a room to the space which is on another server.
|
||||||
self._add_child(self.space, fed_room, self.token)
|
self._add_child(self.space, fed_room, self.token)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
|
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room",
|
||||||
new=summarize_remote_room,
|
new=summarize_remote_room,
|
||||||
):
|
):
|
||||||
result = self.get_success(
|
result = self.get_success(
|
||||||
|
@ -872,10 +872,88 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
self._assert_rooms(result, expected)
|
self._assert_rooms(result, expected)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room_hiearchy",
|
"synapse.handlers.room_summary.RoomSummaryHandler._summarize_remote_room_hierarchy",
|
||||||
new=summarize_remote_room_hiearchy,
|
new=summarize_remote_room_hierarchy,
|
||||||
):
|
):
|
||||||
result = self.get_success(
|
result = self.get_success(
|
||||||
self.handler.get_room_hierarchy(self.user, self.space)
|
self.handler.get_room_hierarchy(self.user, self.space)
|
||||||
)
|
)
|
||||||
self._assert_hierarchy(result, expected)
|
self._assert_hierarchy(result, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomSummaryTestCase(unittest.HomeserverTestCase):
|
||||||
|
servlets = [
|
||||||
|
admin.register_servlets_for_client_rest_resource,
|
||||||
|
room.register_servlets,
|
||||||
|
login.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def prepare(self, reactor, clock, hs: HomeServer):
|
||||||
|
self.hs = hs
|
||||||
|
self.handler = self.hs.get_room_summary_handler()
|
||||||
|
|
||||||
|
# Create a user.
|
||||||
|
self.user = self.register_user("user", "pass")
|
||||||
|
self.token = self.login("user", "pass")
|
||||||
|
|
||||||
|
# Create a simple room.
|
||||||
|
self.room = self.helper.create_room_as(self.user, tok=self.token)
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room,
|
||||||
|
event_type=EventTypes.JoinRules,
|
||||||
|
body={"join_rule": JoinRules.INVITE},
|
||||||
|
tok=self.token,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_own_room(self):
|
||||||
|
"""Test a simple room created by the requester."""
|
||||||
|
result = self.get_success(self.handler.get_room_summary(self.user, self.room))
|
||||||
|
self.assertEqual(result.get("room_id"), self.room)
|
||||||
|
|
||||||
|
def test_visibility(self):
|
||||||
|
"""A user not in a private room cannot get its summary."""
|
||||||
|
user2 = self.register_user("user2", "pass")
|
||||||
|
token2 = self.login("user2", "pass")
|
||||||
|
|
||||||
|
# The user cannot see the room.
|
||||||
|
self.get_failure(self.handler.get_room_summary(user2, self.room), NotFoundError)
|
||||||
|
|
||||||
|
# If the room is made world-readable it should return a result.
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room,
|
||||||
|
event_type=EventTypes.RoomHistoryVisibility,
|
||||||
|
body={"history_visibility": HistoryVisibility.WORLD_READABLE},
|
||||||
|
tok=self.token,
|
||||||
|
)
|
||||||
|
result = self.get_success(self.handler.get_room_summary(user2, self.room))
|
||||||
|
self.assertEqual(result.get("room_id"), self.room)
|
||||||
|
|
||||||
|
# Make it not world-readable again and confirm it results in an error.
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room,
|
||||||
|
event_type=EventTypes.RoomHistoryVisibility,
|
||||||
|
body={"history_visibility": HistoryVisibility.JOINED},
|
||||||
|
tok=self.token,
|
||||||
|
)
|
||||||
|
self.get_failure(self.handler.get_room_summary(user2, self.room), NotFoundError)
|
||||||
|
|
||||||
|
# If the room is made public it should return a result.
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room,
|
||||||
|
event_type=EventTypes.JoinRules,
|
||||||
|
body={"join_rule": JoinRules.PUBLIC},
|
||||||
|
tok=self.token,
|
||||||
|
)
|
||||||
|
result = self.get_success(self.handler.get_room_summary(user2, self.room))
|
||||||
|
self.assertEqual(result.get("room_id"), self.room)
|
||||||
|
|
||||||
|
# Join the space, make it invite-only again and results should be returned.
|
||||||
|
self.helper.join(self.room, user2, tok=token2)
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room,
|
||||||
|
event_type=EventTypes.JoinRules,
|
||||||
|
body={"join_rule": JoinRules.INVITE},
|
||||||
|
tok=self.token,
|
||||||
|
)
|
||||||
|
result = self.get_success(self.handler.get_room_summary(user2, self.room))
|
||||||
|
self.assertEqual(result.get("room_id"), self.room)
|
Loading…
Reference in New Issue