From c27a29b361fb48552761ea2625fa05551224df4c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 28 Apr 2021 19:50:13 -0600 Subject: [PATCH 1/6] Add knocking to the spec Spec for https://github.com/matrix-org/matrix-doc/pull/2998 Spec for https://github.com/matrix-org/matrix-doc/pull/2403 This deliberately does not help towards fixing https://github.com/matrix-org/matrix-doc/issues/3153 in order to remain consistent with prior room versions, and to keep the diff smaller on this change. A future change will address room version legibility. --- content/_index.md | 9 +- content/client-server-api/_index.md | 166 +++++---- content/rooms/_index.md | 1 + content/rooms/v7.md | 52 +++ content/server-server-api.md | 44 ++- .../definitions/public_rooms_response.yaml | 12 +- data/api/client-server/knocking.yaml | 124 +++++++ data/api/client-server/sync.yaml | 44 ++- data/api/server-server/knocks.yaml | 321 ++++++++++++++++++ data/api/server-server/public_rooms.yaml | 12 +- .../examples/knock_room_state.json | 18 + .../m.room.member$knock_room_state.yaml | 15 + .../schema/m.room.join_rules.yaml | 11 +- data/event-schemas/schema/m.room.member.yaml | 35 +- static/diagrams/README.md | 17 + static/diagrams/membership.drawio | 1 + static/diagrams/membership.png | Bin 0 -> 28770 bytes 17 files changed, 779 insertions(+), 103 deletions(-) create mode 100644 content/rooms/v7.md create mode 100644 data/api/client-server/knocking.yaml create mode 100644 data/api/server-server/knocks.yaml create mode 100644 data/event-schemas/examples/knock_room_state.json create mode 100644 data/event-schemas/examples/m.room.member$knock_room_state.yaml create mode 100644 static/diagrams/README.md create mode 100644 static/diagrams/membership.drawio create mode 100644 static/diagrams/membership.png diff --git a/content/_index.md b/content/_index.md index 845fbc9f..4f7c3051 100644 --- a/content/_index.md +++ b/content/_index.md @@ -327,12 +327,12 @@ sent to the room `!qporfwt:matrix.org`: Federation maintains *shared data structures* per-room between multiple homeservers. The data is split into `message events` and `state events`. -Message events: +Message events: These describe transient 'once-off' activity in a room such as an instant messages, VoIP call setups, file transfers, etc. They generally describe communication activity. -State events: +State events: These describe updates to a given piece of persistent information ('state') related to a room, such as the room's name, topic, membership, participating servers, etc. State is modelled as a lookup table of @@ -505,7 +505,7 @@ stable and unstable periodically for a variety of reasons, including discovered security vulnerabilities and age. Clients should not ask room administrators to upgrade their rooms if the -room is running a stable version. Servers SHOULD use room version 6 as +room is running a stable version. Servers SHOULD use **room version 6** as the default room version when creating new rooms. The available room versions are: @@ -522,10 +522,11 @@ The available room versions are: signing key validity periods. - [Version 6](/rooms/v6) - **Stable**. Alters several authorization rules for events. +- [Version 7](/rooms/v7) - **Stable**. Introduces knocking. ## Specification Versions -The specification for each API is versioned in the form `rX.Y.Z`. +The specification for each API is versioned in the form `rX.Y.Z`. - A change to `X` reflects a breaking change: a client implemented against `r1.0.0` may need changes to work with a server which supports (only) `r2.0.0`. diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index a5964fe7..a9103c7a 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -68,118 +68,118 @@ request being made was invalid. The common error codes are: -`M_FORBIDDEN` +`M_FORBIDDEN` Forbidden access, e.g. joining a room without permission, failed login. -`M_UNKNOWN_TOKEN` +`M_UNKNOWN_TOKEN` The access token specified was not recognised. An additional response parameter, `soft_logout`, might be present on the response for 401 HTTP status codes. See [the soft logout section](#soft-logout) for more information. -`M_MISSING_TOKEN` +`M_MISSING_TOKEN` No access token was specified for the request. -`M_BAD_JSON` +`M_BAD_JSON` Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. -`M_NOT_JSON` +`M_NOT_JSON` Request did not contain valid JSON. -`M_NOT_FOUND` +`M_NOT_FOUND` No resource was found for this request. -`M_LIMIT_EXCEEDED` +`M_LIMIT_EXCEEDED` Too many requests have been sent in a short period of time. Wait a while then try again. -`M_UNKNOWN` +`M_UNKNOWN` An unknown error has occurred. Other error codes the client might encounter are: -`M_UNRECOGNIZED` +`M_UNRECOGNIZED` The server did not understand the request. -`M_UNAUTHORIZED` +`M_UNAUTHORIZED` The request was not correctly authorized. Usually due to login failures. -`M_USER_DEACTIVATED` +`M_USER_DEACTIVATED` The user ID associated with the request has been deactivated. Typically for endpoints that prove authentication, such as `/login`. -`M_USER_IN_USE` +`M_USER_IN_USE` Encountered when trying to register a user ID which has been taken. -`M_INVALID_USERNAME` +`M_INVALID_USERNAME` Encountered when trying to register a user ID which is not valid. -`M_ROOM_IN_USE` +`M_ROOM_IN_USE` Sent when the room alias given to the `createRoom` API is already in use. -`M_INVALID_ROOM_STATE` +`M_INVALID_ROOM_STATE` Sent when the initial state given to the `createRoom` API is invalid. -`M_THREEPID_IN_USE` +`M_THREEPID_IN_USE` Sent when a threepid given to an API cannot be used because the same threepid is already in use. -`M_THREEPID_NOT_FOUND` +`M_THREEPID_NOT_FOUND` Sent when a threepid given to an API cannot be used because no record matching the threepid was found. -`M_THREEPID_AUTH_FAILED` +`M_THREEPID_AUTH_FAILED` Authentication could not be performed on the third party identifier. -`M_THREEPID_DENIED` +`M_THREEPID_DENIED` The server does not permit this third party identifier. This may happen if the server only permits, for example, email addresses from a particular domain. -`M_SERVER_NOT_TRUSTED` +`M_SERVER_NOT_TRUSTED` The client's request used a third party server, e.g. identity server, that this server does not trust. -`M_UNSUPPORTED_ROOM_VERSION` +`M_UNSUPPORTED_ROOM_VERSION` The client's request to create a room used a room version that the server does not support. -`M_INCOMPATIBLE_ROOM_VERSION` +`M_INCOMPATIBLE_ROOM_VERSION` The client attempted to join a room that has a version the server does not support. Inspect the `room_version` property of the error response for the room's version. -`M_BAD_STATE` +`M_BAD_STATE` The state change requested cannot be performed, such as attempting to unban a user who is not banned. -`M_GUEST_ACCESS_FORBIDDEN` +`M_GUEST_ACCESS_FORBIDDEN` The room or resource does not permit guests to access it. -`M_CAPTCHA_NEEDED` +`M_CAPTCHA_NEEDED` A Captcha is required to complete the request. -`M_CAPTCHA_INVALID` +`M_CAPTCHA_INVALID` The Captcha provided did not match what was expected. -`M_MISSING_PARAM` +`M_MISSING_PARAM` A required parameter was missing from the request. -`M_INVALID_PARAM` +`M_INVALID_PARAM` A parameter that was specified has the wrong value. For example, the server expected an integer and instead received a string. -`M_TOO_LARGE` +`M_TOO_LARGE` The request or entity was too large. -`M_EXCLUSIVE` +`M_EXCLUSIVE` The resource being requested is reserved by an application service, or the application service making the request has not created the resource. -`M_RESOURCE_LIMIT_EXCEEDED` +`M_RESOURCE_LIMIT_EXCEEDED` The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example, a homeserver held in a shared hosting environment may reach a resource limit if it starts using too @@ -189,7 +189,7 @@ Typically, this error will appear on routes which attempt to modify state (e.g.: sending messages, account data, etc) and not routes which only read state (e.g.: `/sync`, get account data, etc). -`M_CANNOT_LEAVE_SERVER_NOTICE_ROOM` +`M_CANNOT_LEAVE_SERVER_NOTICE_ROOM` The user is unable to reject an invite to join the server notices room. See the [Server Notices](#server-notices) module for more information. @@ -238,23 +238,23 @@ time. In this section, the following terms are used with specific meanings: -`PROMPT` +`PROMPT` Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. -`IGNORE` +`IGNORE` Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available, then the client may use other methods of determining the required parameters, such as prompting the user, or using default values. -`FAIL_PROMPT` +`FAIL_PROMPT` Inform the user that auto-discovery failed due to invalid/empty data and `PROMPT` for the parameter. -`FAIL_ERROR` +`FAIL_ERROR` Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process. At this point, valid data was obtained, but no server is available to serve the client. @@ -606,7 +606,7 @@ flow with three stages will resemble the following diagram: #### Authentication types -This specification defines the following auth types: +This specification defines the following auth types: - `m.login.password` - `m.login.recaptcha` - `m.login.sso` @@ -893,7 +893,7 @@ type of identifier being used, and depending on the type, has other fields giving the information required to identify the user as described below. -This specification defines the following identifier types: +This specification defines the following identifier types: - `m.id.user` - `m.id.thirdparty` - `m.id.phone` @@ -1712,7 +1712,7 @@ event also has a `creator` key which contains the user ID of the room creator. It will also generate several other events in order to manage permissions in this room. This includes: -- `m.room.power_levels` : Sets the power levels of users and required power +- `m.room.power_levels` : Sets the power levels of users and required power levels for various actions within the room such as sending events. - `m.room.join_rules` : Whether the room is "invite-only" or not. @@ -1778,6 +1778,8 @@ in that room. There are several states in which a user may be, in relation to a room: - Unrelated (the user cannot send or receive events in the room) +- Knocking (the user has requested to participate in the room, but has + not yet been allowed to) - Invited (the user has been invited to participate in the room, but is not yet participating) - Joined (the user can send and receive events in the room) @@ -1786,49 +1788,28 @@ relation to a room: There is an exception to the requirement that a user join a room before sending events to it: users may send an `m.room.member` event to a room with `content.membership` set to `leave` to reject an invitation if they -have currently been invited to a room but have not joined it. +have currently been invited to a room but have not joined it. The same +applies for retracting knocks on the room that the user sent. Some rooms require that users be invited to it before they can join; others allow anyone to join. Whether a given room is an "invite-only" room is determined by the room config key `m.room.join_rules`. It can have one of the following values: -`public` +`public` This room is free for anyone to join without an invite. -`invite` +`invite` This room can only be joined if you were invited. +`knock` +This room can only be joined if you were invited, and allows anyone to +request an invite to the room. Note that this join rule is only available +to rooms based upon [room version 7](/rooms/v7). + The allowable state transitions of membership are: -``` - /ban - +------------------------------------------------------+ - | | - | +----------------+ +----------------+ | - | | /leave | | | | - | | v v | | - /invite +--------+ +-------+ | | - ------------>| invite |<----------| leave |----+ | | - +--------+ /invite +-------+ | | | - | | ^ | | | - | | | | | | - /join | +---------------+ | | | | - | | /join if | | | | - | | join_rules | | /ban | /unban | - | | public /leave | | | | - v v or | | | | - +------+ /kick | | | | - ------------>| join |-------------------+ | | | - /join +------+ v | | - if | +-----+ | | - join_rules +-------------------------->| ban |-----+ | - public /ban +-----+ | - ^ ^ | - | | | - ----------------------------------------------+ +----------------------+ - /ban -``` +![membership-flow-diagram](/diagrams/membership.png) {{% http-api spec="client-server" api="list_joined_rooms" %}} @@ -1838,14 +1819,51 @@ The allowable state transitions of membership are: {{% http-api spec="client-server" api="joining" %}} +##### Knocking on rooms + + + +If the join rules allow, external users to the room can `/knock` on it to +request permission to join. Users with appropriate permissions within the +room can then approve (`/invite`) or deny (`/kick`, `/ban`, or otherwise +set membership to `leave`) the knock. Knocks can be retracted by calling +`/leave` or otherwise setting membership to `leave`. + +Users who are currently in the room, already invited, or banned cannot +knock on the room. + +To accept another user's knock, the user must have permission to invite +users to the room. To reject another user's knock, the user must have +permission to either kick or ban users (whichever is being performed). +Note that setting another user's membership to `leave` is kicking them. + +The knocking homeserver should assume that an invite to the room means +that the knock was accepted, even if the invite is not explicitly related +to the knock. + +Homeservers are permitted to automatically accept invites as a result of +knocks as they should be aware of the user's intent to join the room. If +the homeserver is not auto-accepting invites (or there was an unrecoverable +problem with accepting it), the invite is expected to be passed down normally +to the client to handle. Clients can expect to see the join event if the +server chose to auto-accept. + +{{% http-api spec="client-server" api="knocking" %}} + #### Leaving rooms A user can leave a room to stop receiving events for that room. A user must have been invited to or have joined the room before they are eligible to leave the room. Leaving a room to which the user has been -invited rejects the invite. Once a user leaves a room, it will no longer -appear in the response to the [`/sync`](/client-server-api/#get_matrixclientr0sync) API unless it is explicitly -requested via a filter with the `include_leave` field set to `true`. +invited rejects the invite, and can retract a knock. Once a user leaves +a room, it will no longer appear in the response to the +[`/sync`](/client-server-api/#get_matrixclientr0sync) API unless it is +explicitly requested via a filter with the `include_leave` field set +to `true`. Whether or not they actually joined the room, if the room is an "invite-only" room the user will need to be re-invited before they can @@ -1853,7 +1871,7 @@ re-join the room. A user can also forget a room which they have left. Rooms which have been forgotten will never appear the response to the [`/sync`](/client-server-api/#get_matrixclientr0sync) API, -until the user re-joins or is re-invited. +until the user re-joins, is re-invited, or knocks. A user may wish to force another user to leave a room. This can be done by 'kicking' the other user. To do so, the user performing the kick MUST diff --git a/content/rooms/_index.md b/content/rooms/_index.md index 92e54bfd..80c688a2 100644 --- a/content/rooms/_index.md +++ b/content/rooms/_index.md @@ -10,3 +10,4 @@ weight: 60 * [Room Version 4](v4) * [Room Version 5](v5) * [Room Version 6](v6) +* [Room Version 7](v7) diff --git a/content/rooms/v7.md b/content/rooms/v7.md new file mode 100644 index 00000000..d9d01317 --- /dev/null +++ b/content/rooms/v7.md @@ -0,0 +1,52 @@ +--- +title: Room Version 7 +type: docs +weight: 60 +--- + +This room version builds on [version 6](/rooms/v6) to introduce knocking +as a possible join rule and membership state. + +## Client considerations + +This is the first room version to support knocking completely. As such, +users will not be able to knock on rooms which are not based off v7. + +## Server implementation components + +{{% boxes/warning %}} +The information contained in this section is strictly for server +implementors. Applications which use the Client-Server API are generally +unaffected by the intricacies contained here. The section above +regarding client considerations is the resource that Client-Server API +use cases should reference. +{{% /boxes/warning %}} + +Room version 7 adds new authorization rules for events to support knocking. +[Room version 6](/rooms/v6) has details of other authorization rule changes, +as do the versions v6 is based upon. + +For checks perfomed upon `m.room.member` events, the following conditions +are added in context: + + If type is `m.room.member`: + + ... + + * If `membership` is `ban`: + + ... + + * If `membership` is `knock`: + + i. If the `join_rule` is anything other than `knock`, reject. + + ii. If `sender` does not match `state_key`, reject. + + iii. If the `sender`'s current membership is not `ban`, `invite`, or `join`, allow. + + iv. Otherwise, reject. + + ... + +The remaining rules are the same as in [room version 6](/rooms/v6#authorization-rules-for-events). diff --git a/content/server-server-api.md b/content/server-server-api.md index 32db92d7..e87e8085 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -18,7 +18,7 @@ signatures in HTTP Authorization headers at the HTTP layer. There are three main kinds of communication that occur between homeservers: -Persisted Data Units (PDUs): +Persisted Data Units (PDUs): These events are broadcast from one homeserver to any others that have joined the same room (identified by Room ID). They are persisted in long-term storage and record the history of messages and state for a @@ -29,12 +29,12 @@ to deliver that event to its recipient servers. However PDUs are signed using the originating server's private key so that it is possible to deliver them through third-party servers. -Ephemeral Data Units (EDUs): +Ephemeral Data Units (EDUs): These events are pushed between pairs of homeservers. They are not persisted and are not part of the history of a room, nor does the receiving homeserver have to reply to them. -Queries: +Queries: These are single request/response interactions between a given pair of servers, initiated by one side sending an HTTPS GET request to obtain some information, and responded by the other. They are not persisted and @@ -365,19 +365,19 @@ them. #### Definitions -Required Power Level +Required Power Level A given event type has an associated *required power level*. This is given by the current `m.room.power_levels` event. The event type is either listed explicitly in the `events` section or given by either `state_default` or `events_default` depending on if the event is a state event or not. -Invite Level, Kick Level, Ban Level, Redact Level +Invite Level, Kick Level, Ban Level, Redact Level The levels given by the `invite`, `kick`, `ban`, and `redact` properties in the current `m.room.power_levels` state. Each defaults to 50 if unspecified. -Target User +Target User For an `m.room.member` state event, the user given by the `state_key` of the event. @@ -720,6 +720,24 @@ other servers participating in the room. {{% http-api spec="server-server" api="joins-v2" %}} +## Knocking upon a room + +Rooms can permit knocking through the join rules, and if permitted this +gives users a way to request to join (be invited) to the room. Users who +knock on a room where the server is already a resident of the room can +just send the knock event directly without using this process, however +much like [joining rooms](/server-server-api/#joining-rooms) the server +must handshake their way into having the knock sent on its behalf. + +The handshake is largely the same as the joining rooms handshake, where +instead of a "joining server" there is a "knocking server", and the APIs +to be called are different (`/make_knock` and `/send_knock`). + +Servers can retract knocks over federation by leaving the room, as described +below for rejecting invites. + +{{% http-api spec="server-server" api="knocks" %}} + ## Inviting to a room When a user on a given homeserver invites another user on the same @@ -728,6 +746,10 @@ the process defined here. However, when a user invites another user on a different homeserver, a request to that homeserver to have the event signed and verified must be made. +Note that invites are used to indicate that knocks were accepted. As such, +receiving servers should be prepared to manually link up a previous knock +to an invite if the invite event does not directly reference the knock. + {{% http-api spec="server-server" api="invites-v1" %}} {{% http-api spec="server-server" api="invites-v2" %}} @@ -735,10 +757,10 @@ signed and verified must be made. ## Leaving Rooms (Rejecting Invites) Normally homeservers can send appropriate `m.room.member` events to have -users leave the room, or to reject local invites. Remote invites from -other homeservers do not involve the server in the graph and therefore -need another approach to reject the invite. Joining the room and -promptly leaving is not recommended as clients and servers will +users leave the room, to reject local invites, or to retract a knock. +Remote invites/knocks from other homeservers do not involve the server in the +graph and therefore need another approach to reject the invite. Joining +the room and promptly leaving is not recommended as clients and servers will interpret that as accepting the invite, then leaving the room rather than rejecting the invite. @@ -1009,6 +1031,8 @@ The following endpoint prefixes MUST be protected: - `/_matrix/federation/v2/send_leave` - `/_matrix/federation/v1/invite` - `/_matrix/federation/v2/invite` +- `/_matrix/federation/v1/make_knock` +- `/_matrix/federation/v1/send_knock` - `/_matrix/federation/v1/state` - `/_matrix/federation/v1/state_ids` - `/_matrix/federation/v1/backfill` diff --git a/data/api/client-server/definitions/public_rooms_response.yaml b/data/api/client-server/definitions/public_rooms_response.yaml index 04680246..8cf9935c 100644 --- a/data/api/client-server/definitions/public_rooms_response.yaml +++ b/data/api/client-server/definitions/public_rooms_response.yaml @@ -1,4 +1,4 @@ -# Copyright 2018 New Vector Ltd +# Copyright 2018, 2021 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -69,6 +69,13 @@ properties: avatar_url: type: string description: The URL for the room's avatar, if one is set. + join_rule: + type: string + description: |- + The room's join rule. When not present, the room is assumed to + be `public`. Note that rooms with `invite` join rules are not + expected here, but rooms with `knock` rules are given their + near-public nature. next_batch: type: string description: |- @@ -96,7 +103,8 @@ example: { "num_joined_members": 37, "room_id": "!ol19s:bleecker.street", "topic": "Tasty tasty cheese", - "world_readable": true + "world_readable": true, + "join_rule": "public" } ], "next_batch": "p190q", diff --git a/data/api/client-server/knocking.yaml b/data/api/client-server/knocking.yaml new file mode 100644 index 00000000..0db92d5e --- /dev/null +++ b/data/api/client-server/knocking.yaml @@ -0,0 +1,124 @@ +# Copyright 2021 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Knocking API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/knock/{roomIdOrAlias}": + post: + summary: Knock on a room, requesting permission to join. + description: |- + *Note that this API takes either a room ID or alias, unlike other membership APIs.* + + This API "knocks" on the room to ask for permission to join, if the user + is allowed to knock on the room. Acceptance of the knock happens out of + band from this API, meaning that the client will have to watch for updates + regarding the acceptance/rejection of the knock. + + If the room history settings allow, the user will still be able to see + history of the room while being in the "knock" state. The user will have + to accept the invitation to join the room (acceptance of knock) to see + messages reliably. See the `/join` endpoints for more information about + history visibility to the user. + + The knock will appear as an entry in the response of the + [`/sync`](/client-server-api/#get_matrixclientr0sync) API. + operationId: knockRoom + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomIdOrAlias + description: The room identifier or alias to knock upon. + required: true + x-example: "#monkeys:matrix.org" + - in: query + type: array + items: + type: string + name: server_name + description: |- + The servers to attempt to knock on the room through. One of the servers + must be participating in the room. + x-example: ["matrix.org", "elsewhere.ca"] + - in: body + name: body + required: true + schema: + type: object + properties: + reason: + type: string + description: |- + Optional reason to be included as the `reason` on the subsequent + membership event. + example: "Looking for support" + responses: + 200: + description: |- + The room has been knocked upon. + + The knocked room ID must be returned in the `room_id` field. + examples: + application/json: { + "room_id": "!d41d8cd:matrix.org" + } + schema: + type: object + properties: + room_id: + type: string + description: The knocked room ID. + required: ["room_id"] + 403: + description: |- + You do not have permission to knock on the room. A meaningful `errcode` + and description error text will be returned. Example reasons for rejection are: + + - The room is not set up for knocking. + - The user has been banned from the room. + examples: + application/json: { + "errcode": "M_FORBIDDEN", "error": "You are not allowed to knock on this room." + } + schema: + "$ref": "definitions/errors/error.yaml" + 404: + description: |- + The room could not be found or resolved to a room ID. + examples: + application/json: { + "errcode": "M_NOT_FOUND", "error": "That room does not appear to exist." + } + schema: + "$ref": "definitions/errors/error.yaml" + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/errors/rate_limited.yaml" + tags: + - Room membership diff --git a/data/api/client-server/sync.yaml b/data/api/client-server/sync.yaml index a7090d6b..140843a0 100644 --- a/data/api/client-server/sync.yaml +++ b/data/api/client-server/sync.yaml @@ -1,4 +1,4 @@ -# Copyright 2016 OpenMarket Ltd +# Copyright 2016, 2021 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -281,6 +281,28 @@ paths: items: $ref: "../../event-schemas/schema/stripped_state.yaml" type: array + knock: + title: Knocked rooms + type: object + description: |- + The rooms that the user has knocked upon, mapped as room ID to room information. + additionalProperties: + title: Knocked Room + type: object + properties: + knock_state: + title: KnockState + type: object + description: |- + The state of a room that the user has knocked upon. The state + events contained here have the same restrictions as `InviteState` + above. + properties: + events: + description: The StrippedState events that form the knock state. + items: + $ref: "../../event-schemas/schema/stripped_state.yaml" + type: array leave: title: Left rooms type: object @@ -436,6 +458,26 @@ paths: } } }, + "knock": { + "!223asd456:example.com": { + "invite_state": { + "events": [ + { + "sender": "@alice:example.com", + "type": "m.room.name", + "state_key": "", + "content": {"name": "My Room Name"} + }, + { + "sender": "@bob:example.com", + "type": "m.room.member", + "state_key": "@bob:example.com", + "content": {"membership": "knock"} + } + ] + } + } + }, "leave": {} } } diff --git a/data/api/server-server/knocks.yaml b/data/api/server-server/knocks.yaml new file mode 100644 index 00000000..b774d088 --- /dev/null +++ b/data/api/server-server/knocks.yaml @@ -0,0 +1,321 @@ +# Copyright 2021 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +swagger: '2.0' +info: + title: "Matrix Federation Knock Room API" + version: "1.0.0" +host: localhost:8448 +schemes: + - https +basePath: /_matrix/federation/v1 +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/make_knock/{roomId}/{userId}": + get: + summary: Get information required to make a knock event for a room. + description: |- + Asks the receiving server to return information that the sending + server will need to prepare a knock event for the room. + operationId: makeKnock + security: + - signedRequest: [] + parameters: + - in: path + name: roomId + type: string + description: The room ID that is about to be knocked. + required: true + x-example: "!abc123:example.org" + - in: path + name: userId + type: string + description: The user ID the knock event will be for. + required: true + x-example: "@someone:example.org" + - in: query + type: array + items: + type: string + name: ver + description: |- + The room versions the sending server has support for. + required: true # knocking was supported in v7 + x-example: ["1", "7"] + responses: + 200: + description: |- + A template to be used for the rest of the [KNocking Rooms](/server-server-api/#knocking-rooms) + handshake. Note that events have a different format depending on room version - check the + [room version specification](/#room-versions) for precise event formats. **The response body + here describes the common event fields in more detail and may be missing other + required fields for a PDU.** + schema: + type: object + properties: + room_version: + type: string + required: true # knocking was supported in v7 + description: |- + The version of the room where the server is trying to knock. + example: "7" + event: + description: |- + An unsigned template event. Note that events have a different format + depending on the room version - check the [room version specification](/#room-versions) + for precise event formats. + type: object + title: Event Template + properties: + sender: + type: string + description: The user ID of the knocking member. + example: "@someone:example.org" + origin: + type: string + description: The name of the resident homeserver. + example: "matrix.org" + origin_server_ts: + type: integer + format: int64 + description: A timestamp added by the resident homeserver. + example: 1234567890 + type: + type: string + description: The value `m.room.member`. + example: "m.room.member" + state_key: + type: string + description: The user ID of the knocking member. + example: "@someone:example.org" + content: + type: object + title: Membership Event Content + description: The content of the event. + example: {"membership": "knock"} + properties: + membership: + type: string + description: The value `knock`. + example: "knock" + required: ['membership'] + required: + - state_key + - origin + - origin_server_ts + - type + - content + - sender + examples: + application/json: { + "room_version": "7", + "event": { + "$ref": "examples/minimal_pdu.json", + "type": "m.room.member", + "state_key": "@someone:example.org", + "origin": "example.org", + "origin_server_ts": 1549041175876, + "sender": "@someone:example.org", + "content": { + "membership": "knock" + } + } + } + 400: + description: |- + The request is invalid or the room the server is attempting + to knock upon has a version that is not listed in the `ver` + parameters. + + The error should be passed through to clients so that they + may give better feedback to users. + schema: + allOf: + - $ref: "../client-server/definitions/errors/error.yaml" + - type: object + properties: + room_version: + type: string + description: |- + The version of the room. Required if the `errcode` + is `M_INCOMPATIBLE_ROOM_VERSION`. + examples: + application/json: { + "errcode": "M_INCOMPATIBLE_ROOM_VERSION", + "error": "Your homeserver does not support the features required to knock on this room", + "room_version": "7" + } + 404: + description: |- + The room that the knocking server is attempting to knock upon is unknown + to the receiving server. + schema: + allOf: + - $ref: "../client-server/definitions/errors/error.yaml" + examples: + application/json: { + "errcode": "M_NOT_FOUND", + "error": "Unknown room", + } + 403: + description: |- + The knocking server or user is not permitted to knock on the room, such as when the + server/user is banned or the room is not set up for receiving knocks. + schema: + allOf: + - $ref: "../client-server/definitions/errors/error.yaml" + examples: + application/json: { + "errcode": "M_FORBIDDEN", + "error": "You are not permitted to knock on this room", + } + + "/send_knock/{roomId}/{eventId}": + put: + summary: Submit a signed knock event to a resident server. + description: |- + Submits a signed knock event to the resident server for it to + accept into the room's graph. Note that events have + a different format depending on the room version - check + the [room version specification](/#room-versions) for precise event formats. + **The request and response body here describe the common + event fields in more detail and may be missing other required + fields for a PDU.** + operationId: sendKnock + security: + - signedRequest: [] + parameters: + - in: path + name: roomId + type: string + description: The room ID that is about to be knocked upon. + required: true + x-example: "!abc123:example.org" + - in: path + name: eventId + type: string + description: The event ID for the knock event. + required: true + x-example: "$abc123:example.org" + - in: body + name: body + type: object + required: true + schema: + type: object + properties: + sender: + type: string + description: The user ID of the knocking member. + example: "@someone:example.org" + origin: + type: string + description: The name of the knocking homeserver. + example: "matrix.org" + origin_server_ts: + type: integer + format: int64 + description: A timestamp added by the knocking homeserver. + example: 1234567890 + type: + type: string + description: The value `m.room.member`. + example: "m.room.member" + state_key: + type: string + description: The user ID of the knocking member. + example: "@someone:example.org" + content: + type: object + title: Membership Event Content + description: The content of the event. + example: {"membership": "knock"} + properties: + membership: + type: string + description: The value `knock`. + example: "knock" + required: ['membership'] + required: + - state_key + - sender + - origin + - origin_server_ts + - type + - content + example: { + "$ref": "examples/minimal_pdu.json", + "type": "m.room.member", + "state_key": "@someone:example.org", + "origin": "example.org", + "origin_server_ts": 1549041175876, + "sender": "@someone:example.org", + "content": { + "membership": "knock" + } + } + responses: + 200: + description: |- + Information about the room to pass along to clients regarding the + knock. + schema: + type: object + properties: + knock_room_state: + type: array + items: + $ref: "../../event-schemas/schema/stripped_state.yaml" + description: |- + A list of simplified events to help the initiator of the knock identify + the room. The recommended events to include are the join rules, canonical + alias, avatar, name, and encryption state of the room. + example: + $ref: "../../event-schemas/examples/knock_room_state.json" + required: ['knock_room_state'] + examples: + application/json: { + "knock_room_state": {"$ref": "../../event-schemas/examples/knock_room_state.json"} + } + 404: + description: |- + The room that the knocking server is attempting to knock upon is unknown + to the receiving server. + schema: + allOf: + - $ref: "../client-server/definitions/errors/error.yaml" + examples: + application/json: { + "errcode": "M_NOT_FOUND", + "error": "Unknown room", + } + 403: + description: |- + The knocking server or user is not permitted to knock on the room, such as when the + server/user is banned or the room is not set up for receiving knocks. + schema: + allOf: + - $ref: "../client-server/definitions/errors/error.yaml" + examples: + application/json: { + "errcode": "M_FORBIDDEN", + "error": "You are not permitted to knock on this room", + } + + diff --git a/data/api/server-server/public_rooms.yaml b/data/api/server-server/public_rooms.yaml index e52f6d48..23517785 100644 --- a/data/api/server-server/public_rooms.yaml +++ b/data/api/server-server/public_rooms.yaml @@ -1,4 +1,4 @@ -# Copyright 2018 New Vector Ltd +# Copyright 2018, 2021 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -193,6 +193,13 @@ paths: avatar_url: type: string description: The URL for the room's avatar, if one is set. + join_rule: + type: string + description: |- + The room's join rule. When not present, the room is assumed to + be `public`. Note that rooms with `invite` join rules are not + expected here, but rooms with `knock` rules are given their + near-public nature. next_batch: type: string description: |- @@ -221,7 +228,8 @@ paths: "num_joined_members": 37, "room_id": "!ol19s:bleecker.street", "topic": "Tasty tasty cheese", - "world_readable": true + "world_readable": true, + "join_rule": "public" } ], "next_batch": "p190q", diff --git a/data/event-schemas/examples/knock_room_state.json b/data/event-schemas/examples/knock_room_state.json new file mode 100644 index 00000000..9692bb31 --- /dev/null +++ b/data/event-schemas/examples/knock_room_state.json @@ -0,0 +1,18 @@ +[ + { + "type": "m.room.name", + "sender": "@bob:example.org", + "state_key": "", + "content": { + "name": "Example Room" + } + }, + { + "type": "m.room.join_rules", + "sender": "@bob:example.org", + "state_key": "", + "content": { + "join_rule": "knock" + } + } +] diff --git a/data/event-schemas/examples/m.room.member$knock_room_state.yaml b/data/event-schemas/examples/m.room.member$knock_room_state.yaml new file mode 100644 index 00000000..c1ec3a32 --- /dev/null +++ b/data/event-schemas/examples/m.room.member$knock_room_state.yaml @@ -0,0 +1,15 @@ +{ + "$ref": "m.room.member.yaml", + "content": { + "membership": "knock", + "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF", + "displayname": "Alice Margatroid", + "reason": "Looking for support" + }, + "unsigned": { + "age": 1234, + "knock_room_state": { + "$ref": "knock_room_state.json" + } + } +} diff --git a/data/event-schemas/schema/m.room.join_rules.yaml b/data/event-schemas/schema/m.room.join_rules.yaml index faa0f249..5f0e11af 100644 --- a/data/event-schemas/schema/m.room.join_rules.yaml +++ b/data/event-schemas/schema/m.room.join_rules.yaml @@ -1,7 +1,16 @@ --- allOf: - $ref: core-event-schema/state_event.yaml -description: 'A room may be `public` meaning anyone can join the room without any prior action. Alternatively, it can be `invite` meaning that a user who wishes to join the room must first receive an invite to the room from someone already inside of the room. Currently, `knock` and `private` are reserved keywords which are not implemented.' +description: | + A room may be `public` meaning anyone can join the room without any prior action. + Alternatively, it can be `invite` meaning that a user who wishes to join the room + must first receive an invite to the room from someone already inside of the room. + `knock` means that users are able to ask for permission to join the room, where + they are either allowed (invited) or denied (kicked/banned) access. Join rules + of `knock` are otherwise the same as `invite`: the user needs an explicit invite + to join the room. + + Currently, `private` is a reserved keyword which is not implemented. properties: content: properties: diff --git a/data/event-schemas/schema/m.room.member.yaml b/data/event-schemas/schema/m.room.member.yaml index 181c5737..928e1528 100644 --- a/data/event-schemas/schema/m.room.member.yaml +++ b/data/event-schemas/schema/m.room.member.yaml @@ -14,7 +14,7 @@ description: |- - `ban` - The user has been banned from the room, and is no longer allowed to join it until they are un-banned from the room (by having their membership state set to a value other than `ban`). - - `knock` - This is a reserved word, which currently has no meaning. + - `knock` - The user has knocked on the room, requesting permission to participate. They may not participate in the room until they join. The `third_party_invite` property will be set if this invite is an `invite` event and is the successor of an `m.room.third_party_invite` event, and absent otherwise. @@ -31,13 +31,13 @@ description: |- from the `prev_content` object on an event. If not present, the user's previous membership must be assumed as `leave`. - | | to `invite` | to `join` | to `leave` | to `ban` | to `knock` | - |-------------------|---------------------|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|------------------| - | **from `invite`** | No change. | User joined the room. | If the `state_key` is the same as the `sender`, the user rejected the invite. Otherwise, the `state_key` user had their invite revoked. | User was banned. | Not implemented. | - | **from `join`** |Must never happen. | `displayname` or `avatar_url` changed. | If the `state_key` is the same as the `sender`, the user left. Otherwise, the `state_key` user was kicked. | User was kicked and banned. | Not implemented. | - | **from `leave`** |New invitation sent. | User joined. | No change. | User was banned. | Not implemented. | - | **from `ban`** |Must never happen. | Must never happen. | User was unbanned. | No change. | Not implemented. | - | **from `knock`** |Not implemented. | Not implemented. | Not implemented. | Not implemented. | Not implemented. | + | | to `invite` | to `join` | to `leave` | to `ban` | to `knock` | + |-------------------|----------------------|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|--------------------| + | **from `invite`** | No change. | User joined the room. | If the `state_key` is the same as the `sender`, the user rejected the invite. Otherwise, the `state_key` user had their invite revoked. | User was banned. | Must never happen. | + | **from `join`** | Must never happen. | `displayname` or `avatar_url` changed. | If the `state_key` is the same as the `sender`, the user left. Otherwise, the `state_key` user was kicked. | User was kicked and banned. | Must never happen. | + | **from `leave`** | New invitation sent. | User joined. | No change. | User was banned. | User is knocking. | + | **from `ban`** | Must never happen. | Must never happen. | User was unbanned. | No change. | Must never happen. | + | **from `knock`** | Knock accepted. | Must never happen. | If the `state_key` is the same as the `sender`, the user retracted the knock. Otherwise, the `state_key` user had their knock denied. | User was banned. | No change. | properties: content: @@ -124,7 +124,24 @@ properties: - type: object properties: invite_room_state: - description: 'A subset of the state of the room at the time of the invite, if `membership` is `invite`. Note that this state is informational, and SHOULD NOT be trusted; once the client has joined the room, it SHOULD fetch the live state from the server and discard the invite_room_state. Also, clients must not rely on any particular state being present here; they SHOULD behave properly (with possibly a degraded but not a broken experience) in the absence of any particular events here. If they are set on the room, at least the state for `m.room.avatar`, `m.room.canonical_alias`, `m.room.join_rules`, and `m.room.name` SHOULD be included.' + description: |- + A subset of the state of the room at the time of the invite, if `membership` is `invite`. + Note that this state is informational, and SHOULD NOT be trusted; once the client has + joined the room, it SHOULD fetch the live state from the server and discard the + invite_room_state. Also, clients must not rely on any particular state being present here; + they SHOULD behave properly (with possibly a degraded but not a broken experience) in + the absence of any particular events here. If they are set on the room, at least the + state for `m.room.avatar`, `m.room.canonical_alias`, `m.room.join_rules`, and `m.room.name` + SHOULD be included. + items: + $ref: "stripped_state.yaml" + type: array + knock_room_state: + description: |- + A subset of the state of the room at the time of the knock, if `membership` is `knock`. + This has the same restrictions as `invite_room_state`. If they are set on the room, at least + the state for `m.room.avatar`, `m.room.canonical_alias`, `m.room.join_rules`, `m.room.name`, + and `m.room.encryption` SHOULD be included. items: $ref: "stripped_state.yaml" type: array diff --git a/static/diagrams/README.md b/static/diagrams/README.md new file mode 100644 index 00000000..0b74734c --- /dev/null +++ b/static/diagrams/README.md @@ -0,0 +1,17 @@ +# Spec diagrams + +Non-ascii diagrams for the spec can be placed here for reference in the actual spec. +Please include source material so the diagram can be recreated by a future editor. + +https://www.diagrams.net/ is a great ([open source](https://github.com/jgraph/drawio)) +tool for these sorts of things - include your `.drawio` file next to your diagram. + +Suggested settings for diagrams.net: +* Export as PNG. +* 100% size. +* `20` for a border width. +* No transparent background, shadow, or grid. +* Include a copy of the diagram. + +To reference a diagram, use the absolute path when compiled. For example, +`![membership-flow-diagram](/diagrams/membership.png)` diff --git a/static/diagrams/membership.drawio b/static/diagrams/membership.drawio new file mode 100644 index 00000000..1c6708de --- /dev/null +++ b/static/diagrams/membership.drawio @@ -0,0 +1 @@ +3Vvdb6M4EP9rIt09NMIGA3ncZtvbh73TSn243X1ZOYmT0BIcOaZJ9q8/E0z4cggEgulVqoqHsbHHv/nwjDsyp5vDXwxv13/TBfFH0FgcRubnEYTAAa74E1GOMWViopiwYt5CMqWEF+83kURDUkNvQXY5Rk6pz71tnjinQUDmPEfDjNF9nm1J/fxXt3hFSoSXOfbL1H+9BV/HVBc6Kf0L8Vbr5MvAnsRvNjhhlivZrfGC7jMk82lkThmlPH7aHKbEj4SXyCXu93zh7XlijAS8TodvBvr96euj/xL83G+Dn2gaTvgDkMO8Yz+UKxYjvFIvkJPmx0QSZCEEI5uU8TVd0QD7Tyn1kdEwWJDoc4ZopTxfKd0KIhDEV8L5Ue4yDjkVpDXf+PJt/M3oQxfXKEk7GrI5qVqYxApmK8Ir+OB5JwSECd0Qzo6iHyM+5t57fh5YYml15pNdPzGGjxmGrZAf32VG/hYRBINUCziRmJBKYdqFrWvGLx7iGSStzFJS0gkOTaBhqaDhEyykogEb5ODx75nnH9FQY4hk8/NBDn1qHJNGIGTwPcMZtX9kX6b9Tq2kY4dIhKgmFC01FCUGjDEwHDMeqhk6S3Cy3DycoDUZ25kfx8qPGM9cDpLCrinqLVD4rG1Wot4CqIo/j/q0dzIdulzuCC/06EYzSorhBe8eL6tFHvT7teB52eITWvbCXaqM3zthnByqQXcRI9ApSBjJ9j51XcCQtHXGbSV8KgzlpNdYVHaFETEoG0VO6/nNm799cH8DO1JysRduOyW/P/4hVG3qDGuJGRK/YIxRxjOA29wC6NEt1ASMWQmYB2NsI6ulW+gBMSXAKEPMRvZySQP+jDeeH23OVIjbI5E5+Yfs72NMLUu7MXVVehcGujUvF5HVDMhAVu1kr34Uz2xnqWsrWautNks7rdrkQekLQoVDiqldX5R+KrY8hreMVhfOfG8+grYv5vI4E/KwV9FTxPOLhT7Z6XVp8Caf5ug66lg1VavtobsdKsq6Jca5EMH3sduXNq7HYKT2xqFr0QgaeigCnEEGr05W0426mq4tqVEXMBei1340vZzAUmevBuVGzWKuT7sbVUadGu1lO53RFnfCSWdG1rVdq52dPddnCgkjB+WHiFdVSv31arCNjx3F5RPW1ccjLXBraaLrIcuy6yGrs4yDsrCly82nHrtRhHdLbksHhK7mqFxogrsYLGTVg1XTWgUo1CqQWV2hs41Kfn21CqhMwA/Ae9+SNGp6tM3Fal+I/064N8cdK0lHZycRzN+iIU2BbVr5ohqYoIEAVXkuG0qlqGWV4eOitrpeJUy7A4ZffjAHFQzcEBf+L6B0NUpwEGoZJfRgpspJhbeAKozSoJMK5zBKW1IhuRXT8SWKvouC+m9YJEF6RpBPB06YMEknl1k8FP8R0JOmiN8N2cwI+7NC4qChxMsmpl1JqXCGVKXCoELY9r2EbSpzEdIAxMmIU+MBz4QwB5qQKJSVaiebu/E8r+Fmmyw0oAFJSPGsHV1JkKtxjuvCm3JuTQP0wt08YFUfPM+3EtT8Gi/Jle2S/pjrVug3O3ZmIY7ZvGNI171OejXcmvRy4ISTwoETXcmkwEr+ewBaNNNr+DF7+s8M5tN/ \ No newline at end of file diff --git a/static/diagrams/membership.png b/static/diagrams/membership.png new file mode 100644 index 0000000000000000000000000000000000000000..891b338b92a2307a0049aeb04e30781a8ded4386 GIT binary patch literal 28770 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 arL~JkS+X^yPJrHlByfeh@&&Sne*XdiKIv-! literal 0 HcmV?d00001 From 97b11a6d4958574b29a505c4c5f03ae6249de79a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 28 Apr 2021 19:53:02 -0600 Subject: [PATCH 2/6] Add changelogs --- changelogs/client_server/newsfragments/3154.feature | 1 + changelogs/server_server/newsfragments/3154.feature | 1 + changelogs/server_server/newsfragments/3154.new | 1 + 3 files changed, 3 insertions(+) create mode 100644 changelogs/client_server/newsfragments/3154.feature create mode 100644 changelogs/server_server/newsfragments/3154.feature create mode 100644 changelogs/server_server/newsfragments/3154.new diff --git a/changelogs/client_server/newsfragments/3154.feature b/changelogs/client_server/newsfragments/3154.feature new file mode 100644 index 00000000..c73c27ec --- /dev/null +++ b/changelogs/client_server/newsfragments/3154.feature @@ -0,0 +1 @@ +Add support for knocking, as per [MSC2403](https://github.com/matrix-org/matrix-doc/pull/2403). \ No newline at end of file diff --git a/changelogs/server_server/newsfragments/3154.feature b/changelogs/server_server/newsfragments/3154.feature new file mode 100644 index 00000000..c73c27ec --- /dev/null +++ b/changelogs/server_server/newsfragments/3154.feature @@ -0,0 +1 @@ +Add support for knocking, as per [MSC2403](https://github.com/matrix-org/matrix-doc/pull/2403). \ No newline at end of file diff --git a/changelogs/server_server/newsfragments/3154.new b/changelogs/server_server/newsfragments/3154.new new file mode 100644 index 00000000..7791516f --- /dev/null +++ b/changelogs/server_server/newsfragments/3154.new @@ -0,0 +1 @@ +Add `/make_knock` and `/send_knock` endpoints as per [MSC2403](https://github.com/matrix-org/matrix-doc/pull/2403). \ No newline at end of file From 60b5930214c65135d25aa819279af2e4595b1ce9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 28 Apr 2021 19:54:58 -0600 Subject: [PATCH 3/6] Declare required properties correctly --- data/api/server-server/knocks.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/api/server-server/knocks.yaml b/data/api/server-server/knocks.yaml index b774d088..9544c355 100644 --- a/data/api/server-server/knocks.yaml +++ b/data/api/server-server/knocks.yaml @@ -71,7 +71,6 @@ paths: properties: room_version: type: string - required: true # knocking was supported in v7 description: |- The version of the room where the server is trying to knock. example: "7" @@ -122,6 +121,9 @@ paths: - type - content - sender + required: + - room_version # knocking was added in v7 + - event examples: application/json: { "room_version": "7", From cbe362fc7ce198f8226bef6ec5a52aa85ebc8b11 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 29 Apr 2021 16:39:02 -0600 Subject: [PATCH 4/6] Update data/api/server-server/knocks.yaml Co-authored-by: Hubert Chathi --- data/api/server-server/knocks.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/api/server-server/knocks.yaml b/data/api/server-server/knocks.yaml index 9544c355..945362a1 100644 --- a/data/api/server-server/knocks.yaml +++ b/data/api/server-server/knocks.yaml @@ -61,7 +61,7 @@ paths: responses: 200: description: |- - A template to be used for the rest of the [KNocking Rooms](/server-server-api/#knocking-rooms) + A template to be used for the rest of the [Knocking Rooms](/server-server-api/#knocking-rooms) handshake. Note that events have a different format depending on room version - check the [room version specification](/#room-versions) for precise event formats. **The response body here describes the common event fields in more detail and may be missing other @@ -320,4 +320,3 @@ paths: "error": "You are not permitted to knock on this room", } - From 509e056dfa3b59394326432d13b843b6c03459ec Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 29 Apr 2021 22:19:27 -0600 Subject: [PATCH 5/6] Fix general wording --- content/client-server-api/_index.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index a9103c7a..ec26cec8 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1785,11 +1785,17 @@ relation to a room: - Joined (the user can send and receive events in the room) - Banned (the user is not allowed to join the room) -There is an exception to the requirement that a user join a room before -sending events to it: users may send an `m.room.member` event to a room -with `content.membership` set to `leave` to reject an invitation if they -have currently been invited to a room but have not joined it. The same -applies for retracting knocks on the room that the user sent. +There are a few notable exceptions which allow non-joined members of the +room to send events in the room: + +- Users wishing to reject an invite would send `m.room.member` events with + `content.membership` of `leave`. They must have been invited first. + +- If the room allows, users can send `m.room.member` events with `content.membership` + of `knock` to knock on the room. This is a request for an invite by the user. + +- To retract a previous knock, a user would send a `leave` event similar to + rejecting an invite. Some rooms require that users be invited to it before they can join; others allow anyone to join. Whether a given room is an "invite-only" From 5278027778827ece3b85b4caf4e4ab9b60544e6b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 29 Apr 2021 22:19:54 -0600 Subject: [PATCH 6/6] Add missing endpoint changelog --- changelogs/client_server/newsfragments/3154.new | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/client_server/newsfragments/3154.new diff --git a/changelogs/client_server/newsfragments/3154.new b/changelogs/client_server/newsfragments/3154.new new file mode 100644 index 00000000..aa89f639 --- /dev/null +++ b/changelogs/client_server/newsfragments/3154.new @@ -0,0 +1 @@ +Add `/knock` endpoint as per [MSC2403](https://github.com/matrix-org/matrix-doc/pull/2403). \ No newline at end of file