Yii RBAC: access to specific items/rows

2019-05-26 02:43发布

I have a use case where I need to assign a user the right to edit highly dynamic items, which can be in the hundreds or thousands. Each user, while belonging to the same type or group, must be assigned to some of these items (and different users may have access to the same Company Items). Furthermore, these items can rapidly grow in number or disappear. These items have no intrinsic relationship with the users, but must be arbitrarily assigned to them.

Lets call these items Company Items.

So, I want to be able to assign Company Items to users, and revoke that access dynamically. These assignments are then used inside controllers to check if some action can go on... Conceptually, the problem is always the same: test if a user has access to a specific item/row in a table, the Company Items' table.

My idea was to use the yii RBAC system, while trying to keep the authorization tree static, thus avoiding creating/deleting roles or tasks every time a Company Item is created or deleted. Instead, I was wondering If I could do this using the $data parameter in assign($itemName, $userId, $bizRule, $data) and a tree similar to the following:

  • adminUser: role
    • companyAdmin: role
      • editCompanyItemRole: role with bizrule; bizrule tests access to Company Item by simply checking if $params['companyItemId'] exists inside $data['companyItemsAllowed']; at assignment time, should receive a $data containing an array of Company Items' ids the user should be allowed to edit!
        • editItem: operation; used to check access in the Controllers, and should be provided with the Company Item id one wishes to check the user against, e.g., Yii::app()->user->checkAccess('editItem', array('companyItemId' => 666));

This way, whenever we need to change the user assignment to Company Items, the only thing we need to do is to alter the $data['companyItemsAllowed'] array inside the original assignment. The role is always the same!

Questions:

  1. Does this system work, can I use Yii's RBAC system in this fashion ??
  2. Is this the ideal way to accomplish the requirements, assuming we have thousands of Company Items, and we may have dozens of those assigned to each user ?? Why ??

3条回答
Explosion°爆炸
2楼-- · 2019-05-26 03:21

I wrote a small framework for access control. It is simple, small and light. My motivation was to decouple the access control of my applications from other frameworks, because I think the access control from others frameworks very coupled to framework style.

For instance in Yii where you have to program it in the controller, and I don't like it, because is very boring program new rules when you add a new action(from Yii Wiki):

class PostController extends CController
{

    public function accessRules()
    {
        return array(
            array('deny',
                'actions' => array('create', 'edit'),
                'users' => array('?'),
            ),
            array('allow',
                'actions' => array('delete'),
                'roles' => array('admin'),
            ),
            array('deny',
                'actions' => array('delete'),
                'users' => array('*'),
            ),
        );
    }

}

IMHO, it must be done dynamically. You should be able to program the rule in its administrative system visually and then the developer program an action and a model appropriately and then is ready.

To integrate my library with Yii applications I use import, for example:

$path = Yii::getPathOfAlias('application.services.RapidAuthorization');
Yii::setPathOfAlias('RapidAuthorization', $path);

And I use beforeControllerAction() from module(s) to integrate.

When I have time I will write a manual integration for multiple frameworks, first with which I have experience as Yii, Zend and Symfony. And translate the documentation to english. But the code is english now.

I hope that at least give you an idea of ​​how to write your access control.

查看更多
放我归山
3楼-- · 2019-05-26 03:26

You can make use of Yii's concept of data owner in its access control implementation.

The first step to implementing this in your own application is to instruct the controller to enable this rule. This is done by overwriting the filters() function.

class ContentController extends Controller {

    public function filters() {
        return array(
            'accessControl'
        );
    }

    public function accessRules() {
    }
}

The 'accessControl' flag specifies that access control is applied for data management. The actual business rules are defined in the accessRules() function, and specifying the access control expression that will be evaluated to provide the desired control. And example of the function implementation is.

public function accessRules() {
    return array(
        array('allow', // allow all users to perform 'index' and 'view' actions
            'actions' => array('view'),
            'users' => array('*'),
        ),
        array('allow', // allow authenticated user to perform 'add' action
            'actions' => array('add'),
            'users' => array('@'),
        ),
        array('allow', // allow only the owner to perform 'modify' 'delete' actions
            'actions' => array('modify', 'delete'),
            'expression' => array('ContentController','isMyRecord')
        ),
        array('deny', // deny all users
            'users' => array('*'),
        ),
    );
}

The isMyRecord is a method that will be run that returns true or false to indicate if the action should be allowed.

public function isMyRecord(){

    if (Yii::app()->user->checkAccess( ...))
       return true;
    else
       return false;
}
查看更多
干净又极端
4楼-- · 2019-05-26 03:29

I decided to take the following approach, after deciding that simply maintaining an array of Company Items inside $data['companyItemsAllowed'] was not the best for these requirements:

  • created an association table between Users and CompanyItems; call it association_table;
  • created the RBAC tree as shown in the question, but where the bizRule was something like the following:

    $ret = Yii::app()->dbConnection->createCommand('SELECT EXISTS(SELECT 1 FROM `association_table` WHERE user_id=:userId AND company_item_id=:companyItemId)')
    ->queryScalar(array(':userId' => $params['userId'], 'companyItemId' => $params['companyItemId']));
    return $ret;
    

This allows me to maintain the access control interface, like so:

    Yii::app()->user->checkAccess('editItem', array('companyItemId' => 666));

(recall that we do not need to pass on userId on the $params array!.)

Of course, this separates the actual assigning of permissions to Company Items from the RBAC system: I assign editCompanyItemRole to some user using the RBAC mechanisms offered by Yii, but each actual item must be assigned individually by inserting a row onto association_table...

So, although first thought about maintaining an array of Company Items inside $data would probably work, I think this is best and more flexible. Also, the general idea about the bizRule seems to work.

查看更多
登录 后发表回答