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

Commit d936371

Browse files
authoredJun 9, 2021
Implement knock feature (#6739)
This PR aims to implement the knock feature as proposed in matrix-org/matrix-spec-proposals#2403 Signed-off-by: Sorunome [email protected] Signed-off-by: Andrew Morgan [email protected]
1 parent 11846df commit d936371

File tree

29 files changed

+1613
-118
lines changed

29 files changed

+1613
-118
lines changed
 

‎changelog.d/6739.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement "room knocking" as per [MSC2403](https://github.com/matrix-org/matrix-doc/pull/2403). Contributed by Sorunome and anoa.

‎synapse/api/constants.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Membership:
4141

4242
INVITE = "invite"
4343
JOIN = "join"
44-
KNOCK = "knock"
44+
KNOCK = "xyz.amorgan.knock"
4545
LEAVE = "leave"
4646
BAN = "ban"
4747
LIST = (INVITE, JOIN, KNOCK, LEAVE, BAN)
@@ -58,7 +58,7 @@ class PresenceState:
5858

5959
class JoinRules:
6060
PUBLIC = "public"
61-
KNOCK = "knock"
61+
KNOCK = "xyz.amorgan.knock"
6262
INVITE = "invite"
6363
PRIVATE = "private"
6464
# As defined for MSC3083.

‎synapse/api/errors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ def __init__(self, room_version: str):
449449
super().__init__(
450450
code=400,
451451
msg="Your homeserver does not support the features required to "
452-
"join this room",
452+
"interact with this room",
453453
errcode=Codes.INCOMPATIBLE_ROOM_VERSION,
454454
)
455455

‎synapse/api/room_versions.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class RoomVersion:
5656
state_res = attr.ib(type=int) # one of the StateResolutionVersions
5757
enforce_key_validity = attr.ib(type=bool)
5858

59-
# Before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules
59+
# Before MSC2432, m.room.aliases had special auth rules and redaction rules
6060
special_case_aliases_auth = attr.ib(type=bool)
6161
# Strictly enforce canonicaljson, do not allow:
6262
# * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
@@ -70,6 +70,9 @@ class RoomVersion:
7070
msc2176_redaction_rules = attr.ib(type=bool)
7171
# MSC3083: Support the 'restricted' join_rule.
7272
msc3083_join_rules = attr.ib(type=bool)
73+
# MSC2403: Allows join_rules to be set to 'knock', changes auth rules to allow sending
74+
# m.room.membership event with membership 'knock'.
75+
msc2403_knocking = attr.ib(type=bool)
7376

7477

7578
class RoomVersions:
@@ -84,6 +87,7 @@ class RoomVersions:
8487
limit_notifications_power_levels=False,
8588
msc2176_redaction_rules=False,
8689
msc3083_join_rules=False,
90+
msc2403_knocking=False,
8791
)
8892
V2 = RoomVersion(
8993
"2",
@@ -96,6 +100,7 @@ class RoomVersions:
96100
limit_notifications_power_levels=False,
97101
msc2176_redaction_rules=False,
98102
msc3083_join_rules=False,
103+
msc2403_knocking=False,
99104
)
100105
V3 = RoomVersion(
101106
"3",
@@ -108,6 +113,7 @@ class RoomVersions:
108113
limit_notifications_power_levels=False,
109114
msc2176_redaction_rules=False,
110115
msc3083_join_rules=False,
116+
msc2403_knocking=False,
111117
)
112118
V4 = RoomVersion(
113119
"4",
@@ -120,6 +126,7 @@ class RoomVersions:
120126
limit_notifications_power_levels=False,
121127
msc2176_redaction_rules=False,
122128
msc3083_join_rules=False,
129+
msc2403_knocking=False,
123130
)
124131
V5 = RoomVersion(
125132
"5",
@@ -132,6 +139,7 @@ class RoomVersions:
132139
limit_notifications_power_levels=False,
133140
msc2176_redaction_rules=False,
134141
msc3083_join_rules=False,
142+
msc2403_knocking=False,
135143
)
136144
V6 = RoomVersion(
137145
"6",
@@ -144,6 +152,7 @@ class RoomVersions:
144152
limit_notifications_power_levels=True,
145153
msc2176_redaction_rules=False,
146154
msc3083_join_rules=False,
155+
msc2403_knocking=False,
147156
)
148157
MSC2176 = RoomVersion(
149158
"org.matrix.msc2176",
@@ -156,6 +165,7 @@ class RoomVersions:
156165
limit_notifications_power_levels=True,
157166
msc2176_redaction_rules=True,
158167
msc3083_join_rules=False,
168+
msc2403_knocking=False,
159169
)
160170
MSC3083 = RoomVersion(
161171
"org.matrix.msc3083",
@@ -168,6 +178,20 @@ class RoomVersions:
168178
limit_notifications_power_levels=True,
169179
msc2176_redaction_rules=False,
170180
msc3083_join_rules=True,
181+
msc2403_knocking=False,
182+
)
183+
MSC2403 = RoomVersion(
184+
"xyz.amorgan.knock",
185+
RoomDisposition.UNSTABLE,
186+
EventFormatVersions.V3,
187+
StateResolutionVersions.V2,
188+
enforce_key_validity=True,
189+
special_case_aliases_auth=False,
190+
strict_canonicaljson=True,
191+
limit_notifications_power_levels=True,
192+
msc2176_redaction_rules=False,
193+
msc3083_join_rules=False,
194+
msc2403_knocking=True,
171195
)
172196

173197

@@ -183,4 +207,5 @@ class RoomVersions:
183207
RoomVersions.MSC2176,
184208
RoomVersions.MSC3083,
185209
)
210+
# Note that we do not include MSC2043 here unless it is enabled in the config.
186211
} # type: Dict[str, RoomVersion]

