Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
375 changes: 375 additions & 0 deletions proposals/2812-role-based-power.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
# MSC2812: Role-based power structures
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's interest in a tristate. #2812 (comment) needs unpacking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is tri-state:
Each permission gets three states: allow, deny, nothing
By default every permission has nothing. As it is not allowed, you don't have that permission. If a permission is allowed in one role, you have it, except if it is denied in another role.

Arguments against tri-state:
Additional complexity

Arguments in favour of tri-state:
It allows greater flexibility, for example giving someone a temporary "timeout" role. Example given:
I user is bad and the moderators give them a timeout role for 24h, which denies them to talk. Without tri-state, they would have to manually remove all existing roles and then add the timeout role, only to revert that after 24h.
Depending on how the default role should work (needs clarification, too) that might even be impossible as perhaps the default role would be impossible to un-assign.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it should be implemented by specifying that permissions from roles are applied in some specific order (e.g. in the m.roles array order), and that new values override previous ones. That way it's tri-state of omit/allow/deny

Copy link

@TheBrambleShark TheBrambleShark Nov 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two systems to consider here:

  • Ordered overrides, like what @tulir mentions. This would allow the last-most permission to take precedence.
  • Explicit deny wins. This is how Active Directory works, along with many other systems. (e.g. Discord). In this system, roles are cumulative and any m.deny values take precedence.

Pros of ordered overrides:

  1. Easy to bypass permissions

Cons of ordered overrides:

  1. Keeping track of how permissions are ordered becomes important. A mute role, for instance, would need to be manually bumped to the top any time any other roles are added or removed to ensure that they continue to be denied permission to send messages.

Considerations of ordered overrides:

  1. How weighted are overrides? If the send messages permission defaults to m.none or m.omit meaning they cannot send messages, does that override m.allow or does it act as though it's transparent even if it comes after?

Pros of explicit deny wins:

  1. Well-known. Many popular role-based systems use this.
  2. Easy to deny permissions. For example, if you have a muted role, you can place it on a user and guarantee they will not be able to send messages regardless of whatever other roles are added later.
  3. Allows for strong adherence to RBAC principles if this is important to the user.

Cons of explicit deny wins:

  1. You cannot override an explicit deny (though this is by design).

Considerations of explicit deny wins:

  1. m.none should be the default value.
  2. m.none should act as a soft deny when no explicit allow or deny is found. That is, if the send messages permission is m.none, then the user does not have permission to send messages.
  3. m.none should behave transparently (or effectively ignored) when explicit allow or deny permissions are found. That is, m.allow + m.none = m.allow, regardless of order.

Calculating permissions in explicit deny wins:

  1. Throw out all roles where permission has a value of m.none.
    • If no roles are left, result is deny. If roles remain, continue to step 2.
  2. Check for any roles where permission has a value of m.deny.
    • If any are found, result is deny. If none are found, continue to step 3.
  3. All remaining values should be m.allow. Result is allow.

Copy link

@erkinalp erkinalp Dec 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Explicit deny wins. This is how Active Directory works, along with many other systems. (e.g. Discord). In this system, roles are cumulative and any m.deny values take precedence.

Correction: on Discord, explicit allow wins, not explicit deny.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about having both? Like,
*m.allow immediately followed m.deny=m.none
*m.none immediately followed by m.deny=m.deny
*m.deny or m.none immediately followed by m.allow=m.allow,
*anything followed by m.none returns the preceding power state for the permission in question
collapsed in a left-to-right, left-associative manner.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about having both? Like,
*m.allow immediately followed m.deny=m.none
*m.none immediately followed by m.deny=m.deny
*m.deny or m.none immediately followed by m.allow=m.allow,
*anything followed by m.none returns the preceding power state for the permission in question
collapsed in a left-to-right, left-associative manner.

