Yii Framework Forum: Does Anyone Have A Working Example Of Rbac? - Yii Framework Forum

Jump to content

  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

Does Anyone Have A Working Example Of Rbac?

#1 User is offline   DocSolver 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 35
  • Joined: 12-December 12

Posted 21 November 2013 - 05:01 PM

Hi all!

My site has three different levels of authenticated users. I want to allow/deny controllers and actions based on the user role level.

It's a fairly simple, linear scheme: each user level had more rights than the one below it. I guess I need to use RBAC to do this, but I can't find any examples or documentation. I've gone through the source code, but I find it hard to understand how I should set it up.

Does anyone have an example of:
  • Configuring RBAC
  • Using RBAC in a controller
  • The way roles are defined and users are assigned


Thanks!
0

#2 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 06:33 PM

Ok. I hope someone will correct me if I'm wrong.

First af all, you modify your config (web.php),

'authManager' => [
    'class' => 'app\components\PhpManager', // THIS IS YOUR AUTH MANAGER
    'defaultRoles' => ['guest'],
],


Next, create the manager itself (app/components/PhpManager.php)

<?php
namespace app\components;

use Yii;

class PhpManager extends \yii\rbac\PhpManager
{
    public function init()
    {
        if ($this->authFile === NULL)
            $this->authFile = Yii::getAlias('@app/data/rbac') . '.php'; // HERE GOES YOUR RBAC TREE FILE

        parent::init();

        if (!Yii::$app->user->isGuest) {
            $this->assign(Yii::$app->user->identity->id, Yii::$app->user->identity->role); // we suppose that user's role is stored in identity
        }
    }
}


Now, the rules tree (@app/data/rbac.php):

<?php
use yii\rbac\Item;

return [
    // HERE ARE YOUR MANAGEMENT TASKS
    'manageThing0' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
    'manageThing1' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
    'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
    'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],

    // AND THE ROLES
    'guest' => [
        'type' => Item::TYPE_ROLE,
        'description' => 'Guest',
        'bizRule' => NULL,
        'data' => NULL
    ],

    'user' => [
        'type' => Item::TYPE_ROLE,
        'description' => 'User',
        'children' => [
            'guest',
            'manageThing0', // User can edit thing0
        ],
        'bizRule' => 'return !Yii::$app->user->isGuest;',
        'data' => NULL
    ],

    'moderator' => [
        'type' => Item::TYPE_ROLE,
        'description' => 'Moderator',
        'children' => [
            'user',         // Can manage all that user can
            'manageThing1', // and also thing1
        ],
        'bizRule' => NULL,
        'data' => NULL
    ],

    'admin' => [
        'type' => Item::TYPE_ROLE,
        'description' => 'Admin',
        'children' => [
            'moderator',    // can do all the stuff that moderator can
            'manageThing2', // and also manage thing2
        ],
        'bizRule' => NULL,
        'data' => NULL
    ],

    'godmode' => [
        'type' => Item::TYPE_ROLE,
        'description' => 'Super admin',
        'children' => [
            'admin',        // can do all that admin can
            'manageThing3', // and also thing3
        ],
        'bizRule' => NULL,
        'data' => NULL
    ],

];


And voila, now you can add access control filters to controllers

public function behaviors()
{
    return [
        'access' => [
            'class' => 'yii\web\AccessControl',
            'except' => ['something'],            
            'rules' => [
                [
                    'allow' => true,
                    'roles' => ['manageThing1'],
                ],
            ],
        ],
    ];
}


Have fun.

PS. Right now I don't understand what is defaultRoles and how I can use them effectively.

Also, I think this can be done a little bit simpler.

I hope Yii2's core devs will shed some light on it.
God is real unless declared as integer
0

#3 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 06:46 PM

Also, this is the simplest way to use roles avoiding RBAC:
http://www.yiiframew...__1#entry228758

Here how it goes:

First of all, we'll override AccessRule.

<?php

namespace app\components;

class AccessRule extends \yii\web\AccessRule
{
    protected function matchRole($user)
    {
        if (empty($this->roles)) {
            return true;
        }
        foreach ($this->roles as $role) {
            if ($role === '?' && $user->getIsGuest()) {
                return true;
            } elseif ($role === '@' && !$user->getIsGuest()) {
                return true;
            } elseif (!$user->getIsGuest()) {
                // user is not guest, let's check his role (or do something else)
                if ($role === $user->identity->role) {
                    return true;
                }
            }
        }
        return false;
    }
}


Next, we'll add access control filter, pure role-based

<?php

namespace app\modules\admin\controllers; // whatever

use yii\web\Controller;

