Improving the RBAC to accommodate a wider range of popular use-cases

I’m assuming you mean multi-tenant

No, I’m talking multi-account which is not the same as multi-tenant.
Take for example AWS or DO (or any of the plenty SaaS platforms).
It’s very common that you can have 1 user that can have access to multiple accounts and different privileges per account.

Yii::$app->user->realm(‘bigSite’)->can(‘createPost’)

How would you pass the bigSite down to RBAC to the permission code?

Yii::$app->user->can(‘createPost’)

What if you need to check permissions for more than 1 user? Realm was set for first user, not set for next user so prev. realm assumed. Sounds like a lot of prone-to-error cases. And considering it’s around permissions, they’re pretty critical. Imagine giving access to proprietary content to the wrong user.

Having a unique identifier that represents the combination of user-account is foolproof. Serialising-deserialising (string<->object) an identifier is far safer and would prefer to sacrifice performance over such delicate matters.

It’s pretty much a choice between KISS (string) or improved performance/debugging (object). I still find the Object approach more suitable but I understand that the added complexity might hurt the adoption rate of the framework.

I think this solution could help with any multiple account rbac. But it really would depend greatly on how the webuser is authenticated for each account. This would take some thought - I don’t know if there is any standard way to do that and so building an RBAC that can handle that might not be that easy.

The realm() method is part of the RBAC plugin just the same way that the can() method is. They both would ‘apply’ their functionality to the webuser they are attached to. Whereas the can() method returns a bool, the realm() method returns the webuser, but with the realm property set appropriately so that the call to can() has something to work with.

Seriously, my ideas about incorporating a Session/Realm layer were just theoretical at this point. I just started looking into the yii3 project and haven’t really had time to look over the details of the code.

My whole interest was because I’m in the middle of converting a huge ria from Yii1 to Yii2. The ria grew over time and so is not the most elegant - I used AR in a lot of places and now find it a chore to convert. When I saw the work on Yii3 and Hisite projects, I got thrilled. This is the way to go.

The ria has a rather extensive RBAC setup and I had been toying with code to implement a temporary access system, so this discussion is important to me. I would really like to see something along that line incorporated into the RBAC plugin or at least be an extension to it.

I’ve never done anything like that so I don’t know how you would wire that up. I guess you would be passing in an array of User ids? It wouldn’t take much to instead pass in an associative array of [user_id => realm]. However I would imagine that any call for access would probably apply to a single realm, so you could simply pass in another parameter to determine the realm.

What do you get as the result of that check? I’m thinking it would have to be an associative array [user_id => access_bool]. But you are wanting to manage the access for multiple accounts - so that info would have to be passed back as well?

It’s kind of hard to plan out something that would likely be better managed differently for each project. My hope was that by adding in the extra session/realm layer it would be easier to apply RBAC to more scenarios and it would still part of the standard definition of RBAC. I thought that sticking to a standard was kind of important with plugable code.

Seriously, my ideas about incorporating a Session/Realm layer were just theoretical at this point. I just started looking into the yii3 project and haven’t really had time to look over the details of the code.

The Realm functionality you can encounter in other systems as Context or SecurityContext (see ApolloGraphQL, Spring, Symfony, etc). This is usually a simple object that carries stuff (vector) like all relevant identifiers. In this particular use-case, it would carry the user-id, the account-id, Security Resolver (similar to SecurityManager but not singleton), etc.

The realm() method is part of the RBAC plugin

I think emulating this concept in Yii2 the easiest approach would be to see WebUser as a SecurityContext on steroids.

Thanks for that info. I’d like to look into those and how they do it. And the name Context sounds a little more normal than Realm, which I pulled from Token usage.

You may be right. Speaking of Users and Security, I had to create a ConsoleUser to closely emulate a WebUser so commands could run code that was expecting a WebUser, mainly for security tracking purposes, but it could apply to access as well.

I do have some concerns with extending WebUser to implement RBAC - just some thoughts.

[ ] It pushes a part of the Authorization into the Authenticate code. I’d prefer to keep them separate.
[ ] It doesn’t help much with temporary access.
[ ] To me it kind of strays from basic RBAC, in that the Subject would now carry authentication logic. I realize that there are no laws governing this… but it might confuse things.

It seems from your brief description of SecurityContext, it was implemented more along the line of Rules rather than Subjects, following a more standard RBAC implementation, but did not really use the RBAC Session Layer directly - of course, I say this only from your description. Again I would like to look into how they did it.

By the way your question on this usage was right. I don’t think this would be how to implement realms. To do it this way would probably require an extended WebUser, ha.

Currently for RBAC we have a WebUser (Subject), the desired Permission, and any required Context for Rules. With a Realm layer we would have to add in an additional desired Realm. Sounds like more work, but that’s why it would be nice to also have some way to have default Realms for more normal scenarios.

The way I envisioned this to handle the multiple accounts is that for each User each Account would have the Role assignments in a different Realm. It feels logical to me. It isolates the Account from the Subject and keeps it in the authorization code, otherwise you’re back to a contrived id or moving authorization into the Subject which should be governing authentication.

Reading some of this. I think it’s kind of 6 of one, half dozen of the other. The Subject could refer to a WebUser or to an Entity of some sort that represents a User / Account combination and is Authenticated as such. But it needs to be a single Entity. There are no Teams, Groups, Mobs or anything like that in RBAC. All of that has to be handled separately, it’s not supposed to be a part of the RBAC lookup.

Mobs would add an interesting aspect to RBAC - the more users the more access! :wink:

I think the idea in RBAC is that the Subject is identified by a simple id and that id is used to pull up the various Roles. From there the Roles can be searched for the Role itself or it’s Permissions; and then any Rules (Context) can be applied. The point being that it’s a simple id that’s used to retrieve the roles.