Both is not a good option for this from two perspectives:

  1. It is overly-complex. Whatever option we take should be easily demonstrated by a truth table. This truth table should be simple to understand at a glance. With systems like "explicit deny wins," it's easy to understand the user's effective permissions. If you see a deny anywhere, then it's a deny. If you don't see a deny anywhere, but they have any allows, it's allowed. With a system where both are the case, I would almost need to pull out a permissions calculator or a pencil and paper to figure out a user's effective permissions.
  2. We don't need to reinvent the wheel. Most users who will be managing or administering permissions will come from doing so either in Active Directory or Discord. Among those and some other solutions, explicit deny wins is one of the most prevalent systems and the most widely understood. Explicit allow wins is a variation of that same system, but the fundamentals are mostly the same. We are not the first ones to be having this conversation. It may be prudent to look at not only what the others did, but look into their reasons why, and ultimately decide which of those we use based on what system matches best with our goals.

Also, to keep discussion focused, there are two pieces of a permissions system that we should look at here:

  1. How effective permissions for a single permission node are calculated (explicit deny/allow wins, weighted permissions, etc.)
  2. How permissions combine (additive, most restrictive, etc.).
    a. Additive: A user has two roles. One gives permissions to read a channel. One gives permissions to write in a channel. Effective permissions are Read/Write. Examples: File shares, Discord, Active Directory roles.
    b. Most restrictive: A user has two roles. One gives permissions to read a channel. One gives permissions to write in a channel. Effective permissions are Read. Examples: NTFS.

Personally, I would propose we use explicit deny wins with additive permissions. In this system, the effective permissions would be calculated like this:

  1. Iterate through each permission (e.g. channel.read).
    a. If any role denies the permission, effective permission is deny.
    b. If no explicit denies are found, any explicit allow means effective permission is allow.
    c. If no explicit denies or allows are found, effective permission is no permission (having no permission is treated the same as being denied permission, but is not enforced as a deny and can be overridden).
  2. Accumulate calculated permissions to build a new effective permission role. This role will be the one checked to see if a user has permission to perform a certain action.

Effective permissions would be calculated several times, once for global permissions and once for each channel, per user. Permissions calculations with this system are sufficiently fast, especially when adhering to standard RBAC practices. For instance, only granting the user the minimum permissions required to allow them use the channel.

Copy link

@erkinalp erkinalp Feb 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever option we take should be easily demonstrated by a truth table. With a system where both are the case, I would almost need to pull out a permissions calculator or a pencil and paper to figure out a user's effective permissions.

This is your truth table (non-commutative, non-alternative):

allow·allow = allow
allow·neutral = allow
allow·deny = neutral
neutral·allow = allow
neutral·neutral = neutral
neutral·deny = deny
deny·allow = allow
deny·neutral = deny
deny·deny = deny

It is very easy to combine those, left-to-right. First, you combine 1st and 2nd term, then the result of this with the 3rd term, then with the 4th term and so on. Specificity appendments (not overwrites) would be handled the same way: first, combine the two most generic terms, then the result of this with the 3rd most generic term, and so on.

We don't need to reinvent the wheel. Most users who will be managing or administering permissions will come from doing so either in Active Directory or Discord. Among those and some other solutions, explicit deny wins is one of the most prevalent systems and the most widely understood. Explicit allow wins is a variation of that same system, but the fundamentals are mostly the same. We are not the first ones to be having this conversation. It may be prudent to look at not only what the others did, but look into their reasons why, and ultimately decide which of those we use based on what system matches best with our goals.

My consideration was clients and servers with low memory and primitive processors. The currently proposed system requires an n-operand operation. My proposal, on the other hand, only requires chained two-operand calculations. Moreover, my proposal simplifies power updates, as it allows append-only updates (any permission changes will not overwrite, but append to previous permission declarations of the same level).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion assumes that roles would exist in a hierarchy or somehow be ordered. If that's the case, then that works, but now I have to be careful about how I order my roles and would need to be cognizant of their relationship to each other in this hierarchy.

Also, there is an issue with your truth table that makes things complicated for administrators. Let's say I am a member, so I have a few roles but ultimately my permission to speak is allow. As a punishment for breaking the rules, I get muted. So I get the mute role applied to me, which has speak set to deny. My effective permission is now neutral which depending on context could mean anything. If neutral allows me to speak, then this mute role becomes useless. I'd have to have two mute roles so my permission calculation becomes this:

allow + deny = neutral. Result: can (still) speak.
neutral + deny = deny. Result: cannot speak.

