ACL Behavior

by coeus
This behavior is built off of the core Acl behavior. It fixes some issues with the core behavior and allows you to set a model as both an Aro and Aco. It also adds the alias where as the core behavior doesn't.

Description

This Acl behavior makes a number of improvements from the built-in one. It allows you to have a model act as both an "Aro" and "Aco". It creates an alias in this format "Model.id". If parent node is not provided, it'll create one based on root object. For example, if you create Post.1 as an ACO and you set parentNode to null, it'll set the parent_id to the root "Post" Aco if it exists. So your tree would look like the following:


Acos
----------
Post
  |-Post.1
  |-Post.2
  |-Post.3

Usage Instructions


Follow the following steps to use the Acl behavior.
Step 1
Copy the behavior code below into a file named "acl.php" in your /app/models/behaviors folder.

Step 2
Load the behavior in the model you want to use it in.

Examples:

Ex 1: Act as Aco
var $actsAs = array('Acl' => array('Aco'));
Ex 2:
Act as Aco
var $actsAs = array('Acl' => 'Aco'); // Can accept a string as well
Ex 3: Act as Aro
var $actsAs = array('Acl' => array('Aro'));
Ex 4: Act as Aco and Aro
var $actsAs = array('Acl' => array('Aro','Aco'));
Step 3
Create the method parentNode($type) in your model. This will return the parent_id to a method in the Acl behavior.

Here's an example of a User model passing a group id as it's parent id only for the ARO:

function parentNode($type)
{
    if ($type == 'Aro') {
        if (!$this->id) {
            return null;
        }

        $data = $this->read();

        if (!$data['User']['group_id']){
            return null;
        } else {
            return array('model' => 'Group', 'foreign_key' => $data['User']['group_id']);
        }
    } else {
        return false;
    }
}

That's it!

Behavior Class


<?php

class AclBehavior extends ModelBehavior {

/**
 * Maps ACL type options to ACL models
 *
 * @var array
 * @access protected
 */

/**
 * Sets up the configuation for the model, and loads ACL models if they haven't been already
 *
 * @param mixed $config
 */
    
function setup(&$model$config = array()) {

        if (empty(
$config)) {
            
$config = array('Aro');
        } 
        elseif (
is_string($config)) {
            
$config = array($config);
        }

        
$this->settings[$model->name]['types'] = $config;

        foreach (
$this->settings[$model->name]['types'] as $type)
        {
            if (!
ClassRegistry::isKeySet($type)) {
                
uses('model' DS 'db_acl');
                
$object =& new $type();
            } else {
                
$object =& ClassRegistry::getObject($type);
            }
            
$model->{$type} =& $object;
        }
        

        if (!
method_exists($model'parentNode')) {
            
trigger_error("Callback parentNode() not defined in {$model->name}"E_USER_WARNING);
        }
    }
/**
 * Retrieves the Aro/Aco node for this model
 *
 * @param mixed $ref
 * @return array
 */
    
function node(&$model$type$ref null) {
        if (empty(
$ref)) {
            
$ref = array('model' => $model->name'foreign_key' => $model->id);
        }
        return 
$model->{$type}->node($ref);
    }
/**
 * Creates a new ARO/ACO node bound to this record
 *
 * @param boolean $created True if this is a new record
 */
    
function afterSave(&$model$created) {
        if (
$created) {

            foreach (
$this->settings[$model->name]['types'] as $type)
            {
                if (
$parent $model->parentNode($type)) {
                    
$parent $this->node($model$type$parent);
                } else {
                    
$parent $model->{$type}->node($model->name);
                }
                
$parent_id Set::extract($parent"0.{$type}.id");
            
                
$model->{$type}->create();
                
$model->{$type}->save(array(
                    
'parent_id'        => $parent_id,
                    
'model'            => $model->name,
                    
'foreign_key'    => $model->id,
                    
'alias'            => $model->name "." $model->id
                
));
            }
        }
    }
/**
 * Destroys the ARO/ACO node bound to the deleted record
 *
 */
    
function afterDelete(&$model) {
        foreach (
$this->settings[$model->name]['types'] as $type)
        {
            
$node Set::extract($this->node($model$type), "0.{$type}.id");
            if (!empty(
$node)) {
                
$model->{$type}->delete($node);
            }
        }
    }
}

