You are viewing revision #6 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version or see the changes made in this revision.
Authentication and Authorization is a good tutorial. Among other topics, it describes basic aspects of Yii's RBAC implementation. But however hard I read the tutorial, I couldn't understand how exactly the hierarchy works. I found how to define authorization hierarchy, how business rules are evaluated, how to configure authManager, but almost nothing about how I should build my hierarchy, in what sequence its nodes are checked, when the checking process stops and what would be the checking result.
There was no other way for me but to dig into Yii's code and I would like to present my findings in this article. I have to mention that digging in Yii's code is not difficult at all, it's well-structured and everyone can do it, but the following info can save you a bit of time particularly when you're a Yii newbie.
I must say it would be much easier for you to understand the article if you got familiar with the above-mentioned tutorial especially with the topics starting from Role-Based Access Control.
Let's consider the hierarchy example from the tutorial (this example illustrates how security can be built for some blog system):
$auth=Yii::app()->authManager;
$auth->createOperation('createPost','create a post');
$auth->createOperation('readPost','read a post');
$auth->createOperation('updatePost','update a post');
$auth->createOperation('deletePost','delete a post');
$bizRule='return Yii::app()->user->id==$params["post"]->authID;';
$task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule);
$task->addChild('updatePost');
$role=$auth->createRole('reader');
$role->addChild('readPost');
$role=$auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
$role=$auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
$role=$auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
First of all I'd like to convert this to a more human-readable form:
Sample blog system authorization hierarchy |
The turquoise boxes represent roles, the yellow box is a task, and the most fine-grained level of the authorization hierarchy - operations - are tan. Collectively roles, tasks and operations are called authorization items. You should keep in mind that functionally all auth item types are equal. It's completely up to you to make some auth item a role or a task - still it would do the same thing. Different types of auth items are introduced solely for the purpose of naming convenience. You are not limited to the three authorization levels: there can be multiple levels of roles, tasks and operations. (Getting back to our diagram, you can see this point illustrated by multiple levels of roles.) Also you may skip any of these levels (the role author has immediate child operation create). The only restriction is that in the auth hierarchy roles should stay higher than tasks and tasks should stay higher than operations.
Now let's take a quick look at what was on blog system creator's mind. Everything seems to be quite logical. The weakest role is reader: the only thing he is allowed to do is to read. An author has a bit more power: he also can create posts and update his own posts. Editors can read posts and update (edit) all posts, not own ones (in fact, according to the hierarchy, editors can't create posts and that's why editors haven't got any own posts at all). And of course, the most powerful role is admin which can do anything.
If you are familiar with the principles of object-oriented hierarchy, your former knowledge may lead you to a confusion. In every subsequent level of an object tree, objects obtain (inherit) all (or part) of the features of their parent (base) objects. This results in that bottommost objects are most "loaded" with features, while the root objects have only basic features. The opposite happens with RBAC hierarchy in Yii. The bottommost items in the authorization hierarchy represent basic operations, while the topmost authorization items (usually roles) are the most powerful and compound ones in the whole authorization system.
So now that the idea behind the hierarchy is clear, let's understand how the access checking works. To check if the current user as allowed to perform a particular action, you should call the the checkAccess method, for example:
if(Yii::app()->user->checkAccess('createPost'))
{
// create post
}
How our hierarchy is used by Yii to check the access? Although you are not required to read this to understand the rest of the article, I provide here an example piece of Yii's code responsible for access checking (an implementation of CAuthManager for databases - CDbAutManager) for your reference:
if(($item=$this->getAuthItem($itemName))===null)
return false;
Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CDbAuthManager');
if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
{
if(in_array($itemName,$this->defaultRoles))
return true;
if(isset($assignments[$itemName]))
{
$assignment=$assignments[$itemName];
if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
return true;
}
$sql="SELECT parent FROM {$this->itemChildTable} WHERE child=:name";
foreach($this->db->createCommand($sql)->bindValue(':name',$itemName)->queryColumn() as $parent)
{
if($this->checkAccessRecursive($parent,$userId,$params,$assignments))
return true;
}
}
return false;
When you call checkAccess, Yii begins to recursively climb along the authorization hierarchy and check each item's business rule. For instance, when you make a call like this
Yii::app()->user->checkAccess('readPost')
Yii first checks the readPost's business rule (recall that an empty business rule is equivalent to a business rule always returning true). Then it searches for all readPost's parents - these are author and editor - and checks their business rules as well. The process doesn't stop when a business rule has been evaluated to true; it only stops when some rule returned false or we have reached the top of the hierarchy and there are no more parents to check.
So what are ways for the checkAccess method to return true? They are two. First, the iteration can stop with a positive result when Yii encounters in the hierarchy a so-called default role - a role that is assigned by default to all authenticated users. For our blog system this can be the reader role. Default roles can be set up in the web app configuration file; how this is done is described thoroughly in the Using Default Roles section of the tutorial.
The second way to make checkAccess return true is explicitly creating an authorization assignment which is basically defining an <auth item>-<user> pair. In code, this can be done like this:
$auth->assign('reader','Pete');
$auth->assign('author','Bob');
$auth->assign('editor','Alice');
$auth->assign('admin','John');
which is semantically equivalent to assigning roles to users. You're not limited to assigning roles; individual tasks and operations can be assigned to users as well. In real life, it is more practical not to hard code all auth assignments but to store them in a database. You can implement this scenario using the CDbAuthManager component which is described in the Yii tutorial.
Let's get back to the checkAccess discussion. Before hierarchy iteration begins, Yii collects all authorization items assigned to the current user and at each iteration step checks if current hierarchy's auth item is in the assignment list. If it is, the iteration stops and returns a positive result.
Assume we are implementing security for the "update post" user action. Whoever is logged in into our blog system should pass our authorization check before he is able to edit a post. Therefore the most appropriate place to check the access is the beginning of the respective controller action:
public function actionUpdatePost()
{
if(!Yii::app()->user->checkAccess('updatePost'))
Yii::app()->end();
// ... more code
}
Suppose the current user is Alice. Let's see how Yii processes the auth hierarchy. Although updateOwnPost is an immediate parent of updatePost and returns false, Yii quickly finds another parent auth item which returns true - the editor role. As a result, Alice gets a permission to do a post update. What happens if Bob logs in? In this case the branch of the hierarchy going through the editor item is also processed but no item along it returns true. The only possible way for the access check to succeed is then to go through the updateOwnPost item.
But instead of an empty "always-true" business rule updateOwnPost has a more complex one (see the first code snippet at the beginning of the article) and for the evaluation it requires the post creator's ID. How can we supply it to the business rule? In the form of checkAccess's parameter. To achieve this we need to modify our controller action handler in the following way:
public function actionUpdatePost()
{
// here we obtain $post, probably via active record ...
if(!Yii::app()->user->checkAccess('updatePost', array('post'=>$post)))
Yii::app()->end();
// ... more code
}
Note that despite updateOwnPost returns true for Bob, the iteration through auth hierarchy still goes on. It only stops and returns success when it reaches the author item.
I think now you're able to figure out how Yii would check access given that Pete or John logged in.
Returning to the above code snippet, it may seem that we're providing the post parameter to the updatePost operation whose business rule is empty and requires no parameters at all. This is truth but not all of it. In fact Yii passes the same parameter set (there can be several parameters as they are passed as an array) to every hierarchy item at every iteration. If item's business rule requires no parameters, it simply ignores them. If it does require them, it takes only those that it needs.
This leads to the two possible parameter passing strategies. The first one is to remember for every auth item what other auth items can be reached from it in the hierarchy and provide each call to checkAccess with the exact number of parameters. The advantage of this strategy is code brevity and probably efficiency. The other strategy is to always pass all parameters to every auth item, no matter if they would actually be used for business rule evaluation. This is a "fire-and-forget" method which can help to avoid much of trial and error while implementing you app's security. Its downside is possible code clutter and maybe drop in script performance.
This is only basic information about the RBAC authorization model in Yii; much more advanced security models can be built using it. Please refer to The Definitive Guide to Yii and Class Reference for more details. Also there's a number of web interfaces implemented as extensions which can help you do the Yii RBAC administration.
Excellent explanation
Thanks for this tutorial. Your graphical explanation really helps to those like me have visual learning styles.
Question about hierachy
I'm a little confused about the hierachy of "update" and "updateOwn". If an author has the "updateOwn" permission, doesn't that mean that he also inherently has the "update" permission? That is, he can update any post, not just his.
Edit:
Figured it out. The hierarchy is counter-intuitive: users DON'T automatically gain inherited permissions.
Specifically, the author wouldn't inherit "update" just because it is a child of "updateOwn". Instead, the author gains "update" IF AND ONLY IF he passes the bizRule in "updateOwn".
Link to post
Thanks very much for this phenomenal tutorial
It helped me much to understand RBAC logical
CPhpAuthManager vs CDbAuthManager
This page (http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#role-based-access-control) explains that the role-based access control settings can be either stored in a PHP file or in the database.
With respect of storing which user has which roles, I find the database storage to be more logical. It may envolve a lot of data, one or more row per user, so database seems better. This data changes all the time with the life of the site.
However with respect of storing hierarchy, authorization items, I find it makes more sense to store them in a file. The reason being that I can easily ship my PHP project with built-in hierarchy without having to export SQL data. Defining the auth items is part of the creation of the site, therefore I don't think it should live in the database.
Does it make sense? Is there a way to cleanly combine both?
On a smilar note, the example of a "BizRule" is very simple in the documentation. What if I had more complex business rules? Can I have a function/method somewhere that the business rule will call?
Third question: Let's say I want to have a "moderator" role. However I have moderators for "categories". So Moderator A can moderatore category A while moderator B can moderate category B. Can the basic hierarchy work with this?
See my post: http://www.yiiframework.com/forum/index.php/topic/33540-cphpauthmanager-vs-cdbauthmanager/
helpful
really helpful
exceptions
I have defined a role with many tasks and operations.
Is there any way to assign this role to a user but EXCLUDE one or more operations, which normally would be allowed thorugh the role?
Exceptions possible but...
@Lo3ty First off, you'd better ask this question in the forum. Second, what you want is not a generic hierarchical design. Lastly, here are the ways to do what you want:
Generate Graph
Anyone know of a tool to generate a graph like that demo graph posted here? But with my real hierarchy?
It'd be a nice way to visualize my work and catch potential logic errors...
Great tutorial
Thank you for posting this tutorial. It is so much better that original Yii tutorial on RBAC.
Lovely
Great job. Thanks a lot
checkAccess in Controller action
Very good article on RBAC! It took me long enough to finally understand it. I'm concerned about how to implement the authorization 'more effeciently'. As the article suggested, it's appropriate to place the checking in controller action. If I have a lot of controller actions to be checked, wouldn't it be hard to maintain all the checkAccess? Any better way?
public function actionUpdatePost() { if (!Yii::app()->user->checkAccess('updatePost')) Yii::app()->end(); // ... more code }
Tool to Graph
Great Wiki. Thanks.
What tool you used to generated the graph of "Sample blog system authorization hierarchy"?
Re: Tool to Graph
I created the current image using OmniGraffle Pro.
I don't know what the original image was created with. It was gone one day and I just found a copy of it on Google.
Thanks
Thanks man.
Graphing Tool
@rAWTAZ Thanks for supporting this article! My initial image was hosted on a free hosting and then I suppose it was deleted.
For those who are interested in what tool I used to draw this image, it was no more than just good old MS PowerPoint 2003. It has all that is needed to create such images and even more complex ones.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.