This becomes rather cumbersome to maintain. If I outright deny a permission, then that permission should be outright denied, regardless of whatever other permissions they have. There should be sane defaults here (such as a neutral result/no permissions meaning cannot speak), however if this sane default means that no permissions is equivalent to an allow state, then we run into the same issue above when denying that permission.

Explicit deny win calculations work extremely well for slower/older machines too, since there are only three steps:

  1. If there are any denies, permission is deny.
  2. If there are any allows, permission is allowed.
  3. Otherwise, permission is neutral/no permissions.

These operations can short circuit as well. If there are any denies, then it doesn't need to check if there are any allows. At worst, the speed of this is O(n) where n is the number of roles the user has. It can potentially faster than this though. Here is some sample code I've written in C#:

bool hasAllow = false;

foreach (var role in user.Roles)
{
    if (role.Permission == Permission.Deny)
        return Permission.Deny; // Stop iterating, return deny immediately.

    if (role.Permission == Permission.Allow)
        hasAllow = true;
}

// If allow was found
return hasAllow ? Permission.Allow : Permission.Neutral;

The slowest piece of this code is iterating through the user's role collection. However, any proposal would have us do the same so I don't feel this is a big concession. This might be improved some, performance-wise, but it should be pretty fast as is. Also, this code is hierarchy-agnostic, meaning it'll function regardless of whether the roles exist in a hierarchical structure or are all equivalent (think Discord vs Active Directory).

As an added benefit, this code is very simple, meaning it can be easily understood at a glance and can therefore be easily maintained and diagnosed should something go wrong.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Foxtrek64 Thanks for your input. I am now preparing a new MSC with those suggestions applied, as Travis said it diverged from this proposal so much that it should be discussed separately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, soru has been asked to comment on the concept of permission roles and why she personally thinks that
they are a good improvement over the current power level system. Please note that this comment is
about permission roles in general, and does not comment on specific implementations / forms that
this MSC take. Specific comments have already been made in the past.

Now, to start off, permission roles are more flexible. While power levels allow you to finely grain
how high a power you need to send which event, power levels do not allow to give different people
conflicting permissions: So one person should have permission A and not B, and another person should
have permission B and not A. Permission roles allows one to do that. Why that is good will be further
outlined below.

In sorus opinion permission roles only really start to shine when gouverning multiple rooms at once,
which is why she often mentioned them in the context of spaces. For just a single room, power levels
seem sufficient that there aren't really many cases where you need the extra flexibility that permission
roles give you. The idea is basically that you define the same permission roles which are valid across
the entire space, and then add members of the space to these roles, thus them taking affect throughout
rooms of the entire space. This of course does not solve #2962, it might even complicate it.

So, why is the added flexibility so good? It is sadly hard to pinpoint one big thing that just
objectively shows that this is needed. Instead, it seems like many small situations, which, while
they could be solved with a simple bot, adding permission roles would directly bake these mechanics
into matrix itself, removing the need of such a bot. Such scenarios are frequently found in e.g. safe
spaces: You want one group of people to have access to one room of your space, and another, partly
disjoint group of people, access to another room of your space, all while adhering to the same moderation
team, policies, etc. Or perhaps you want to have some people being allowed to invite people to the
space, but some other, partly disjoint, group of people have access to a room (Specific example: LGBTQ+
safespace with a trans room only for trans/enby folks).

There are often such kind of things where the extra flexibility is needed outside of safespaces, too.
@anoadragon453 talked about this permission system being helpful for DnD campaigns, for example:

We run regular DnD sessions. The DM wanted the ability to use Discord's Priority Speaker function,
which allows certain people to temporarily talk while lowering everyone else's mic. It's a pretty
useful function for when everyone's talking over each other in a session.

I just want to give them this exact role, but no other role in the room. At the same time I gave
another person the power to change people's nicks in the room, because it's funny. Because I don't
want to give them the ability to speak over everyone during a game.

#2962 actually already saw the need of something like permission roles: Access to a room is automatically
granted to people who are in a specific room. So.....#2962 kinda introduces permission-roles-as-rooms
already. Abstracting this to be general permission roles seems to make sense here, so that we unify
the two permission systems (power levels and room-access-by-being-in-a-room) into only a single
permission system.

