Caching ACL permissions with CachedAclComponent

By Frank de Graaf (Frank)
When you set up ACL with a bunch of groups with subgroups, you will end up with five or maybe more queries per request. When you have a lot of active users it might fry your database. This is a simple solution to get rid of all those queries.

How it works

This component checks if the permission is stored in the cache. If this is not the case, Cake's AclComponent is called to get the permission. The result is caught and placed in the cache for next time. So when the user lands on the page and the permission is cached, no queries are issued. The permission is retrieved from the cache.

When someone changes permissions on a group using allow() or deny(), the cache for that ACO (controller/action combination) is cleared. Beware though, if you have multiple servers and no central cache, this won't work as expected, because the cache can't be cleared on all servers. Instead the cache is only cleared on a single server, the other servers depend on the cache duration.

Requirements

The namespace supporting cache engine, found here: http://bakery.cakephp.org/articles/view/efficient-caching-with-namespacefileengine and a proper and working ACL setup.

Installation

First, copy the component file to APP/controllers/components/cached_acl.php. Next, place the following line(s) in your bootstrap.php at APP/config/bootstrap.php:

Download code
<?php
App
::import('Vendor''NamespaceFile');
Cache::config('acl', array('engine' => 'NamespaceFile''duration'=> '+1 month''prefix' => 'acl.'));
?>

Now it is time to see some magic happen. Change (in your AppController) this:

Controller Class:

Download code <?php 
var $components = array('Auth''Acl');
?>

To:

Controller Class:

Download code <?php 
var $components = array('Auth''CachedAcl');
?>

The permissions are now beeing cached.

Phally
http://www.frankdegraaf.net

Page 2: Benchmarks

Comments 972

CakePHP Team Comments Author Comments
 

Bug

1 Don't work with different aro syntax

Hello,

I tried distinct syntax for the SAME aro object and results are different / wrong :

$this->Acl->check($this->Auth->user(), 'Tools/list'); -> this works fine (result = true)

$this->Acl->check(array('model'=>'User','foreign_key'=>$this->user["User"]["id"]), 'Tools/list'); -> bad result (result = false)

$this->Acl->check('User:4', 'Tools/list'); -> dispatch a warning: Invalid argument supplied for foreach() [ROOT/cake_1.2.2.8120/cake/libs/set.php, line 1046] -> and bad result (result = false)

With the core Acl components, the 3 syntaxes return true...

It seems that __cachePath() in the component don't return the same thing :
For syntax 1: return "Tools_list.User_id_4"
For syntax 2: return "Tools_list.model_User"
For syntax 3: return "Tools_list."
Posted Apr 15, 2009 by Philippe bobis
 

Comment

2 Confirmed

Yes, that is right. This was designed to work with Auth. CRUD isn't supported either, I found out recently. You are however welcome to supply a patch to improve this.

- Edit: I have fixed it for you. Let me know if you have any issues.
Posted Apr 15, 2009 by Frank de Graaf
 

Comment

3 Update

The component is updated. It supports the three aro formats Philippe gave and supports CRUD too.
Posted Apr 15, 2009 by Frank de Graaf
 

Comment

4 another problem

Thank's for this update. It's Ok.

But another problem occurs with the check() function.

