Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Show joinable rooms in the spaces summary. #10298

Merged
merged 9 commits into from
Jul 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10298.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The spaces summary API now returns any joinable rooms, not only rooms which are world-readable.
1 change: 1 addition & 0 deletions changelog.d/10305.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The spaces summary API now returns any joinable rooms, not only rooms which are world-readable.
1 change: 0 additions & 1 deletion changelog.d/10305.misc

This file was deleted.

68 changes: 48 additions & 20 deletions synapse/handlers/space_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
EventContentFields,
EventTypes,
HistoryVisibility,
JoinRules,
Membership,
RoomTypes,
)
Expand Down Expand Up @@ -150,14 +151,21 @@ async def get_space_summary(
# 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 is in a space that has been granted access to
# the room.
# 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.
include_room = room.get("world_readable") is True

# 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.
Expand Down Expand Up @@ -420,9 +428,8 @@ async def _is_room_accessible(

It should be included if:

* The requester is joined or invited to the room.
* The requester can join without an invite (per MSC3083).
* The origin server has any user that is joined or invited to the room.
* 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 history visibility is set to world readable.
Comment on lines 429 to 433
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I debated making all of this a method on Auth: can_user_view_stripped_state or something, but seemed a bit silly until we want to use it in another place.


Args:
Expand All @@ -441,13 +448,39 @@ async def _is_room_accessible(

# If there's no state for the room, it isn't known.
if not state_ids:
# The user might have a pending invite for the room.
if requester and await self._store.get_invite_for_local_user_in_room(
requester, room_id
):
return True

logger.info("room %s is unknown, omitting from summary", room_id)
return False

room_version = await self._store.get_room_version(room_id)

# if we have an authenticated requesting user, first check if they are able to view
# stripped state in the room.
# Include the room if it has join rules of public or knock.
join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""))
if join_rules_event_id:
join_rules_event = await self._store.get_event(join_rules_event_id)
join_rule = join_rules_event.content.get("join_rule")
if join_rule == JoinRules.PUBLIC or (
room_version.msc2403_knocking and join_rule == JoinRules.KNOCK
):
return True

# Include the room if it is peekable.
hist_vis_event_id = state_ids.get((EventTypes.RoomHistoryVisibility, ""))
if hist_vis_event_id:
hist_vis_ev = await self._store.get_event(hist_vis_event_id)
hist_vis = hist_vis_ev.content.get("history_visibility")
if hist_vis == HistoryVisibility.WORLD_READABLE:
return True

# Otherwise we need to check information specific to the user or server.

# If we have an authenticated requesting user, check if they are a member
# of the room (or can join the room).
if requester:
member_event_id = state_ids.get((EventTypes.Member, requester), None)

Expand All @@ -470,9 +503,11 @@ async def _is_room_accessible(
return True

# If this is a request over federation, check if the host is in the room or
# is in one of the spaces specified via the join rules.
# has a user who could join the room.
elif origin:
if await self._event_auth_handler.check_host_in_room(room_id, origin):
if await self._event_auth_handler.check_host_in_room(
room_id, origin
) or await self._store.is_host_invited(room_id, origin):
return True

# Alternately, if the host has a user in any of the spaces specified
Expand All @@ -490,18 +525,10 @@ async def _is_room_accessible(
):
return True

# otherwise, check if the room is peekable
hist_vis_event_id = state_ids.get((EventTypes.RoomHistoryVisibility, ""), None)
if hist_vis_event_id:
hist_vis_ev = await self._store.get_event(hist_vis_event_id)
hist_vis = hist_vis_ev.content.get("history_visibility")
if hist_vis == HistoryVisibility.WORLD_READABLE:
return True

logger.info(
"room %s is unpeekable and user %s is not a member / not allowed to join, omitting from summary",
"room %s is unpeekable and requester %s is not a member / not allowed to join, omitting from summary",
room_id,
requester,
requester or origin,
)
return False

Expand Down Expand Up @@ -535,6 +562,7 @@ async def _build_room_entry(self, room_id: str) -> JsonDict:
"canonical_alias": stats["canonical_alias"],
"num_joined_members": stats["joined_members"],
"avatar_url": stats["avatar"],
"join_rules": stats["join_rules"],
"world_readable": (
stats["history_visibility"] == HistoryVisibility.WORLD_READABLE
),
Expand Down
13 changes: 11 additions & 2 deletions synapse/storage/databases/main/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,13 +703,22 @@ async def _get_joined_profiles_from_event_ids(self, event_ids: Iterable[str]):

@cached(max_entries=10000)
async def is_host_joined(self, room_id: str, host: str) -> bool:
return await self._check_host_room_membership(room_id, host, Membership.JOIN)

@cached(max_entries=10000)
async def is_host_invited(self, room_id: str, host: str) -> bool:
return await self._check_host_room_membership(room_id, host, Membership.INVITE)

async def _check_host_room_membership(
self, room_id: str, host: str, membership: str
) -> bool:
if "%" in host or "_" in host:
raise Exception("Invalid host name")

sql = """
SELECT state_key FROM current_state_events AS c
INNER JOIN room_memberships AS m USING (event_id)
WHERE m.membership = 'join'
WHERE m.membership = ?
AND type = 'm.room.member'
AND c.room_id = ?
AND state_key LIKE ?
Expand All @@ -722,7 +731,7 @@ async def is_host_joined(self, room_id: str, host: str) -> bool:
like_clause = "%:" + host

rows = await self.db_pool.execute(
"is_host_joined", None, sql, room_id, like_clause
"is_host_joined", None, sql, membership, room_id, like_clause
)

if not rows:
Expand Down
Loading