Furthermore, most people want to give people a specific permission to do something. They don't want
to have to think about which power level they have to set which user to etc. So, they can just give
a role the permission "send a sticker" and add that user to the role, instead of setting "send sticker"
to PL 64, give the user that PL, notice that that also means they can now, uuuuuh, kick users from a
voice call, change the PL around again and then make it work. Of course good client UI can simplify
this problem, if the user does not try to configure something for which you need the greater flexibility
of permission roles.

Of course one has to think about how to make permission roles work well across an entire space, what
permissions there should be, if there should be room-level and/or user-level overrides (user-level-at
-room-level overrides?).

For the simple room where there is only really admin, moderator and everyone else needed, a set of
default roles, namely "default", "mod" and "admin" could be introduces, like, that the homeserver sets
those roles along with room creation or something. That way sophisticated admins can still change
everything they want, and "simple" users can still just set people to admin / moderator and then pick
which permissions are valid for them, if they do not like the default.

Copy link

@joepie91 joepie91 Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd actually like to bring up another argument for permission-based access control as a concept, that's potentially a much stronger argument for Matrix: permission-based access control is generic over nearly every other form of access control. That is, you can express every common access control mechanism using a sufficiently complete set of permissions.

This is especially important for Matrix due to bridging; it is currently often difficult to 1:1 map between Matrix's access control system and that of the bridged platform, since bridged platforms do not always have a strictly linear access control system. Subtly tweaking the powerlevel system could only solve this issue for specific other access control systems.

Permissions would resolve this by making arbitrary other access control systems representable, at the potential cost of some state event size overhead, as the permission-based representation can get pretty expansive for complex access systems - you'd essentially be 'unpacking' or 'evaluating' the other platform's access control algorithm into a full set of individual permissions.

This of course does not solve #2962, it might even complicate it.

The alternative proposal I'm working on (incomplete draft, MSC in progress), should be able to deal with a permission-based system just fine as far as I can tell.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the idea of "permission-roles-as-rooms" interesting and came to post that here: In light of #3083, it would be interesting to be able to specify for each permission, one (or more) rooms whose members would inherit the permission.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uranuspucksaxophone says:

I'm going to drop what I think here, ok?

I do like the power level system a lot, so I would like to propose a kind of hybrid model, merging both roles and power levels.

That would turn power levels into roles representations, for example, if we have the moderator role, we list it as power level 50 (the moderator role itself contains all of the actions that a moderator can do like ban, kick...), the same with an Admin (power 100) the only difference is that the Admin would have (by default) all possible actions and tools available.

We obviously would include a "Custom" option which shouldn't have a power level associated to it, just that the user/Admin can choose what actions that custom role can do.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @turt2live thanks for the quick answer. Why convert that into a separate comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(there's no reply - just moved it to a thread)

Comments on MSCs which aren't in threads are not considered part of the discussion, so when we can we gently move such comments into threads for consideration.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@turt2live Do you think my idea could work? I just came up with it today

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly haven't read it, but it's a threaded comment now so it'll be considered when this MSC ends up back on my list of things to look at :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hybrid system seems unclear/complex.

Copy link
Member Author

@turt2live turt2live Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ftr, the hybrid model actually fixes a security issue, and isn't that complex. It's what Discord does.

Edit: see #4056


**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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have yet to ever come up against the need to use roles in native Matrix. To try to gauge how to prioritise the MSC, it'd be really useful to have a real life bridging example - i.e. of a permissions bridge that can't be represented in Matrix today.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discord is the largest example, though this needs to be codified into the MSC properly.

Other examples (which imo are weaker arguments) are familiarity and ease of use - trying to do the mental math for whether or not you're about to give someone the power to destroy the room is often times difficult or requires a notepad (when trying to do non-standard, complex, things).

Copy link
Member

@tulir tulir Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telegram is a less obvious example, but their permissions are also not linear. There are 6 admin permissions (change group info, delete messages, ban users, add users, pin messages, add new admins) and they can be set completely arbitrarily for each user.