That said, RBAC does also support the idea of Sessions. Kind of like applying the Context to determine which Roles are retrieved. But the sessions are unique to a single id. There are no cross-sessions, shared sessions or groups.

Reading some of this. I think it’s kind of 6 of one, half dozen of the other. The Subject could refer to a WebUser or to an Entity of some sort that represents a User / Account combination and is Authenticated as such. But it needs to be a single Entity. There are no Teams, Groups, Mobs or anything like that in RBAC. All of that has to be handled separately, it’s not supposed to be a part of the RBAC lookup.

Not really. It’s about

  1. adding custom columns/attributes to the Items
  2. the format of the ID when calling the AccessChecker.

At the end of the day it all comes down to the id representing the user. Consider the following

Yii::$app->user->can(sprintf('%s-%s', Yii::$app->user->getId(), $accountId));

// will produce results such as
Yii::$app->user->can('1-', ....);
Yii::$app->user->can('1-1', ....);
Yii::$app->user->can('1-2', ....);

As you see the ID for RBAC is not the user id but the combination of userId and account id.
To perform the actual checks in permissions you need to explode($id). So you always need to compose and decompose the id. This can be done much safer and simpler with the proposed Object approach.

Now in RBAC we can of course directly store the composite IDs in the assignments table and voila, right? Not as simple as that.

You want to

  1. store the userId and accountID in separate columns to maintain integrity (account is deleted, assignments are deleted).
  2. easily perform searches such as - find all the users who have assigned access to this account.

Now, imagine how much more complex it can get if you say, multi-account and multi-tenant (with shared database - not a good practice but business is business right?). Your RBAC access checker id there would be userId-accountId-tenantId.

So in synopsis, all practically need is

  1. Custom columns support
  2. Composite ID support (string will convenience the single id systems, object conveniences the composite-id systems - but both solutions do not hinder development)

@ptheofan, I just want to preface this with an apology. I’m kind of old and stubborn. I think the official term is “Ornery”. :wink: I’m guessing we might never agree. Not sure I work well with teams…

I agree with this wholly

These should not be done in or by the RBAC plugin. That would break separation of concerns. RBAC is concerned only with finding proper permissions for a given identity in a given context. Managing Users, Accounts and Tenants is outside of that domain and should be managed separately.

If you want to make sure corresponding Role assignments are deleted when an account (or anything similar) is deleted then that would handled by the identity used to assign the Roles in the first place, not some partial-identity. My guess is that when you delete an account you would be managing the deletion of the user and account link as well, which should not be stored in RBAC, so as you’re doing this remove the role assignment.

If you really really want the RBAC to help manage Accounts, I believe that would need to be handled as Context, not as part of the identity. The context can be managed with Rules.

This is actually the reason for not passing in an object. There is no reason for RBAC itself to manage that complexity.

I think this thread brings up a similar point about separation of concerns:

RBAC library design

The only reason I can think of for why this is wrong is separation of concerns.

Personally I think assigning permissions (or rather Roles) directly to users makes a lot of sense and makes coding way “easier”. Every user should have roles assigned to them, right? However it tends to cripple RBAC in that it limits how you can use it. So it should only be used where it would be a benefit and not a limitation. You would want to make sure you have a good reason for crippling RBAC.

So I agree that it should not be a part of an RBAC plugin because it breaks separation of concerns.

To me it is wrong to assign permission directly to a user, somehow is an old heritage of monolithic apps

It is bad practice to assign permissions directly to users. I don’t recall ever implying anything different.
The direction should be Users -> Roles -> Permissions -> Rules.

Considering the RBAC requirements of a high-performance high traffic SaaS IoT platform

Typically at first, I do a cheap role level check then load resources and perform a real deeper permission level check. A typical action looks like

$ctx = Yii::$app->request->getSecurityContext();
Yii::$app->user->can($ctx) // on fail throw 403
// load relevant resources - if missing throw 404
// attach resources to context
Yii::$app->user->can($ctx) // on fail throw 403

SecurityContext
Constructs itself based on request data. It can create itself either from the session cookie or JWT token. At a minimum, at all times it has identity-id and account-id.

  1. Identity-id the API Access Token the request is using (a user can have multiple Identities - typical SaaS use-case)
  2. account-id the account the request is referring to

The 2 different checks might seem unintuitive at first but there’s a very good (measurable) reason.

  1. Cheap Check: Checking against identityId-accountId makes this lightning-fast (caching) as it’s a basic check - does this identity for that account have link to that permission via some assigned role?

  2. Expensive Check: Now that we have loaded the resources, go deeper and check if this user can access the particular resource (this utilises the loaded objects and most times ends up to some rule). For performance reasons, context response is temporarily cached (with dependency).

When it comes to Identities and permissions (RBAC), my favourite source of inspiration is AWS IAM :slight_smile:

I’m sorry. I was only using that as an example of separation of concerns.

I wouldn’t call AWS IAM an RBAC implementation. It includes a lot of Authentication, including multi-factor and federated access. It even handles Credit Cards with PCI Compliance! It might encapsulate RBAC, but it’s not strict RBAC.

It even handles Credit Cards with PCI Compliance

Are you sure? AWS itself describes it as the following.

AWS Identity and Access Management (IAM) enables you to manage access to AWS services and resources securely. Using IAM, you can create and manage AWS users and groups, and use permissions to allow and deny their access to AWS resources

I’ve always been using it for Users -> Groups -> Policies + Configurations (those little wonderful json snippets).

In any case, the level of detail and customization that IAM offers is awesome - if you don’t get overwhelmed by the countless policies :smiley: