RBAC caching

I have started to use rbac implementing operations, tasks and roles.

For each controller-action pair I have an operation (i.e. user_create, user_delete, user_show, user_update).

To only show the buttons and menus people are allowed to use quite a lot of access checking is performed on every request. This results in up to 50 SQL queries per request, which I think is not acceptable.

My solution to this problem is to cache the access rights in the session:

Yii already provides access rights caching within the same request through the CWebUser method ‘checkAccess()’

So I wrote my own user class MyCWebUser extending CWebUser, it only overrides checkAccess().




<?php

<?php

class MyCWebUser extends CWebUser {

    public function checkAccess($operation,$params=array(),$allowCaching=true) {

        if($allowCaching && !$this->getIsGuest() && isset(Yii::app()->session['access'][$operation])) {

            return Yii::app()->session['access'][$operation];

        }

        $checkAccess = Yii::app()->getAuthManager()->checkAccess($operation,$this->getId(),$params);

        if($allowCaching && !$this->getIsGuest()) {

            $access = isset(Yii::app()->session['access']) ? Yii::app()->session['access'] : array();

            $access[$operation] = $checkAccess;

            Yii::app()->session['access'] = $access;

        }

        return $checkAccess;

    }

}



In your config file you also need to set the new user class:




'components'=>array(

    ...    

    'user'=>array(

        'class'=>'MyCWebUser',

    ),

    ...

)



When using accessControl filters in the controller the access rights are now cached in the session. Manually checking for access rights also works just like before: Yii::app()->user->checkAccess(‘operation_name’).

There is one disadvantage though: changes to the operations/tasks/roles structure or assignment of roles do not take effect until the user has logged out and then logged in again. In my project I can live with that.

To solve that problem as well access rights could be stored in the file cache for example using a key that contains the userId. When changing role assignment to a user you would then have to delete the cache entry for that user, when changing the operations/tasks/roles structure you would have to delete the cache entries for all users.

I hope these ideas can help somebody, good luck with rbac!

This is very useful. Thanks. :)

I have also the same issue with the rbac and high sql query count thanks for your solution to settle down the problem a bit.

I made ​​some improvements to reduce the problem with the change of permission during the session.

I added a control that forces the revision of permission every 30 minutes.

And I also added the cache control by request of the original class.




class TWebUser extends RWebUser

{

	private $_access=array();

        

        public function checkAccess($operation,$params=array(),$allowCaching=true)

        {

                if($this->isSuperuser===true)

                        return true;                

                

		if($allowCaching && $params===array() && isset($this->_access[$operation]))

			return $this->_access[$operation];                

                

                $cache = Yii::app()->session['checkAccess'];

                if($allowCaching && !$this->getIsGuest() 

                    && isset($cache[$operation])

                    && time() - $cache[$operation]['t'] < 1800)

                {

                    $checkAccess = $cache[$operation]['p'];

                }

                else

                {                

                    $checkAccess = Yii::app()->getAuthManager()->checkAccess($operation,$this->getId(),$params);

                    

                    if($allowCaching && !$this->getIsGuest())

                    {

                        $access = isset($cache) ? $cache : array();

                        $access[$operation] = array('p'=>$checkAccess, 't'=>time());

                        Yii::app()->session['checkAccess'] = $access;

                    }

                }

                

                return $this->_access[$operation] = $checkAccess;

        }

}



I use Rights and my class extends the class of the extension.

It’s important to know that above solution doesn’t work when using bizrules (ex. checking content author by $_GET[‘id’])