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 ?

Bonjour, je pense avoir bien suivi toutes les étapes de l’article, mais lorsque j’arrive sur la page /admin j’ai une page toute blanche sans message d’erreur … et ce, pareil pour toutes les autres pages nécessitant une authentification

Auriez-vous une piste pour m’aider? car sans message d’erreur je ne sais vers quoi me tourner pour résoudre ce problème.

J’essaierai ceci :
- vérifier que le niveau de debug est à 2 dans /config/core.php
- lire le fichier /tmp/error.log
- lire le fichier {répertoire d’Apache}/logs/error.log

Merci, j’ai résolu mon problème : j’étais en mode debug 3 et quand je l’ai mis a 2, tout a marché normalement!!

Pour ceux que cela intéresserait :
Avec Cakephp 1.2.0.7125 RC1, il est nécessaire de créer tous les Acos définissant “l’arborescence” de l’application. Ce qui nous donne ici :

Application
|-Articles
| |-index
| |-view
| |-admin_add
| |-admin_edit
|-Users
| |-admin_menu
|…
Et ainsi de suite.
Bien sûr, s’il y a une autre méthode moins contraignante je suis preneur.

Deux astuces :
- afficher l’aco en cours de vérification : ajouter temporairement debug($aco); au début de la fonction check() dans le fichier cake/libs/controller/components/acl.php
- Utiliser le plugin aclmp qui permet de gérer visuellement l’acl http://bakery.cakephp.org/articles/view/acl-management-plugin
(ajouter Configure::write(’debug’, 0); dans la fonction beforeFilter() du fichier acl_app_controller.php)

très bon tutoriel une fois de plus.
J’ai ainsi pu comprendre le fonctionnement des acl, et l’adapter a mes besoins.
Merci cake et merci … PEF(^^)

Je me remets aux ACL et je reprends ton très bon tutoriel comme exemple.

Je constate juste un tout petit souci SQL : si dans Groups “parent_id” est un INT, alors il ne peut pas avoir NULL comme valeur par défaut, ce sera forcément 0.
J’ai remarqué cela en faisant les insert dans la table Groups via PhpMyAdmin, qui m’a renvoyé un warning sur ce champ et qui a automatiquement remplacé NULL par 0.

Autre remarque :

Dans le tuto “officiel” du CookBook http://book.cakephp.org/view/645/Acts-As-a-Requester, il y a une inversion des choses par rapport à toi…

En fait dans le tuto officiel, la fonction parentNode qui ne retourne pas Null est dans le modèle User, alors que toi tu la mets dans Group, qui a raison ?

Et le tuto officiel ne parle pas de bindNode(), est-ce vraiment important de l’implémenter ?

Je viens d’essayer ton tutoriel, qui est vraiment très clairement expliqué, et j’ai pourtant un soucis.

quand je me rends sur:
admin/
je tombe sur le formulaire de connexion.
lorsque je me connecte, je retombe sur /users/login.
si je change le login redirect (admin/users en genre /articles), je suis bien redirige.

la route est apparemment bien faite dans routes.php, je desespere un peu a arriver a me logger dans une section administration :(

J’aimerai afficher une liste d’articles et rendre visible un bouton ‘delete’ uniquement si l’utilisateur a les droits.

Connaissez-vous un moyen détourné de tester les droits dans les vues (utiliser le composant ACL dans la vue)?

Bonjour,

tout d’abord merci pour ce précieux tuto …

Dans les tutos que j’ai lu ainsi que dans celui ci, si j’ai bien compris, on gère des permissions permettant à un rôle d’effectuer une opération sur une Ressource.

Existe il une méthode particulière pour définir une sorte de rôle permettant d’accéder seulement à certains enregistrement d’une table ?

Je m’explique, imaginons que nous ayons
– une table Articles
– deux utilisateurs ‘Abonné’ et ‘Invité’

je voudrais que lors d’une requette sur la table ‘Articles’ , par exemple find(’all’) , le résultat diffère pour ces deux utilisateurs
(par exemple l’utilisateur ‘Invité’ ne verra pas certains enregistrement) mais de façon automatique comme pour l’accès aux ressources.

De de la même manière que cake test, de façon transparente pour le développeur, si le rôle de l’utilisateur permet l’accès à une méthode du controleur
existe il une méthode qui nous permette d’accéder a des enregistrements d’une table

On peut faire ce ‘filtrage’ directement dans la vue mais je suppose que niveau sécurité c est pas terrible et en plus cela force à charger tous les articles même ceux qui ne seront pas affichés

On peut aussi tester manuellement dans le contrôleur quel rôle est attribué à l’utilisateur et d’exécuter des méthodes/instructions distinctes pour chaque cas, mais cette solution ne me protège pas d’un oublie de ma part

Une piste (s’il n’existe pas déjà une solution ..) ne serait elle pas pour la méthode find(’all’) de remonter ce filtrage au niveau du model ?
il faudrait alors :
– définir un champs rôle dans la table Article (qui aura pour valeur par exemple ‘abon’ et ‘invit’
(ce champs rôle est bien sur obligatoire non null et non vide)
– attribuer un rôle différent a ‘Abonné’ et ‘invité’
– surcharger la méthode find(’all’) dans ‘appModel’ (le super model si j ‘ai bien compris) en faisant un truc du genre

sql = “SELECT * FROM ”
if(role == ‘abonn’)
{
sql += ” WHERE ROLE = ‘abon’
}else if(role == ‘invit’) {
sql += ” WHERE ROLE = ‘invit’
}else {
sql += ” WHERE ROLE = ”
}

De cette façon je n’ai plus à me soucier des droits dans le contrôleur de ‘Article’ (ainsi que dans tous les models faisant référence à un table contenant le champs ‘rôle’)

Est ce que quelqu’un aurait un avis sur ce sujet ?
merci
a bientot

Bonjour

Etant un véritable noob en la matiere (aussi bien sur cake que sur php) j’espere que vous m’excuserez si des suppositions ou des questions vous paraissent absurdes …

En lisant la documentation sur la gestion des droits d’acces avec Acl quelques questions restent sans reponses.

si j’ai bien compris (je compte sur vous pour me le confirmer wink ) lorsque on utilise la gestion des droits d’accès avec Acl le processus d’acces à une ressource ’sécurisée ‘ se déroule comme ceci

Exemple avec une demande d’accès à une fonction admin_index de UsersController qui affichera la liste des administrateurs.

Code:

class UsersController extends AppController {

var $name = ‘Users’;
var $helpers = array(’Html’, ‘Form’);

function beforeFilter() {
$this->Auth->actionPath = ‘controllers/’;

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 = ‘/articles/index’;
$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’;
}
}

function admin_index() {
$users= $this->User->find(’all’);
$this->set(’users’, $users);
}

function login(){
$this->layout = ‘admin_default’;
}
}

Soit un utilisateur qui veut aller sur la page correspondant à la fonction admin_index(),

- la fonction ‘beforefilter’ est exécutée , cette dernière renvoie l’utilisateur sur la page de connexion ( l’adresse de la page admin_index est mise en cache.
- l ‘utilisateur s’identifie (correctement)
- l’utilisateur est dirigé vers la page admin_index et la fonction admin_index est exécutée
en admettant que mon implémentation soit correcte (en tout ça le résultat semble correcte) , comment puis-je avoir accès dans ma fonction ‘admin_index()’ aux informations de l’utilisateur connecté, par exemple son login, son nom ou même mieux encore, son Id dans la table User ?
Etant donné que lorsque une personne est deja connectée on ne lui redemande pas une identification si elle est exécute la fonction ‘admin_index’, c’est que l’information est stockée quelque part.

Ma Première question est donc de savoir comment est stockée cette information. Existe seulement l’information comme quoi l’utilisateur courant est identifié correctement ou bien avons nous non seulement cette information mais en plus l’identité de la personne ?
Ma deuxième question étant bien sur de savoir comment récupérer cette ou ces informations

En espérant qu’une âme charitable aura pitié de moi wink

merci
a+

Oui le Component Auth conserve certaines informations du User identifié : l’id et le login, que tu peux récupérer dans toute action de contrôleur par $this->Auth->user(’id’)

Tu peux aussi accéder à ces valeurs depuis une vue, via le Helper Session, en faisant par exemple :

echo $session->read(’Auth.login’);

Si tu as besoin d’autres infos sur l’utilisateur, alors tu peux faire un read() ou un find() dans ton contrôleur, en utilisant la valeur de $this->Auth->user(’id’) comme paramètre ou même faire : $this->Model->findByLogin($this->Auth->user(’login’))

merci Avairet pour ces précisions.

En essayant de palier a ces incompréhensions, je me suis dit que j’allais enregistrer ces informations dans variable $this->Session depuis mon controleur.

Je pensais que la variable $this-Session de mon controleur UserControlleur correspondait aux données relatives à une session. Je dois sans doute me tromper car lorsque j’essaie de faire $this->Session depuis mon modèle UserModel cette fois, j’ai l’erreur “variable User::Session inconnue”
Par contre si je fais $_SESSION depuis mon modele, je retrouve bien mes variables placées dans la session.

Ma question est donc “Quelle est la difference entre $this->Session et $_SESSION ?

merci beaucoup

Attention ! Quand tu utilises dans ton contrôleur $this->Session, tu fais appel au Component Session, or les Components ne sont accessibles que dans les contrôleurs.

Il est donc normal que tu aies un message d’erreur si tu cherches à l’appeler depuis un modèle. Par contre, la variable globale PHP classique $_SESSION est accessible par nature dans toute ton application.

Sans être un intégriste du modèle MVC, il me semble de toutes façons mal approprié de recourir aux sessions dans les modèles…

merci Beaucoup pour ces précisions…

encore moi …

lorsque tu dis qu’il te semble mal approprié d’utiliser les sessions dans le modèle, c’est spécifique aux sessions ?

merci

Non ce n’est pas spécifique aux sessions, les modèles devraient ne gérer que les données (en base, issus d’annuaires LDAP, de fichier XML, etc.) et ne pas gérer l’environnement applicatif (session, cookie, requêtes HTTP, données en POST ou en GET, etc.).

Mais je ne suis pas un expert de MVC…

Sinon, je t’invite à utiliser le site de la communauté francophone et notamment le forum “Entraide” ou “Entraide pour débutants” si tu souhaites échanger sur les problématiques de fond, plutôt que d’utiliser les commentaires ici.
http://www.cakephp-fr.org

encore merci pour ces precisions,
J’arrete de “polluer” ce site de mes questions pour aller sur http://www.cakephp-fr.org ;)

Bonjour,

Je crois que je vais poser une question conne et enfoncer une porte entrouverte.

Pour que l’ACL fonctionne il faut déclarer toute les pages (Controleurs/Actions) qu’un utilisateur peut accéder.

C’est bien ça

Salut Vanitom

De ma (très petite) expérience d’ACl, il apparait qu’il faut effectivement définir toutes les pages (contrôleur et action) qu’un utilisateur peut accéder (il faut donc avoir un enregistrement dans la table Aco pour chaque contrôleur, et pour chaque action de ce contrôleur.
Par contre je crois que ce n’est pas nécessaire de la faire pour les actions privée (méthode private) d’un contrôleur (car un utilisateur ne pourra de toute façon pas y accéder )

Je précise encore une fois que mon expérience est assez limité…

a+

Toutes les actions nécessitant une authentification doivent bien être dans la table des ACOs, mais les actions dont l’accès est autorisé à tout le monde, les méthodes “protected” et “private”, ainsi que toutes les méthodes natives de Cake, telles les callbacks “beforeFilter”, “beforeRender”, etc. ne doivent pas y figurer.

Pour les actions à accès public, il suffit de les mettre dans le tableau $this->Auth->allowedActions, au niveau du beforeFilter() des contrôleurs concernés.

Quand on a une grosse application, il devient vite fastidieux d’entrer à la main tous les contrôleurs et toutes leurs actions… il faut donc créer une fonction qui va faire cela toute seule !

Et dans cette fonction on pourra faire ce qu’il faut pour que toutes les méthodes “protected” et “private” ne soient pas prises en compte, ni les méthodes natives de Cake. On peut même lui dire d’éviter tel ou tel contrôleur dont on sait que l’accès à toutes les méthodes sera public.

Toutefois, la mise en place de cette méthode dépasse le cadre de ce tutoriel…

Merci Avairet pour cette confirmation

sur
http://book.cakephp.org/view/647/An-Automated-tool-for-creating-ACOs on peut y trouver un exemple pour parser tous les controleur/actions d’une appli (ainsi que les plugin)

Oui, mais cet exemple ne vas pas assez loin, il ne prend pas en compte les méthodes “private” et “protected” par exemple.

Dans le cas d’un besoin de gestion d’accès dynamique en fonction du controlleur utilisé et de l’utilisateur connecté…
exemple : un auteur peut modifier et supprimer ses propres articles et pas ceux des autres auteurs.
On ferait un traitement dans le code pour comparer le user_id auteur et le user_id connecté ?

Une nouvelle question sur l’utilisation du multilingue. J’ai plusieurs langues, comment faire pour traduire les rangs ? Que faut-il mettre dans les champs lors de la création des AROs ? Doit-on utiliser i18n en parallèle ?

Merci d’avance

Participez

Pour insérer une portion de code, utilisez <pre lang="php">...</pre>