class AdminController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => 'yii\web\AccessControl',
                'ruleConfig' => [
                    'class' => 'app\components\AccessRule' // OUR OWN RULE
                ],
                'rules' => [
                    [
                        'allow' => true,
                        'roles' => ['role1', 'role2'], // ACCESS IS ALLOWED ONLY FOR role1 and role2
                    ],
                ],
            ],
        ];
    }
}

God is real unless declared as integer
0

#4 User is offline   danschmidt5189 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 55
  • Joined: 19-February 13

Posted 21 November 2013 - 06:49 PM

If you don't need to manage permissions via the web, I would strongly recommend forgetting about RBAC and just using methods in your user models. With a setup this simple, you will save yourself the headache that is RBAC.

Why not just do something like this?

<?php
public function accessRules()
{
    return array(
        array(
            'allow',
            'expression' =>'WebUser::LEVEL_ADMIN === Yii::app()->user->level',
        ),
    );
}


I really can't stress it enough: if you can effectively manage permissions through the code and don't NEED to use a database, don't use RBAC. It is massive overkill and will waste your time.
0

#5 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 07:11 PM

View Postdanschmidt5189, on 21 November 2013 - 06:49 PM, said:

If you don't need to manage permissions via the web, I would strongly recommend forgetting about RBAC and just using methods in your user models.
I really can't stress it enough: if you can effectively manage permissions through the code and don't NEED to use a database, don't use RBAC. It is massive overkill and will waste your time.


Frankly speaking, I have no idea how to solve the following pretty common task w/o using RBAC:

1. User can create and modify his own post
2. User can upload and manage his own images for this post.
3. Admin can manage everything.

So the post looks like this: [id, owner_id, description]
and the image looks like this: [id, owner_id, post_id, file]

W/o RBAC full rights check would be something like
if (((Yii::$app->user->id == $image->owner_id) && (Yii::$app->user->id == $post->owner_id)) || (Yii::$app->user->identity->role == 'admin')) {
    ... can update image
} 


Looks not good.

Using RBAC, I can do something like

if (Yii::$app->user->checkAccess('editImage', ['record' => $image]) && Yii::$app->user->checkAccess('editPost', ['record' => $post])) {
    ... can edit
}


Any idea how to avoid using RBAC here?
God is real unless declared as integer
0

#6 User is offline   danschmidt5189 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 55
  • Joined: 19-February 13

Posted 21 November 2013 - 07:29 PM

View PostORey, on 21 November 2013 - 07:11 PM, said:

Frankly speaking, I have no idea how to solve the following pretty common task w/o using RBAC:

1. User can create and modify his own post
2. User can upload and manage his own images for this post.
3. Admin can manage everything.

So the post looks like this: [id, owner_id, description]
and the image looks like this: [id, owner_id, post_id, file]

W/o RBAC full rights check would be something like
if (((Yii::$app->user->id == $image->owner_id) && (Yii::$app->user->id == $post->owner_id)) || (Yii::$app->user->identity->role == 'admin')) {
    ... can update image
} 


Looks not good.

Any idea how to avoid using RBAC here?


Assuming your user is associated with some AR model:

<?php
// access rules
return array(
    // Admin has full access. Applies to every controller action.
    array(
        'allow',
        'expression' =>'Yii::app()->user->model->isAdmin',
    ),
    // delegate to user model methods to determine ownership
    array(
        'allow',
        'expression' =>'Yii::app()->user->model->isPostOwner(Yii::app()->request->getQuery("postId"))',
        'actions'    =>array('editPost', 'uploadPostImage'),
    ),
    array(
        'allow',
        'expression' =>'Yii::app()->user->model->isImageOwner(Yii::app()->request->getQuery("imageId"))',
        'actions'    =>array('editImage'),
    ),
    array('deny'),
);
...

// In your user model
public function isPostOwner($postId)
{
    $post = Post::model()->findByPk($postId);
    return !$post || ($post->owner_id === $this->id);
}

public function isImageOwner($imageId)
{
    $image = Image::model()->findByPk($imageId);
    return $image && ($image->owner_id === $this->id) && $this->isPostOwner($image->post_id);
}


If you abstract your action params away from the HTTP request, a la my yii-rest extension or the Symfony2 FOSRESTBundle, you can do this more systematically.

In general, I think action parameters should be treated as a model, not as a raw array like Yii does out of this box. This is a good reason why. Suppose the action param $postId was actually just an instance of Post; your access rules would be extremely clear. (See below.) You could also validate them!

// Access rules if action params was an object/model
return [
    [
        'allow',
        'expression' =>'Yii::app()->user->isAdmin',
    ],
    [
        'allow',
        'expression' =>[$this->actionParams->post, 'isOwnedByUser'],
        'actions' =>...
    ],
    [
        'allow',
        'expression' =>[$this->actionParams->image, 'isOwnedByUser'],
        'actions' =>...
    ],
];

