|
20 | 20 | from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple |
21 | 21 |
|
22 | 22 | from synapse import types |
23 | | -from synapse.api.constants import AccountDataTypes, EventTypes, Membership |
| 23 | +from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership |
24 | 24 | from synapse.api.errors import ( |
25 | 25 | AuthError, |
26 | 26 | Codes, |
|
29 | 29 | SynapseError, |
30 | 30 | ) |
31 | 31 | from synapse.api.ratelimiting import Ratelimiter |
| 32 | +from synapse.api.room_versions import RoomVersion |
32 | 33 | from synapse.events import EventBase |
33 | 34 | from synapse.events.snapshot import EventContext |
34 | 35 | from synapse.types import JsonDict, Requester, RoomAlias, RoomID, StateMap, UserID |
@@ -178,6 +179,62 @@ async def ratelimit_invite( |
178 | 179 |
|
179 | 180 | await self._invites_per_user_limiter.ratelimit(requester, invitee_user_id) |
180 | 181 |
|
| 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 | + |
181 | 238 | async def _local_membership_update( |
182 | 239 | self, |
183 | 240 | requester: Requester, |
@@ -235,9 +292,25 @@ async def _local_membership_update( |
235 | 292 |
|
236 | 293 | if event.membership == Membership.JOIN: |
237 | 294 | newly_joined = True |
| 295 | + user_is_invited = False |
238 | 296 | if prev_member_event_id: |
239 | 297 | prev_member_event = await self.store.get_event(prev_member_event_id) |
240 | 298 | 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 | + ) |
241 | 314 |
|
242 | 315 | # Only rate-limit if the user actually joined the room, otherwise we'll end |
243 | 316 | # up blocking profile updates. |
|
0 commit comments