Return unable to authorise join if there are unrecognized restricted join rules (#20132)

Rooms may use custom restricted join `allow` rules for things like
pre-validating joins before they're allowed into the room graph.
However, currently any Synapse with invite permissions that doesn't know
about the custom rule will throw a 403, which cancels the entire join
process. By returning `M_UNABLE_TO_AUTHORISE_JOIN` instead, the origin
server can keep trying other servers to join through.

For reference, continuwuity already implements this correctly:
https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/5ceed2c2bb4ad80160cd1b7365afd8fe388261eb/src/api/server/make_join.rs#L255-L256
This commit is contained in:
Tulir Asokan authored and GitHub committed 2026-08-28 16:20:32 +01:00
1 parent a92a21600c
commit 6460a96d6f
4 files changed
+44 -19

No files matched your search

+1
View File
@@ -0,0 +1 @@
Fixed joining rooms with unrecognized restricted join rules being rejected outright instead of returning the proper `M_UNABLE_TO_AUTHORISE_JOIN` error code. Contributed by @tulir @ Beeper.
+25 -9
View File
@@ -234,11 +234,21 @@ class EventAuthHandler:
# Get the rooms which allow access to this room and check if the user is
# in any of them.
allowed_rooms = await self.get_rooms_that_allow_join(state_ids)
allowed_rooms, has_unknown_rules = await self.get_rooms_that_allow_join(
state_ids
)
if not await self.is_user_in_rooms(allowed_rooms, user_id):
# If this is a remote request, the user might be in an allowed room
# that we do not know about.
if not self._is_mine_id(user_id):
# If there are unknown allow rules, there could be other servers
# that are able to authorise the join. Alternatively, if this is
# a remote request, the user might be in an allowed room that we
# do not know about.
if has_unknown_rules:
raise SynapseError(
400,
"Unrecognized restricted join rules found.",
Codes.UNABLE_AUTHORISE_JOIN,
)
elif not self._is_mine_id(user_id):
for room_id in allowed_rooms:
if not await self._store.is_host_joined(room_id, self._server_name):
raise SynapseError(
@@ -289,7 +299,7 @@ class EventAuthHandler:
async def get_rooms_that_allow_join(
self, state_ids: StateMap[str]
) -> StrCollection:
) -> tuple[StrCollection, bool]:
"""
Generate a list of rooms in which membership allows access to a room.
@@ -297,12 +307,16 @@ class EventAuthHandler:
state_ids: The current state of the room the user wishes to join
Returns:
A collection of room IDs. Membership in any of the rooms in the list grants the ability to join the target room.
A tuple of a collection of room IDs and a boolean indicating whether
there are any unknown allow rules. Membership in any of the rooms in
the list grants the ability to join the target room. If unknown rules
are present, failure to authorise the join should not be treated as
a permanent error.
"""
# If there's no join rule, then it defaults to invite (so this doesn't apply).
join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""), None)
if not join_rules_event_id:
return ()
return (), False
# If the join rule is not restricted, this doesn't apply.
join_rules_event = await self._store.get_event(join_rules_event_id)
@@ -310,16 +324,18 @@ class EventAuthHandler:
# If allowed is of the wrong form, then only allow invited users.
allow_list = join_rules_event.content.get("allow", [])
if not isinstance(allow_list, list):
return ()
return (), False
# Pull out the other room IDs, invalid data gets filtered.
result = []
has_unknown_rules = False
for allow in allow_list:
if not isinstance(allow, dict):
continue
# If the type is unexpected, skip it.
if allow.get("type") != RestrictedJoinRuleTypes.ROOM_MEMBERSHIP:
has_unknown_rules = True
continue
room_id = allow.get("room_id")
@@ -328,7 +344,7 @@ class EventAuthHandler:
result.append(room_id)
return result
return result, has_unknown_rules
async def is_user_in_rooms(self, room_ids: StrCollection, user_id: str) -> bool:
"""
+9 -3
View File
@@ -1379,9 +1379,15 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
return True, list(servers_that_can_issue_invite)
# Ensure the member should be allowed access via membership in a room.
await self.event_auth_handler.check_restricted_join_rules(
state_before_join, room_version, user_id, previous_membership
)
try:
await self.event_auth_handler.check_restricted_join_rules(
state_before_join, room_version, user_id, previous_membership
)
except SynapseError as e:
if e.errcode == Codes.UNABLE_AUTHORISE_JOIN:
servers_that_can_issue_invite.discard(self.hs.hostname)
return True, list(servers_that_can_issue_invite)
raise
# If this is going to be a local join, additional information must
# be included in the event content in order to efficiently validate
+9 -7
View File
@@ -685,9 +685,10 @@ class RoomSummaryHandler:
if await self._event_auth_handler.has_restricted_join_rules(
state_ids, room_version
):
allowed_rooms = (
await self._event_auth_handler.get_rooms_that_allow_join(state_ids)
)
(
allowed_rooms,
_,
) = await self._event_auth_handler.get_rooms_that_allow_join(state_ids)
if await self._event_auth_handler.is_user_in_rooms(
allowed_rooms, requester
):
@@ -707,9 +708,10 @@ class RoomSummaryHandler:
if await self._event_auth_handler.has_restricted_join_rules(
state_ids, room_version
):
allowed_rooms = (
await self._event_auth_handler.get_rooms_that_allow_join(state_ids)
)
(
allowed_rooms,
_,
) = await self._event_auth_handler.get_rooms_that_allow_join(state_ids)
for space_id in allowed_rooms:
if await self._event_auth_handler.is_host_in_room(space_id, origin):
return True
@@ -822,7 +824,7 @@ class RoomSummaryHandler:
if room_version and await self._event_auth_handler.has_restricted_join_rules(
join_rules_state_ids, room_version
):
allowed_rooms = await self._event_auth_handler.get_rooms_that_allow_join(
allowed_rooms, _ = await self._event_auth_handler.get_rooms_that_allow_join(
join_rules_state_ids
)
if allowed_rooms: