RBAC and AuthManager Hierarcy

Hi!

I’m new to Yii and are still in the learning process, but now I need some help.

I want to extend the blog tutorial with roles using RBAC. My idea is that I have a column in my user table (database) that tells the system what wole a given user has. The role have one/multiple tasks and each task has multiple operations.

Example: User Pegleg has role Admin

Role admin has UserManagment, PostManagment tasks

User/Post, Managment have create, update, delete operations.

How do I make this happen:) The only thin I know is taht I want to use the CAuthManager’s functions since these look just to sexy to walk past;)

PegLeg

Edit:

Sorry for the double post, having some problems with my internet connection.

This is an example of a permissions structure that I am using for some of my user management stuff. It creates the operations and roles that we want, then assigns them how we want them. It assigns both operations and roles as children of other roles.




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


// Create our operations

$auth->createOperation('createUser', 'Create a user');

$auth->createOperation('editUser', 'Edit a user');

$auth->createOperation('updateUserStatus', 'Update a user\'s status');

$auth->createOperation('deleteUser', 'Delete a user');

$auth->createOperation('purgeUser', 'Purge a user');


// Create the moderator roles

$role = $auth->createRole('userModerator', 'User with user moderation permissions');

$role->addChild('updateUserStatus');


// Create the admin roles

$role = $auth->createRole('userAdmin', 'User with user administration permissions');

$role->addChild('userModerator');

$role->addChild('createUser');

$role->addChild('editUser');



Then, you can assign users specific permissions that you want them to have, or you can assign them to roles. Then, with each action when you check for the permissions you can either check for a given role or for the specific permissions. These checks may be a bit over the top. They first check for the roles required for the action, then check for the individual operations that I want to be able to do each action.




public function accessRules()

{

	return array(

		array(

			'allow',

			'actions' => array('show', 'admin', 'list', 'moderator', 'activate'),

			'roles' => array('moderator', 'userModerator', 'admin', 'userAdmin', 'createUser', 'editUser', 'deleteUser', 'purgeUser', 'updateUserStatus')

		),

		array(

			'allow',

			'actions' => array('update'),

			'roles' => array('admin', 'userAdmin', 'createUser', 'editUser', 'deleteUser', 'purgeUser')

		),

		array(

			'allow',

			'actions' => array('delete'),

			'roles' => array('admin', 'userAdmin', 'deleteUser', 'createUser', 'purgeUser')

		),

		array(

			'allow',

			'actions' => array('create'),

			'roles' => array('admin', 'userAdmin', 'createUser', 'purgeUser')

		),

		array(

			'allow',

			'actions' => array('purge'),

			'roles' => array('purgeUser')

		),

		array(

			'deny',

			'users' => array('*')

		)

	);

}



Hi killermonk,

thanks for the example of below code.

Where is it placed in? I mean what file/directory:

""""""""""""""""""""""""""""""""""""""""""""""""""""""""

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

// Create our operations

$auth->createOperation(‘createUser’, ‘Create a user’);

$auth->createOperation(‘editUser’, ‘Edit a user’);

$auth->createOperation(‘updateUserStatus’, 'Update a user\‘s status’);

$auth->createOperation(‘deleteUser’, ‘Delete a user’);

$auth->createOperation(‘purgeUser’, ‘Purge a user’);

// Create the moderator roles

$role = $auth->createRole(‘userModerator’, ‘User with user moderation permissions’);

$role->addChild(‘updateUserStatus’);

// Create the admin roles

$role = $auth->createRole(‘userAdmin’, ‘User with user administration permissions’);

$role->addChild(‘userModerator’);

$role->addChild(‘createUser’);

$role->addChild(‘editUser’);

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

because the next "public function accessRules()" is in the appropriate Controller.

Thanks in advance

Greg

I put this code in my config controller. I have a config controller that sets up the database, creates all the permissions, creates the default users, assigns the permissions to those users, etc.

Thanks a lot killermonk.

I put my code in protected/components/UserIdentity.php inside the UserIdentity class, but it doesn’t work.

Is Your code in protected/config.main.php? This file is created automatically by Yii framework (yiic webapp), but main.php returns only configuration array and I cannot see the place there to put this access hierarchy code with auth-creator (inside this array).

Let me know, please, exactly the path of Your access-hierarchy file and the explanation why and when this code is run/performed. I’m preparing my first Yii supported application - quite real, more complicated then demo-blog (25 tables in MySQL database). Do You know any better real Yii application example? Where to find it?

Sincerely regards

Greg

Well, the problem with putting it inside something like UserIdentity is that you don’t want it to be run unless it needs to be run. The create functions for roles, tasks, and operations will fail if items of those names already exist. Because of this, I made a sub-class of CDbAuthManager and overloaded all the create functions to first check if the item exists and only try to create it if it doesn’t exist. If that makes sense.

I have a special controller for configuring my application. It’s in protected/controllers/ConfigController.php. It then has 1 action: ‘initialize’. This action then calls all the methods to create my database structure (and update it if necessary), create all of my permissions, and then create any default items I want that don’t already exist. It is run only when I specifically visit the URL for it in the browser and force it to run.

Because it is a page that I want to be able to access even on my live server. The initialize action is also password protected and has a captcha to prevent automated brute force attempts. Because the script is designed to be run without a database structure being set up, the password is hardcoded into the script. Not secure if someone gets access to my code, but if they have access to my code then they already have access to my database username/password (since it’s in the config) and everything is already compromised.

Since the permission structure, etc, is fairly constant I only have to run it once manually and then it just works. When I make changes to these things, which doesn’t happen very often, I just re-run the initialize action in the config controller to do all the new work for me. And, since I have my override class, I don’t need to worry about role/task/operation collisions.

Does that help?

You are very helpful Killermonk.

  1. I thought that UserIdentity is good place for identifying user and set the access hierarchy for him. It is executed each time the user is loging successfully. It seems I’m wrong although I don’t understand the reason.

  2. My access hierarchy is quite simple for now. I’m not going to use CDbAuthManager or any its sub-class.

  3. On my low level of experience with Yii I only gently develop existing (after yiic webapp) files. I hesitate to create new because I actually don’t understand Yii workflow enough.

  4. In MySQL database I keep all my users (particular data with passwords) and after authentication, user should have properly access set through CRUD (own data table, own records in other tables), thus database structure is constant.

  5. I think in my webapp initialisation of auth and all the access rules could be executed live every user login (not mamualy with captcha as Yours way). I need a good example of BizRule/$params. The example in the Guide is nor clear enough for me.

  6. Could You suggest me some direction for better Yii code examples searching? Or send some samples of Your code (with paths/files)?

I will be grateful.

Greg

poczta@gbkonto.net

Well, if you are not using CDbAuthManager then you can have all the permissions initialized for each user when they log in. I am storing all my permissions in the database, so when I initialize the permissions themselves, not the assignments, only once. Auth assignments happen on user creation and when I want to give specific user’s permissions.

So the authManager code I gave you is what I have in the config controller and is executed manually when I want to.

I’m not sure how you are storing your permissions, but I would recommend just using the CDbAuthManager. That way they are set up once and you don’t have to worry about the extra overhead associated with constantly creating them. You can find the SQL required to create the tables needed in [yii_dir]/framework/web/auth/schema.sql

Then you just assigned the roles/permissions/tasks you want a user to have when they are created, etc, then you just have to worry about checking that the user has the proper permissions on a given action. Your application may be running with different logic, though, so this might not be the best solution.

Personally, if I have something that is going to be executing the same code repeated and their is a way to cache the result of that code execution, then I try to cache it. It seems to me like creating the information at every login is something that could be done once, cached (aka saved in the database), and then referenced when it is needed.

I, personally, do not use bizRules in my permissions. They have their use, but they are evaluated using an ‘eval’ statement, and I try to avoid anything that works with evals. You can look at RBAC - Just can’t get my head around it, it might be useful to help you wrap your head around it a little more.

The code I posted above is literally everything I am doing for permissions. I have all the authManager code in the ConfigController that I execute manually to create everything. Then I have the access rules at the top of each restricted controller that defines the permissions required to execute each action.

Does that help?

in protected/components/UserIdentity.php…in class UserIdentity extends CUserIdentity in public function authenticate() I have:

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

$auth->createOperation(‘zobaczWszystko’,‘pokaz/show wszystkich’);

$role=$auth->createRole(‘podgladacz’);

$role->addChild(‘zobaczWszystko’);

$auth->assign(‘podgladacz’,‘apiano’);

in protected/controllers/UserController in class UserController extends CController in public function actionShow() I have:

if(Yii::app()->user->checkAccess(‘zobaczWszystko’))

After successfully login of apiano user the access checking is always failed (false)!!! Why???? I tried also:

if(Yii::app()->user->checkAccess(‘podgladacz’)) the same result. How is the access hierarchy connected with Yii::app()->user->checkAccess(‘AccesItem’)? How is the AccessItem accessible for …checkAcces? I suppose the problem is hidden in this connection (disconnection!!!). But how to ensure? How to trace the problem? Have You any idea? I’m tired (frustrated) with repeating the same steps. Could You help me, please?

Thanks in advance.

Greg

Ok, I think I figured out what your problem is. The default authManager for Yii (CPhpAuthManager) is not persistent across page loads the way you are using it. If you do a checkAccess() just below where you do your assignments, the checks will come back as true. Once this page execution finished, though, all of the auth data is lost.

This auth manager can be persistent, there are just a few extra steps that need to be performed.

First: Create the file ‘protected/data/auth.php’ and make it writable by your server

Second: Use the following code inside your authenticate function




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


$changed = false;

if ($auth->getAuthItem('zobaczWszystko') === null) {

	$auth->createOperation('zobaczWszystko','pokaz/show wszystkich');

	$changed = true;

}

if ($auth->getAuthItem('podgladacz') === null) {

	$role=$auth->createRole('podgladacz');

	$role->addChild('zobaczWszystko');

	$changed = true;

}

if (!$auth->isAssigned('podgladacz','apiano')) {

	$auth->assign('podgladacz','apiano');

	$changed = true;

}


if ($changed) {

	$auth->save();

}



This will create the permissions for a user the first time they log in and save them so that they are persistent between page requests. I have tested it and it works. Just make sure to create and make writable the ‘protected/data/auth.php’ file.

Thanks for Your patience Killermonk, unfortunately Your latest script doesn’t work, the error message given by FF-browser is (was):

PHP Error

Description

Invalid argument supplied for foreach()

Source File

/home…/web/protected/framework/web/auth/CPhpAuthManager.php(449)

00437: $this->saveToFile($items,$this->authFile);

00438: }

00439:

00440: /**

00441: * Loads authorization data.

00442: */

00443: public function load()

00444: {

00445: $this->clearAll();

00446:

00447: $items=$this->loadFromFile($this->authFile);

00448:

00449: foreach($items as $name=>$item)

It seems that the problem is with empty file …protected/data/auth.php I’ve created with proper access rights but without any content. CPhpAuthManager is trying to loadFromFile($this->authFile); and execute it with foreach($items as $name=>$item) THIS LINE IS POINNTED AS ERROR. May be I should fill some initial data to this file but I don’t know the format.

OK, above is the history already!!! I’ve deleted empty auth.php and Yii has created his own with the content:

<?php

return array (

‘zobaczWszystko’ =>

array (

'type' =&gt; 0,


'description' =&gt; 'pokaz/show wszystkich',


'bizRule' =&gt; NULL,


'data' =&gt; NULL,

),

‘podgladacz’ =>

array (

'type' =&gt; 2,


'description' =&gt; '',


'bizRule' =&gt; NULL,


'data' =&gt; NULL,


'children' =&gt; 


array (


  0 =&gt; 'zobaczWszystko',


),


'assignments' =&gt; 


array (


  'apiano' =&gt; 


  array (


    'bizRule' =&gt; NULL,


    'data' =&gt; NULL,


  ),


),

),

);

But it doesn’t resolve the problem, the error message is the same as before, it seems the “if (Yii…checkAccess(…))” is still false.

I’ve not found any mention about …protected/data/auth.php in the GUIDE (OK, I’ve found 2 small mentions in CPI). I hadn’t …protected/data path at all, because I don’t use sqlite, which database file sqlitedatabasename.db is placed in there. Where can I find any doc/giude/help about the mechanism of persistence of AccesItems? I thought they are persistent objects of Yii framework, not kept in the file accessible by UserControler checkAccess() function. I have the auth.php file with (I hope) proper content (created by Yii) and… it doesn’t work.

Now, the access hierarchy is loaded from the auth.php file in the UserIdentity.php, checked if not changed and left unchanged, am I right? Exactly the same meaning has the php code (createOperation, createRole and Assign). I cannot see the connection/communication between AccesItems created in …UserIdentity.php and checkAccess(AccessItem) in …UserControler. Do You see it?

What to do then?

Regards

Greg

Ok, I took exactly what you pasted for your permissions structure and it worked for me. I used the following code snippet to test it:




$identity = new UserIdentity('apiano','apiano');

Yii::app()->user->login($identity, 0);


echo'<pre>';

echo'1: ';var_dump(Yii::app()->user->checkAccess('zobaczWszystko'));

echo'2: ';var_dump(Yii::app()->user->checkAccess('podgladacz'));

echo'3: ';var_dump(Yii::app()->authManager->checkAccess('zobaczWszystko', 'apiano'));

echo'4: ';var_dump(Yii::app()->authManager->checkAccess('podgladacz', 'apiano'));

exit;


Output:

1: true

2: true

3: true

4: true



Are you sure that the user ‘apiano’ is properly logged in before you are doing your permissions checks?

As for the documentation about the files, I found them in the comments of CPhpAuthManager. If you look in framework/web/auth/CPhpAuthManager.php it talks about the file at the top. The rest of what I figured out came from reading the comments.

If you are looking in the guide at the tutorials on access control (Docs/Guide/Topics/Auth) you will notice that to have persistent permissions they are using CDbAuthManager.




return array(

    'components'=>array(

        'db'=>array(

            'class'=>'CDbConnection',

            'connectionString'=>'sqlite:path/to/file.db',

        ),

        'authManager'=>array(

            'class'=>'CDbAuthManager',

            'connectionID'=>'db',

        ),

    ),

);



If you are using a different database, then change the database config. There is no way to have persistent data without saving it somewhere. Either to the database or to a file on the server.

And, on a side note, if you want the code that you are pasting to be nicely formatted, press the button on the format bar of the editor that looks like this "<>". It will insert a code block for you where you can paste your code.

What does “var_dump(Yii::app()->user->getId());” output? You can see by number 3/4 that the permissions are being set in such a way that allows the auth system to recognize that ‘apiano’ has those permissions. It might be with how your UserIdentity class is set up.

"The target is: let properly logged user to see his own database (MySQL) records, simply (?)." What do you mean by this? Is there data in the database that you want them to be able to read, but you want to check to make sure they are properly logged into the application first?

Sorry about the delay, I was recently moved and hadn’t got my internet set up yet.

  1. "var_dump(Yii::app()->user->getId());" is: string(1) "2" (it is "id" value if apiano record) - I see the results of outputs 3/4 but I can see the above result of 0 … it is "apiano" as well. Why "apiano" and "apiano" are not equal? How my UserIdentity class could be set up? Are there some different ways?

  2. My target with other words is: even properly logged user can see only his own records of database (MySQL, not SQLite). Thus he must be properly logged && must be proper user.

Thanks Your explanations and guides Killermonk, I’ve found another way to reach my target:

$userid=user::model()->findbyPk($id!==null ? $id : $_GET[‘id’]);

if(Yii::app()->user->id==$userid[‘id’]) {show Your own record}

IT DOES WORK

Regards

Greg

PS Where are You from Killermonk? I’m from Poland small village near Warsaw.

Yeah, it seems like your way works better for what you are trying to do.

I’ve been living in Las Vegas, NV for the past 2 years. Moved for work as a full-time web developer. Picked up Yii and haven’t looked back.

Ok, I’m having similar problem here. I’m trying to extend the blog demo application to have RBAC.




$identity = new UserIdentity('demo','demo');

Yii::app()->user->login($identity, 0);

echo'<pre>';

echo'1: ';var_dump(Yii::app()->user->checkAccess('createPost'));

echo'2: ';var_dump(Yii::app()->authManager->checkAccess('createPost', 'demo'));

echo'3: ';var_dump(Yii::app()->authManager->checkAccess('createPost', 1));



Which returns:




1: bool(false)

2: bool(true)

3: bool(false)



And I figured out why this happens, but I would like to know why it works with killermonk.

The reason is that ->user->checkAccess uses getId() which is INTEGER USER ID, and

we have given the permission to user by it’s name. And this doesn’t work.

If I add assignment to the users id also that first checkAccess starts to work,

then the database has:




| itemname | userid | bizrule | data |

+----------+--------+---------+------+

| master   | 1      | NULL    | N;   | 

| master   | demo   | NULL    | N;   | 



Which is stupid to have both there. But reading the documentation:

http://www.yiiframework.com/doc/api/CDbAuthManager#checkAccess-detail

That says it’s mixed! So both should work. Well let’s remove that ‘master’ from

userid 1 if I add test 3 to the upper php and result is false, we can see that

the mixed doesn’t work! So it’s mixed like just use ONE of them, not mix them up.

I would give a tip so that, check what user->getId() returns and use that

to assign rights to users.

But have to admit all the RBAC documentation is … just pain.

By default, CUserIdentity::getId returns the username of the user.

CWebUser::checkAccess uses the following line of code to check the user’s access: Yii::app()->getAuthManager()->checkAccess($operation,$this->getId(),$params);

When you login in a user, it uses the UserIdentity you are passing and uses the return value of $identity->getId() as the id for the web user.

So, if you are using the default code, and haven’t overridden CWebUser::getId() and CUserIdentity::getId(), then as long as you do your $authManager->assign($role, $username), everything should work without a hitch.