‎synapse/appservice/api.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from prometheus_client import Counter
1919

20-
from synapse.api.constants import EventTypes, ThirdPartyEntityKind
20+
from synapse.api.constants import EventTypes, Membership, ThirdPartyEntityKind
2121
from synapse.api.errors import CodeMessageException
2222
from synapse.events import EventBase
2323
from synapse.events.utils import serialize_event
@@ -247,9 +247,14 @@ def _serialize(self, service, events):
247247
e,
248248
time_now,
249249
as_client_event=True,
250-
is_invite=(
250+
# If this is an invite or a knock membership event, and we're interested
251+
# in this user, then include any stripped state alongside the event.
252+
include_stripped_room_state=(
251253
e.type == EventTypes.Member
252-
and e.membership == "invite"
254+
and (
255+
e.membership == Membership.INVITE
256+
or e.membership == Membership.KNOCK
257+
)
253258
and service.is_interested_in_user(e.state_key)
254259
),
255260
)

‎synapse/config/account_validity.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
# Copyright 2020 The Matrix.org Foundation C.I.C.
32
#
43
# Licensed under the Apache License, Version 2.0 (the "License");

‎synapse/config/experimental.py

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
1516
from synapse.config._base import Config
1617
from synapse.types import JsonDict
1718

@@ -29,3 +30,9 @@ def read_config(self, config: JsonDict, **kwargs):
2930

3031
# MSC3026 (busy presence state)
3132
self.msc3026_enabled = experimental.get("msc3026_enabled", False) # type: bool
33+
34+
# MSC2403 (room knocking)
35+
self.msc2403_enabled = experimental.get("msc2403_enabled", False) # type: bool
36+
if self.msc2403_enabled:
37+
# Enable the MSC2403 unstable room version
38+
KNOWN_ROOM_VERSIONS[RoomVersions.MSC2403.identifier] = RoomVersions.MSC2403

‎synapse/event_auth.py