1

#7 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 07:34 PM

Also, if we're talking about images, what's the best way to check access to 'parent' record ('post' in this case)?

The thing is, if we're not checking access to parent, nasty user can attach images to other users' posts, that's insecure. So we need another check for owner or rights.

That includes yet another DB query and checkAccess() call. Is there any way to avoid it, or make less annoying?

PS. One day I was thinking about merging owner_id and primary key, like this:
111111_222222
^----^ ^----^
owner   PK


That's crazy but no need to query DB for parent :)
God is real unless declared as integer
0

#8 User is offline   danschmidt5189 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 55
  • Joined: 19-February 13

Posted 21 November 2013 - 07:54 PM

View PostORey, on 21 November 2013 - 07:34 PM, said:

Also, if we're talking about images, what's the best way to check access to 'parent' record ('post' in this case)?

The thing is, if we're not checking access to parent, nasty user can attach images to other users' posts, that's insecure. So we need another check for owner or rights.

That includes yet another DB query and checkAccess() call. Is there any way to avoid it, or make less annoying?

PS. One day I was thinking about merging owner_id and primary key, like this:
111111_222222
^----^ ^----^
owner   PK


That's crazy but no need to query DB for parent :)


I'd say just query the AR relationship. Use "with =>'post'" if you want to avoid the extra DB call.

I definitely would not combine the keys. That seems like a maintenance nightmare down the road.

You could also validate the post_id attribute in your model. E.g. instead of just validating that the post exists, validate that the current user is owns it.

And to simplify the above further, you could just make your own authmanager component implementing the CDbAuthManager::checkAccess() method/signature. That way if you ever wanted to switch to RBAC you could just swap components.
1

#9 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 07:55 PM

// In your user model
public function isPostOwner($postId)
{
    $post = Post::model()->findByPk($postId);
    return !$post || ($post->owner_id === $this->id);
}


This doesn't seem good to me also.
First of all, on real-world examples user model will become bulky very soon.
Next, you use extra query here.

As for me, I'd like to
1. keep the code as clean as possible
2. avoid extra queries for rights check.

Let's look at common update action :

function actionUpdate($id)
{
    $record = Model::find($id);
    if (...POST) {... $record->save();}
}


We've already found the record, so the only thing left is to check for rights.
function actionUpdate($id)
{
    $record = Model::find($id); // HERE IT IS
    if (... cannot edit...) throw exception.
    if (...POST) {... $record->save();}
}


Yes, we can store it somewhere (private $_record) and use $this->getRecord($id) to avoid extra query.
But still we need to check for parent... :(
God is real unless declared as integer
0

#10 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 07:58 PM

View Postdanschmidt5189, on 21 November 2013 - 07:54 PM, said:

I'd say just query the AR relationship. Use "with =>'post'" if you want to avoid the extra DB call.


Yes, finally I've come to that. Don't like it either, btw.
God is real unless declared as integer
0

#11 User is offline   danschmidt5189 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 55
  • Joined: 19-February 13

Posted 21 November 2013 - 08:02 PM

View PostORey, on 21 November 2013 - 07:55 PM, said:

This doesn't seem good to me also.
First of all, on real-world examples user model will become bulky very soon.
Next, you use extra query here.


Make your own auth manager component implementing checkAccess(). Takes 10 minutes and you consolidate all access checks there. If you switch from RBAC later, you keep the same interface. (Only have to swap component.)

Also you don't eliminate the multi-db-query problem using RBAC. If anything, RBAC adds a LOT of extra DB calls.

Any decent-sized RBAC implementation will require a web interface to manage. If you're not planning on taking it that far it's not worth the effort.

Besides, access checks should not be in your actions. The action should be dead simple. The ideal is something like:

<?php
public function actionSave(CActiveRecord $model, array $data=null)
{
    $model->setAttributes($data);
    $saved = $model->save();
    $this->render('saved', array('saved' =>$saved, 'model' =>$model));
}


That means handling parameter mapping and authorization before the action is ever invoked.
1

#12 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 08:06 PM

View Postdanschmidt5189, on 21 November 2013 - 08:02 PM, said:

If anything, RBAC adds a LOT of extra DB calls.


In most cases PhpManager is enough, so no DB calls (except for those for record and parent)
God is real unless declared as integer
0

#13 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 21 November 2013 - 08:17 PM

View Postdanschmidt5189, on 21 November 2013 - 08:02 PM, said:

Besides, access checks should not be in your actions. The action should be dead simple. The ideal is something like:


Ok, I'll think about that. Thanks for a lot of code :)

PS. I hope we didn't give a fright to this post's author :)
God is real unless declared as integer
0

#14 User is offline   danschmidt5189 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 55
  • Joined: 19-February 13

Posted 21 November 2013 - 08:21 PM

View PostORey, on 21 November 2013 - 08:17 PM, said:

Ok, I'll think about that. Thanks for a lot of code :)

PS. I hope we didn't give a fright to this post's author :)


Haha, yeah! I hope some good ideas came across. I think Yii in general is REALLY bad about keeping controllers/actions thin. Here's the gist of my thinking. Actions should be EXTREMELY thin. Action parameters should be supplied by a persistent (throughout the request) model, so that they can be filtered. Controllers just link together filters and actions; they should only be minimally involved in the logic required to load a model.
1

#15 User is offline   DocSolver 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 35
  • Joined: 12-December 12

Posted 22 November 2013 - 01:05 PM

First of all, thank you very much. I really appreciate both the extensiveness of your responses, and the thorough discussion!

ORey: thanks for explaining RBAC. It enables me to make an informed decision. I think it would be a good basis for the RBAC doc on yiisoft github. If you want I can try to submit it, with credit going to you.

In my application, everyone except admin is only able to view (different amounts of) data, so there is no need to assign owners to uploaded assets ets.

Therefore, I think I'll skip RBAC and go with @danschmidt5189's suggestion. My roles are expressed as an integer, where a higher number means more acces. This means I can simply check whether the user's role number is equal to the minimum allowed role number.
0

#16 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 22 November 2013 - 01:41 PM

View PostDocSolver, on 22 November 2013 - 01:05 PM, said:

ORey: thanks for explaining RBAC. It enables me to make an informed decision. I think it would be a good basis for the RBAC doc on yiisoft github. If you want I can try to submit it, with credit going to you.


Actually this is my very first bite of RBAC (before Yii2 I was using something similar to danschmidt's approach), so I'm not sure I get it right. This needs to be reviewed and corrected by Yii core devs (or by somebody who knows how to do it the right way).

But in general I don't mind if you use it for commit or something. Just fix my terrible English :)
God is real unless declared as integer
0

#17 User is offline   danschmidt5189 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 55
  • Joined: 19-February 13

Posted 22 November 2013 - 03:44 PM

View PostDocSolver, on 22 November 2013 - 01:05 PM, said:

First of all, thank you very much. I really appreciate both the extensiveness of your responses, and the thorough discussion!

ORey: thanks for explaining RBAC. It enables me to make an informed decision. I think it would be a good basis for the RBAC doc on yiisoft github. If you want I can try to submit it, with credit going to you.

In my application, everyone except admin is only able to view (different amounts of) data, so there is no need to assign owners to uploaded assets ets.

Therefore, I think I'll skip RBAC and go with @danschmidt5189's suggestion. My roles are expressed as an integer, where a higher number means more acces. This means I can simply check whether the user's role number is equal to the minimum allowed role number.


Future proof yourself by implementing this through your own AuthManager component that implements checkAccess(). Use the \yii\rbac\Manager::checkAccess() signature. ($itemName, $userId, $params.)

It's a quick implementation, and should you ever decide to switch to RBAC you will only have to change the component and not all of your authorization checks. IF it ever comes to that, you will be happy you did.

Ok, I'm done. Happy coding. :)
0

#18 User is offline   ORey 

  • Elite Member
  • PipPipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,695
  • Joined: 20-April 09
  • Location:Moscow, Russia

Posted 22 November 2013 - 03:49 PM

View Postdanschmidt5189, on 22 November 2013 - 03:44 PM, said:

Use the CDbAuthManager::checkAccess() signature.


Only one little correction: we're inside Yii 2.0 thread, so class names and signatures may differ.
God is real unless declared as integer
0

#19 User is offline   DocSolver 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 35
  • Joined: 12-December 12

Posted 23 November 2013 - 08:46 AM

Thanks for the advice!

By the way, I added your info to the Yii doc: https://github.com/y.../yii2/pull/1300
0

#20 User is offline   Gregoire 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 11
  • Joined: 11-March 10

Posted 29 July 2014 - 05:30 AM

View PostORey, on 21 November 2013 - 06:33 PM, said:

public function behaviors()
{
    return [
        'access' => [
            'class' => 'yii\web\AccessControl',
            'except' => ['something'],            
            'rules' => [
                [
                    'allow' => true,
                    'roles' => ['manageThing1'],
                ],
            ],
        ],
    ];
}


...


I'm not very sure this is the way RBAC is running but I'm just starting trying to understand yii2 RBAC. In the above example, I suppose a role should be assigned to 'roles' and not an operation. EG:
[
    'allow' => true,
    'roles' => ['moderator'],
]

Or am I totally wrong?
0

Share this topic:


  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users