ACL Behavior
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);
}
}
}
}
?>

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
}
}
}
?>
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);
}
}
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.
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.
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)
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.