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

Commit

Permalink
Show all joinable rooms in the spaces summary. (#10298)
Browse files Browse the repository at this point in the history
Previously only world-readable rooms were shown. This means that
rooms which are public, knockable, or invite-only with a pending invitation,
are included in a space summary. It also applies the same logic to
the experimental room version from MSC3083 -- if a user has access
to the proper allowed rooms then it is shown in the spaces summary.

This change is made per MSC3173 allowing stripped state of a room to
be shown to any potential room joiner.
  • Loading branch information
clokep authored Jul 13, 2021
1 parent 475fcb0 commit 2d16e69
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 38 deletions.
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.
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

0 comments on commit 2d16e69

Please sign in to comment.