pull/2812/merge
Travis Ralston 2 months ago committed by GitHub
commit 19e2ef58b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,375 @@
# MSC2812: Role-based power structures
**Caution to the reader**: this is presently an information dump from my thoughts on how this
could work. It needs a lot of validation and work before it's ready as a proper proposal.
Currently Matrix operates off a power level structure where higher numbers have more power in a
room and lower numbers (with zero being a typical default) have the least power. This structure
can be used to represent a number of systems and can allow for a form of roles (moderator, admin,
etc) to be represented, though can be challenging to bridge to other platforms.
A true role-based power structure, in the eyes of this proposal, would be one which is more of a
permissions model rather than power model - a user could be granted a ban permission to ban other
members of the room, but could be denied all other typical moderator functions.
## Proposal
In a future room version...
**Note**: All identifiers are to follow [MSC2758](https://github.com/matrix-org/matrix-doc/pull/2758)
in this proposal.
Roles are declared using `m.role` state events where the state keys are arbitrary identifiers used
to differentiate between roles. An example `m.role` event's `content` would be:
```json
{
"m.name": {
"en": "Administrator"
},
"m.permissions": {
"m.ban": {"m.allowed": true},
"m.roles": {
"m.change": ["org.example.sponsors"],
"m.assign": ["*"],
"m.revoke": []
}
},
"org.example.colour": "#f00"
}
```
The content is highly extensible/namespaced to permit additional fields being added by implementations
which may be interested, such as (in the example) a colour to represent the role. Role names have
translation support, and must at least have an English definition for consistency reasons. Language codes
are per [BCP47](https://tools.ietf.org/html/bcp47), with `en` being representative of English.
Roles are only required to have an English name. By default, a role has no permissions associated with
it. This can be used to simply categorize members of a role for easy identification rather than granting
them any specific power - such examples may be wanting to identify supporters of a project within a room.
Note: groups (or communities) might also be used to categorize members within a room through flair. The
difference with roles is that they'd typically affect organization/grouping within the room list rather
than at a per-message level as flair currently does.
Permissions are identifiers with an associated object which varies depending on the permission itself.
Most permissions will have a single `m.allowed` boolean property (which defaults to `false`). The
proposed `m.*` namespace of permissions are defined later in this proposal with their relevant
specifications.
When using this roles system, `m.room.power_levels` serves zero meaning including for the purposes of
authorization rules. The changes to the authorization rules are defined later in this proposal.
### Identifying members in a role
On the applicable user's `m.room.member` state event, a new field of `m.roles` is added to be an array
of role IDs (state keys for `m.role` state events). For example:
```json
{
"type": "m.room.member",
"sender": "@alice:example.org",
"content": {
"membership": "join",
"displayname": "Alice",
"m.roles": [
"m.admin",
"org.example.supporter"
]
},
"state_key": "@alice:example.org",
"origin_server_ts": 1579809459351,
"event_id": "$tKStv-i0ympmbHEhnxZxwSkXJP5r-0Svf19HACNYKG4",
"room_id": "!example:example.org"
}
```
Adding/removing (changing) roles associated with a user is protected by a permission - see the proposed
permissions later in this proposal for more information.
### Default permissions structure
Upon creation of a room, the server creates a default `m.role` state event with state key `m.admin`.
This role consists of all permissions being granted as per each permission's specification. This
role is automatically assigned to the room creator when they join for the first time, and the
authorization rules will be modified to allow this.
Permissions are otherwise granted as per their defaults to all users without any roles defined on
their membership event. By default, members do not get any roles associated with them upon joining
the room (with the exception of the room creator, as outlined above).
### Execution order / inheritence
Users can perform an action if any of their roles permit it. Roles do not have inheritence under this
proposal, though in future it may be possible to do so. Instead, it is recommended that applications
needing inheritence will create smaller, more specific, roles and assign those as needed.
### Proposed initial permissions
The following permissions are proposed to be included in the spec. They are all direct correlations
to the existing `m.room.power_levels` fields.
#### Common permission format
For simple permissions (ones that can be represented as an allowed/disallowed flag), the following
permission body is used:
```json
{
"m.allowed": true
}
```
By default, unless indicated otherwise, `m.allowed` is `false`. When `true`, users with the applicable
role are able to perform the specified action.
#### `m.invite`
Whether or not a user can be invited to the room by someone with the applicable role.
This uses the common permission format and is **disallowed** by default. When the server creates the `m.admin`
role, this would be explicitly set to allowed.
#### `m.ban`
Whether or not a user can be banned from the room by someone with the applicable role.
This uses the common permission format and is **disallowed** by default. When the server creates the `m.admin`
role, this would be explicitly set to allowed.
#### `m.kick`
Whether or not a user can be kicked from the room by someone with the applicable role.
This uses the common permission format and is **disallowed** by default. When the server creates the `m.admin`
role, this would be explicitly set to allowed.
#### `m.redact`
Which senders can have their events redacted by someone with the applicable role. Like `redact` in the
power level structure, this only affects other people than the sender - the event sending permissions
cover restricting self-redaction.
The permission body for this would be:
```json
{
"m.senders": [
"@*:example.org"
]
}
```
The `m.senders` is an array of [globs under MSC2810](https://github.com/matrix-org/matrix-doc/pull/2810)
for which senders can have their events redacted by users with the applicable role. By default, this
array will be empty to denote that users do not have permission to redact other people's messages. When
the server creates the `m.admin` role, this would be explicitly set to `["*"]` to denote that anyone
may have their messages redacted by users in the applicable role.
#### `m.events`
Which room events (state and otherwise) can be sent by users with the applicable role.
The permission body for this would be:
```json
{
"m.state": [
{"type": "*", "m.allowed": true}
],
"m.room": [
{"type": "m.room.message", "m.allowed": true},
{"type": "*", "m.allowed": false}
]
}
```
Room events are split into two kinds: `m.state` for state events, and `m.room` for all other room events.
Note that EDUs like presence and typing notifications are not (currently) handled by this proposal. Each
kind of event is an array of rules which are executed in order - the first rule that matches as allowed
will permit the user to send the applicable event.
The `type` within the rule is a glob ([MSC2810](https://github.com/matrix-org/matrix-doc/pull/2810)).
`m.allowed` is simply an indicator for whether or not event types matching the given rule are allowed.
Both the `type` and `m.allowed` properties are required on rules, however `m.state` and `m.room` are
not required and have the following defaults:
* `m.state` defaults to an implicit `{"type": "*", "m.allowed": false}` rule. When the array is explicitly
empty, this deny rule persists.
* `m.room` defaults to an implicit `{"type": "*", "m.allowed": true}` rule. When the array is explicitly
empty, an implicit deny rule of `{"type": "*", "m.allowed": false}` is present. This is to ensure that
announcement-only rooms can be created by simply specifying `"m.room": []`.
When the server creates the default `m.admin` role, the following permission body is to be used:
```json
{
"m.state": [
{"type": "*", "m.allowed": true}
],
"m.room": [
{"type": "*", "m.allowed": true}
]
}
```
#### `m.notifications`
Which kinds of notifications users in the applicable role are able to trigger.
The permission body for this would be:
```json
{
"m.room": true
}
```
The key of the object is the notification kind (with `m.room` being the `@room` permission level), and the
value is whether or not the role allows it to be triggered. By default, all notifications are disallowed.
When the server creates the default `m.admin` role, the `m.room` permission must be set as `true`.
#### `m.roles`
Whether or not users in the applicable role are able to add/change roles or add/remove them to users.
The permission body for this would be:
```json
{
"m.change": [
"org.example.*",
],
"m.assign": [
"m.admin"
],
"m.revoke": [
"*"
]
}
```
All three properties are arrays of globs ([MSC2810](https://github.com/matrix-org/matrix-doc/pull/2810))
which are matched against role IDs (state keys of `m.role` events). All 3 arrays default to empty, implying
that all related actions are denied. Arrays are ordered and are matched as first-allowed wins.
`m.change` denotes which roles a user in the applicable role will be able to modify the properties of.
For example, if the array lists `m.admin` then users in the role will be able to modify the name, permissions,
and other properties of the `m.admin` role. This can mean that the user might be able to modify a role they
are currently assigned to.
`m.assign` denotes which roles a user in the applicable role will be able to assign (add) to users in
the room. This would be done through the `m.roles` property of the target user's membership event. The user
is able to target themselves.
`m.revoke` is the opposite of `m.assign`: it is which roles users in the applicable role will be able
to *remove* from a user's `m.roles` array on their membership event. Users are still able to target themselves
here.
When the server is creating the default `m.admin` role, the following permission body is to be used:
```json
{
"m.change": ["*"],
"m.assign": ["*"],
"m.revoke": ["*"]
}
```
### Use-case adoption
Not all rooms will require the changes proposed here, and thus it may be important to support the existing
power levels structure in parallel. Some potential solutions for this include sending state events into
a room to indicate the switch of systems, however this could potentially cause problems with authorization
if a server were to miss an event. This proposal offers an awkward, but hopefully viable, solution that
may be extended to other similar features in the future.
Room versions reserved by the Matrix protocol ending with `.1` are indicative of the server supporting
the principles of that room version with the role system proposed here used in place of `m.room.power_levels`.
For the purposes of authorization rules, this proposal does not support room versions 1 through 5 as
currently reserved by the specification - the minimum viable set of authorization rules are a modified
v6 set as described later in this proposal.
The specification will remain responsible for defining what the `.1` version of a room version looks like,
when new versions are being introduced.
**Rationale**: The specification reserves room versions consisting of `[0-9.]` for use by the protocol,
but does not reserve anything using `[a-z\-]` as otherwise allowed by room versions. Ideally, the protocol
would have reserved a dash and some letters to assist with denoting various features that may be included
in a given room version, however `.1` works just as well.
For clarity: room version `6.1` would mean the room uses a role-based permission system while room version
`6` uses the existing power levels structure. When room version `7` is introduced through an MSC, it would
also define a `7.1` with any modifications required to continue supporting a role-based approach.
This proposal does not include a solution for custom room versions intentionally. Implementations using
custom room versions are welcome to invent their own scheme for identifying role-based approach usage.
### Expected server behaviour for profile/membership changes
***TODO - This needs defining***
### Precise changes to v6's authorization rules
Using room version 6 as a reference for authorization rules, the authorization rules for this MSC
would be as follows.
For determining whether a given user in a given room has a given permission:
* If the user's membership is not `join`, the user does not have any permissions.
* For each role ID defined by the `m.roles` array (default empty, ordered) on the user's membership event:
* If there is no associated `m.role` state event in the room, skip.
* If the `m.role` state event does not have an English name, skip.
* Interpret the permission on the `m.role` state event to a single boolean flag to denote whether
the user is allowed (true) or disallowed (false) to continue.
* For unknown permission types (eg: custom namespaces), the default is to imply disallowed.
* If the user is granted (allowed) the permission, return true to let the user continue the action.
* If no roles have granted (allowed) the permission, return false to deny the user's action.
For authorizing events themselves:
***TODO - This needs defining***
## Potential issues
Roles are controversial as a power scheme and moderation structure - this is why the proposal actively
tries to keep the `m.room.power_levels` around. A roles approach is often better bridged to some
platforms (like Discord), whereas a power levels approach has a much stronger use case for others.
Similarly, it can be argued by several communities that roles are more natural feeling while other
communities will argue that power levels are more natural - it's largely a matter of preference and
community-specific interactions which define which is "better".
This roles approach is quite confusing as well and may lead to several implementation issues. This
MSC, and the relevant specification if this MSC makes it that far, should include examples ranging
from simple to complex for implementations to test against. As the ecosystem makes more general use
of a roles-based approach, those examples should be updated to better represent what is available
in the wild.
As already discussed, the room version identification approach is suboptimal but appears to be a
good enough compromise pending larger discussions with members of the ecosystem. Refer to that
section for more information.
## Alternatives
Roles are already an alternative to existing permissions model. By extension, there are several other
systems which may be valuable and have their own merits. The intention of this proposal is to
demonstrate an opt-in style permissions systems for the rooms/communities which have a requirement
to use such a system. It is not proposed that this system become the default under any circumstance
for all of Matrix.
## Security considerations
Changing the entire permissions system is dangerous and could lead to multiple security vulnerabilities.
Many have been already solved or considered by the existing power level system, and where possible
those semantics have been brought into this proposal.
TODO: There's certainly more words that can be put here, such as why roles are the way they are.
## Unstable prefix
Implementations should use a room version of `org.matrix.msc2812` while this MSC is not in a published
version of the specification. Because all the events would be isolated to this highly customized
room version, there is no requirement to avoid the usage of the `m.*` namespace.
Loading…
Cancel
Save