Fix an edge-case with invited rooms over federation in the spaces summary. (#10560)
If a room which the requesting user was invited to was queried over federation it will now properly appear in the spaces summary (instead of being stripped out by the requesting server).
This commit is contained in:
parent
52bfa2d59a
commit
691593bf71
|
@ -0,0 +1 @@
|
|||
Add pagination to the spaces summary based on updates to [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946).
|
|
@ -158,48 +158,10 @@ class SpaceSummaryHandler:
|
|||
room = room_entry.room
|
||||
fed_room_id = room_entry.room_id
|
||||
|
||||
# The room should only be included in the summary if:
|
||||
# a. the user is in the room;
|
||||
# b. the room is world readable; or
|
||||
# c. the user could join the room, e.g. the join rules
|
||||
# are set to public or the user is in a space that
|
||||
# has been granted access to the room.
|
||||
#
|
||||
# Note that we know the user is not in the root room (which is
|
||||
# why the remote call was made in the first place), but the user
|
||||
# could be in one of the children rooms and we just didn't know
|
||||
# about the link.
|
||||
|
||||
# The API doesn't return the room version so assume that a
|
||||
# join rule of knock is valid.
|
||||
include_room = (
|
||||
room.get("join_rules") in (JoinRules.PUBLIC, JoinRules.KNOCK)
|
||||
or room.get("world_readable") is 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"
|
||||
)
|
||||
if (
|
||||
not include_room
|
||||
and allowed_rooms
|
||||
and isinstance(allowed_rooms, list)
|
||||
):
|
||||
include_room = await self._event_auth_handler.is_user_in_rooms(
|
||||
allowed_rooms, requester
|
||||
)
|
||||
|
||||
# Finally, if this isn't the requested room, check ourselves
|
||||
# if we can access the room.
|
||||
if not include_room and fed_room_id != queue_entry.room_id:
|
||||
include_room = await self._is_room_accessible(
|
||||
fed_room_id, requester, None
|
||||
)
|
||||
|
||||
# The user can see the room, include it!
|
||||
if include_room:
|
||||
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)
|
||||
|
@ -336,7 +298,7 @@ class SpaceSummaryHandler:
|
|||
Returns:
|
||||
A room entry if the room should be returned. None, otherwise.
|
||||
"""
|
||||
if not await self._is_room_accessible(room_id, requester, origin):
|
||||
if not await self._is_local_room_accessible(room_id, requester, origin):
|
||||
return None
|
||||
|
||||
room_entry = await self._build_room_entry(room_id, for_federation=bool(origin))
|
||||
|
@ -438,7 +400,7 @@ class SpaceSummaryHandler:
|
|||
|
||||
return results
|
||||
|
||||
async def _is_room_accessible(
|
||||
async def _is_local_room_accessible(
|
||||
self, room_id: str, requester: Optional[str], origin: Optional[str]
|
||||
) -> bool:
|
||||
"""
|
||||
|
@ -550,6 +512,51 @@ class SpaceSummaryHandler:
|
|||
)
|
||||
return False
|
||||
|
||||
async def _is_remote_room_accessible(
|
||||
self, requester: str, room_id: str, room: JsonDict
|
||||
) -> bool:
|
||||
"""
|
||||
Calculate whether the room received over federation should be shown in the spaces summary.
|
||||
|
||||
It should be included if:
|
||||
|
||||
* The requester is joined or can join the room (per MSC3173).
|
||||
* The history visibility is set to world readable.
|
||||
|
||||
Note that the local server is not in the requested room (which is why the
|
||||
remote call was made in the first place), but the user could have access
|
||||
due to an invite, etc.
|
||||
|
||||
Args:
|
||||
requester: The user requesting the summary.
|
||||
room_id: The room ID returned over federation.
|
||||
room: The summary of the child room returned over federation.
|
||||
|
||||
Returns:
|
||||
True if the room should be included in the spaces summary.
|
||||
"""
|
||||
# The API doesn't return the room version so assume that a
|
||||
# join rule of knock is valid.
|
||||
if (
|
||||
room.get("join_rules") in (JoinRules.PUBLIC, JoinRules.KNOCK)
|
||||
or room.get("world_readable") is True
|
||||
):
|
||||
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")
|
||||
if allowed_rooms and isinstance(allowed_rooms, list):
|
||||
if await self._event_auth_handler.is_user_in_rooms(
|
||||
allowed_rooms, requester
|
||||
):
|
||||
return True
|
||||
|
||||
# Finally, check locally if we can access the room. The user might
|
||||
# already be in the room (if it was a child room), or there might be a
|
||||
# pending invite, etc.
|
||||
return await self._is_local_room_accessible(room_id, requester, None)
|
||||
|
||||
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.
|
||||
|
|
|
@ -30,7 +30,7 @@ from synapse.handlers.space_summary import _child_events_comparison_key, _RoomEn
|
|||
from synapse.rest import admin
|
||||
from synapse.rest.client.v1 import login, room
|
||||
from synapse.server import HomeServer
|
||||
from synapse.types import JsonDict
|
||||
from synapse.types import JsonDict, UserID
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
@ -149,6 +149,36 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
|||
events,
|
||||
)
|
||||
|
||||
def _poke_fed_invite(self, room_id: str, from_user: str) -> None:
|
||||
"""
|
||||
Creates a invite (as if received over federation) for the room from the
|
||||
given hostname.
|
||||
|
||||
Args:
|
||||
room_id: The room ID to issue an invite for.
|
||||
fed_hostname: The user to invite from.
|
||||
"""
|
||||
# Poke an invite over federation into the database.
|
||||
fed_handler = self.hs.get_federation_handler()
|
||||
fed_hostname = UserID.from_string(from_user).domain
|
||||
event = make_event_from_dict(
|
||||
{
|
||||
"room_id": room_id,
|
||||
"event_id": "!abcd:" + fed_hostname,
|
||||
"type": EventTypes.Member,
|
||||
"sender": from_user,
|
||||
"state_key": self.user,
|
||||
"content": {"membership": Membership.INVITE},
|
||||
"prev_events": [],
|
||||
"auth_events": [],
|
||||
"depth": 1,
|
||||
"origin_server_ts": 1234,
|
||||
}
|
||||
)
|
||||
self.get_success(
|
||||
fed_handler.on_invite_request(fed_hostname, event, RoomVersions.V6)
|
||||
)
|
||||
|
||||
def test_simple_space(self):
|
||||
"""Test a simple space with a single room."""
|
||||
result = self.get_success(self.handler.get_space_summary(self.user, self.space))
|
||||
|
@ -416,24 +446,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
|||
joined_room = self.helper.create_room_as(self.user, tok=self.token)
|
||||
|
||||
# Poke an invite over federation into the database.
|
||||
fed_handler = self.hs.get_federation_handler()
|
||||
event = make_event_from_dict(
|
||||
{
|
||||
"room_id": invited_room,
|
||||
"event_id": "!abcd:" + fed_hostname,
|
||||
"type": EventTypes.Member,
|
||||
"sender": "@remote:" + fed_hostname,
|
||||
"state_key": self.user,
|
||||
"content": {"membership": Membership.INVITE},
|
||||
"prev_events": [],
|
||||
"auth_events": [],
|
||||
"depth": 1,
|
||||
"origin_server_ts": 1234,
|
||||
}
|
||||
)
|
||||
self.get_success(
|
||||
fed_handler.on_invite_request(fed_hostname, event, RoomVersions.V6)
|
||||
)
|
||||
self._poke_fed_invite(invited_room, "@remote:" + fed_hostname)
|
||||
|
||||
async def summarize_remote_room(
|
||||
_self, room, suggested_only, max_children, exclude_rooms
|
||||
|
@ -570,3 +583,58 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
|||
(subspace, joined_room),
|
||||
],
|
||||
)
|
||||
|
||||
def test_fed_invited(self):
|
||||
"""
|
||||
A room which the user was invited to should be included in the response.
|
||||
|
||||
This differs from test_fed_filtering in that the room itself is being
|
||||
queried over federation, instead of it being included as a sub-room of
|
||||
a space in the response.
|
||||
"""
|
||||
fed_hostname = self.hs.hostname + "2"
|
||||
fed_room = "#subroom:" + fed_hostname
|
||||
|
||||
# Poke an invite over federation into the database.
|
||||
self._poke_fed_invite(fed_room, "@remote:" + fed_hostname)
|
||||
|
||||
async def summarize_remote_room(
|
||||
_self, room, suggested_only, max_children, exclude_rooms
|
||||
):
|
||||
return [
|
||||
_RoomEntry(
|
||||
fed_room,
|
||||
{
|
||||
"room_id": fed_room,
|
||||
"world_readable": False,
|
||||
"join_rules": JoinRules.INVITE,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
# Add a room to the space which is on another server.
|
||||
self._add_child(self.space, fed_room, self.token)
|
||||
|
||||
with mock.patch(
|
||||
"synapse.handlers.space_summary.SpaceSummaryHandler._summarize_remote_room",
|
||||
new=summarize_remote_room,
|
||||
):
|
||||
result = self.get_success(
|
||||
self.handler.get_space_summary(self.user, self.space)
|
||||
)
|
||||
|
||||
self._assert_rooms(
|
||||
result,
|
||||
[
|
||||
self.space,
|
||||
self.room,
|
||||
fed_room,
|
||||
],
|
||||
)
|
||||
self._assert_events(
|
||||
result,
|
||||
[
|
||||
(self.space, self.room),
|
||||
(self.space, fed_room),
|
||||
],
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue