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.
In a future room version...
Note: All identifiers are to follow MSC2758 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:
{
"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, 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.
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:
{
"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.
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).
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.
The following permissions are proposed to be included in the spec. They are all direct correlations
to the existing m.room.power_levels
fields.
For simple permissions (ones that can be represented as an allowed/disallowed flag), the following permission body is used:
{
"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.
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.
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.
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.
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:
{
"m.senders": [
"@*:example.org"
]
}
The m.senders
is an array of globs under MSC2810
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.
Which room events (state and otherwise) can be sent by users with the applicable role.
The permission body for this would be:
{
"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).
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:
{
"m.state": [
{"type": "*", "m.allowed": true}
],
"m.room": [
{"type": "*", "m.allowed": true}
]
}
Which kinds of notifications users in the applicable role are able to trigger.
The permission body for this would be:
{
"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
.
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:
{
"m.change": [
"org.example.*",
],
"m.assign": [
"m.admin"
],
"m.revoke": [
"*"
]
}
All three properties are arrays of globs (MSC2810)
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:
{
"m.change": ["*"],
"m.assign": ["*"],
"m.revoke": ["*"]
}
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.
TODO - This needs defining
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.
- If there is no associated
For authorizing events themselves:
TODO - This needs defining
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.
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.
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.
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.