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

Check for space membership during a local join of restricted rooms. #9735

Merged
merged 11 commits into from
Apr 8, 2021
1 change: 1 addition & 0 deletions changelog.d/9735.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add experimental support for [MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083): restricting room access via group membership.
2 changes: 1 addition & 1 deletion scripts-dev/complement.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ if [[ -n "$1" ]]; then
fi

# Run the tests!
COMPLEMENT_BASE_IMAGE=complement-synapse go test -v -tags synapse_blacklist -count=1 $EXTRA_COMPLEMENT_ARGS ./tests
COMPLEMENT_BASE_IMAGE=complement-synapse go test -v -tags synapse_blacklist,msc3083 -count=1 $EXTRA_COMPLEMENT_ARGS ./tests
72 changes: 71 additions & 1 deletion synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple

from synapse import types
from synapse.api.constants import AccountDataTypes, EventTypes, Membership
from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership
from synapse.api.errors import (
AuthError,
Codes,
Expand All @@ -29,6 +29,7 @@
SynapseError,
)
from synapse.api.ratelimiting import Ratelimiter
from synapse.api.room_versions import RoomVersion
from synapse.events import EventBase
from synapse.events.snapshot import EventContext
from synapse.types import JsonDict, Requester, RoomAlias, RoomID, StateMap, UserID
Expand Down Expand Up @@ -178,6 +179,59 @@ async def ratelimit_invite(

await self._invites_per_user_limiter.ratelimit(requester, invitee_user_id)

async def _can_join_restricted_room(
self, state_ids: StateMap[str], room_version: RoomVersion, user_id: str
) -> bool:
"""
Check whether a user can join a restricted room.
clokep marked this conversation as resolved.
Show resolved Hide resolved

Args:
state_ids: The state of the room as it currently is.
room_version: The room version of the room being joined.
user_id: The user joining the room.

Returns:
True if the user can join the room, false otherwise.
"""
# This only applies to room versions which support the new join rule.
if not room_version.msc3083_join_rules:
return True

# If there's no join rule, then it defaults to public (so this doesn't apply).
join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""), None)
if not join_rules_event_id:
return True

# If the join rule is not restricted, this doesn't apply.
join_rules_event = await self.store.get_event(join_rules_event_id)
if join_rules_event.content.get("join_rule") != JoinRules.MSC3083_RESTRICTED:
return True

# If allowed is of the wrong form, then ignore it (and treat the room as public).
clokep marked this conversation as resolved.
Show resolved Hide resolved
allowed_spaces = join_rules_event.content.get("allow", [])
if not isinstance(allowed_spaces, list):
return True

# Get the list of joined rooms and see if there's an overlap.
joined_rooms = await self.store.get_rooms_for_user(user_id)

# Pull out the other room IDs, invalid data gets filtered.
for space in allowed_spaces:
if not isinstance(space, dict):
continue

soace_id = space.get("space")
clokep marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(soace_id, str):
continue

# The user was joined to one of the spaces specified, they can join
# this room!
if soace_id in joined_rooms:
return True

# The user was not in any of the required spaces.
return False

async def _local_membership_update(
self,
requester: Requester,
Expand Down Expand Up @@ -235,9 +289,25 @@ async def _local_membership_update(

if event.membership == Membership.JOIN:
newly_joined = True
is_invite = False
clokep marked this conversation as resolved.
Show resolved Hide resolved
if prev_member_event_id:
prev_member_event = await self.store.get_event(prev_member_event_id)
newly_joined = prev_member_event.membership != Membership.JOIN
is_invite = prev_member_event.membership == Membership.INVITE

# If the member is not already in the room and is not accepting an invite,
# check if they should be allowed access via membership in a space.
if (
newly_joined
and not is_invite
and not await self._can_join_restricted_room(
prev_state_ids, event.room_version, user_id
)
):
Copy link
Member Author

Choose a reason for hiding this comment

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

This partially feels like we're re-implementing the auth checks here, but not sure there's a more reasonable way to do it.

Copy link
Member

Choose a reason for hiding this comment

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

agreed, but 🤷‍♂️

raise AuthError(
403,
"You do not belong to any of the required spaces to join this room.",
)

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