?>

Report

More on Behaviors

Advertising

Comments

  • screamdamage posted on 10/01/09 05:03:05 PM
    I know it's not good practice to modify core files but I needed to move users and groups in the tree:

    Model Class:

    <?php 

            
    function node(&$model$ref null) {
            
    $type $this->__typeMaps[strtolower($this->settings[$model->name]['type'])];
            if (empty(
    $ref)) {
                
    $ref = array('model' => $model->name'foreign_key' => $model->id);
            }
            return 
    $model->{$type}->node($ref);
        }

        function 
    afterSave(&$model$created) {
            if (
    $created) {
                
    $type $this->__typeMaps[strtolower($this->settings[$model->name]['type'])];
                
    $parent $model->parentNode();
                if (
    $type == 'Aro'){
                    if (
    is_array($parent)){
                        
    $parent=$parent['Aclgroup']['id'];
                    }
                    
    $parentaro=$model->{$type}->find('first',array('conditions'=>array('model'=>'Aclgroup','foreign_key'=>$parent)));
                    
    $parent=$parentaro{$type}['id'];
                    
                    if (isset(
    $model->data['Acluser']['username']))
                        
    $alias=$model->data['Acluser']['username'];
                    elseif (isset(
    $model->data['Aclgroup']['name']))
                        
    $alias=$model->data['Aclgroup']['name'];
                }else{
                    
    $alias=NULL;
                    
    $parentaco=$model->{$type}->find('first',array('conditions'=>array('model'=>$model->name,'alias'=>$model->name,'foreign_key'=>NULL)));
                    
    $parent=$parentaco{$type}['id'];
                }
                
    $model->{$type}->create();
                
    $model->{$type}->save(array(
                    
    'parent_id'        => $parent,
                    
    'model'            => $model->name,
                    
    'foreign_key'    => $model->id,
                    
    'alias'    => $alias
                
    ));
            }
            if (!
    $created) {
                
                
    $type $this->__typeMaps[strtolower($this->settings[$model->name]['type'])];
                if (
    $type=='Aro'){
                    
    $parent $model->parentNode();
                    
    $usersacoaro=$model->{$type}->find('first',array('conditions'=>array('model'=>$model->name,'foreign_key'=>$model->id)));
                    
    $usersacoaroid=$usersacoaro{$type}['id'];

                    if (
    is_array($parent)){
                        
    $parent=$parent['Aclgroup']['id'];
                    }
                    
    $parentaro=$model->{$type}->find('first',array('conditions'=>array('model'=>'Aclgroup','foreign_key'=>$parent)));
                    
    $parent=$parentaro{$type}['id'];
                    if (isset(
    $model->data['Acluser']['username'])){
                        
    $alias=$model->data['Acluser']['username'];
                    }elseif (isset(
    $model->data['Aclgroup']['name'])){
                        
    $alias=$model->data['Aclgroup']['name'];
                    }
                    
    $guardar=array(
                        
    'id'            => $usersacoaroid,
                        
    'parent_id'        => $parent,
                        
    'foreign_key'    => $model->id,
                        
    'alias'    => $alias
                    
    );
                    
                    
    $model->{$type}->save($guardar);
                
                }else{
                    
    //aco doesn't move by now
                
    }
            }
        }
    ?>
  • Kumazatheef posted on 10/22/08 01:44:33 PM
    Except this still doesn't take into account if a User changes a Group:
    http://mark-story.com/posts/view/auth-and-acl-automatically-updating-user-aros
    Not that it has to, but would be nice ... should be an easy fix-up:

    In User model:

        function afterSave($created) {
            if (!$created) {
                $parent = $this->parentNode('Aro');
                $parent = $this->node('Aro', $parent);
                $node = $this->node('Aro');
                $aro = $node[0];
                $aro['Aro']['parent_id'] = $parent[0]['Aro']['id'];
                $this->Aro->save($aro);
            }
        }
  • pr1001 posted on 05/14/08 11:07:12 AM
    Steve, thanks for the great behavior. Unfortunately, it doesn't seem to play nice with Auth.

    The basic problem is that the ACL code above creates ACOs based upon the model name (e.g. 'User') while Auth uses the controller name (e.g. 'users') when setting Auth to use 'crud' authentication. For model-level access, this can easily be addressed by editing auth.php to use Inflector::singularize() before passing ACO aliases to $this->Acl->check(). However, this doesn't solve the problem of record-specific ACOs (e.g. 'User.1'), since Auth in 'crud' mode only searches for ACOs based on the controller (as mentioned above).

    Have you run into this before? How did you solve this? Did you just use one of Auth's other authentication methods? If this is a problem for other people, I'd be happy to contribute a little patch to auth.php that addresses this.
    • bmcclure posted on 08/27/08 10:58:11 PM
      Have you run into this before? How did you solve this? Did you just use one of Auth's other authentication methods? If this is a problem for other people, I'd be happy to contribute a little patch to auth.php that addresses this.
      I think I just solved this problem for me by changing the acl.php behavior's afterSave() method to the following:

      PHP Snippet:

      <?php /**
       * Creates a new ARO/ACO node bound to this record
       *
       * @param boolean $created True if this is a new record
       */
          
      function afterSave(&$model$created) {
              if (
      $created) {

                  foreach (
      $this->settings[$model->name]['types'] as $type)
                  {
                      if (
      $parent $model->parentNode($type)) {
                          
      $parent $this->node($model$type$parent);
                      } else {
                          
      $parentName = ($type == 'Aco') ? Inflector::pluralize($model->name) : $model->name;
                          
      $parent $model->{$type}->node($parentName);
                      }
                      
      $parent_id Set::extract($parent"0.{$type}.id");
                      
      $modelName = ($type == 'Aco') ? Inflector::pluralize($model->name) : $model->name;
                      
                      
      $model->{$type}->create();
                      
      $model->{$type}->save(array(
                          
      'parent_id'        => $parent_id,
                          
      'model'            => $modelName,
                          
      'foreign_key'    => $model->id,
                          
      'alias'            => $model->name.'.'.$model->id
                      
      ));
                  }
              }
          }
      ?>

      Have not fully tested, but it works if crud mode in my initial attempts.

      All I have changed is that, if the type of request is 'Aco', a call to Inflector::pluralize is made when creating the records. It still creates a singular alias for actual user records so that you don't need to change any custom checks you are doing (since the core behavior didn't even create aliases, I figured that wouldn't matter).

      Can anyone comment on this? Thanks.
  • ttwhy posted on 04/09/08 08:44:19 AM
    Hi,

    i wrote a little class to increase the speed of ACL's by decreasing the SQL queries by saveing them into the SESSION data. This might be useful for everybody who dont change the permission very frequently.

    But the weakpoint -> its written in german, but the classes are quite clearly to understand (on request i would translate it if some facts are unclear).

    http://ttwhy.wordpress.com/2008/03/13/cakephp-mit-access-listen-beschleunigen/
    Hope you enjoy it (please leave a comment, and tell me your sugestions)
  • uramis posted on 03/27/08 10:02:12 AM
    Nice, thanks a lot for the hard work.
    Im still trying to wrap my mind around ACL, and something tells me is simpler that I think... but what the hell. :)
    • coeus posted on 04/15/08 06:07:00 PM
      Nice, thanks a lot for the hard work.
      Im still trying to wrap my mind around ACL, and something tells me is simpler that I think... but what the hell. :)

      Thank you for the compliments, I'd just like to note that most of the hard work should be credited to the CakePHP team, I simply made minor modifications to their behaviour. This is nowhere near where it should be and I expect a better behaviour to be released for 1.2 stable.
  • ANDSENS posted on 03/13/08 05:06:19 PM
    This is insane, you got some mad PHP skills alright.
login to post a comment.