+29-4
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ def check(
160160
if logger.isEnabledFor(logging.DEBUG):
161161
logger.debug("Auth events: %s", [a.event_id for a in auth_events.values()])
162162

163+
# 5. If type is m.room.membership
163164
if event.type == EventTypes.Member:
164165
_is_membership_change_allowed(room_version_obj, event, auth_events)
165166
logger.debug("Allowing! %s", event)
@@ -257,6 +258,11 @@ def _is_membership_change_allowed(
257258

258259
caller_in_room = caller and caller.membership == Membership.JOIN
259260
caller_invited = caller and caller.membership == Membership.INVITE
261+
caller_knocked = (
262+
caller
263+
and room_version.msc2403_knocking
264+
and caller.membership == Membership.KNOCK
265+
)
260266

261267
# get info about the target
262268
key = (EventTypes.Member, target_user_id)
@@ -283,6 +289,7 @@ def _is_membership_change_allowed(
283289
{
284290
"caller_in_room": caller_in_room,
285291
"caller_invited": caller_invited,
292+
"caller_knocked": caller_knocked,
286293
"target_banned": target_banned,
287294
"target_in_room": target_in_room,
288295
"membership": membership,
@@ -299,9 +306,14 @@ def _is_membership_change_allowed(
299306
raise AuthError(403, "%s is banned from the room" % (target_user_id,))
300307
return
301308

302-
if Membership.JOIN != membership:
309+
# Require the user to be in the room for membership changes other than join/knock.
310+
if Membership.JOIN != membership and (
311+
RoomVersion.msc2403_knocking and Membership.KNOCK != membership
312+
):
313+
# If the user has been invited or has knocked, they are allowed to change their
314+
# membership event to leave
303315
if (
304-
caller_invited
316+
(caller_invited or caller_knocked)
305317
and Membership.LEAVE == membership
306318
and target_user_id == event.user_id
307319
):
@@ -339,7 +351,9 @@ def _is_membership_change_allowed(
339351
and join_rule == JoinRules.MSC3083_RESTRICTED
340352
):
341353
pass
342-
elif join_rule == JoinRules.INVITE:
354+
elif join_rule == JoinRules.INVITE or (
355+
room_version.msc2403_knocking and join_rule == JoinRules.KNOCK
356+
):
343357
if not caller_in_room and not caller_invited:
344358
raise AuthError(403, "You are not invited to this room.")
345359
else:
@@ -358,6 +372,17 @@ def _is_membership_change_allowed(
358372
elif Membership.BAN == membership:
359373
if user_level < ban_level or user_level <= target_level:
360374
raise AuthError(403, "You don't have permission to ban")
375+
elif room_version.msc2403_knocking and Membership.KNOCK == membership:
376+
if join_rule != JoinRules.KNOCK:
377+
raise AuthError(403, "You don't have permission to knock")
378+
elif target_user_id != event.user_id:
379+
raise AuthError(403, "You cannot knock for other users")
380+
elif target_in_room:
381+
raise AuthError(403, "You cannot knock on a room you are already in")
382+
elif caller_invited:
383+
raise AuthError(403, "You are already invited to this room")
384+
elif target_banned:
385+
raise AuthError(403, "You are banned from this room")
361386
else:
362387
raise AuthError(500, "Unknown membership %s" % membership)
363388

@@ -718,7 +743,7 @@ def auth_types_for_event(event: EventBase) -> Set[Tuple[str, str]]:
718743

719744
if event.type == EventTypes.Member:
720745
membership = event.content["membership"]
721-
if membership in [Membership.JOIN, Membership.INVITE]:
746+
if membership in [Membership.JOIN, Membership.INVITE, Membership.KNOCK]:
722747
auth_types.add((EventTypes.JoinRules, ""))
723748

724749
auth_types.add((EventTypes.Member, event.state_key))

‎synapse/events/utils.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ def format_event_for_client_v1(d):
242242
"replaces_state",
243243
"prev_content",
244244
"invite_room_state",
245+
"knock_room_state",
245246
)
246247
for key in copy_keys:
247248
if key in d["unsigned"]:
@@ -278,7 +279,7 @@ def serialize_event(
278279
event_format=format_event_for_client_v1,
279280
token_id=None,
280281
only_event_fields=None,
281-
is_invite=False,
282+
include_stripped_room_state=False,
282283
):
283284
"""Serialize event for clients
284285
@@ -289,8 +290,10 @@ def serialize_event(
289290
event_format
290291
token_id
291292
only_event_fields
292-
is_invite (bool): Whether this is an invite that is being sent to the
293-
invitee
293+
include_stripped_room_state (bool): Some events can have stripped room state
294+
stored in the `unsigned` field. This is required for invite and knock
295+
functionality. If this option is False, that state will be removed from the
296+
event before it is returned. Otherwise, it will be kept.
294297
295298
Returns:
296299
dict
@@ -322,11 +325,13 @@ def serialize_event(
322325
if txn_id is not None:
323326
d["unsigned"]["transaction_id"] = txn_id
324327

325-
# If this is an invite for somebody else, then we don't care about the
326-
# invite_room_state as that's meant solely for the invitee. Other clients
327-
# will already have the state since they're in the room.
328-
if not is_invite:
328+
# invite_room_state and knock_room_state are a list of stripped room state events
329+
# that are meant to provide metadata about a room to an invitee/knocker. They are
330+
# intended to only be included in specific circumstances, such as down sync, and
331+
# should not be included in any other case.
332+
if not include_stripped_room_state:
329333
d["unsigned"].pop("invite_room_state", None)
334+
d["unsigned"].pop("knock_room_state", None)
330335

331336
if as_client_event:
332337
d = event_format(d)

0 commit comments

Comments
 (0)
This repository has been archived.