You write in this function:
 if ($check == false) { But if the cached value for acl is "0", Cache:read() returns "0" and then this condition is true, so the function calls "parent::check...'

I modified the code like this :
function check($aro, $aco, $action = "*") {
        $path = $this->__cachePath($aro, $aco, $action);
        $check = Cache::read($path, 'acl');
        if ($check === false) {
            $check = parent::check($aro, $aco, $action);
            Cache::write($path, $check ? 1 : 0, 'acl');
        } else {
            $check = $check == true;
        }
        return $check;
    }

And it's work fine for me (no sql request left when acl are cached)
The modifications are:
1. if ($check === false) (=== in place of ==)
2. Cache::write($path, $check ? 1 : 0, 'acl'); (1 in place of true)
3. $check = $check == true; (== in place of ===)
Posted Apr 16, 2009 by Philippe bobis
 

Comment

5 Solved, but

Ok, you were right, however all those modifications aren't needed, simply doing

<?php
if ($check === false)
?>

instead of

<?php
if ($check == false)
?>

is enough. Sorry for missing that, I have updated the code.
Posted Apr 16, 2009 by Frank de Graaf
 

Comment

6 acl check not working in component

I have extended the original acl component to check for group access as well as user access. Everything works until I enable the cached acl component too. The result is true when I run the acl check from the controller using:
$this->Acl->check(array('model' => 'User', 'foreign_key' => $aroId), array('model' => 'User', 'foreign_key' => $acoId), $action) The result is null when it should be true when I run the acl check from the custom component (stripped down to only do the basic one line check:
$this->controller->Acl->check(array('model' => 'User', 'foreign_key' => $aroId), array('model' => 'User', 'foreign_key' => $acoId), $action) Any ideas?
Posted Jun 12, 2009 by Ken Frey
 

Question

7 Any news?

Talked to you on IRC yesterday, have you found the problem?

I just thought of one looking at your code again. That second param is an array, with a model, but was built to support action paths like "/controllers/users/add". So this might be the problem you are facing.
Posted Jun 13, 2009 by Frank de Graaf
 

Comment

8 possibly solved

Talked to you on IRC yesterday, have you found the problem?

I just thought of one looking at your code again. That second param is an array, with a model, but was built to support action paths like "/controllers/users/add". So this might be the problem you are facing.

Initially that seems to have solved the problem. I have tested Acl->check() using the alias instead of model=>, user=> for the aco.
Posted Jun 15, 2009 by Ken Frey
 

Comment

9 ta

Nice work - thanks Frank. I love it when something works straight out of the box!
Posted Jul 10, 2009 by Conrad Leonard
 

Comment

10 Error with __deleteCache

I have my $action as array, and I change __deleteCache function as:


/**
     * Deletes an ACO from the cache.
     *
     * @param string $aro ARO
     * @param string $aco ACO
     * @access private
     */

    function __deleteCache($aro, $aco, $action) {
        if (is_array($action)) {
            foreach ($action as $act) {
                Cache::delete($this->__cachePath($aro, $aco, $act, true), 'acl');
            }
        }else {
            Cache::delete($this->__cachePath($aro, $aco, $action, true), 'acl');
        }
    }
Posted Jul 16, 2009 by Arantxa Grajal
 

Comment

11 question about paths

I have a question about how the cachePath is supposed to work. I tried an action, controllers/Users/test, for User:2 that didn't have permission. The cache gets written as denied like it should, then I granted access User:2 for controllers, all. When I check again it still says access denied because it didn't delete the cache file for User:2 with 'controllers_users_test.user_2'. It only checks 'controllers_users_test.user_2' path instead of anything below controllers, controllers_*.user_2 to delete. Is that the way it's supposed to work and is there an easy way to make it delete the cached files of the whole path instead of that single action for that user so that it works correctly?
Posted Sep 11, 2009 by Ken Frey
 

Comment

12 A suggestion for a big performance boost

The point of this Component is obviously to help you speed things up, however, there is one big drawback to it. You've still got to read and serialize a file for every check that you make.

One way that this can be alleviated is to update the component to understand user types/roles/groups and to flag users that have permissions specially assigned to them (instead of the user group). This takes a little bit of work to modify the user model, your user data structure, and the CacheAcl component itself but the end result is a big performance boost.

Say, for example, I were to check 50 ACL permissions on a single page (because I'm crazy like that). The component checks the user session and sees that the user does not have special permissions, so it changes the Aro to the User Type instead. That alone will give you a boost by consolidating cache files down for most users.

Now, if we take it a step further and keep all of the checks for that user type in a single associative array, written out to a single permissions cache file for the User Type and suddenly we're reading a single cache file for all the permissions associated with that user - 1 time.

If the Aro isn't a user or user type, default back to the original caching behavior (to handle everything else). These updates will allow you to fully take advantage of ACL for users across your entire application with very little concern for performance.
Posted Sep 29, 2009 by Barry
 

Question

13 cahed acl component

Hello!
I can't find the cached_acl.php component. Can anybody say from where I can get the component?
Posted May 13, 2010 by Mohammad Abu Bakar Siddique
 

Comment

14 link

Thank you for the link. Have a great weekend.
cotes football
Posted Jul 29, 2010 by Phil