From 85b49895e2530ef5982bfc623be3ab37fe2b3ce4 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Wed, 19 Jan 2022 12:37:03 +0000 Subject: [PATCH 01/12] Create 3653-invite-rules.md --- proposals/3653-invite-rules.md | 79 ++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 proposals/3653-invite-rules.md diff --git a/proposals/3653-invite-rules.md b/proposals/3653-invite-rules.md new file mode 100644 index 00000000000..f1c5148578f --- /dev/null +++ b/proposals/3653-invite-rules.md @@ -0,0 +1,79 @@ +# MSC3653 - Invite Rules + +This MSC proposes the creation of an optional account state which allows users to control how invites directed at them +are processed by their homeserver. + +*Homeservers may choose to ignore an Invitee's invite rules *if* the Inviter is a homeserver admin.* + +While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3653.invite_rules` + +### Glossery +- Inviter: The matrix user which has created the invite request. +- Invitee: The matrix user which is receiving the invite request. +- Invite request: An invite that the homeserver is to process. For Synapse, this would be handled by [`on_invite_request`](https://github.com/matrix-org/synapse/blob/develop/synapse/handlers/federation.py#L752). + +### `m.invite_rules` + +An invite rules state contains one required key, and two optional keys. +- `"invite_rule"`: A required String-Enum which has four values + - `"invite_rule": "all"`: Identical behaviour to the `m.invite_rules` state not existing, no special processing is performed + for the Invitee. + - `"invite_rule": "has-shared-room"`: Only allow invites where the Inviter shares at least one room with the Invitee. + - `"invite_rule": "has-direct-room"`: Only allow invites where the Inviter shares at least one direct room with the Invitee. + - `"invite_rule": "none"`: Prevent any invites from being sent to the Invitee. +- `"allow"`: An Array of RuleItems where if any are true, an invite request will not be blocked. +- `"deny"`: An Array of RuleItems where if any are true, an invite request will be blocked. + +#### `RuleItem` +A RuleItem defines a Rule that can test against an invite request. This primarily exists for structural consistency with the room state `m.join_rules`. +It also serves to allow `m.invite_rules` to be easily extended in the future, such as to introduce an `m.ruleset` type that would accept Mjolnir ruleset rooms. + +- `"type"`: Required type, only valid value is `"m.user"` +- `"user_id"`: Required type if the `"type"` is set to `"m.user"`, should contain a valid MXID. + + +#### Evaluation + +In order to prevent homeservers from interpriting `m.invite_rule` states differently, an evaluation order is defined here: + +- An Inviter attempts to create an invite request to the Invitee. + - If `"m.invite_rules"` exists as an account state: + - If `"allow"` exists, evaluate the defined Rulesets. If one evaluates as True, break from the `"m.invite_rules"` check. + - If `"deny"` exists, evaluate the defined Rulesets. If one evaluates as True, reject the invite request. + - If `"type"` within content of `"m.invite_rules"` is: + 1. `"all"`: Break from the `m.invite_rules` check and continue. + 2. `"has-shared-room"`: Get all Rooms Invitee is in, and check if the Inviter has a `"join"` membership state. + If the Inviter does not have at least one shared room, Reject the invite request. + 3. `"has-direct-room"`: Get the Invitee's account state `"m.direct"` exists. + - If true, test if the content of `"m.direct"` contains a key which is the Inviter's MXID. + - If `True`, test if the Invitee has a `"join"` membership state in any rooms defined in the key's value. If no matches are found, reject the invite request. + - If `False`, reject the invite request. + 4. `"none"`: Reject the invite request. + +#### Ruleset evaluation: m.user +Ruleset evaluation is fairly simple. For each ruleset, test if the Inviter's MXID is equal to the defined `"user_id"`. + +#### Invite Rejection +If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN error code, and the error message: "This user is not permitted to send invites to this server/user" + +#### Example: +```js +{ + "type": "m.invite_rules", + "content": { + "invite_rule": "has-direct-room", + "allow": [ + { + "type": "m.user", + "user_id": "@bob:example.com" + } + ], + "deny": [ + { + "type": "m.user", + "user_id": "@alice:example.com" + } + ] + } +} +``` From 1da1061d337505f46fb23cb006e90334bde18ebf Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Wed, 19 Jan 2022 12:47:20 +0000 Subject: [PATCH 02/12] /MSC3653/MSC3659/s --- proposals/{3653-invite-rules.md => 3659-invite-rules.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename proposals/{3653-invite-rules.md => 3659-invite-rules.md} (97%) diff --git a/proposals/3653-invite-rules.md b/proposals/3659-invite-rules.md similarity index 97% rename from proposals/3653-invite-rules.md rename to proposals/3659-invite-rules.md index f1c5148578f..a300bceeab8 100644 --- a/proposals/3653-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -1,11 +1,11 @@ -# MSC3653 - Invite Rules +# MSC3659 - Invite Rules This MSC proposes the creation of an optional account state which allows users to control how invites directed at them are processed by their homeserver. *Homeservers may choose to ignore an Invitee's invite rules *if* the Inviter is a homeserver admin.* -While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3653.invite_rules` +While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3659.invite_rules` ### Glossery - Inviter: The matrix user which has created the invite request. From 3fbaaa10bcb3ae6c022996ed27a30718b1e54258 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:04:52 +0000 Subject: [PATCH 03/12] Update 3659-invite-rules.md - Added Alternatives - Added Potential Issues - Moved unstable prefix to it's own section - Added additional RuleItem types, m.shared_room and m.target_room. --- proposals/3659-invite-rules.md | 42 ++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index a300bceeab8..a9e69ab20d6 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -5,7 +5,7 @@ are processed by their homeserver. *Homeservers may choose to ignore an Invitee's invite rules *if* the Inviter is a homeserver admin.* -While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3659.invite_rules` +## Proposal ### Glossery - Inviter: The matrix user which has created the invite request. @@ -28,9 +28,19 @@ An invite rules state contains one required key, and two optional keys. A RuleItem defines a Rule that can test against an invite request. This primarily exists for structural consistency with the room state `m.join_rules`. It also serves to allow `m.invite_rules` to be easily extended in the future, such as to introduce an `m.ruleset` type that would accept Mjolnir ruleset rooms. -- `"type"`: Required type, only valid value is `"m.user"` -- `"user_id"`: Required type if the `"type"` is set to `"m.user"`, should contain a valid MXID. +- `"type"`: Required String-Enum, must be one of the defined types below. + +##### `m.user` +Validates as true if the Inviter MXID is equal to the defined `"user_id"` +- `"user_id"`: Required String, a valid MXID. + +##### `m.shared_room` +Validates as true if the Inviter and Invitee are in the defined `"room_id"`. +- `"room_id"`: Required String, a valid room id. +##### `m.target_room` +Validates as true if the room which the Invitee is being invited to has the same room id as the defined `"room_id"`. +- `"room_id"`: Required String, a valid room id. #### Evaluation @@ -44,15 +54,13 @@ In order to prevent homeservers from interpriting `m.invite_rule` states differe 1. `"all"`: Break from the `m.invite_rules` check and continue. 2. `"has-shared-room"`: Get all Rooms Invitee is in, and check if the Inviter has a `"join"` membership state. If the Inviter does not have at least one shared room, Reject the invite request. - 3. `"has-direct-room"`: Get the Invitee's account state `"m.direct"` exists. + 3. `"has-direct-room"`: Check if the Invitee's account state `"m.direct"` exists. - If true, test if the content of `"m.direct"` contains a key which is the Inviter's MXID. - If `True`, test if the Invitee has a `"join"` membership state in any rooms defined in the key's value. If no matches are found, reject the invite request. - If `False`, reject the invite request. + - If false, reject the invite request. 4. `"none"`: Reject the invite request. -#### Ruleset evaluation: m.user -Ruleset evaluation is fairly simple. For each ruleset, test if the Inviter's MXID is equal to the defined `"user_id"`. - #### Invite Rejection If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN error code, and the error message: "This user is not permitted to send invites to this server/user" @@ -66,14 +74,34 @@ If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN { "type": "m.user", "user_id": "@bob:example.com" + }, + { + "type": "m.shared_room", + "room_id": "!a:example.com" } ], "deny": [ { "type": "m.user", "user_id": "@alice:example.com" + }, + { + "type": "m.target_room", + "room_id": "!b:example.com" } ] } } ``` + +## Alternatives +Currently, there is no way outside of homeserver-wide restrictions (mjolnir, anti-spam plugins), for users to control who can send them invites. While users can ignore single users to prevent them from sending them invites, this does nothing since a malicious user simply create another matrix account. + +## Potential Issues +There is a potential denial of service for the `has-shared-room` and `has-direct-room` invite rules, as they require searching through all rooms a user is in, which could be a lot. This heavily depends on the homeserver's internals of course. + +Additionally, as no limit for the `"allow"` and `"deny"` rulesets is specified, an Invitee could make hundreads of rules to attempt to +slow the homeserver down. For homeservers where this may be a concern, a sensible limit should be configurable by the homeserver admin. + +## Unstable prefix +While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3659.invite_rules` From 4c777e931b92b92f137d7b04259fe2b945f5b955 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:31:50 +0000 Subject: [PATCH 04/12] s/account state/account data state/ --- proposals/3659-invite-rules.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index a9e69ab20d6..c7aae09a9b4 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -1,6 +1,6 @@ # MSC3659 - Invite Rules -This MSC proposes the creation of an optional account state which allows users to control how invites directed at them +This MSC proposes the creation of an optional account data state which allows users to control how invites directed at them are processed by their homeserver. *Homeservers may choose to ignore an Invitee's invite rules *if* the Inviter is a homeserver admin.* @@ -54,7 +54,7 @@ In order to prevent homeservers from interpriting `m.invite_rule` states differe 1. `"all"`: Break from the `m.invite_rules` check and continue. 2. `"has-shared-room"`: Get all Rooms Invitee is in, and check if the Inviter has a `"join"` membership state. If the Inviter does not have at least one shared room, Reject the invite request. - 3. `"has-direct-room"`: Check if the Invitee's account state `"m.direct"` exists. + 3. `"has-direct-room"`: Check if the Invitee's account data state `"m.direct"` exists. - If true, test if the content of `"m.direct"` contains a key which is the Inviter's MXID. - If `True`, test if the Invitee has a `"join"` membership state in any rooms defined in the key's value. If no matches are found, reject the invite request. - If `False`, reject the invite request. From 635b8e364f1d934bb1f8988d4663f75453165e83 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Thu, 20 Jan 2022 10:48:26 +0000 Subject: [PATCH 05/12] Small changes --- proposals/3659-invite-rules.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index c7aae09a9b4..e673cd1d901 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -62,7 +62,7 @@ In order to prevent homeservers from interpriting `m.invite_rule` states differe 4. `"none"`: Reject the invite request. #### Invite Rejection -If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN error code, and the error message: "This user is not permitted to send invites to this server/user" +If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN, and the error message: "This user is not permitted to send invites to this server/user" #### Example: ```js @@ -95,7 +95,7 @@ If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN ``` ## Alternatives -Currently, there is no way outside of homeserver-wide restrictions (mjolnir, anti-spam plugins), for users to control who can send them invites. While users can ignore single users to prevent them from sending them invites, this does nothing since a malicious user simply create another matrix account. +Currently, there is no way outside of homeserver-wide restrictions (mjolnir, anti-spam plugins), for users to control who can send them invites. While users can ignore single users to prevent them from sending them invites, this does little since a malicious user simply create another matrix account. ## Potential Issues There is a potential denial of service for the `has-shared-room` and `has-direct-room` invite rules, as they require searching through all rooms a user is in, which could be a lot. This heavily depends on the homeserver's internals of course. From c118a1ddabd73a3eb9606c1e4a4b49b3392b9f9c Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Sat, 22 Jan 2022 20:34:18 +0000 Subject: [PATCH 06/12] s/true/True s/false/False --- proposals/3659-invite-rules.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index e673cd1d901..acf7aab16f0 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -21,8 +21,8 @@ An invite rules state contains one required key, and two optional keys. - `"invite_rule": "has-shared-room"`: Only allow invites where the Inviter shares at least one room with the Invitee. - `"invite_rule": "has-direct-room"`: Only allow invites where the Inviter shares at least one direct room with the Invitee. - `"invite_rule": "none"`: Prevent any invites from being sent to the Invitee. -- `"allow"`: An Array of RuleItems where if any are true, an invite request will not be blocked. -- `"deny"`: An Array of RuleItems where if any are true, an invite request will be blocked. +- `"allow"`: An Array of RuleItems where if any are True, an invite request will not be blocked. +- `"deny"`: An Array of RuleItems where if any are True, an invite request will be blocked. #### `RuleItem` A RuleItem defines a Rule that can test against an invite request. This primarily exists for structural consistency with the room state `m.join_rules`. @@ -31,15 +31,15 @@ It also serves to allow `m.invite_rules` to be easily extended in the future, su - `"type"`: Required String-Enum, must be one of the defined types below. ##### `m.user` -Validates as true if the Inviter MXID is equal to the defined `"user_id"` +Validates as True if the Inviter MXID is equal to the defined `"user_id"` - `"user_id"`: Required String, a valid MXID. ##### `m.shared_room` -Validates as true if the Inviter and Invitee are in the defined `"room_id"`. +Validates as True if the Inviter and Invitee are in the defined `"room_id"`. - `"room_id"`: Required String, a valid room id. ##### `m.target_room` -Validates as true if the room which the Invitee is being invited to has the same room id as the defined `"room_id"`. +Validates as True if the room which the Invitee is being invited to has the same room id as the defined `"room_id"`. - `"room_id"`: Required String, a valid room id. #### Evaluation @@ -55,10 +55,10 @@ In order to prevent homeservers from interpriting `m.invite_rule` states differe 2. `"has-shared-room"`: Get all Rooms Invitee is in, and check if the Inviter has a `"join"` membership state. If the Inviter does not have at least one shared room, Reject the invite request. 3. `"has-direct-room"`: Check if the Invitee's account data state `"m.direct"` exists. - - If true, test if the content of `"m.direct"` contains a key which is the Inviter's MXID. - - If `True`, test if the Invitee has a `"join"` membership state in any rooms defined in the key's value. If no matches are found, reject the invite request. - - If `False`, reject the invite request. - - If false, reject the invite request. + - If True, test if the content of `"m.direct"` contains a key which is the Inviter's MXID. + - If True, test if the Invitee has a `"join"` membership state in any rooms defined in the key's value. If no matches are found, reject the invite request. + - If False, reject the invite request. + - If False, reject the invite request. 4. `"none"`: Reject the invite request. #### Invite Rejection From 89dda605d9244db9737769a237e2a2e03f6749a4 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Wed, 26 Jan 2022 03:58:20 +0000 Subject: [PATCH 07/12] Merge allow-deny, change invite_rule to RuleItem Justification for change: https://github.com/matrix-org/matrix-doc/pull/3659#discussion_r790186574 --- proposals/3659-invite-rules.md | 121 +++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 43 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index acf7aab16f0..c6a4f2b4a0f 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -14,80 +14,114 @@ are processed by their homeserver. ### `m.invite_rules` -An invite rules state contains one required key, and two optional keys. -- `"invite_rule"`: A required String-Enum which has four values - - `"invite_rule": "all"`: Identical behaviour to the `m.invite_rules` state not existing, no special processing is performed - for the Invitee. - - `"invite_rule": "has-shared-room"`: Only allow invites where the Inviter shares at least one room with the Invitee. - - `"invite_rule": "has-direct-room"`: Only allow invites where the Inviter shares at least one direct room with the Invitee. - - `"invite_rule": "none"`: Prevent any invites from being sent to the Invitee. -- `"allow"`: An Array of RuleItems where if any are True, an invite request will not be blocked. -- `"deny"`: An Array of RuleItems where if any are True, an invite request will be blocked. +An invite rules state contains one required key. +- `"rules"`: An Array of `RuleItem`s. The Array should contain no more than 127 entries. + +*Homeservers may wish to implement a smaller maximum, if so that maximum should be no smaller than 8* + +*Homeservers may also wish to exceed the defined maximum, doing so is allowed, but at their own peril.* + +#### `RuleItemAction` +A String-Enum that defines an action that the ruleset evaluator is to perform. + +* `"allow"`: Allow the invite request, breaks from ruleset evaluation. +* `"deny"`: Reject the invite request. +* `"continue"`: Do not take any action and continue ruleset evaluation. + +*Ruleset evaluation is performed before an invite request is acknowledged by the homeserver, invite rejection here refers to rejecting the invite request in the form of returning a HTTP error to the Inviter's homeserver. Not to reject an invite request which has already been acknowledged (visible to the Invitee) by the homeserver.* #### `RuleItem` -A RuleItem defines a Rule that can test against an invite request. This primarily exists for structural consistency with the room state `m.join_rules`. -It also serves to allow `m.invite_rules` to be easily extended in the future, such as to introduce an `m.ruleset` type that would accept Mjolnir ruleset rooms. +A RuleItem defines a Rule that can test against an invite request. - `"type"`: Required String-Enum, must be one of the defined types below. +- `"pass":` A required `RuleItemAction` that will be performed if the rule evaluates as True +- `"fail":` A required `RuleItemAction` that will be performed if the rule evaluates as False ##### `m.user` Validates as True if the Inviter MXID is equal to the defined `"user_id"` -- `"user_id"`: Required String, a valid MXID. +- `"user_id"`: Required String, a valid user id. ##### `m.shared_room` Validates as True if the Inviter and Invitee are in the defined `"room_id"`. - `"room_id"`: Required String, a valid room id. ##### `m.target_room` -Validates as True if the room which the Invitee is being invited to has the same room id as the defined `"room_id"`. -- `"room_id"`: Required String, a valid room id. +Validation depends on the keys defined. Either `"room_id"` or `"room_type"` must be defined. +- `"room_id"`: Optional String, a valid room id. Rule evaluates as True if the target room id is equal to the defined `room_id`. +- `"room_type"`: Optional String-Enum. + - `"room_type": "is-direct-room"`: Rule evaluates as True if the Invitee's membership state in the target room has `"is_direct"` set to True. + - `"room_type": "is-space"`: Rule evaluates as True if the target room's `m.room.create` `type` is `"m.space"` + - `"room_type": "is-room"`: Rule evaluates as True if the target room is not a direct room or a space. + +##### `m.invite_rule` +Evaluation is dependant on the defined `"rule"`. +* `"rule"`: An `InviteRule`. + +#### `InviteRule` +A String-Enum. + +* `"any"`: Always evaluates as True. +* `"has-shared-room"`: Evaluates as True if the Inviter shares at least one room with the Invitee. +* `"has-direct-room"`: Evaluates as True if the Inviter has an active room defined in the Invitee's `m.direct` account data state. *Active is defined as "if both the Invitee and Inviter are present".* +* `"none"`: Always evaluates as False. #### Evaluation -In order to prevent homeservers from interpriting `m.invite_rule` states differently, an evaluation order is defined here: - -- An Inviter attempts to create an invite request to the Invitee. - - If `"m.invite_rules"` exists as an account state: - - If `"allow"` exists, evaluate the defined Rulesets. If one evaluates as True, break from the `"m.invite_rules"` check. - - If `"deny"` exists, evaluate the defined Rulesets. If one evaluates as True, reject the invite request. - - If `"type"` within content of `"m.invite_rules"` is: - 1. `"all"`: Break from the `m.invite_rules` check and continue. - 2. `"has-shared-room"`: Get all Rooms Invitee is in, and check if the Inviter has a `"join"` membership state. - If the Inviter does not have at least one shared room, Reject the invite request. - 3. `"has-direct-room"`: Check if the Invitee's account data state `"m.direct"` exists. - - If True, test if the content of `"m.direct"` contains a key which is the Inviter's MXID. - - If True, test if the Invitee has a `"join"` membership state in any rooms defined in the key's value. If no matches are found, reject the invite request. - - If False, reject the invite request. - - If False, reject the invite request. - 4. `"none"`: Reject the invite request. +* The Inviter attempts to create an invite request to the Invitee: + * If the `"m.invite_rules"` account data state exists, then: + * If `"rules"` is defined, then for each `RuleItem`: + * Evaluate the `RuleItem` and save either the `"pass"` or `"fail"` `RuleItemAction` depending on the result. + * If the `RuleItemAction` is: + * `"allow"`, then: Break from the invite rules loop. + * `"deny"`, then: Respond with `M_FORBIDDEN`. + * `"continue"`, then: Continue for each. + +*If the rules loop is iterated through without any action taken, it is treated as `"allow"`.* + +Implementations may wish to utilise result caching where applicable to improve performance. Such as for rules that may require comparing the joined rooms of each user. + +*Such cache would be storing the resulting `Boolean` returned during `RuleItem` evaluation, **not** the `RuleItemAction` which is picked from the defined `"pass"` or `"fail"` keys.* #### Invite Rejection If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN, and the error message: "This user is not permitted to send invites to this server/user" -#### Example: +#### Example +The following example will allow any invites from `@bob:example.com` or members of `!a:example.com`, deny any invites from `@alice:example.com`, and allow direct invites from any user who shares at least one room with the Invitee. + ```js { "type": "m.invite_rules", "content": { - "invite_rule": "has-direct-room", - "allow": [ + "rules": [ + { + "type": "m.user", + "user_id": "@bob:example.com", + "pass": "allow", + "fail": "continue" + }, { "type": "m.user", - "user_id": "@bob:example.com" + "user_id": "@alice:example.com", + "pass": "deny", + "fail": "continue" }, { "type": "m.shared_room", - "room_id": "!a:example.com" - } - ], - "deny": [ + "room_id": "!a:example.com", + "pass": "allow", + "fail": "continue" + }, { - "type": "m.user", - "user_id": "@alice:example.com" + "type": "m.invite_rule", + "rule": "has-shared-room", + "pass": "continue", + "fail": "deny" }, { "type": "m.target_room", - "room_id": "!b:example.com" + "room_type": "is-direct-room", + "pass": "allow", + "fail": "deny" } ] } @@ -100,8 +134,9 @@ Currently, there is no way outside of homeserver-wide restrictions (mjolnir, ant ## Potential Issues There is a potential denial of service for the `has-shared-room` and `has-direct-room` invite rules, as they require searching through all rooms a user is in, which could be a lot. This heavily depends on the homeserver's internals of course. -Additionally, as no limit for the `"allow"` and `"deny"` rulesets is specified, an Invitee could make hundreads of rules to attempt to -slow the homeserver down. For homeservers where this may be a concern, a sensible limit should be configurable by the homeserver admin. +The `"rules"` Array's defined maximum may not be suitable for resource-strained, or particularly large homeservers. Homeservers should make the maximum rules configurable for the homeserver admin. + +As homeservers may set a custom rule limit, clients currently have no reliable way of knowing that limit. Some way of signalling the limit to the client should be looked into ## Unstable prefix While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3659.invite_rules` From 13eeb22bfb854f3fb92553836f46c8cb157511c3 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Sat, 26 Mar 2022 01:44:30 +0000 Subject: [PATCH 08/12] correct ambigious wording --- proposals/3659-invite-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index c6a4f2b4a0f..047724f1e58 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -67,7 +67,7 @@ A String-Enum. #### Evaluation -* The Inviter attempts to create an invite request to the Invitee: +* The Invitee's homeserver receives an invite request from the Inviter: * If the `"m.invite_rules"` account data state exists, then: * If `"rules"` is defined, then for each `RuleItem`: * Evaluate the `RuleItem` and save either the `"pass"` or `"fail"` `RuleItemAction` depending on the result. From 641257cf9c038c3a81f3154bd8fd31699b725360 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Fri, 18 Nov 2022 18:58:15 +0000 Subject: [PATCH 09/12] break up m.target_room --- proposals/3659-invite-rules.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index 047724f1e58..432396a2d87 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -38,28 +38,26 @@ A RuleItem defines a Rule that can test against an invite request. - `"fail":` A required `RuleItemAction` that will be performed if the rule evaluates as False ##### `m.user` -Validates as True if the Inviter MXID is equal to the defined `"user_id"` +Validates as True if the Inviter MXID is equal to the defined `"user_id"`. - `"user_id"`: Required String, a valid user id. ##### `m.shared_room` Validates as True if the Inviter and Invitee are in the defined `"room_id"`. - `"room_id"`: Required String, a valid room id. -##### `m.target_room` -Validation depends on the keys defined. Either `"room_id"` or `"room_type"` must be defined. -- `"room_id"`: Optional String, a valid room id. Rule evaluates as True if the target room id is equal to the defined `room_id`. -- `"room_type"`: Optional String-Enum. +##### `m.target_room_id` +Validates as True if the target room id is equal to the defined `room_id`. +- `"room_id"`: Required String, a valid room id. + +##### `m.target_room_type` +Validation depends on the value of `room_type`. +- `"room_type"`: Required String-Enum. - `"room_type": "is-direct-room"`: Rule evaluates as True if the Invitee's membership state in the target room has `"is_direct"` set to True. - `"room_type": "is-space"`: Rule evaluates as True if the target room's `m.room.create` `type` is `"m.space"` - `"room_type": "is-room"`: Rule evaluates as True if the target room is not a direct room or a space. -##### `m.invite_rule` -Evaluation is dependant on the defined `"rule"`. -* `"rule"`: An `InviteRule`. - #### `InviteRule` A String-Enum. - * `"any"`: Always evaluates as True. * `"has-shared-room"`: Evaluates as True if the Inviter shares at least one room with the Invitee. * `"has-direct-room"`: Evaluates as True if the Inviter has an active room defined in the Invitee's `m.direct` account data state. *Active is defined as "if both the Invitee and Inviter are present".* @@ -118,7 +116,7 @@ The following example will allow any invites from `@bob:example.com` or members "fail": "deny" }, { - "type": "m.target_room", + "type": "m.target_room_type", "room_type": "is-direct-room", "pass": "allow", "fail": "deny" From 4fdd734055efa7f8623ad8bf5dac263c0e84f698 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:16:18 +0000 Subject: [PATCH 10/12] add capability entry, /InviteRule/m.compare/s --- proposals/3659-invite-rules.md | 39 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index 432396a2d87..809c701598c 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -15,11 +15,8 @@ are processed by their homeserver. ### `m.invite_rules` An invite rules state contains one required key. -- `"rules"`: An Array of `RuleItem`s. The Array should contain no more than 127 entries. - -*Homeservers may wish to implement a smaller maximum, if so that maximum should be no smaller than 8* - -*Homeservers may also wish to exceed the defined maximum, doing so is allowed, but at their own peril.* +- `"rules"`: An Array of `RuleItem`s. The Array must contain no more than the number of rules the homeserver is +willing to process. #### `RuleItemAction` A String-Enum that defines an action that the ruleset evaluator is to perform. @@ -56,12 +53,11 @@ Validation depends on the value of `room_type`. - `"room_type": "is-space"`: Rule evaluates as True if the target room's `m.room.create` `type` is `"m.space"` - `"room_type": "is-room"`: Rule evaluates as True if the target room is not a direct room or a space. -#### `InviteRule` -A String-Enum. -* `"any"`: Always evaluates as True. -* `"has-shared-room"`: Evaluates as True if the Inviter shares at least one room with the Invitee. -* `"has-direct-room"`: Evaluates as True if the Inviter has an active room defined in the Invitee's `m.direct` account data state. *Active is defined as "if both the Invitee and Inviter are present".* -* `"none"`: Always evaluates as False. +##### `m.compare` +Compares information about the Invitee and Inviter. Behaviour depends on the value of `compare_type` +- `"compare_type"`: Required String-Enum. + - `"compare_type": "has-shared-room"`: Evaluates as True if the Inviter shares at least one room with the Invitee. + - `"compare_type": "has-direct-room"`: Evaluates as True if the Inviter has an active room defined in the Invitee's `m.direct` account data state. *Active is defined as "if both the Invitee and Inviter are present".* #### Evaluation @@ -83,6 +79,19 @@ Implementations may wish to utilise result caching where applicable to improve p #### Invite Rejection If an invite is to be rejected, the homeserver *should* respond with M_FORBIDDEN, and the error message: "This user is not permitted to send invites to this server/user" +#### Capabilities +Invite Rules requires an additional capability entry at `client/r0/capabilities` to signal to clients how many rules +the homeserver is willing to process. The suggested maximum is 128. Homeservers are free to set their own maximum + +```js +{ + "m.invite_rules": { + "enabled": true, + "maximum_rules": Integer + } +} +``` + #### Example The following example will allow any invites from `@bob:example.com` or members of `!a:example.com`, deny any invites from `@alice:example.com`, and allow direct invites from any user who shares at least one room with the Invitee. @@ -110,8 +119,8 @@ The following example will allow any invites from `@bob:example.com` or members "fail": "continue" }, { - "type": "m.invite_rule", - "rule": "has-shared-room", + "type": "m.compare", + "compare_type": "has-shared-room", "pass": "continue", "fail": "deny" }, @@ -132,9 +141,7 @@ Currently, there is no way outside of homeserver-wide restrictions (mjolnir, ant ## Potential Issues There is a potential denial of service for the `has-shared-room` and `has-direct-room` invite rules, as they require searching through all rooms a user is in, which could be a lot. This heavily depends on the homeserver's internals of course. -The `"rules"` Array's defined maximum may not be suitable for resource-strained, or particularly large homeservers. Homeservers should make the maximum rules configurable for the homeserver admin. - -As homeservers may set a custom rule limit, clients currently have no reliable way of knowing that limit. Some way of signalling the limit to the client should be looked into +The `"rules"` Array's suggested maximum may not be suitable for resource-strained, or particularly large homeservers. Homeservers should make the maximum rules configurable for the homeserver admin. ## Unstable prefix While this MSC is in development, implementations of this MSC should use the state type `org.matrix.msc3659.invite_rules` From 1fdbf98c95cd3895cac3e1389e5261874108a60b Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Sat, 19 Nov 2022 09:42:14 +0000 Subject: [PATCH 11/12] Clean up, make room_id & user_id globbable --- proposals/3659-invite-rules.md | 55 +++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index 809c701598c..0274a2f2ad5 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -3,8 +3,6 @@ This MSC proposes the creation of an optional account data state which allows users to control how invites directed at them are processed by their homeserver. -*Homeservers may choose to ignore an Invitee's invite rules *if* the Inviter is a homeserver admin.* - ## Proposal ### Glossery @@ -21,11 +19,9 @@ willing to process. #### `RuleItemAction` A String-Enum that defines an action that the ruleset evaluator is to perform. -* `"allow"`: Allow the invite request, breaks from ruleset evaluation. -* `"deny"`: Reject the invite request. -* `"continue"`: Do not take any action and continue ruleset evaluation. - -*Ruleset evaluation is performed before an invite request is acknowledged by the homeserver, invite rejection here refers to rejecting the invite request in the form of returning a HTTP error to the Inviter's homeserver. Not to reject an invite request which has already been acknowledged (visible to the Invitee) by the homeserver.* +- `"allow"`: Allow the invite request, breaks from ruleset evaluation. +- `"deny"`: Reject the invite request. +- `"continue"`: Do not take any action and continue ruleset evaluation. #### `RuleItem` A RuleItem defines a Rule that can test against an invite request. @@ -36,15 +32,15 @@ A RuleItem defines a Rule that can test against an invite request. ##### `m.user` Validates as True if the Inviter MXID is equal to the defined `"user_id"`. -- `"user_id"`: Required String, a valid user id. +- `"user_id"`: Required String, a valid user id. This value may also be a glob ##### `m.shared_room` Validates as True if the Inviter and Invitee are in the defined `"room_id"`. -- `"room_id"`: Required String, a valid room id. +- `"room_id"`: Required String, a valid room id. This value may also be a glob ##### `m.target_room_id` Validates as True if the target room id is equal to the defined `room_id`. -- `"room_id"`: Required String, a valid room id. +- `"room_id"`: Required String, a valid room id. This value may also be a glob ##### `m.target_room_type` Validation depends on the value of `room_type`. @@ -60,15 +56,17 @@ Compares information about the Invitee and Inviter. Behaviour depends on the val - `"compare_type": "has-direct-room"`: Evaluates as True if the Inviter has an active room defined in the Invitee's `m.direct` account data state. *Active is defined as "if both the Invitee and Inviter are present".* #### Evaluation - -* The Invitee's homeserver receives an invite request from the Inviter: - * If the `"m.invite_rules"` account data state exists, then: - * If `"rules"` is defined, then for each `RuleItem`: - * Evaluate the `RuleItem` and save either the `"pass"` or `"fail"` `RuleItemAction` depending on the result. - * If the `RuleItemAction` is: - * `"allow"`, then: Break from the invite rules loop. - * `"deny"`, then: Respond with `M_FORBIDDEN`. - * `"continue"`, then: Continue for each. +Ruleset evaluation is performed before an invite request is acknowledged by the homeserver, invite rejection refers to rejecting the invite request in the form of returning a HTTP error to the Inviter's homeserver. Not to reject an invite request which has already been acknowledged (visible to the Invitee) by the homeserver. +Homeservers may choose to skip ruleset evaluation entirely if the Invitee is a homeserver admin. + +- The Invitee's homeserver receives an invite request from the Inviter: + - If the `"m.invite_rules"` account data state exists, then: + - If `"rules"` is defined, then for each `RuleItem`: + - Evaluate the `RuleItem` and save either the `"pass"` or `"fail"` `RuleItemAction` depending on the result. + - If the `RuleItemAction` is: + - `"allow"`, then: Break from the invite rules loop. + - `"deny"`, then: Respond with `M_FORBIDDEN`. + - `"continue"`, then: Continue for each. *If the rules loop is iterated through without any action taken, it is treated as `"allow"`.* @@ -93,13 +91,30 @@ the homeserver is willing to process. The suggested maximum is 128. Homeservers ``` #### Example -The following example will allow any invites from `@bob:example.com` or members of `!a:example.com`, deny any invites from `@alice:example.com`, and allow direct invites from any user who shares at least one room with the Invitee. +The following example will enforce the following: +- Any invites from `badguys.com` will be blocked. +- Invites from `@bob:example.com` will be allowed. +- Invites from `@alice:example.com` will be blocked. +- Invites from any user who is also in `!a:example.com` will be allowed. +- Invites from any user who shares a room with the Invitee will be allowed on the condition they are inviting them into a direct message room. ```js { "type": "m.invite_rules", "content": { "rules": [ + { + "type": "m.user", + "user_id": "*:badguys.com", + "pass": "deny", + "fail": "continue" + }, + { + "type": "m.user", + "user_id": "*.badguys.com", + "pass": "deny", + "fail": "continue" + }, { "type": "m.user", "user_id": "@bob:example.com", From d3c3ddd917bcf298005f3505616e442e7c6adaf2 Mon Sep 17 00:00:00 2001 From: joshqou <97894002+joshqou@users.noreply.github.com> Date: Sat, 19 Nov 2022 09:43:29 +0000 Subject: [PATCH 12/12] typo --- proposals/3659-invite-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3659-invite-rules.md b/proposals/3659-invite-rules.md index 0274a2f2ad5..754fabb2c32 100644 --- a/proposals/3659-invite-rules.md +++ b/proposals/3659-invite-rules.md @@ -57,7 +57,7 @@ Compares information about the Invitee and Inviter. Behaviour depends on the val #### Evaluation Ruleset evaluation is performed before an invite request is acknowledged by the homeserver, invite rejection refers to rejecting the invite request in the form of returning a HTTP error to the Inviter's homeserver. Not to reject an invite request which has already been acknowledged (visible to the Invitee) by the homeserver. -Homeservers may choose to skip ruleset evaluation entirely if the Invitee is a homeserver admin. +Homeservers may choose to skip ruleset evaluation entirely if the Inviter is a homeserver admin. - The Invitee's homeserver receives an invite request from the Inviter: - If the `"m.invite_rules"` account data state exists, then: