Yii 1.1: How to setup RBAC with a php file

23 followers

In this cookbook I will attempt to explain how to use the lightweight version of Role-Based Access Control using a php file. This version does not use database but a php file and is controlled by CPhpAuthManager class.

Configuring the roles:

By default when setting up this particular type of rbac Yii will look for any defined roles in a file named auth.php and located in protected/data/auth.php - for sake of easiness we'll use the example of user RBAC to the blog demo.

Info: Yii expects to read auth.php and get an array() out of it. So we need to create auth.php and return array(); Yii also needs to write to that file when changing roles so make sure to provide enough permission access to that file needed by the system.

HOWEVER: since Yii rewrites the auth.php file into a form that's far more difficult to edit, it's a good idea to edit a auth.txt file, where you can format and comment properly, then copy it to auth.php when you're ready to try it. The first time Yii rewrites auth.php it will be apparent why you want to do this.

Declare some roles in our auth.txt file:

// protected/data/auth.php
 
return array(
    'reader' => array (
        'type'=>CAuthItem::TYPE_ROLE,
        'description'=>'Can only read a post',
        'bizRule'=>'',
        'data'=>''
   ),
 
    'commentor' => array (
        'type'=>CAuthItem::TYPE_ROLE,
        'description'=>'Can post a comment',
        'bizRule'=>'',
        'data'=>''
    ),
 
    'admin' => array (
        'type'=>CAuthItem::TYPE_ROLE,
        'description'=>'Can read a post and post a comment',
        'children'=>array(
            'reader','commentor'
        ),
        'bizRule'=>'',
        'data'=>''
   )
);

The above code declares 3 different types of roles:

1 - reader - this type of role can only read a post but not post any comments

2 - commentor - this role gives access only to the comments form section to post a comment.

3 - admin - which can read a post and post a comment (consists of both roles above).

The bizRules and data elements aren't needed by this role scheme, but CPhpAuthManager seems to require them (and it will fail without them).

Once you've edited the file, put it in place:

$ cp auth.txt auth.php
$ chmod a+w auth.php               _# make sure Apache can write to it_

If you have to make a change to the roles, do it in the .txt file and re-copy to auth.php

Configuring the accessRules():

Now that we've setup our roles we should move to apply them in action. In this example I will only apply them to our PostController as below:

// in protected/controllers/PostController.php
 
class PostController extends CController {
 
    public function filters()
    {
        return array(
            'accessControl'           // required to enable accessRules
        );
    }
 
    public function accessRules()
    {
        return array(
            array('allow', // allow readers only access to the view file
                'actions'=>array('view'),
                'roles'=>array('reader')
            ),
 
            array('deny',   // deny everybody else
                'users' => array('*')
            )
        );
    }
    ...

The above code should be pretty clear - allow user with 'reader' role access to the view action.

NOTE: Yii accessRules default to allow, so the explicit deny is required if you want this behavior.

Configuring our tbl_user in our database:

Next we add an additional field to our tbl_user. We call that field role (varchar 30). We also need two user entries in this table. We already have the 'demo' one from the blog tutorial and add a 'test' one. In the 'demo' role field entry 'reader' as data and for 'test' enter 'admin' as a role.

Assigning roles:

Now we need to tell Yii when a user logs in what role s/he gets. I do this part in my UserIdentity class which takes care of my authentication for my blog. Here is how my UserIdentity class looks like:

public function authenticate()
{
    $user=User::model()->find('LOWER(username)=?',array(strtolower($this->username)));
 
    if($user===null)
        $this->errorCode=self::ERROR_USERNAME_INVALID;
    else if(!$user->validatePassword($this->password))
        $this->errorCode=self::ERROR_PASSWORD_INVALID;
    else
    {
        $this->_id=$user->id;
        $this->username=$user->username;
        $auth=Yii::app()->authManager;
        if(!$auth->isAssigned($user->role,$this->_id))
        {
            if($auth->assign($user->role,$this->_id))
            {
                Yii::app()->authManager->save();
            }
        }
 
        $this->errorCode=self::ERROR_NONE;
    }
    return $this->errorCode==self::ERROR_NONE;
}

The code we have added to the original UserIdentity class is:

$auth=Yii::app()->authManager; //initializes the authManager
 
if(!$auth->isAssigned($user->role,$this->_id)) //checks if the role for this user has already been assigned and if it is NOT than it returns true and continues with assigning it below
{
    if($auth->assign($user->role,$this->_id)) //assigns the role to the user
    {
        Yii::app()->authManager->save(); //saves the above declaration
    }           
}

Info: Please see comments at the end of the lines for explanation on what every line of code does. It is important to remember that it is good practice to check if a roles has already been assigned becuase Yii assignes roles and does not delete them until you call the revoke() function. In case you forget and try to re-assign a role Yii will return an error. Another important point is when you assign a role you must save it by calling Yii::app()->authManager->save();

Configuring our main.php to use authManager:

$auth=Yii::app()->authManager;

The code snippet above initializes the authManager, which we need to setup in our protected/config/main.php config file as follows:

// protected/config/main.php
 
return array(
    ...
    'components'=>array(
        'authManager'=>array(
            'class'=>'CPhpAuthManager',
//          'authFile' => 'path'                  // only if necessary
        ),
        ...

This basically activates the authorization Manager of the application and tells Yii that we want to use CPhpAuthManager class to take care of our accessControll. When you login Yii will assign a role to your user id. After you login open up the auth.php file and see that Yii has re-arranged it in the appropriate way.

For the sake of testing our functionality we should now add some RBAC check to our views/post/view.php:

<?php if(Yii::app()->user->checkAccess('commentor')): ?>
 
    <h3>Leave a Comment</h3>
            .........//your /commnet/_form here
<?php endif; ?>

Place the above code around your comments form section in the view file to check if the user has enough access privileges to post a comment.

You should now have a working privilege based system. One more thing left for our cookbook to be complete.

Info: When the user logs out we need to delete the assigned role otherwise if you change that user's role while he is offline and when he comes back and logs in again he will end up with two roles: the old one and the new one! So we place the below code in our logout action in the SiteController:

// protected/controllers/SiteController.php
 
public function actionLogout()
{
    $assigned_roles = Yii::app()->authManager->getRoles(Yii::app()->user->id); //obtains all assigned roles for this user id
    if(!empty($assigned_roles)) //checks that there are assigned roles
    {
        $auth=Yii::app()->authManager; //initializes the authManager
        foreach($assigned_roles as $n=>$role)
        {
            if($auth->revoke($n,Yii::app()->user->id)) //remove each assigned role for this user
                Yii::app()->authManager->save(); //again always save the result
        }
    }
 
    Yii::app()->user->logout(); //logout the user
    $this->redirect(Yii::app()->homeUrl); //redirect the user
}

Additional/optional settings:

In your auth.php file you can use the following parameters: - type => role,task,operation - description => describe the type - bizRule => apply business rule - data => used in the business rule - children => inherit other roles/tasks/operations

The 'type' is represented by the following constants in the CAuthItem class:

const TYPE_OPERATION=0;
const TYPE_TASK=1;
const TYPE_ROLE=2;

Related readings

Role-Based Access Control
CModel::rules()
CAuthManager
RBAC clarification
another related rbac approach
CPhpAuthManager - how it works, and when to use it

Disclaimer: The above code works for me. I do not guarantee that it will work in all situations. If you need more complex RBAC structure use the DB one. I've read all posts in the forum re: RBAC but none of them helped me so the above code has been discovered through trial & error. Use it on your own responsibility.

Total 17 comments

#14007 report it
bettor at 2013/07/11 10:12am
variable data

@Amini

http://www.yiiframework.com/doc/api/1.1/CAuthItem#data-detail

#14005 report it
Amini at 2013/07/11 08:11am
variable data

hi, Thanks for your good article. What is the use of variable data? thx alot for replay.

#13926 report it
bettor at 2013/07/06 03:50pm
@srm

Hi srm,

I don't use Windows sorry. I wrote this tutorial very long time ago. Please double check all methods used in this tutorial against the documentation and make sure nothing has changed. Can you show me where you have used enabled property in your code. It is not part of this tutorial neither of the CPhpAuthManager.

#13921 report it
srm at 2013/07/06 02:19am
unix command

Hi again, i use windows, is it possible to you explain how could i write auth.php file in windows and give access to Yii for write in that? thank you

#13920 report it
srm at 2013/07/06 02:16am
error

Hi everybody, my website occur to this error Property "CPhpAuthManager.enabled" is not defined. could you help me please?

#12160 report it
ram87 at 2013/03/02 08:23am
RBAC

USERiDENTITY.PHP public function authenticate() { $record = RRegister::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID;

    else if($record->password!==$this->password)
        $this->errorCode=self::ERROR_PASSWORD_INVALID;
    else
    {
        $this->id=$record->id;
        echo $this->setState('role', $record->role); 

        $this->errorCode=self::ERROR_NONE;
    }
    return !$this->errorCode;
}

public function getId(){
    return $this->id;
}

MAIN.PHP

'user'=>array( // enable cookie-based authentication 'allowAutoLogin'=>true, //set user role class 'class' => 'WebUser', ),

components/webuser.php

<?php /* * To change this template, choose Tools | Templates * and open the template in the editor. */ class WebUser extends CWebUser { /** * Overrides a Yii method that is used for roles in controllers (accessRules). * * @param string $operation Name of the operation required (here, a role). * @param mixed $params (opt) Parameters for this operation, usually the object to access. * @return bool Permission granted? */ public function checkAccess($operation, $params=array()) { if (empty($this->id)) { // Not identified => no rights return false; } $role = $this->getState("role"); if ($role === 'admin') { return true; // admin role has access to everything } // allow access if the operation request is the current user's role return ($operation === $role); } } ?>

VIEW FORM:

<div id="mainmenu">
    <?php 
            $user = Yii::app()->user;
            $this->widget('zii.widgets.CMenu',array(
        'items'=>array(
            array('label'=>'Home', 'url'=>array('/site/index')),
            //array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')),
            //array('label'=>'Contact', 'url'=>array('/site/contact')),
                            array('label'=>'Create Account', 'url'=>array('/RRegister/create'),'visible'=>$user->checkAccess('admin')),
                            array('label'=>'Amount Deposit', 'url'=>array('/RDEposit/create'),'visible'=>$user->checkAccess('staff')),
                            array('label'=>'Amount Transaction', 'url'=>array('/RTransaction/create'),'visible'=>$user->checkAccess('staff')),
                            array('label'=>'Amount Withdraw', 'url'=>array('/RWithdraw/create'),'visible'=>$user->checkAccess('normal')),
            array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest),
            array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest)
        ),
    )); ?>
</div><!-- mainmenu -->
#12159 report it
ram87 at 2013/03/02 08:22am
role base access control..

USERiDENTITY.PHP [code] public function authenticate() { $record = RRegister::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID;

    else if($record->password!==$this->password)
        $this->errorCode=self::ERROR_PASSWORD_INVALID;
    else
    {
        $this->id=$record->id;
        echo $this->setState('role', $record->role); 

        $this->errorCode=self::ERROR_NONE;
    }
    return !$this->errorCode;
}

public function getId(){
    return $this->id;
}

[/code] MAIN.PHP [code] 'user'=>array( // enable cookie-based authentication 'allowAutoLogin'=>true, //set user role class 'class' => 'WebUser', ),

[/code] components/webuser.php [code] <?php /* * To change this template, choose Tools | Templates * and open the template in the editor. */ class WebUser extends CWebUser { /** * Overrides a Yii method that is used for roles in controllers (accessRules). * * @param string $operation Name of the operation required (here, a role). * @param mixed $params (opt) Parameters for this operation, usually the object to access. * @return bool Permission granted? */ public function checkAccess($operation, $params=array()) { if (empty($this->id)) { // Not identified => no rights return false; } $role = $this->getState("role"); if ($role === 'admin') { return true; // admin role has access to everything } // allow access if the operation request is the current user's role return ($operation === $role); } } ?> [/code]

VIEW FORM: [code]

<?php $user = Yii::app()->user; $this->widget('zii.widgets.CMenu',array( 'items'=>array( array('label'=>'Home', 'url'=>array('/site/index')), //array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')), //array('label'=>'Contact', 'url'=>array('/site/contact')), array('label'=>'Create Account', 'url'=>array('/RRegister/create'),'visible'=>$user->checkAccess('admin')), array('label'=>'Amount Deposit', 'url'=>array('/RDEposit/create'),'visible'=>$user->checkAccess('staff')), array('label'=>'Amount Transaction', 'url'=>array('/RTransaction/create'),'visible'=>$user->checkAccess('staff')), array('label'=>'Amount Withdraw', 'url'=>array('/RWithdraw/create'),'visible'=>$user->checkAccess('normal')), array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest), array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest) ), )); ?>

[/code]

#11934 report it
SteveD at 2013/02/13 02:05pm
This is not so bad for some use cases

@emix @fsb You're both right to say that this subverts the intention of having authManager keep the role assignments in permanent storage. However, I find a variation of this technique very useful in a particular use case I've encountered.

The generalized use case is a site where some users (call them "account administrators") can create and modify other users with fewer privileges (less full-featured roles). Rather than a complex integration with authManager, it makes sense to define a small set of roles in a lookup table tied to a column in the user profile table (not the user table, as suggested in this article). The lookup table has a column for the role's common description (seen by the account admins) and one for the corresponding role name in authManager.

Now in implementing this technique, we can let the account admins modify the role column in the user profiles of people they've created. When this occurs, the next time that user logs in, he or she will have a new set of permissions.

In this use case, the reassignment at login is not such an outlandish way to handle roles. You can argue that with the same set-up, the best place to reassign a user's role is in the profile model when the role column value is altered. Sure. That's probably better. But this article's technique isn't so very terrible either.

#10244 report it
Backslider at 2012/10/13 12:48pm
This is better

I thought this was ok after having looked at several RBAC extensions... these days I use the methods outlined here: http://www.yiiframework.com/doc/guide/1.1/en/topics.auth

Much better!

#10243 report it
fsb at 2012/10/13 12:31pm
emix is right, this is wrong

This wiki demonstrates a fundamental misunderstanding of CAuthManager.

Yii's RBAC stores user role assignments in its own persistent store. But this wiki proposes storing role assignments in the application's tbl_user.

This creates a disconnect: without a role assignments in the RBAC store, CAuthManager cannot do anything useful. The wiki closes the disconnect by copying the user's role assignment from the app's tbl_user into RBAC on every page load and saving it.

That's just wrong. In Yii RBAC, you store the definition of auth items, the auth item hierarchy AND the user auth assignments in RBAC, not in your app's table of users.

The copy and save of auth assignments causes needless server load but that may nor may not be a problem. What is certainly a problem is a tutorial teaching incorrect use of Yii RBAC.

#10240 report it
emix at 2012/10/13 11:28am
That's so wrong

Sorry, but I think you misunderstood the concept of PhpAuthManager and you are making other people repeat your mistakes..

Imagine having 1.000.000 users, hundreds of them logging in and out every second, haven't it occurred to you that this approach is just wrong?

You should only define roles, task and operations inside auth.php file and assign the user to his role each time he logs in to the system and store his roles inside a cookie or in the session, that's dead simple, never call save() too.

#7864 report it
kiennguyen at 2012/04/23 11:35pm
Not a good choice

CPhpAuthManager ovewrites the auth.txt file every time save() is called. It means with thousands of users logging in/out a day, that would take up a lot of time to execute this.

#7253 report it
Vicente Russo at 2012/03/07 08:45am
Error?
if(!$auth->isAssigned($user->role,$this->_id))
{
    if($auth->assign($user->role,$this->_id))
    {
        Yii::app()->authManager->save();
    }
}

Haven't tried the code, but the above code shouldn't be changed to something this?

if(!$auth->isAssigned($user->role,$this->_role))
{
    if($auth->assign($user->role,$this->_role))
    {
        Yii::app()->authManager->save();
    }
}
#5365 report it
alexandrep at 2011/10/07 04:28am
auth.php has storage data?

Hi, In your example I don't really understand why you are using this auth.php to save the role of a user. You load the role of the user from the database, then you save this role in the auth.php. And when the user logout you remove the role from the auth.php. Why?? In this case, why just not use session?

When I saw this way of storing roles and so on, I was thinking that should not have interaction at all with the database. The file should keep the roles all the time, and give us the possibility to make the relation between a userId and roles.

#4091 report it
Backslider at 2011/06/05 12:24pm
Makes sense

This method makes a lot more sense to me that what I see around as database centered RBAC managers:

  1. Zero database queries (this is a BIG plus)
  2. Very simple to implement
  3. Far mor granular by being able to use: Yii::app()->user->checkAccess() wherever required.

Sure, its a little more work, but so what?

#3688 report it
Askani at 2011/04/28 06:48am
getID error

Just as a side note here: for anyone doing this; please check and make sure that you have the getID() function in your UserIdentity component... without it, this method fails.

public function getID()
        {
            return $this->_id;
        }
#3675 report it
Askani at 2011/04/27 10:12am
Very helpful

Thanks for this! Made my life a million times easier this morning... got through the whole of RBAC just by reading this and a few other snippets around the site.

Thanks! :)

Leave a comment

Please to leave your comment.

Write new article
  • Written by: bettor
  • Updated by: Stageline
  • Category: How-tos
  • Yii Version: 1.1
  • Votes: +26 / -4
  • Viewed: 51,014 times
  • Created on: Feb 7, 2010
  • Last updated: Aug 27, 2012
  • Tags: Authentication