Gestion des droits d’accès par groupes d’utilisateurs avec ACL

Pour faire suite à notre tutorial sur l’authentification simple avec le Composant Auth, nous allons mettre en place une gestion de droits d’accès différents selon des groupes d’utilisateurs, en utilisant les ACL (Access Control Lists). Nous partons du principe que le lecteur a déjà mis en place l’authentification décrite dans le tutorial.

Nos besoins sont les suivants :

  • les Articles sont accessibles en consultation par tous ;
  • l’administration est accessible à l’adresse /admin et requiert une authentification ;
  • l’administration sert à gérer les Groupes, les Utilisateurs et les Articles ;
  • nous distinguons deux groupes d’utilisateurs : Administration et Rédaction ;
  • un Utilisateur appartient à un seul Groupe ;
  • les administrateurs ont droit à tout ;
  • les rédacteurs n’ont droit qu’à la gestion des Articles.

Note importante : pour les besoins de ce tutorial, la page d’accueil de l’administration est maintenant gérée par l’action admin_menu du Contrôleur UsersController, et non plus par l’action admin_index du Contrôleur ArticlesController. Ce changement, quoique mineur, devait être mentionné pour une bonne compréhension de ce sujet.

1. Principe

CakePHP intègre un outil de gestion de Permissions d’accès à des Ressources par des Rôles.

Une Ressource peut représenter notre Application dans son ensemble, un Contrôleur complet, une Action, ou un enregistrement d’un Modèle.

Un Rôle peut représenter un Groupe d’Utilisateurs ou un Utilisateur précis.

Une Permission représente les opérations que peut effectuer un Rôle sur une Ressource. Ces opérations sont les quatre opérations de base : create, read, update et delete (CRUD).

2. Mise en place

CakePHP stocke l’Acl dans trois tables :

  • aros (Access Role Object : les Rôles) ;
  • acos (Access Control Object : les Ressources) ;
  • aros_acos (relations entre AROs et ACOs : les Permissions).

Nous allons d’autre part enregistrer les Groupes d’Utilisateurs dans la table groups, et modifier notre table existante users pour la relier à la table groups.

2.1. Création des tables

2.1.1. Les tables aros, acos et aros_acos

La création des trois tables aros, acos et aros_acos peut se faire en ligne de commande avec la console de CakePHP :

cake schema run create DbAcl

ou simplement en important le script {app}/config/sql/db_acl.sql dans la base de données.

2.1.2. La table groups

CREATE TABLE `groups` (
`id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`parent_id` mediumint(8) UNSIGNED DEFAULT NULL,
`name` varchar(255) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY  (`id`),
KEY `parent_id` (`parent_id`)
) ENGINE=MyISAM;

Remarque : le champ parent_id sert à créer une arborescence de Groupes qui peut être utilisée automatiquement par l’Acl : en effet, CakePHP est capable de remonter cette arborescence pour trouver la Permission d’un Groupe donné.

Nous insérons ensuite nos deux groupes : Administration et Rédaction. Nous n’avons pas besoin dans notre étude d’une arborescence de Groupes, le parent_id est donc nul.

INSERT INTO `groups` (`id`, `parent_id`, `name`, `created`) VALUES
(1, NULL, 'Administration', NOW()),
(2, NULL, 'Rédaction', NOW());

2.1.3. Modification de la table users

ALTER TABLE `users` ADD `group_id` MEDIUMINT UNSIGNED NOT NULL AFTER `id`;
ALTER TABLE `users` ADD INDEX (`group_id`);

Nous modifions l’Utilisateur déjà présent (admin) pour l’affecter au Groupe Administration :

UPDATE `users` SET `group_id` = 1 WHERE `id` = 1;

Nous ajoutons un nouvel Utilisateur dans le Groupe Rédaction, avec pour login redacteur et mot de passe redacteur :

INSERT INTO `users` (`id`, `group_id`, `login`, `password`, `disabled`, `created`) VALUES
(2, 2, 'redacteur', SHA1('6a10cdde80fb56150efda09365f91579ea74a944redacteur'), 0, NOW());

2.2. Création des modèles

Nous allons créer le Modèle Group et modifier le Modèle User existant.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// {app}/models/group.php
class Group extends AppModel
{
  var $name = 'Group';
  var $actsAs = array('Acl');
  var $hasMany = 'User';
 
  function parentNode()
  {
    if (!$this->id)
    {
      return null;
    }
 
    $data = $this->read();
 
    if(!$data['Group']['parent_id'])
    {
      return null;
    }
 
    return array(
      'model' => 'Group',
      'foreign_key' => $data['Group']['parent_id']
    );
  }
}

Quelques explications :

  • var $actsAs = array('Acl');
    Le Comportement Acl sert à lire, créer, modifier et supprimer les Permissions de l’enregistrement du Modèle.
  • la méthode parentNode() renvoie l’id du Groupe parent : CakePHP s’en sert pour parcourir l’arborescence jusqu’à trouver les Permissions qui s’y rapportent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// {app}/models/user.php
class User extends AppModel
{
  var $name = 'User';
  var $actsAs = array('Acl');
  var $belongsTo = 'Group';
 
  function parentNode()
  {
    return null;
  }
 
  function bindNode($object)
  {
    return array(
      'model' => 'Group',
      'foreign_key' => $object['User']['group_id']
    );
  }
}

Quelques explications :

  • La méthode bindNode($object) sert à associer l’Utilisateur en cours à son Groupe. Le paramètre $object contient les données de l’Utilisateur en cours, et la fonction renvoie son Groupe. Cela évite de devoir créer un Rôle pour chaque Utilisateur : CakePHP est ainsi capable d’appliquer des Permissions à un Utilisateur en fonction du Rôle associé à son Groupe.
  • La méthode parentNode() renvoie null car il n’y a pas d’arborescence d’Utilisateurs. Cette méthode est requise, car contrairement à bindNode(), CakePHP ne vérifie pas la définition de la méthode dans le Modèle avant de l’invoquer. On peut imaginer placer la méthode parentNode() dans le fichier app_model.php et la redéfinir uniquement dans les Modèles arborescents (comme le Modèle Groupe).

2.3 Import de l’ACL

Nous allons créer un Contrôleur temporaire qui ne sera appelé qu’une seule fois, et dont le but est d’insérer dans la base de données les Rôles, les Ressources et les Permissions.

Nous appellerons l’adresse /init_acl/init pour lancer le script. Nous pourrons ensuite le supprimer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class InitAclController extends AppController
{
  var $name = 'InitAcl';
  var $components = array('Acl');
  var $uses = array('Aro', 'Aco');
 
  function createAro($model, $foreign_key, $parent_id, $alias)
  {
    $this->Aro->create();
    $this->Aro->save(
      array(
        'model'=>$model,
        'foreign_key'=>$foreign_key,
        'parent_id'=>$parent_id,
        'alias'=>$alias
      )
    );
    return $this->Aro->id;
  }
 
  function createAco($model, $foreign_key, $parent_id, $alias)
  {
    $this->Aco->create();
    $this->Aco->save(
      array(
        'model'=>$model,
        'foreign_key'=>$foreign_key,
        'parent_id'=>$parent_id,
        'alias'=>$alias
      )
    );
    return $this->Aco->id;
  }
 
  function deleteDB()
  {
    $this->Aro->query("TRUNCATE acos");
    $this->Aro->query("TRUNCATE aros");
    $this->Aro->query("TRUNCATE aros_acos");
  }
 
  function message($mssg)
  {
    echo $mssg.'<br/>';
  }
 
  function init()
  {
    $this->message("Initialisation des droits d'acces...");
 
    $this->message("Suppression des droits existants...");
    $this->deleteDB();
 
    // AROs
    // ----
    // |-Administrateur (group_id=1)
    // |-Redacteur (group_id=2)
    $this->message("Creation des AROs...");
    $this->createAro('Group', 1, null, 'Administrateur');
    $this->createAro('Group', 2, null, 'Redacteur');
 
    // ACOs
    // ----
    // |-Application
    //   |-Groups
    //   |-Users
    //     |-admin_menu
    //   |-Articles
    $this->message("Creation des ACOs...");
    $Application_id = $this->createAco(null, null, null, 'Application');
    $this->createAco(null, null, $Application_id, 'Groups');
    $Users_id = $this->createAco(null, null, $Application_id, 'Users');
    $this->createAco(null, null, $Users_id, 'admin_menu');
    $this->createAco(null, null, $Application_id, 'Articles');
 
    // AROs-ACOs
    // ---------
    // Administrateur - Application, all
    // Redacteur - Articles, all
    // Redacteur - admin_menu, all
    $this->message("Creation des permissions...");
    $this->Acl->allow('Administrateur', 'Application', '*');
    $this->Acl->allow('Redacteur', 'Articles', '*');
    $this->Acl->allow('Redacteur', 'admin_menu', '*');
 
    $this->message("Initialisation terminee.");
    $this->autoRender = false;
  }
}

Quelques explications :

  • var $components = array('Acl');
    Le Composant Acl permet d’insérer les Permissions dans la table aros_acos.
  • var $uses = array('Aro', 'Aco');
    Les modèles Aro et Aco permettent d’insérer les Rôles dans la table aros et les Ressources dans la table acos.
  • les fonctions createAro et createAco attendent quatre paramètres :
    • $model : nom du Modèle associé au Rôle (Group ou User) ou à la Ressource (Article) ;
    • $foreign_key : id de l’enregistrement du Modèle concerné ;
    • $parent_id : id du Rôle ou de la Ressource parent pour gérer l’héritage des Permissions ;
    • $alias : nom (unique) du Rôle ou de la Ressource.
  • la méthode init() crée les Rôles, les Ressources, et enfin les Permissions associées :
    • $this->Acl->allow('Administrateur', 'Application', '*');
      Le Rôle Administrateur (associé au Groupe Administration) a accès à toute l’Application (Contrôleurs, actions, enregistrements) avec toutes les opérations CRUD.
    • $this->Acl->allow('Redacteur', 'Articles', '*');
      Le Rôle Redacteur a accès à tout le Contrôleur Articles (actions, enregistrements) avec toutes les opération CRUD.
    • $this->Acl->allow('Redacteur', 'admin_menu', '*');
      Le Rôle Redacteur a accès au menu de l’administration qui est une action du Contrôleur User, normalement interdit au Rôle Redacteur (à qui nous n’avons autorisé l’accès qu’aux articles). Notons que pour une action, le fait de ne pas donner toutes les Permissions d’opérations CRUD avec ‘*’ en interdit l’accès. Les paramètres représentés par ‘*’ étant finalement eux-mêmes des actions, interdire par exemple l’opération ‘delete’ sur l’action index du Contrôleur ArticlesController n’aurait aucun sens.

Remarque : pour restreindre une Permission à l’une des opérations de base (create, read, update, delete), nous pouvons passer un array avec les opérations autorisées à la place de ‘*’. Par exemple, pour permettre au Rôle Redacteur de gérer les Articles sans pouvoir les supprimer, nous ferions :

$this->Acl->allow('Redacteur', 'Articles', array('create', 'read', 'update'));
Ou encore plus simplement :
$this->Acl->deny('Redacteur', 'Articles', array('delete'));

3. Intégration avec le Composant Auth

Maintenant que les Rôles, Ressources et Permissions sont en place, nous allons indiquer au Composant Auth de ne plus se baser sur la fonction isAuthorized pour autoriser ou non l’accès à une action, mais de se brancher automatiquement sur l’Acl pour savoir si l’Utilisateur connecté est relié à un Rôle qui lui permet d’accéder à l’opération demandée sur la Ressource. Nous modifions donc le Contrôleur général AppController :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// {app}/controllers/app_controller.php
class AppController extends Controller
{
  var $helpers = array ('Html', 'Text', 'Form');
  var $components = array('Acl','Auth');
 
  function beforeFilter()
  {
    if(isset($this->Auth))
    {
      $this->Auth->userModel = 'User';
      $this->Auth->userScope = array('User.disabled' => 0);
      $this->Auth->fields = array('username' => 'login', 'password' => 'password');
      $this->Auth->loginAction = '/users/login';
      $this->Auth->loginRedirect = '/admin/users/menu';
      $this->Auth->logoutRedirect = '/';
      $this->Auth->loginError = "Identifiant ou mot de passe incorrects.";
      $this->Auth->authError = "Vous n'avez pas accès à cette page.";
      $this->Auth->autoRedirect = true;
      $this->Auth->authorize = 'actions';
 
      if((empty($this->params['prefix']) || $this->params['prefix'] != 'admin') && $this->action != 'login')
      {
        $this->Auth->allow();
      }
    }
  }
 
  function beforeRender()
  {
    if(isset($this->params['prefix']) && $this->params['prefix'] == 'admin')
    {
      $this->layout = 'admin_default';
    }
  }
}

Quelques explications :

  • var $components = array('Acl','Auth');
    Attention à l’ordre d’inclusion des Composants, d’abord l’Acl puis l’Auth.
  • $this->Auth->authorize = 'actions';
    Cette instruction indique au Composant Auth qu’il doit obtenir les droits d’accès auprès du Composant Acl.
  • if((empty($this->params['prefix']) || $this->params['prefix'] != ‘admin’) && $this->action != ‘login’) { $this->Auth->allow(); }
    Nous ajoutons cette étape pour nous dispenser d’ajouter un Rôle pour le simple visiteur : si l’action demandée n’est pas une action d’administration et si l’action n’est pas le login, Auth autorise l’accès sans demander à Acl. Cette logique est liée à nos besoins et peut tout à fait être omise pour laisser à l’Acl le soin de gérer tous les accès, à charge au lecteur de prévoir un Rôle pour un utilisateur non enregistré.

4. Conclusion

La difficulté dans la mise en place d’une solution de gestion d’accès par groupes d’utilisateurs tient surtout aux faits que la documentation actuelle est plutôt ténue à ce sujet et à l’absence d’exemple concret d’une implémentation réelle. Nous espérons que cet article permettra au lecteur de se lancer dans l’étude de cette fonctionnalité, car la souplesse apportée par les Acl (gérer les droits au niveau de l’application complète, d’un Contrôleur, d’une action, d’un enregistrement ou même d’un seul champ d’un enregistrement !) permet de l’inclure par défaut dans tout projet, quels que soient les besoins du client.

Pierre-Emmanuel Fringant et Matthieu Sadouni

Commentaires

Superbe article, une fois de plus !

Peut-on gérer l’appartenance d’un membre à plusieurs groupes ?

Merci pour votre travail qui nous apporte de précieux éclaircissements sur la cuisine de ce bon gâteau !

Je n’ai pas testé mais cela doit être compliqué. Avec une relation User HABTM Group, attention à ce que j’appelerais des “conflits de permissions” : comment CakePHP détermine quel groupe l’emporte ?

Pour l’appartenance à plusieurs groupes, effectivement, c’est complexe et la communauté a souvent buté sur ce sujet… mais c’est pourtant un besoin possible dans une application ! Bien entendu, il faut éviter les conflits de permissions et rester dans la cascade, mais on peut imaginer que deux groupes régissent des permissions sur des éléments distincts, qui n’interfèrent pas entre eux…

Je ne me fierais pas à un système de gestion d’accès qui peut, potentiellement, même si on fait bien attention, engendrer des conflits de permissions.
Les Acl sont à mon sens suffisamment souples pour pouvoir ajouter une permission à un utilisateur particulier qui appartient à un Groupe qui lui n’a pas cette permission.
Reprenons notre exemple : nous souhaitons donner à l’un des rédacteurs (le rédacteur en chef) le droit de modifier les Users. Notre User rédacteur en chef appartient au Groupe Rédaction qui est associé au Rôle Rédacteur et à ce titre n’a accès qu’aux Articles. Et bien nous créons un nouveau Rôle ‘RédacteurEnChef’, fils du Rôle Rédacteur et ayant droit d’accès aux Users, et nous associons ce nouveau Rôle à notre User rédacteur en chef, toujours membre du Groupe Rédaction.
Nul besoin de créer un nouveau Groupe ni de modifier la relation entre Group et User pour que le rédacteur en chef puisse appartenir à deux Groupes à la fois.

Ah effectivement, expliqué de cette manière, je comprends mieux et j’entrevois la solution à mes besoins.

Pour être sûr : en fait nous créons actuellement un portail avec la possibilité pour les users, de créer des “groupes” thématiques (au sens de communauté d’intérêt). Pour visualiser les articles d’un groupe et y participer (par exemple créer son blog de groupe), un user doit donc appartenir à ce groupe. Mais il peut tout à fait appartenir à plusieurs groupes thématiques !

Donc pour toi, on ne créerait qu’un groupe “utilisateur” (au sens ACL bien entendu) et autant de rôles qu’il y a de groupes thématiques ?

Merci par avance de tes commentaires.

Aurélien

Pas facile de répondre en quelques lignes, mais à première vue je dirais que tu crées un Aro par User, et que les groupes thémathiques et leurs “enfants” (articles, blogs, etc.) sont des Acos. Tu peux ensuite relier un rôle (Aro) d’un user à une ou plusieurs ressources (Aco) “groupe thématique”.

Merci !

Je pense effectivement que c’est une bonne solution. Je dois encore m’imprégner davantage des Aros/Acos et valider au niveau de notre modèle si c’est viable.

Juste une petite precision concernant les multiples groupes.
C’est tres facile si les groupes sont hierarchises (ie: voir tutorial sur cakephpforum.net), maintenant pour des groupes complements independant c’est assez complique. En fait le plus difficile est la maintenance des groupes et des Aro en cas de modifications.
J’ai realise quelque chose d’experimental qui permet de le faire, le truc et de “creer” un association HABTM qui ne sert qu’a cela entre les users et les acos. C’est ce que j’ai appelle “profile”. Un profile contient plusieurs groupes d’utilisateurs, et les parent des ARO user sont des Aros de type “Profile” qui sont eux meme lies avec la parent_id … Enfin pas facile a expliquer en quelques lignes, mais pour des multiples parents hierarchises il y a tres peu de modification a faire dans cet excellent tutorial.

Merci pour votre tutorial :)

J’ai quelques questions :

* Admettons que j’ai un contrôleur Posts et un contrôleur Comments qui contiennent chacun une fonction nommée admin_add. Mon arbre d’ACOs donne quelque chose comme ça :

// |-Posts
// |-admin_add
// |-Comments
// |-admin_add

Mais dans ce cas, pour donner les autorisations, comment distingue-t-on l’action admin_add du contrôleur Posts et celle de Comments ?
(lorsqu’on fait : $this->Acl->allow(’Redacteurs’, ‘admin_add’, ‘*’); )

* J’ai mal compris un passage de votre article concernant la spécification des actions CRUD. J’ai un contrôleur Articles avec une action admin_ajouter et une action admin_supprimer. Je souhaites que les rédacteurs ne puisse que ajouter des articles. En toute logique, je devrais faire : $this->Acl->allow(’Redacteurs’, ‘Articles’, array(’read’, ‘create’));
Hors, en faisant comme cela, l’utilisateur n’a pas du tout accès au contrôleur … Je comprend donc pas très bien l’intérêt de spécifier quelle action CRUD l’utilisateur peut executer. (je suis donc obligé d’enregistrer en ACO chacune de mes actions du controleur, puis de spécifier les quelles peuvent être utilisées par les rédacteurs).

* Une dernière question : vous utilisez une table group, mais elle semble faire doublons avec les infos de la table ARO, n’y a t-il pas moyen de factoriser tout cela ?

Merci d’avance, et merci pour vos articles très intéressants !

Ok, une première réponse :

Pour autoriser deux ACO portant le même nom mais n’ayant pas le même parent, il faut y accéder comme cela (exemple) :

$this->Acl->allow(’Redacteurs’, ‘Posts/admin_add’, ‘*’);
$this->Acl->allow(’Redacteurs’, ‘Comments/admin_add’, ‘*’);

Bonjour,

Encore quelques petites question (pour la route :) ) :

Si j’ai bien compris, les utilisateurs ne font pas directement parti des AROs mais par l’intermédiaire des groupes. Dans ce cas, je ne comprend pas l’utilité de rajouter le comportement ACL au modèle User, car à l’ajout d’un nouvel utilisateur , une entrée sera également créé dans les AROs …

En revanche pour les groupes, je vois l’utilité. Le problème, est qu’à l’ajout d’un groupe (méthode save() sur le modèle Group) , Cake ajoute bien une entrée dans les AROs, mais l’alias du groupe et son éventuel parent restent vides dans la tables des AROs…

Merci d’avance !

Je vais essayer de répondre dans l’ordre :

1) “J’ai un contrôleur Articles avec une action admin_ajouter et une action admin_supprimer. En toute logique, je devrais faire : $this->Acl->allow(’Redacteurs’, ‘Articles’, array(’read’, ‘create’));
Hors, en faisant comme cela, l’utilisateur n’a pas du tout accès au contrôleur”.
> Il s’agit ici d’un problème de nommage des fonctions. Il faut rester dans la norme anglaise et rebaptisez les actions en admin_add et admin_del ou admin_delete.

2) “vous utilisez une table group, mais elle semble faire doublons avec les infos de la table ARO”
> On peut très bien imaginer des groupes distincts qui ont exactement les mêmes droits, par exemple dans les gestion d’une usine, le groupe Equipe de Nuit et le groupe Equipe de Jour ont tous deux le même rôle Equipe, inutile de dupliquer les droits.

3) “Si j’ai bien compris, les utilisateurs ne font pas directement parti des AROs mais par l’intermédiaire des groupes”
> Les utilisateurs peuvent tout à fait avoir un ARO associé, tout en appartenant à un groupe qui lui-même a un ARO, d’où la souplesse de la solution.

4) “En revanche pour les groupes, je vois l’utilité. Le problème, est qu’à l’ajout d’un groupe (méthode save() sur le modèle Group) , Cake ajoute bien une entrée dans les AROs, mais l’alias du groupe et son éventuel parent restent vides dans la tables des AROs…”
> Je ne suis pas sûr d’avoir bien compris, voudriez-vous préciser ? L’idéal serait même de fournir une portion de code via http://bin.cakephp.org/

Merci beaucoup pour ta réponse :)

Pour reprendre :

1) Oui, en fait je me suis emmêlé les pinceaux en voulant taper un petit exemple… C’était en fait pour savoir comment différencier deux actions qui se nomme pareil mais se trouvant dans deux contrôleurs différents lorsque qu’on assigne les droits via allow(). Il faut en fait y accéder via ‘nomModèle.nomAction’ dans ce cas.

2) Merci pour votre explication, je vois mieux le but de la séparation !

3)4) Si j’ai bien compris les comportements (avec le $actsAs) : cela permet lors d’une action CRUD sur le modèle contenant le comportement ACL d’ automatiquement mettre à jour les infos de la table ARO ou ACO. Donc ici, lorsqu’on ajoute un utilisateur via la méthode save() du modèle User, il y aura également d’office une entrée enregistrée dans les ARO. De la même façon pour la lecture, mise à jour et suppression d’utilisateurs j’imagine.

Merci encore !

A quoi pourrait servir l’arborescence d’utilisateurs (parent_id) que tu as choisie de ne pas implémenter ?

Tout simplement à gérer une hiérarchie d’utilisateurs, indépendamment des groupes.

Excuse moi mais cela ne me parle pas plus, je ne vois pas ce que cela apporte en plus ? As-tu un exemple plus précis ? C’est pour savoir si je l’intègre dans mon projet ou si je fais comme toi…

Imagine le Group Rédaction et les Users Redacteur1, Redacteur2 et RedacteurEnChef : avec un parent_id dans la table users, tu peux dire que les rédacteurs 1 et 2 sont “fils” de RedacteurEnChef. Ceci fait, tu peux gérer les Users comme les Groups pour que les enfants d’un User héritent de ses droits.

Ok, donc cela ne peut servir que dans le cas où on attribue des permissions à un User plutôt qu’à un Group ?

… ou aux deux, ou lorsque tu ne veux gérer que des users, pas de groupes.

Merci ! Par anticipation, je vais donc utiliser les deux, on ne sait jamais !
Par contre, dans d’autres tutos, j’ai constaté une différence d’implémentation de la méthode parentNode() dans le modèle Group :

return $data['Group']['parent_id'];

au lieu de :

return array(’model’ => ‘Group’, ‘foreign_key’ => data['Group']['parent_id']);

j’essaie de générer mes Aro/Aco avec la ligne de commande et le script cake/acl/.

Je fais :
acl create aro ‘Group’, 1, null, ‘Administrateur’

Et je reçois un beau message d’erreur :

Error: Could not find parent node using reference “‘Group’,”

As-tu une vague idée du problème ?

Participez