diff --git a/changelog.d/12623.feature b/changelog.d/12623.feature
new file mode 100644
index 0000000000..cdee19fafa
--- /dev/null
+++ b/changelog.d/12623.feature
@@ -0,0 +1 @@
+Add support for [MSC3787: Allowing knocks to restricted rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/3787).
\ No newline at end of file
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index 0ccd4c9558..330de21f6b 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -65,6 +65,8 @@ class JoinRules:
PRIVATE: Final = "private"
# As defined for MSC3083.
RESTRICTED: Final = "restricted"
+ # As defined for MSC3787.
+ KNOCK_RESTRICTED: Final = "knock_restricted"
class RestrictedJoinRuleTypes:
diff --git a/synapse/api/room_versions.py b/synapse/api/room_versions.py
index a747a40814..3f85d61b46 100644
--- a/synapse/api/room_versions.py
+++ b/synapse/api/room_versions.py
@@ -81,6 +81,9 @@ class RoomVersion:
msc2716_historical: bool
# MSC2716: Adds support for redacting "insertion", "chunk", and "marker" events
msc2716_redactions: bool
+ # MSC3787: Adds support for a `knock_restricted` join rule, mixing concepts of
+ # knocks and restricted join rules into the same join condition.
+ msc3787_knock_restricted_join_rule: bool
class RoomVersions:
@@ -99,6 +102,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V2 = RoomVersion(
"2",
@@ -115,6 +119,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V3 = RoomVersion(
"3",
@@ -131,6 +136,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V4 = RoomVersion(
"4",
@@ -147,6 +153,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V5 = RoomVersion(
"5",
@@ -163,6 +170,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V6 = RoomVersion(
"6",
@@ -179,6 +187,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
MSC2176 = RoomVersion(
"org.matrix.msc2176",
@@ -195,6 +204,7 @@ class RoomVersions:
msc2403_knocking=False,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V7 = RoomVersion(
"7",
@@ -211,6 +221,7 @@ class RoomVersions:
msc2403_knocking=True,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V8 = RoomVersion(
"8",
@@ -227,6 +238,7 @@ class RoomVersions:
msc2403_knocking=True,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
V9 = RoomVersion(
"9",
@@ -243,6 +255,7 @@ class RoomVersions:
msc2403_knocking=True,
msc2716_historical=False,
msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=False,
)
MSC2716v3 = RoomVersion(
"org.matrix.msc2716v3",
@@ -259,6 +272,24 @@ class RoomVersions:
msc2403_knocking=True,
msc2716_historical=True,
msc2716_redactions=True,
+ msc3787_knock_restricted_join_rule=False,
+ )
+ MSC3787 = RoomVersion(
+ "org.matrix.msc3787",
+ RoomDisposition.UNSTABLE,
+ EventFormatVersions.V3,
+ StateResolutionVersions.V2,
+ enforce_key_validity=True,
+ special_case_aliases_auth=False,
+ strict_canonicaljson=True,
+ limit_notifications_power_levels=True,
+ msc2176_redaction_rules=False,
+ msc3083_join_rules=True,
+ msc3375_redaction_rules=True,
+ msc2403_knocking=True,
+ msc2716_historical=False,
+ msc2716_redactions=False,
+ msc3787_knock_restricted_join_rule=True,
)
@@ -276,6 +307,7 @@ KNOWN_ROOM_VERSIONS: Dict[str, RoomVersion] = {
RoomVersions.V8,
RoomVersions.V9,
RoomVersions.MSC2716v3,
+ RoomVersions.MSC3787,
)
}
diff --git a/synapse/event_auth.py b/synapse/event_auth.py
index 621a3efccc..4c0b587a76 100644
--- a/synapse/event_auth.py
+++ b/synapse/event_auth.py
@@ -414,7 +414,12 @@ def _is_membership_change_allowed(
raise AuthError(403, "You are banned from this room")
elif join_rule == JoinRules.PUBLIC:
pass
- elif room_version.msc3083_join_rules and join_rule == JoinRules.RESTRICTED:
+ elif (
+ room_version.msc3083_join_rules and join_rule == JoinRules.RESTRICTED
+ ) or (
+ room_version.msc3787_knock_restricted_join_rule
+ and join_rule == JoinRules.KNOCK_RESTRICTED
+ ):
# This is the same as public, but the event must contain a reference
# to the server who authorised the join. If the event does not contain
# the proper content it is rejected.
@@ -440,8 +445,13 @@ def _is_membership_change_allowed(
if authorising_user_level < invite_level:
raise AuthError(403, "Join event authorised by invalid server.")
- elif join_rule == JoinRules.INVITE or (
- room_version.msc2403_knocking and join_rule == JoinRules.KNOCK
+ elif (
+ join_rule == JoinRules.INVITE
+ or (room_version.msc2403_knocking and join_rule == JoinRules.KNOCK)
+ or (
+ room_version.msc3787_knock_restricted_join_rule
+ and join_rule == JoinRules.KNOCK_RESTRICTED
+ )
):
if not caller_in_room and not caller_invited:
raise AuthError(403, "You are not invited to this room.")
@@ -462,7 +472,10 @@ def _is_membership_change_allowed(
if user_level < ban_level or user_level <= target_level:
raise AuthError(403, "You don't have permission to ban")
elif room_version.msc2403_knocking and Membership.KNOCK == membership:
- if join_rule != JoinRules.KNOCK:
+ if join_rule != JoinRules.KNOCK and (
+ not room_version.msc3787_knock_restricted_join_rule
+ or join_rule != JoinRules.KNOCK_RESTRICTED
+ ):
raise AuthError(403, "You don't have permission to knock")
elif target_user_id != event.user_id:
raise AuthError(403, "You cannot knock for other users")
diff --git a/synapse/handlers/event_auth.py b/synapse/handlers/event_auth.py
index d441ebb0ab..6bed464351 100644
--- a/synapse/handlers/event_auth.py
+++ b/synapse/handlers/event_auth.py
@@ -241,7 +241,15 @@ class EventAuthHandler:
# If the join rule is not restricted, this doesn't apply.
join_rules_event = await self._store.get_event(join_rules_event_id)
- return join_rules_event.content.get("join_rule") == JoinRules.RESTRICTED
+ content_join_rule = join_rules_event.content.get("join_rule")
+ if content_join_rule == JoinRules.RESTRICTED:
+ return True
+
+ # also check for MSC3787 behaviour
+ if room_version.msc3787_knock_restricted_join_rule:
+ return content_join_rule == JoinRules.KNOCK_RESTRICTED
+
+ return False
async def get_rooms_that_allow_join(
self, state_ids: StateMap[str]
diff --git a/synapse/handlers/room_summary.py b/synapse/handlers/room_summary.py
index ff24ec8063..af83de3193 100644
--- a/synapse/handlers/room_summary.py
+++ b/synapse/handlers/room_summary.py
@@ -562,8 +562,13 @@ class RoomSummaryHandler:
if join_rules_event_id:
join_rules_event = await self._store.get_event(join_rules_event_id)
join_rule = join_rules_event.content.get("join_rule")
- if join_rule == JoinRules.PUBLIC or (
- room_version.msc2403_knocking and join_rule == JoinRules.KNOCK
+ if (
+ join_rule == JoinRules.PUBLIC
+ or (room_version.msc2403_knocking and join_rule == JoinRules.KNOCK)
+ or (
+ room_version.msc3787_knock_restricted_join_rule
+ and join_rule == JoinRules.KNOCK_RESTRICTED
+ )
):
return True
|