How to use bizRules in standard accessControl filter

8 followers

This wiki article has not been tagged with a corresponding Yii version yet.
Help us improve the wiki by updating the version information.

I assume you are familiar with accessControl standard filter. It allows you to separate privileges to run some action from action code itself. There is however one problem with most common usage - you cannot use bizRules, at least based on some parameters passed to 'checkAccess' just because it is hard to pass them in accessRules() result.

I would like to describe here programming pattern similar to Ruby On Rails, which simplifies some things and makes possible to use all advantages of RBAC with bizRules.

To do this you will need very simple extension: http://www.yiiframework.com/extension/csdataloaderfilter/

Then you may use it to load some models based on GET/POST params BEFORE accessControll filter is used, so you can pass models loaded this way in accessRules.

Example:

class MyController extends Controller {
   protected $model;
 
    public function filters() {
        return array(
            array( 'CSDataLoaderFilter', 'loadModel', 'on'=>array( 'action' ) ),
            'accessControl',
        );
    }
 
    public function accessRules() {
        return array(
            array( 'allow',
                'roles'=>array( 'allowEdit' => array( 'model'=>$this->model ) 
                'actions'=>array( 'action' ),
            ),
            array( 'deny' ),
        );
    }
 
    public function loadModel() {
        $this->model = Model::model()->findByPk( Yii::app()->request->getParam( 'id' ) );
        if( $this->model === null ) {
            throw new CHttpException( 404, 'Model not found' );
        }
    }
 
    public function actionAction() {
        //you may use $this->model safely here - it is gauranteed that it is loaded, exists and user has privileges to run this action for loaded model
        ...
    }

Now in details. Assume you have controller which runs some action against data models referenced with some id in URL, like: index.php?r=controller/action&id=123.

  1. first you define controller attribute that will hold loaded model. It is easily accessible also from views.

  2. then you define filters(). It is important to know, that filters are used in the same order they are defined in this method. This means you can load all required models and finally run authorization process to check if user is allowed to access them.

  3. Access rules are defined as usual except that now you can reference loaded model as parameter to access rules (it is guaranteed that it is loaded or loadModel would throw exception). Passing attributes is crucial to properly use bizRules in authorization items.

  4. data loader method - it is required that it must be "public" method (accessible from filter). You may implement scenarios when model is required (throw exception if it does not exists) or just optional models (leave null value in $this->model attribute)

  5. you code actions as usual except that you do not need to load data in the beginning and check privileges - this pattern assures you that those checks are already done.

As you can see - it is extremely easy to implement and greatly clarifies controller code. Also makes possible to use standard access rules but with parameters needed for bizRules.

Total 8 comments

#13907 report it
redguy at 2013/07/05 04:01am
re: re re: bizRules in CAccessControl.

well... accessRules is called only once :) maybe it was not a very good argument. I just thought rather about something similar to 'rules' in model, which is used in massive assignements, validation, etc and it is required that this function must return static rule set independent from current model values. I just think that accessRules should be treated similar...

#13896 report it
seenivasan at 2013/07/04 01:28pm
re re: bizRules in CAccessControl.

Dear redguy

Your inputs are really valuable to me.

Considering our three aspects, assigning loaded model to a property and reusing it will eliminate problems 1 and 3.

I am curious to know the situations where controller calls the CController::accessRule() many times in a single request.

Regarding third problem, we can select the actions for bizRules by using $this->action->id .

By saying that I not arguing for my case. I tried to put the Iogic somewhere else. I considered the following options.

  1. CController::beforeAction. (But it is called after filters).
  2. CwebApplication::beforeControllerAction. (But it needs overiding of CWebApplication)

That made me to put the things in accessRule method.

Your extension is very simple and will be certainly useful.

I thank you for going through my wiki and giving very useful feedbacks.

That will surely make me a better PHP coder.

Regards.

#13893 report it
redguy at 2013/07/04 10:47am
re: bizRules in CAccessControl.

yes you can, but there are three aspects of that:

  1. in your example loaded models are not reusable (you are not assigning them to controllers property), you cannot access it in action body. This is pretty easy to solve, of course.

  2. you cannot distinguish whether model you are loading is really required to evaluate access rules or not - accessRules must return valid rules not depending on 'action' that is called, so you must fetch your model also for actions that do not require model in its accessRules...

  3. it is very bad programming pattern because you are using some interface method (accessRules) for something that it was not intended for - i.e. loading data. If for some reason Yii will call that function more than once - you will end up with many redundant db queries.

Your solution works as well, but sometimes you should not program things in some way just because it works (unfortunately PHP programmers tend to do it too often). It is harder to maintain such project later.

The main difference is that CSDataLoaderFilter is consistent and valid programming pattern.

I am talking here only about data loading part of your wiki - rest is ok, and look that you could easily combine your approach with mine dataloaderfilter and it only requires simple change in data loading in accessRules :)

#13891 report it
seenivasan at 2013/07/04 10:14am
bizRules in CAccessControl.

We can load model(s) prirorhand and implement the bizRules in CController::accessRules method itself.

KIndly check this wiki.

Yet another implementation of CPhpAuthManager.

Regards.

#13889 report it
redguy at 2013/07/04 09:22am
re: re: More detailed samples, please

yes, you are correct. The main problem is that you need model instance during access check, so you need to load it somehow earlier. Even "beforeAction" is called AFTER filters, so you cannot use it to implement data loading code. You could only put it in beforeControllerAction hansdler of whole application... Using csdataloaderfilter is much more flexible :)

#13888 report it
softark at 2013/07/04 09:12am
re: More detailed samples, please

Thanks, I think I got it.

So, in the code above, "allowEdit" role (auth item) should have some biz rule like the following:

return Yii::app()->user->isAdmin()
    ? true
    : ((isset($params["model"])
        ? Yii::app()->user->getId()==$params["model"]->owned_by
        : false);

Am I right?

And without CSDataLoaderFilter we can not use this kind of biz rule for the filter because we won't have the model yet at this point.

#13885 report it
redguy at 2013/07/04 08:22am
re: More detailed samples, please

@softark This article is not intended to explain basics of RBAC bizRules, but only how to enable accessControl filter to use them. For more info on bizRules itself you should read:

http://www.yiiframework.com/wiki/136/getting-to-understand-hierarchical-rbac-scheme/

but for the case you described, you need two auth items: EditModels and EditOwnModels

EditOwnModels has bizRule = 'return isset($params["model"])?Yii::app()->user->getId()==$params["model"]->owned_by:false;'

and EditOwnModels has EditModels as a child item.

to check permissions you always call: Yii::app()->user->checkAccess( 'EditModels', array( 'model'=>$model ) )

now - if you assign "EditModels" to a user, then he will be able to edit any model (Yii::app()->user->checkAccess( 'EditModels', array( 'model'=>$model ) ); will always return true because EditModels has no additional logic)

if you assign "EditOwnModels" to a user, then checkAccess will evaluate bizRule, if it returns true - then user has EditOwnModels right to provided $model, and so he has also EditModels permission (because EditModels is also a child of EditOwnModels)

#13884 report it
softark at 2013/07/04 07:43am
More detailed samples, please

Would you please provide us with a little more usage examples?

I'd like to know how to implement:

  1. Role with 'admin' can edit any instance of the model.
  2. The owner of the instance can edit it.
  3. Other users can not edit it.

Leave a comment

Please to leave your comment.

Write new article