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

Commit 4529915

Browse files
authored
MSC3083: Check for space membership during a local join of restricted rooms. (#9735)
When joining a room with join rules set to 'restricted', check if the user is a member of the spaces defined in the 'allow' key of the join rules. This only applies to an experimental room version, as defined in MSC3083.
1 parent 48d44ab commit 4529915

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

changelog.d/9735.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add experimental support for [MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083): restricting room access via group membership.

scripts-dev/complement.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ if [[ -n "$1" ]]; then
4646
fi
4747

4848
# Run the tests!
49-
COMPLEMENT_BASE_IMAGE=complement-synapse go test -v -tags synapse_blacklist -count=1 $EXTRA_COMPLEMENT_ARGS ./tests
49+
COMPLEMENT_BASE_IMAGE=complement-synapse go test -v -tags synapse_blacklist,msc3083 -count=1 $EXTRA_COMPLEMENT_ARGS ./tests

synapse/handlers/room_member.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple
2121

2222
from synapse import types
23-
from synapse.api.constants import AccountDataTypes, EventTypes, Membership
23+
from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership
2424
from synapse.api.errors import (
2525
AuthError,
2626
Codes,
@@ -29,6 +29,7 @@
2929
SynapseError,
3030
)
3131
from synapse.api.ratelimiting import Ratelimiter
32+
from synapse.api.room_versions import RoomVersion
3233
from synapse.events import EventBase
3334
from synapse.events.snapshot import EventContext
3435
from synapse.types import JsonDict, Requester, RoomAlias, RoomID, StateMap, UserID
@@ -178,6 +179,62 @@ async def ratelimit_invite(
178179

179180
await self._invites_per_user_limiter.ratelimit(requester, invitee_user_id)
180181

182+
async def _can_join_without_invite(
183+
self, state_ids: StateMap[str], room_version: RoomVersion, user_id: str
184+
) -> bool:
185+
"""
186+
Check whether a user can join a room without an invite.
187+
188+
When joining a room with restricted joined rules (as defined in MSC3083),
189+
the membership of spaces must be checked during join.
190+
191+
Args:
192+
state_ids: The state of the room as it currently is.
193+
room_version: The room version of the room being joined.
194+
user_id: The user joining the room.
195+
196+
Returns:
197+
True if the user can join the room, false otherwise.
198+
"""
199+
# This only applies to room versions which support the new join rule.
200+
if not room_version.msc3083_join_rules:
201+
return True
202+
203+
# If there's no join rule, then it defaults to public (so this doesn't apply).
204+
join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""), None)
205+
if not join_rules_event_id:
206+
return True
207+
208+
# If the join rule is not restricted, this doesn't apply.
209+
join_rules_event = await self.store.get_event(join_rules_event_id)
210+
if join_rules_event.content.get("join_rule") != JoinRules.MSC3083_RESTRICTED:
211+
return True
212+
213+
# If allowed is of the wrong form, then only allow invited users.
214+
allowed_spaces = join_rules_event.content.get("allow", [])
215+
if not isinstance(allowed_spaces, list):
216+
return False
217+
218+
# Get the list of joined rooms and see if there's an overlap.
219+
joined_rooms = await self.store.get_rooms_for_user(user_id)
220+
221+
# Pull out the other room IDs, invalid data gets filtered.
222+
for space in allowed_spaces:
223+
if not isinstance(space, dict):
224+
continue
225+
226+
space_id = space.get("space")
227+
if not isinstance(space_id, str):
228+
continue
229+
230+
# The user was joined to one of the spaces specified, they can join
231+
# this room!
232+
if space_id in joined_rooms:
233+
return True
234+
235+
# The user was not in any of the required spaces.
236+
return False
237+
181238
async def _local_membership_update(
182239
self,
183240
requester: Requester,
@@ -235,9 +292,25 @@ async def _local_membership_update(
235292

236293
if event.membership == Membership.JOIN:
237294
newly_joined = True
295+
user_is_invited = False
238296
if prev_member_event_id:
239297
prev_member_event = await self.store.get_event(prev_member_event_id)
240298
newly_joined = prev_member_event.membership != Membership.JOIN
299+
user_is_invited = prev_member_event.membership == Membership.INVITE
300+
301+
# If the member is not already in the room and is not accepting an invite,
302+
# check if they should be allowed access via membership in a space.
303+
if (
304+
newly_joined
305+
and not user_is_invited
306+
and not await self._can_join_without_invite(
307+
prev_state_ids, event.room_version, user_id
308+
)
309+
):
310+
raise AuthError(
311+
403,
312+
"You do not belong to any of the required spaces to join this room.",
313+
)
241314

242315
# Only rate-limit if the user actually joined the room, otherwise we'll end
243316
# up blocking profile updates.

0 commit comments

Comments
 (0)