Right now it's impossible to bridge permissions. With this proposal, the permissions could be bridged, although the bridge would need to invent a role name for every combination.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discord's permission system is a patchwork, though. Its role hierarchy is bolted-on, the override ordering is only partially intuitive (some orderings are additive, some are hierarchical).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also attract business users - being able to tightly couple roles in Matrix to groups in Active Directory. Not applicable to everyone, but if businesses are looking to replace Teams (because it's a dreadful mess) this may be the feature that convinces them to switch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soru thinks most real-life examples come when you combine role-based permissions with spaces, so that the same roles gouvern all rooms in that space.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soru thinks most real-life examples come when you combine role-based permissions with spaces, so that the same roles gouvern all rooms in that space.

While this is likely the end goal, it's outside of the scope for this specific change. A separate proposal for allowing this was being drafted but I'm not sure the status of that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in random exploration there's a potential use case in virtual worlds where objects need dynamic ownership - power levels could be used for this, but scale might become a concern if too many things are trying to update power levels at once. Equally, granting power levels would mean granting permission to the whole world, which might not be desirable (unless we use objects as rooms? That feels a bit wasteful at first glance though)


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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. IMO, the roles should only be required to have a name, regardless of language
  2. Maybe some state keys, like m.admin could have default names in the spec and could be exempt from having to insert m.name? It just seems awkward to have to add Administrator to the m.name in almost every language, considering that it's a default role...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Names reserved in the spec could be admin, default and moderator, just like it is currently with PLs (not sure if the PL thing is in the spec, but clients practically do that)

them any specific power - such examples may be wanting to identify supporters of a project within a room.
Comment on lines +43 to +49

This comment was marked as resolved.


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about also adding a default role, maybe if the state key is m.default so that, if present, you can set different default permissions


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.
Comment on lines +92 to +95

This comment was marked as resolved.


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.
Comment on lines +130 to +131

This comment was marked as resolved.


#### `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.
Comment on lines +137 to +138

This comment was marked as resolved.


#### `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.
Comment on lines +144 to +145

This comment was marked as resolved.


#### `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.
Comment on lines +166 to +167

This comment was marked as resolved.


#### `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}
]
Comment on lines +180 to +183
Copy link

@Fox32 Fox32 Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this play together we e2ee? The server that is validating these room events would only see the m.room.encrypted, right? Meaning that this rule would deny sending any events, except unencrypted room messages?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand your question. I don't believe the role system would interact with e2ee at all. That is, they would be entirely unaware of each other.

You should only be sent events that are relevant to you. So if you have access to a room but not encrypted contents, you should simply not be sent events for when an encrypted message is sent, modified, or deleted. Likewise, you would not receive events for roles that you are not a member of. This isn't really a function of e2ee, but rather the eventing/messaging bus being efficient with network traffic and limiting unnecessary dispersal of information.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my question was a bit confusing, sorry, I try to explain it better. I'm also not sure if I misunderstood something.

So for the whole proposed system, my expectation is that a homeserver is validating the permissions when sending events.

While e2ee is just a transport protocol detail, in this case I think it is relevant. As far as I understand e2ee, the original event (e.g. m.room.message) is wrapped inside a m.room.encrypted event. This means that the homeserver ia not seeing the type of the original event and being unable to enforce the permissions. At least not as a normal user thinks they are working?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal isn't the one to fix power levels with respect to event types in encrypted rooms - that's more likely to be done by #3842

The example here is for demonstrative (unencrypted) purposes, largely.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer 👍

}
```

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:

This comment was marked as resolved.


```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.
Comment on lines +224 to +231

This comment was marked as resolved.


When the server creates the default `m.admin` role, the `m.room` permission must be set as `true`.

This comment was marked as resolved.


#### `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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Is this required to add roles? Ex, in the example m.roles above, would the user granted these permissions be able to create the role org.example.test?
  2. How is role creation abuse handled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes.
  2. Like with most things in Matrix, don't give people the power to do things you don't want them to do.

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:

This comment was marked as resolved.


```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***

This comment was marked as resolved.


### 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.
Copy link

@erkinalp erkinalp Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this requirement and derive an algorithmic fallback name in the form of "can do this, cannot do that" using permission metadata instead.

Suggested change
* 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***

This comment was marked as resolved.


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