RBAC controls

I have a few different levels of people that will be able to manage users. Is it possible to check if a user has a "higher" or same level of permission.

For instance, moderators shouldn’t be able to edit/manage admin level users admins shouldn’t be able to manage super admin. Ideally I don’t want to add a bunch of if-statements to check the user’s level against the user they’re attempting to manage.

Maybe permission of the level of users that role is allowed to manage?

Check access rules.

I’m not sure I follow. Because access rules are attached to permissions, so then I’d need a permission/permissions hierarchy. So then I’d pass a user object of the current user and test that user’s role against the user the person is attempting to manage?

Can that be calculated, though? For instance if it used a numerical value or a bit I could check if the user that person is trying to manage has a higher level by just comparing those values.


current_user_role > other_user_role

The way I see it right now is that if I added additional roles, like a lower level moderator, I’d have to refactor the access rule every time.

Something like this?




if($user->can('moderator') && $params['user']->can('admin')) {

return false;

}

If there’s a permission canManageUser then that can be set for moderators, canManageModerator for admins, canManageAdmin for super admins, but then if moderator is a parent of the user role, and subsequently admin is the parent of moderator and super admin is the parent of admin then admins would be able to edit super admin because super admin is also a user.

It seems paradoxical, but adding access rules to check above seems like it would require refactoring every time a new role is added. I’m not expecting roles to be added/removed regularly, but in the event that it does happen it seems that refactoring would be necessary. Or am I overlooking something, can you check if a role/permission is the parent of another role/permission?

So there are 4 kinds of users:

[list=1]

[*] normal

[*] moderator

[*] admin

[*] superAdmin

[/list]

First, you will want these permissions:

[list=1]

[*] manageUsers - can manage normal users

[*] manageModerators - can manage moderators

[*] manageAdmins - can manage admins

[*] manageSuperAdmins - can manage superAdmins

[/list]

Secondly you can create these roles:

[list=A]

[*] moderator

[*] admin

[*] superAdmin

[/list]

And then you can construct the RBAC hierarchy:

  • Add ‘manageUsers’ permission as a child of ‘moderator’ role.

  • Add ‘moderator’ role and ‘manageModerators’ permission as children of ‘admin’ role.

  • Add ‘admin’ role and ‘manageAdmins’ permission as children of ‘superAdmin’ role.

Now you can check whether the current user has the right to manage a certain target user like the following:




public function canManageUser($user, $targetUser)

{

    switch ($targetUser->kind) {

        case 'normal'      : $ret = $user->can('manageUsers');       break;

        case 'moderator'   : $ret = $user->can('manageModerators');  break;

        case 'admin'       : $ret = $user->can('manageAdmins');      break;

        case 'superAdmin'  : $ret = $user->can('manageSuperAdmins'); break;

        default            : $ret = false;                           break;

    }

    return $ret;

}



Note that you don’t need to (and should not) check the user’s role. You just have to check the specific permission for the action.

Sometimes you may need some refactoring because you may want to add some new role. Usually it can be done by adding a new permission, modifying the RBAC hierarchy, and adding some code to check the added permission.

So, I was roughly thinking about it the right way then. Thanks for that!

I usually do this kind of construction using migrations.

[list=1]

[*] [migration] Specifies the table schema for the users. It should have a column that distinguishes the user’s type.

[*] [php] Creates a model for the users, probably using Gii. Specifies certain constants for the user type in the model class.

[*] [migration] Creates the permissions for the user management.

[*] [migration] Creates the roles that correspond the user types.

[*] [migration] Creates the RBAC hierarchy among those permissions and roles.

[*] [php] Creates the CRUD for the user model using Gii. Modifies the generated code to check the permissions.

[/list]

In this way I could maintain the whole thing as the source codes that can be version controlled.

I would also like to recommend you to check some extensions for RBAC management, after you have gathered enough knowledge on the basics of it.

My favourite is mdmsoft/yii2-admin. (https://github.com/mdmsoft/yii2-admin)

So then I have this question about it. Why does RBAC not have relational foreign key constraints? For instance, auth_item_child has the parent and the child in text. While I understand this makes it easier to read it would be better if it relied on foreign key constraints from the auth_item table for performance and enforcing data integrity.

Which then the user table could easily reference the user’s “type” from the roles in the auth_item table. I don’t think user’s “type” should be independent. Even though the user’s table would have to be updated separately from the the RBAC tables, there’s no enforcement that the user’s “type” matches a role. The user could end up with the type “fish”, but “fish” isn’t a role. This would need to be programmatically enforced in PHP vs being enforced by database constraints.

yii\rbac\DbManager is extended from yii\rbac\BaseManager which also has yii\rbac\PhpManager as its child. While DbManager is meant for relatively large and complex RBAC, PhpManager is for small and simple one.

As you see in the description of DbManager::$cache, yii\rbac\DbManager is a kind of compromise. In other words, traditional db system is not the best vehicle for RBAC items and their relations, because they are not a 2 dimensional table but rather a network.

http://www.yiiframework.com/doc-2.0/yii-rbac-dbmanager.html#$cache-detail

So I think it’s better not have too deep a coupling between RBAC items and the user objects which are based on the traditional db system.

You can consider using "Default Roles" (http://www.yiiframework.com/doc-2.0/guide-security-authorization.html#using-default-roles).