Skip to content

MSC4056: Role-Based Access Control (mk II) #4056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

Conversation

turt2live
Copy link
Member

@turt2live turt2live changed the title MSC: Role-Based Access Control (mk II) MSC4056: Role-Based Access Control (mk II) Sep 15, 2023
@turt2live turt2live added requires-room-version An idea which will require a bump in room version proposal A matrix spec change proposal s2s Server-to-Server API (federation) unassigned-room-version Remove this label when things get versioned. kind:core MSC which is critical to the protocol's success needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. labels Sep 15, 2023
@gabrc52

This comment was marked as duplicate.

Copy link
Member Author

Choose a reason for hiding this comment

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

@gabrc52 says:

(I'm not sure what's the process for regular users to leave comments, so let me know if I'm doing it wrong)

Thanks for this MSC! I really like the role feature (but I am biased because I'm also a Discord user)

  • Another alternative to consider with ordering is to put it in the m.role itself, in a similar way to m.space.child. However, it might make it harder for clients to implement reordering. What are the arguments for and against having a separate m.role_map (other than consistency)?
  • I can confirm that in my experience, Discord's per-user role ordering is barely used if at all.
  • How would looking up someone's roles in a specific room look like? (for example, if clients want to implement displaying the color for the highest role, or showing someone's roles when clicking on their profile) Would it make sense to have an endpoint for this, or should clients be responsible for keeping track of the state events (as is done for names and avatars)?

Off-topic(?) suggestions for future MSCs which may inform design of this MSC:

  • (Discord feature) It would be nice to be able to mention roles
  • (Discord feature) It would be nice to have the ability for roles to be space-wide as opposed to/in addition to room-wide. How could space-wide display names, roles, etc look like?

(please post comments on the Files Changed tab)

Copy link
Member Author

Choose a reason for hiding this comment

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

  • Another alternative to consider with ordering is to put it in the m.role itself, in a similar way to m.space.child. However, it might make it harder for clients to implement reordering. What are the arguments for and against having a separate m.role_map (other than consistency)?

This isn't done due to the state resolution concerns raised in the MSC. It opens the door to two roles having the same order, creating problems (aside from the problems associated with non-canonical events).

  • I can confirm that in my experience, Discord's per-user role ordering is barely used if at all.

I couldn't even find it as an option.

  • How would looking up someone's roles in a specific room look like? (for example, if clients want to implement displaying the color for the highest role, or showing someone's roles when clicking on their profile) Would it make sense to have an endpoint for this, or should clients be responsible for keeping track of the state events (as is done for names and avatars)?

This MSC assumes clients have ways of managing state events in rooms on their own. If a dedicated endpoint is required, it would be in another MSC.

Off-topic(?) suggestions for future MSCs which may inform design of this MSC:

  • (Discord feature) It would be nice to be able to mention roles
  • (Discord feature) It would be nice to have the ability for roles to be space-wide as opposed to/in addition to room-wide. How could space-wide display names, roles, etc look like?

These suggestions sound very reasonable and desirable, though they'd be handled by a future MSC (or two).

Choose a reason for hiding this comment

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

❤️

Comment on lines +97 to +98
themselves through `order`. When state resolution is attempting to resolve a power struggle, it would
first determine which role has higher `order`, then let that event "win" the conflict. In the cases

Choose a reason for hiding this comment

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

Can users have multiple roles? And if so, how do you decide which role to use for each user in the power struggle?

For a given sender you could take their role with the highest order. But then does this require that the powers given to higher-order roles must be a superset of lower-order roles? Because what if the highest-order role does not itself include the power to send the given event?

You could decide whether the event itself is authorized by looking at the user's total powers as the set union of all their roles' powers. And to resolve conflicts, you could take the user's highest-order role as the user's order in the power struggle. Does that work?

Choose a reason for hiding this comment

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

@cvwright I think going with how it works in Discord whenever possible is preferable, and in Discord it is the cumulative powers that roles assigned to you give you. So for instance, if you have the ability to join a sub-room in a space with one role, but not the ability to post messages in it, but you have the ability both to join the room and post from another role, you can both join the room and post in it, no matter the order of the roles.

Comment on lines +109 to +112
For the above reasons relating to state resolution, we intentionally mix user assignment and ordering
into the same event type. Ideally, the `order` (and possibly even assignment) would be done on the
`m.role` events themselves, however in a conflicted set of state it becomes difficult or impossible
to enforce "one role per order number" during event authorization.

Choose a reason for hiding this comment

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

Yes, it seems important to have all of the orders for all the roles kept in one place so you can guarantee it's always internally consistent.

Would it be possible to move the assignment of users to roles out of the role map? If you had one "authorization" event per user, then that could save you from some of the challenges with the current m.power_levels event:

  1. You're no longer limited by the max size of an event. For a very large room, you could have tens of thousands of muted users.
  2. Less chance of conflicts in state res when you have mods / admins on different servers all taking action at the same time. You could have mod A muting user X, mod B muting user Y, and mod C promoting user Z all at the same time.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also was wondering whether more separation is possible, brought the point up in #matrix-spec:matrix.org - @turt2live said that separating role assignment and role ordering into two separate big events would be a point of conflict already, but was not sure about the exact argument anymore. @Gnuxie remembered a big discussion on the topic, thinking that the basic issue is that changing role assignments and role ordering might very well require atomicity of changes so that there is no time in between the then-separate events where the users have something else than the intended final permissions.

Copy link
Contributor

@Gnuxie Gnuxie Aug 27, 2024

Choose a reason for hiding this comment

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

The discussion started here, picks up on the point here and ended here.

From reading this over, I misremembered. There are two problems:

  1. When membership and permissions are described in the same event, and the subject sends this event, there is nothing in the auth rules that can constrain that user from declaring themselves to have whatever permissions they like. discussion.
  2. When membership and permissions are described in the same event, room admins also have the power to change the permissions of other users. If an admin changes the permissions while another user leaves, that user can either be forced back into the room against their will, or the admin's permission changes are ignored. discussion.

If it helps, the way i have avoided this issue in other proposals (MSC4124, describing a kind of membership for servers) is to separate membership into two events. One event that room admins use to describe whether the subject is allowed to participate, and one that the subject controls that describes their intent to participate.

@Cloudwalk9
Copy link

Cloudwalk9 commented Jun 27, 2024

I just want to throw my idea in. I already mentioned it to @turt2live a while ago but I want to share it more publicly.

What if we implement something like this in two stages that don't necessarily depend on each other, and maintain backwards compatibility with the existing power level system, essentially retaining it and making migration to newer room versions very seamless?

Per-user permission overrides

Allow users to be granted permissions on an individual basis regardless of their power level, and they can perform an action/send an event with the authority of their power level. Say, kick requires PL50, User is PL25 but is granted kick. User can only kick users PL24 and below.

Groups

Implement "roles" as groups. Users can be in multiple groups. Groups are assigned power levels just like users, and inherit permissions in the same way as users. Multiple groups can have the same power level, obviously. Power levels are retained for hierarchy and precedence in the member list, etc.

Putting these together

Per-group permission overrides and you got RBAC as an emergent property of both of these.

Security considerations

Overrides would usually take precedence over power levels, and ultimate authority must be evaluated based on the power level of the group or user that the override is set. A few example scenarios:

If a user of PL25 is denied kick, but is in a group of PL50 that grants kick, and kick otherwise requires PL25, nope, the user cannot kick. The user's override takes precedence because he is a lower power level than the group.

If a user with PL75 is denied kick, kick requires PL75, but is in a group with PL50 that grants kick, the user can only kick PL49 and below.

(This section has changed numerous times as I overthink and flesh out the potential pitfalls of this approach so bear with me.)

Comment on lines +78 to +88
Each role MUST have a distinct `order` number. Roles with higher `order` override conflicting permissions
from lower `order` roles. For example, given the following:

* "Role A" with `order: 1` and permission `first: true, second: true`.
* "Role B" with `order: 2` and permissions `first: false, third: true`.

... a user with both roles would have permissions `first: false, second: true, third: true`. `first` is
overridden by the `order: 2` role, but `second` and `third` are not conflicting.

When an event is sent by a user, it MUST reference the `m.role_map` and `m.role` events applicable
to the `sender` from that map as `auth_events`.

Choose a reason for hiding this comment

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

I've used several systems with this approach and I've never really been a fan. In many cases, I have several roles that should be considered "equal" in power. For example, I might have separate donor roles for individual subscription-style donation services (think patreon) - I don't want patreon donors to have more sway than donors of other platforms, which is to me what this order sets up. Many systems with this order property also explicitly disallow multiple roles having the same order, because then the conflict resolution system falls apart.

Additionally, this sort of weighting creates situations where you have users able to do things that you thought they weren't, or unable to do things that you think they should be able to do.

The order can work for some places - Discord uses this for determining what roles a user with the Manage Roles permission is allowed to manage, the color of a user's name, and a few other cosmetic items. However, conflict resolution is handled differently.

The two most prevalent systems appears to be Explicit Allow Wins (Discord) and Explicit Deny Wins (Microsoft AD). In order to implement this however, we would need to effectively have a tri-state. I believe this can be implemented in json by omitting the value, such as in your example (role 2 does not specify a value for second and therefore it doesn't apply any rules to it). In code, this could be represented as a null value.

The "undefined" value is effectively a pass-through. It is never considered in the permission flow. By default, all permissions are "undefined", which effectively functions as a soft deny which can be then overridden by an explicit allow. This leads to three possible conditions:

  1. No permission - the user is not a member of any roles that allow this permission. Effective: deny.
  2. Denied - the user is a member of a role that denies this permission.
  3. Allowed - the user a member of a role that allows this permission.

On the UI side, I like the phrase "unset" or "not set" to describe the functionality of this value.

We would still need to choose between allow/deny winning, but here's what those might look like:

Explicit Allow Wins

  • "Role A" with and permission first: true, second: true.
  • "Role B" with and permissions first: false, third: true.

first is set true. Override for false is ignored. Result: allow.
second is set true. Value is not overridden. Result: deny.
third is set true. Value is not overridden. Result: deny.

Explicit Deny Wins

  • "Role A" and permission first: true, second: true.
  • "Role B" and permissions first: false, third: true.

first is set true but overridden by false. Result is: deny
second is set true and not overridden. Result is: allow.
third is set true and is not overridden. Result is: allow

Also to consider - we should have a built-in everyone role. Just about every system has this, and it is a base set of permissions applied to all users. This allows for simplified configuration scenarios, allowing you to set common values for everyone (e.g. everyone can read chat history and speak). It also allows for more advanced scenarios, such as marking speak as undefined and granting it via another role.

Copy link

@Cloudwalk9 Cloudwalk9 Apr 7, 2025

Choose a reason for hiding this comment

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

Also to consider - we should have a built-in everyone role. Just about every system has this, and it is a base set of permissions applied to all users. This allows for simplified configuration scenarios, allowing you to set common values for everyone (e.g. everyone can read chat history and speak). It also allows for more advanced scenarios, such as marking speak as undefined and granting it via another role.

Power level 0 is the everyone role. That is, I think we should keep PLs around to enforce rank hierarchy on groups/roles. Multiple roles can have the same power level but different permissions, if we consider a "permission override" feature.

So, they can be used for vanity or permissions. I don't like the idea of wholesale getting rid of PLs for that reason, and additionally keeping them would keep backwards compatibility and make RBAC opt in on old rooms after upgrade, and opt out on new rooms if for some reason you decide you want PL based permissions. The idea is the "everyone" role would be implicit by what ever perms you give PL0 and UX could reflect that by abstracting away the fact that you're changing the permissions for PL0.

Copy link

@mpeter50 mpeter50 Apr 8, 2025

Choose a reason for hiding this comment

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

@Foxtrek64 I think you made a mistake in the Explicit Allow Wins example. Shouldnt all of them result in an allow?

This approach looks more user friendly to me than what is in the current document. But, maybe it would worth it to be able to still override an allow, either with a third (or actually forth) value for a permission, or a property that applies to the whole role. One use of it could be to temporarily revoke permissions even for a moderator.
How is it normally handled in discord communities when a moderator does something questionable, and their permissions are suspended until the circumstances are clarified?

But, as Cloudwalk9 also said, maybe we could redefine what a power level means: a role with a higher power level has more power, and so it can revoke permissions granted by roles with a lower level.
This way users wouldnt have a power level anymore, but roles instead.
Roles with the same power levels would work according to the Explicit Allow Wins principle, like on Discord. If a community does not want to deal with power levels and the complications of overrides, they could just keep using the same power levels for all roles, maybe a separate one for room staff.
But to avoid confusion, it should be explicitely noted in the spec then that power levels have an entirely new meaning.

Copy link

@Cloudwalk9 Cloudwalk9 Apr 8, 2025

Choose a reason for hiding this comment

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

But, as Cloudwalk9 also said, maybe we could redefine what a power level means: a role with a higher power level has more power, and so it can revoke permissions granted by roles with a lower level.
This way users wouldnt have a power level anymore, but roles instead.
Roles with the same power levels would work according to the Explicit Allow Wins principle, like on Discord. If a community does not want to deal with power levels and the complications of overrides, they could just keep using the same power levels for all roles, maybe a separate one for room staff.
But to avoid confusion, it should be explicitely noted in the spec then that power levels have an entirely new meaning.

Not necessarily a new meaning but an extension of their purpose. "Permission overrides" are key here. I think RBAC should be emergent from two separate MSCs.

  1. Should implement roles and figure out their ranking and coloring semantics just for vanity, so users can be assigned named roles.

  2. Crucially, the ability to allow or deny a user or group specific permissions regardless of their power level. So we have a concept of "effective power level" vs actual power level. Someone is granted kick at PL25 when kick requires 50. They can only kick 24 and below. PL25 is their effective power level for kick. A group/role is granted ban at PL20 and they're added to it, they can only ban PL19 and below because that's the effective power level inherited from their group. But now they're added to another group with PL40 that has kick granted explicitly. Logically whether or not this PL40 group exists, this would create a weird inversion where someone can kick people who are allowed to ban, but I can foresee some weird niche use cases. Ultimately power level should have the final say (your vulnerability to action and what authorization you have over others) just as it does now.

They can be implemented independently and RBAC can emerge from them either naturally or with a third, simpler MSC that makes groups subject to the auth rules, or the MSC implementing groups/roles could make them subject to the auth rules from the get-go and permission overrides are figured out in another MSC. I personally think this is the most dynamic, backwards compatible way forward and it keeps the old system around nonintrusively.

Copy link

Choose a reason for hiding this comment

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

when kick requires 50

Do we want to keep using power levels for plain permissions too? I think that would complicate things unnecessarily.

personally think this is the most dynamic, backwards compatible way forward

Is it useful to have backwards compatibility here? This would need a new room version anyway.

Choose a reason for hiding this comment

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

when kick requires 50

Do we want to keep using power levels for plain permissions too? I think that would complicate things unnecessarily.

personally think this is the most dynamic, backwards compatible way forward

Is it useful to have backwards compatibility here? This would need a new room version anyway.

Nah, the defaults for new rooms under this model could be every current mod permission set to PL100 (owner) by default with an optional preset "Moderator" role at PL50, but it's granted the permissions using new allow/deny list overrides. Room upgrades would just be ridiculously easy, perhaps clients could show a prompt indicating the new feature so admins can transition at their leisure, since the old system is not only still around but forms the new system's rank enforcement.

Copy link

@Foxtrek64 Foxtrek64 Apr 9, 2025

Choose a reason for hiding this comment

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

But, maybe it would worth it to be able to still override an allow, either with a third (or actually forth) value for a permission, or a property that applies to the whole role. One use of it could be to temporarily revoke permissions even for a moderator.

I come from the Microsoft world so I may be a bit biased here. While I described both systems, I prefer and am more familair with Explicit Deny Wins.

In your example, if I had a moderator that was abusing their power I would simply remove them from the moderator role. I can always restore them later once the situation is resolved. However, if I wanted to follow your example I could create a restricted moderator role that prevents them from exercising certain moderator powers just by setting those permissions to deny. Because of the explicit deny, the effective permission is deny.

Or another example - let's say I have a community where people are unable to speak unless they have the Member role. How they get this role is not important but maybe it's provided via an integration, e.g. having an account on a website and linking your Matrix account. But then for moderation I also want to have a Muted role that prevents a particular member from speaking. This is how I would set that up:

Everyone:

  • Read Messages: Allow
  • Send Messages: Not Set

Member:

  • Read Messages: Not Set
  • Send Messages: Allow

Muted:

  • Read Messages: Not Set
  • Send Messages: Deny

In practice, what this means is that if the user does not have the member role, they are allowed to read messages and not speak.
When the member role is added, Read Messages is not set so it doesn't apply, meaning the Allow value from Everyone is what is used. The Send Messages value is now set to Allow, enabling speaking perms.
When the member is added to the Muted role, their ability to read messages is not changed. But their ability to send messages is explicitly denied. This prevents them once again from sending the message but allows them to keep their member role. This may be desirable depending on the meaning of the role - someone could be a patron for instance, but have their ability to speak be temporarily rescinded. Just because they are in trouble does not mean they are no longer a member or a patron.

I think you made a mistake in the Explicit Allow Wins example. Shouldnt all of them result in an allow?

Yes, thanks for catching that.

Copy link

@Foxtrek64 Foxtrek64 Apr 9, 2025

Choose a reason for hiding this comment

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

Nah, the defaults for new rooms under this model could be every current mod permission set to PL100 (owner) by default with an optional preset "Moderator" role at PL50, but it's granted the permissions using new allow/deny list overrides. Room upgrades would just be ridiculously easy, perhaps clients could show a prompt indicating the new feature so admins can transition at their leisure, since the old system is not only still around but forms the new system's rank enforcement.

I do like the "let's do both" idea. These systems could be (and should be) entirely separate from each other, and I think the cleanest option is to just prompt when creating the room what system you want. This also allows older rooms to be upgraded to a version that supports RBAC and to transition at the admin's leisure, as you suggest.

I would use an enum property to denote whether the room uses RBAC or Power Levels, and which configuration is used and which is ignored depends on the value of this property. A boolean value could be used but I tend to reserve boolean values for things that can be answered with yes or no (e.g. whether to overwrite a file). When it's a "which one" question, I prefer enums.

If Power Levels is selected, the system continues to work exactly as it does today. But if RBAC is selected, then the power level configuration is ignored (but still allowed to be present), and instead the role permission check is used.

This bright line separation means that we don't have to figure out how to get roles and power levels to behave together. I personally believe that RBAC and Power Levels cannot be made to work together - you could use roles and power levels, and anyone with the role has the associated power level, but then that's not RBAC.

The only consideration we would need if we move to the system proposed in this thread is to somehow recognize the room owner. There may be a facility for this already, or it may need to be added. The idea is that the room owner should, even with only the Everyone role, be able to create roles, delegate permissions, and manage other aspects of the room.


Edit: I may create a separate proposal for this one, since this proposal is so fundamentally different.

Choose a reason for hiding this comment

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

I agree that its probably better to have it either traditional power levels or RBAC, and not both at the same time.

But I think it would be useful to be able to assign roles to power levels (maybe a different name), for setting up an explicit order in which roles' contradicting directives override each other.
In this setup users wouldnt have power levels, only roles would. Roles with the same power level would be processed according to Explicit Allow Wins, they would "add up", otherwise the role with the higher power level wins for permissions that are specified in multiple roles.

The only consideration we would need if we move to the system proposed in this thread is to somehow recognize the room owner. There may be a facility for this already, or it may need to be added.

I dont think there is a system for that now. For rooms that work with power levels, or are upgraded from a previous room version, perhaps the owner could be designated as the user with the higher power level. If there are multiple such, then the one that's latest join time was the earliest.
For rooms that work with RBAC, assuming that the specification ends up assigning power levels to roles, the owner could be designated as the oldest member in any of the highest power level roles.

Perhaps it could be further limited so that a bot account cannot be designated as the owner, to avoid trouble.
This way, basically permissions cant be messed up in such a way that the room is broken.

Edit: I may create a separate proposal for this one, since this proposal is so fundamentally different.

Yeah, room owner designation is better to have in a separate MSC, it is a different concept that this one would make use of.

Copy link

@Cloudwalk9 Cloudwalk9 Apr 12, 2025

Choose a reason for hiding this comment

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

What I'm proposing basically is that they can coexist but it's not one or the other. They kinda mesh together (if the owner wants). Power level always take precedence but ideally in a new room version, it takes a backseat to RBAC and is just used to enforce ranking in a new default configuration. The caveat of course is clients SHOULD NOT expose the knobs for both simultaneously. It's just, if for some reason you want PL50 to be granted kick no matter what, but you still want lesser roles to have kick, any new role you make assigned that PL or higher is automatically granted kick, if that makes sense. You can explicitly deny the permission too if for some reason you need to, on a user or group/role basis.

Any user not explicitly listed in an explicit allow or deny list inherits the permission granted by their role, and PL derived permissions remain authoritative, especially for the purpose of defining who the owner is absent roles. Just that it ideally should not be used for general purpose permissions other than defining the permissions for an implicit everyone role (PL0) in new rooms. Upgraded rooms can transition at their leisure and have a functioning in-between because they should both mesh together.

I could see like a "diplomatic immunity" use case where someone is granted a power level that can't kick or ban, but someone in a role with the same power level who CAN otherwise kick or ban by an override can't do it to this person, because the "effective power level" for the kick matches the user's power level, so it's not allowed.

As for room owner, Discord for example does not have any special role, even internally, afaik. So, it could be reasoned that the first person to be granted PL100 is the owner.

Make no mistake, this is intended to fully introduce RBAC but in a way that keeps the old system around and repurposes it for new rooms and makes transitioning smoother. This is in the interest of backwards compatibility and flexibility. A soft deprecation. No longer recommending the old way, but it is still a backbone for the new way and sticks around to bridge both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:core MSC which is critical to the protocol's success needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. proposal A matrix spec change proposal requires-room-version An idea which will require a bump in room version s2s Server-to-Server API (federation) unassigned-room-version Remove this label when things get versioned.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants