Skip to content

Latest commit

 

History

History
375 lines (280 loc) · 15.8 KB

2812-role-based-power.md

File metadata and controls

375 lines (280 loc) · 15.8 KB

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 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.

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:

{
  "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:

{
  "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:

{
  "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.

m.events

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}
  ]
}

m.notifications

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.

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:

{
  "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": ["*"]
}

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.