Les autorisations

Parmi les composants de Symfony, il existe celui qui traite la sécurité. Ce dernier introduit deux concepts importants :

  • l’authentification : ce sont les mécanismes qui déterminent l’identité de l’utilisateur qui effectue le traitement. Parmi les informations associées à l’utilisateur, on retrouve notamment les rôles.
  • l’autorisation : afin de déterminer si l’utilisateur a le droit d’accéder à une ressource ou d’effectuer un traitement spécifique.

Pour tester l’autorisation d’un utilisateur, il existe la méthode isGranted() que l’on retrouve dans le contrôleur abstrait du FrameworkBundle, mais aussi dans Twig et dans les annotations @Security utilisant l’ExpressionLanguage. Cette méthode va déclencher les appels aux voteurs qui indiquent s’ils donnent l’autorisation ou non ou encore s’ils ne savent pas prendre la décision.

Dans un projet, il est courant de définir des profils d’utilisateurs qui auront accès à différentes parties. Les profils sont souvent en cascade c’est-à-dire que le profil suivant a les mêmes droits que le profil précédent auquel s’ajoutent des droits supplémentaires. Les profils sont représentés par des rôles dans Symfony, exemple ROLE_USER, et la cascade est gérée par la hiérarchie des rôles définie dans le fichier security.yml.

La problématique

Prenons l’exemple d’une application de rédaction d’articles. Les articles peuvent avoir plusieurs états : brouillon, validé, publié et archivé. On peut définir plusieurs rôles, par exemple ROLE_USER qui peut lire les articles publiés, le rôle ROLE_AUTHOR qui peut créer et éditer un article qui sera en état brouillon, un ROLE_VALIDATOR qui peut relire et valider un article et enfin le ROLE_ADMINISTRATOR qui peut publier ou archiver un article. Tous ces rôles sont en cascade, le profil ROLE_AUTHOR as les mêmes droits que le ROLE_USER, etc.

L’application compte 6 fonctionnalités relatives aux articles qui comportent des tests du profil de l’utilisateur pour l’utilisation des contrôleurs ou l’affichage d’un bouton. On peut compter une moyenne de 6 vérifications par fonctionnalité, soit 36 endroits où des tests sont effectués.

Le réflexe primal est d’utiliser les rôles comme ROLE_AUTHOR, car c’est pratique et que Symfony possède le RoleVoter qui permet de tester rapidement si l’utilisateur possède un rôle qui commence par ROLE_. On peut donc retrouver des tests du genre isGranted('ROLE_VALIDATEUR') || isGranted('ROLE_ADMINISTRATOR').

Problème, si une fonctionnalité évolue, ça fait potentiellement 6 tests à revoir, si les responsabilités d’un profil changent ça fait 36 tests à revoir. Si un nouveau profil est ajouté, 36 tests à revoir. La complexité évolue en fonction du nombre des fonctionnalités. Si un profil doit avoir accès à une fonctionnalité sans que les autres en héritent, impossible.

Le RoleVoter ça dépanne, mais on arrive vite aux limites.

Les permissions

Il est plus pratique de penser en termes de permissions liées à des fonctionnalités. Comme un rôle, une permission peut-être représentée par une chaîne préfixée par PERM_. Par exemple, PERM_ARTICLE_EDIT permettant de créer et de modifier un article ou PERM_ARTICLE_PUBLISH permettant de publier un article sur le site.

Ceci simplifie les tests, en effet, au lieu de tester plusieurs rôles, on teste la permission. Mais il faut mettre en place un Voter qui saura prendre une décision pour les tests sur les chaînes préfixée par PERM_. Grâce au RoleVoter, nous pouvons mettre cela rapidement en place, en effet, ce dernier possède un argument dans son constructeur qui définit le préfixe des chaînes qu’il sait traiter. Nous pouvons donc mettre un nouveau Voter en place dans service.yml :

services:
    app.security.access.permission_voter:
        class: Symfony\Component\Security\Core\Authorization\Voter\RoleVoter
        arguments: ['PERM_']
        tags:
              - { name: security.voter }

Pour autoriser l’accès ou non, la classe RoleVoter va chercher la présence de la chaîne testée dans la liste des rôles d’un utilisateur. Pour le faire, nous pouvous utiliser le système de hiérarchie définie dans le fichier security.yml, qui traite des chaînes et qui les ajoute dans la liste des rôles de l’utilisateur.

security:
    role_hierarchy:
        ROLE_AUTHOR: [PERM_ARTICLE_EDIT]
        ROLE_VALIDATOR: [PERM_ARTICLE_EDIT, PERM_ARTICLE_VALIDATE]

Conclusion

Grâce à ce nouveau voteur et ces permissions, vous pouvez aisément organiser les profiles, ajouter de nouveaux profiles et ajouter/retirer des permissions aux profils. Tout ça, au même endroit !

Pour éviter de laborieux tests de sécurité les voteurs sont là pour aider, n’hésitez pas à en implémenter.