MainelyDesign.com Blog

Getting All ACL Permissions in One Lookup (CakePHP 1.3)

Posted on 09/19/2010 at 11:36 am by Kevin Wentworth
Viewed 20,710 times | 1 comment

The biggest hurdle I've had to overcome migrating my CakePHP application from 1.2 to the much improved 1.3 branch involves Plugins and the ACL component.  A while back I noticed that my backend was kind of sluggish due to all of the ACL lookups (that happen with each request) so I optimized my ACL database tables.  At that same time, I also discovered an incredible mysql query that would lookup all of the ACL permissions for a particular ARO.  This setup worked great... until I started upgrading to CakePHP 1.3. 

ACL Now Includes Plugin Name (controllers/Plugin/PluginController/action)

In CakePHP 1.3, the ACL component added in another level of parent for Plugins.  I agree with this change (now you can can allow/deny for an entire plugin in one ACO/ARO definition) but it broke that crazy query.  Now, I tried and tried to implement that additional layer of parent relationship into the query that worked for 1.2 but I just couldn't get it working.  I learned a little bit about MySQL sub-sub-sub queries but didn't end up with a working solution.

Get All ACL Permissions On Login

The beauty in Neil Crookes' approach was that you could get all of the permissions as a user logged in and then dynamically generate a list of all permissions for each user.  I used this approach to customize the menus based on the level of user that was logged in to Site Avenger.  I depended on this functionality and built my entire back-end system to use dynamic menus based on the access list of the logged in user.  I had to recreate this functionality... but I'm not a mysql genius so I decided to do it another way...

Introducing the Permissions Array Component

  1. /**
  2.  * CakePHP Permissions Array by Kevin Wentworth (Saco Design, Inc.)
  3.  *
  4.  * Handles retrieving all ACL Permissions and storing them in an array.
  5.  *
  6.  *
  7.  * Comments and bug reports welcome at kevin at sacodesign dot com
  8.  *
  9.  * Licensed under UYOR (Use at Your Own Risk)
  10.  */
  11. class PermissionsArrayComponent extends Object {
  12.    
  13.     var $components = array('Acl', 'Auth', 'Session');
  14.     var $options = array('model'=>'Aco', 'field'=>'alias');
  15.    
  16.    
  17.     //used for recursive variable setting/checking
  18.     var $perms = array();   //for ACL defined permissions
  19.     var $permissionsArray = array();    //for all permissions
  20.     var $inheritPermission = array();   //array indexed by level to hold the inherited permission
  21.    
  22.     //called before Controller::beforeFilter()
  23.     function initialize(&$controller, $settings = array()) {
  24.         // saving the controller reference for later use
  25.         $this->controller =& $controller;
  26.     }
  27.  
  28.     //called after Controller::beforeFilter()
  29.     function startup(&$controller) {
  30.     }
  31.  
  32.     //called after Controller::beforeRender()
  33.     function beforeRender(&$controller) {
  34.     }
  35.  
  36.     //called after Controller::render()
  37.     function shutdown(&$controller) {
  38.     }
  39.  
  40.     //called before Controller::redirect()
  41.     function beforeRedirect(&$controller, $url, $status=null, $exit=true) {
  42.     }
  43.  
  44.     function create($group_id = 0, $options = array()) {
  45.         $this->options = array_merge($this->options, $options);
  46.        
  47.         //GET ACL PERMISSIONS
  48.         $acos = $this->Acl->Aco->find('threaded');
  49.         $group_aro = $this->Acl->Aro->find('threaded',array('conditions'=>array('Aro.foreign_key'=>$group_id, 'Aro.model'=>'Group')));
  50.         $group_perms = Set::extract('{n}.Aco', $group_aro);
  51.         $gpAco = array();
  52.         foreach($group_perms[0] as $value) {
  53.             $gpAco[$value['id']] = $value;
  54.         }
  55.        
  56.         $this->perms = $gpAco;
  57.         $this->_addPermissions($acos, $this->options['model'], $this->options['field'], 0, '');
  58.        
  59.         $this->Session->write('Auth.Permissions', $this->permissionsArray);
  60.         return $this->controller->redirect($this->Auth->redirect());
  61.     }
  62.    
  63.    
  64.     function _addPermissions($acos, $modelName, $fieldName, $level, $alias) {
  65.  
  66.         foreach ($acos as $key=>$val)
  67.         {
  68.             $thisAlias = $alias . $val[$modelName][$fieldName];
  69.              
  70.             if(isset($this->perms[$val[$modelName]['id']])) {
  71.                 $curr_perm = $this->perms[$val[$modelName]['id']];
  72.                 if($curr_perm['Permission']['_create'] == 1) {
  73.                     $this->permissionsArray[] = $thisAlias;
  74.                     $this->inheritPermission[$level] = 1;
  75.                 } else {
  76.                     $this->inheritPermission[$level] = -1;
  77.                 }
  78.             } else {
  79.                 if(!empty($this->inheritPermission)) {
  80.                     //echo $level.'::'.$thisAlias;
  81.                     //var_dump($this->inheritPermission);
  82.                     //check for inheritedPermissions, by checking closest array element
  83.                     $revPerms = array_reverse($this->inheritPermission);             
  84.                     if($revPerms[0] == 1) {
  85.                         $this->permissionsArray[] = $thisAlias; //the level above was set to 1, so this should be a 1
  86.                     }
  87.                      
  88.                 }
  89.             }
  90.  
  91.             if(isset($val['children'][0])) {
  92.                 $old_alias = $alias;
  93.                 $alias .= $val[$modelName][$fieldName] .'/';
  94.                 $this->_addPermissions($val['children'], $modelName, $fieldName, $level+1, $alias);
  95.                 unset($this->inheritPermission[$level+1]);  //don't want the last level's inheritance, in case it was set
  96.                 unset($this->inheritPermission[$level]);    //don't want this inheritance anymore, in case it was set
  97.                 $alias = $old_alias;
  98.             }
  99.         }
  100.  
  101.         return;
  102.     }
  103. }

