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.
first you define controller attribute that will hold loaded model. It is easily accessible also from views.
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.
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.
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)
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.
More detailed samples, please
Would you please provide us with a little more usage examples?
I'd like to know how to implement:
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)
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.
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 :)
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.
re: bizRules in CAccessControl.
yes you can, but there are three aspects of that:
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.
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...
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 :)
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.
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.
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...
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.