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

Commit 2d16e69

Browse files
authored
Show all joinable rooms in the spaces summary. (#10298)
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.
1 parent 475fcb0 commit 2d16e69

File tree

6 files changed

+237
-38
lines changed

6 files changed

+237
-38
lines changed

changelog.d/10298.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The spaces summary API now returns any joinable rooms, not only rooms which are world-readable.

changelog.d/10305.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The spaces summary API now returns any joinable rooms, not only rooms which are world-readable.

changelog.d/10305.misc

Lines changed: 0 additions & 1 deletion
This file was deleted.

synapse/handlers/space_summary.py

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
EventContentFields,
2525
EventTypes,
2626
HistoryVisibility,
27+
JoinRules,
2728
Membership,
2829
RoomTypes,
2930
)
@@ -150,14 +151,21 @@ async def get_space_summary(
150151
# The room should only be included in the summary if:
151152
# a. the user is in the room;
152153
# b. the room is world readable; or
153-
# c. the user is in a space that has been granted access to
154-
# the room.
154+
# c. the user could join the room, e.g. the join rules
155+
# are set to public or the user is in a space that
156+
# has been granted access to the room.
155157
#
156158
# Note that we know the user is not in the root room (which is
157159
# why the remote call was made in the first place), but the user
158160
# could be in one of the children rooms and we just didn't know
159161
# about the link.
160-
include_room = room.get("world_readable") is True
162+
163+
# The API doesn't return the room version so assume that a
164+
# join rule of knock is valid.
165+
include_room = (
166+
room.get("join_rules") in (JoinRules.PUBLIC, JoinRules.KNOCK)
167+
or room.get("world_readable") is True
168+
)
161169

162170
# Check if the user is a member of any of the allowed spaces
163171
# from the response.
@@ -420,9 +428,8 @@ async def _is_room_accessible(
420428
421429
It should be included if:
422430
423-
* The requester is joined or invited to the room.
424-
* The requester can join without an invite (per MSC3083).
425-
* The origin server has any user that is joined or invited to the room.
431+
* The requester is joined or can join the room (per MSC3173).
432+
* The origin server has any user that is joined or can join the room.
426433
* The history visibility is set to world readable.
427434
428435
Args:
@@ -441,13 +448,39 @@ async def _is_room_accessible(
441448

442449
# If there's no state for the room, it isn't known.
443450
if not state_ids:
451+
# The user might have a pending invite for the room.
452+
if requester and await self._store.get_invite_for_local_user_in_room(
453+
requester, room_id
454+
):
455+
return True
456+
444457
logger.info("room %s is unknown, omitting from summary", room_id)
445458
return False
446459

447460
room_version = await self._store.get_room_version(room_id)
448461

449-
# if we have an authenticated requesting user, first check if they are able to view
450-
# stripped state in the room.
462+
# Include the room if it has join rules of public or knock.
463+
join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""))
464+
if join_rules_event_id:
465+
join_rules_event = await self._store.get_event(join_rules_event_id)
466+
join_rule = join_rules_event.content.get("join_rule")
467+
if join_rule == JoinRules.PUBLIC or (
468+
room_version.msc2403_knocking and join_rule == JoinRules.KNOCK
469+
):
470+
return True
471+
472+
# Include the room if it is peekable.
473+
hist_vis_event_id = state_ids.get((EventTypes.RoomHistoryVisibility, ""))
474+
if hist_vis_event_id:
475+
hist_vis_ev = await self._store.get_event(hist_vis_event_id)
476+
hist_vis = hist_vis_ev.content.get("history_visibility")
477+
if hist_vis == HistoryVisibility.WORLD_READABLE:
478+
return True
479+
480+
# Otherwise we need to check information specific to the user or server.
481+
482+
# If we have an authenticated requesting user, check if they are a member
483+
# of the room (or can join the room).
451484
if requester:
452485
member_event_id = state_ids.get((EventTypes.Member, requester), None)
453486

@@ -470,9 +503,11 @@ async def _is_room_accessible(
470503
return True
471504

472505
# If this is a request over federation, check if the host is in the room or
473-
# is in one of the spaces specified via the join rules.
506+
# has a user who could join the room.
474507
elif origin:
475-
if await self._event_auth_handler.check_host_in_room(room_id, origin):
508+
if await self._event_auth_handler.check_host_in_room(
509+
room_id, origin
510+
) or await self._store.is_host_invited(room_id, origin):
476511
return True
477512

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

493-
# otherwise, check if the room is peekable
494-
hist_vis_event_id = state_ids.get((EventTypes.RoomHistoryVisibility, ""), None)
495-
if hist_vis_event_id:
496-
hist_vis_ev = await self._store.get_event(hist_vis_event_id)
497-
hist_vis = hist_vis_ev.content.get("history_visibility")
498-
if hist_vis == HistoryVisibility.WORLD_READABLE:
499-
return True
500-
501528
logger.info(
502-
"room %s is unpeekable and user %s is not a member / not allowed to join, omitting from summary",
529+
"room %s is unpeekable and requester %s is not a member / not allowed to join, omitting from summary",
503530
room_id,
504-
requester,
531+
requester or origin,
505532
)
506533
return False
507534

@@ -535,6 +562,7 @@ async def _build_room_entry(self, room_id: str) -> JsonDict:
535562
"canonical_alias": stats["canonical_alias"],
536563
"num_joined_members": stats["joined_members"],
537564
"avatar_url": stats["avatar"],
565+
"join_rules": stats["join_rules"],
538566
"world_readable": (
539567
stats["history_visibility"] == HistoryVisibility.WORLD_READABLE
540568
),

synapse/storage/databases/main/roommember.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -703,13 +703,22 @@ async def _get_joined_profiles_from_event_ids(self, event_ids: Iterable[str]):
703703

704704
@cached(max_entries=10000)
705705
async def is_host_joined(self, room_id: str, host: str) -> bool:
706+
return await self._check_host_room_membership(room_id, host, Membership.JOIN)
707+
708+
@cached(max_entries=10000)
709+
async def is_host_invited(self, room_id: str, host: str) -> bool:
710+
return await self._check_host_room_membership(room_id, host, Membership.INVITE)
711+
712+
async def _check_host_room_membership(
713+
self, room_id: str, host: str, membership: str
714+
) -> bool:
706715
if "%" in host or "_" in host:
707716
raise Exception("Invalid host name")
708717

709718
sql = """
710719
SELECT state_key FROM current_state_events AS c
711720
INNER JOIN room_memberships AS m USING (event_id)
712-
WHERE m.membership = 'join'
721+
WHERE m.membership = ?
713722
AND type = 'm.room.member'
714723
AND c.room_id = ?
715724
AND state_key LIKE ?
@@ -722,7 +731,7 @@ async def is_host_joined(self, room_id: str, host: str) -> bool:
722731
like_clause = "%:" + host
723732

724733
rows = await self.db_pool.execute(
725-
"is_host_joined", None, sql, room_id, like_clause
734+
"is_host_joined", None, sql, membership, room_id, like_clause
726735
)
727736

728737
if not rows:

0 commit comments

Comments
 (0)