Used in conjunction with hasPermission() in app_helper.php (thanks Neil)

  1. function hasPermission($url) {
  2.  
  3.         if (!is_array($url)) {
  4.           return false;
  5.         }
  6.    
  7.         extract($url);
  8.        
  9.         if(isset($plugin)) {
  10.             $plugin = Inflector::camelize($plugin);
  11.         }
  12.        
  13.         if (!isset($controller)) {
  14.           $controller = $this->params['controller'];
  15.         }  
  16.         $controller = Inflector::camelize($controller);
  17.    
  18.         if (!isset($action)) {
  19.           $action = $this->params['action'];
  20.         }
  21.    
  22.         $_admin = Configure::read('Routing.admin');
  23.    
  24.         if ((isset(${$_admin}) && ${$_admin}) || $this->params['action'][$_admin]) {
  25.           $action = $_admin.'_'.$action;
  26.         }
  27.    
  28.         if(isset($plugin) and !empty($plugin)) {
  29.             $controller = $plugin.'/'.$controller;
  30.         }
  31.        
  32.         $permission = 'controllers/'.$controller.'/'.$action;
  33.    
  34.         return in_array($permission, $this->Session->read('Auth.Permissions'));
  35.  
  36.     }

Tell Auth Component to Stop Hijacking Login() function

In order to get the PermissionsArray->create() function to work, you must stop the Auth component from auto-redirecting.  This will allow any logic in the login() function to run after the Auth component has done it's part. Place this wherever you define your Auth component settings:

  1. //in Auth config:
  2. $this->Auth->autoRedirect = false;

Then, Initialize CakePHP Permissions Array at Login

  1. function avenger_login($data = null) {
  2.     Configure::write('debug', 0);
  3.     if ($this->Auth->user()) {
  4.          $this->PermissionsArray->create($this->Auth->user('group_id'));
  5.     }
  6. }

Cheers,
-Kevin Wentworth

Bookmark and Share

Tags for Getting All ACL Permissions in One Lookup (CakePHP 1.3)

Cakephp | Component | Database | Example | Mysql | Tutorial | Upgrade | Usage | Web Programming

Comments for this Posting

Posted by Geoff Douglas

on 1/10/10

Thanks Kevin. This is great stuff. I will try it out and let you know if I have any issues, questions, additions...

www.neverbehind.com

Sorry, comments are closed for this posting.

Please Email Kevin if you have any questions. Thanks!

Meet Site Avenger - Hosted Content Management System

Powered By: Site Avenger | Site Production: Saco Design