Symfony2 : Protéger ses entités avec les voters

Hier, je me devais d’avancer sur mon projet Symfony2. Il fallait que je sécurise mes entités pour restreindre l’accès en fonction de divers paramètres. Posons déjà les bases pour comprendre mon objectif :

On dispose de plusieurs entités : User, Game et  Team. L’entité à sécuriser est Game. En effet, le site étant communautaire, tout utilisateur peut créer un jeu qui appartient à une équipe.

La base de la sécurité : l’accès par rôle

Une première sécurisation reste l’accès par rôle. Chaque contrôleur dispose d’une vérification (soit par l’annotation @Security, soit directement une condition). On veut que les jeux ne soient créés uniquement que par des membres du site. Je rappelle que depuis Symfony 2.6, le service security.context a été déprécié en faveur de security.token_storage pour la récupération de l’utilisateur courant et security.authorization_checker pour autoriser ou non l’utilisateur.

Il s’agit là d’une première sécurisation mais qui montre néanmoins ses limites ! On veut que l’utilisateur ne puisse pas modifier les jeux qui ne lui appartiennent pas.

Pourquoi j’ai laissé tomber les ACLs au profit des voters

Je me suis donc lancé dans l’implémentation d’un système d’ACLs. Bien pratiques, ces listes permettent d’affiner les droits à chaque entité ou même à chaque champ de l’entité. Mais après réflexion et quelques petites recherches sur le net, je me suis rendu compte qu’il s’agissait d’une solution coûteuse et qui n’apporterait pas beaucoup dans ce cas-là. Je vous conseille de jeter un oeil à ces slides qui résument le pourquoi : Drop ACE, use voters et le talk que je n’ai pas vu.

Peut-être y reviendrai-je si je dois implémenter un système hyper précis mais dans mon cas j’ai choisi les voters. Voici l’implémentation à partir de cet exemple qui rend plus simple leur utilisation :

Je définis dans un premier temps, les différentes actions que l’on peut effectuer (ça sera amené à évoluer).

Dans un second temps, on peut établir la fonction isGranted, dans laquelle vous devez faire les tests vérifiant les autorisations ou non d’accès.

Comme vous pouvez le constater, on renvoie true pour valider l’accès mais on aurait très bien pu adopter une politique inverse et renvoyer false à chaque condition. Il ne tient qu’à vous de choisir, bien que généralement, on préfère valider l’accès à quelques trucs et refuser tout le reste.

Je suppose que vous avez remarqué l’utilisation du RoleHierarchyVoter qui permet de valider en fonction de la hiérarchie des roles. En effet, lorsque qu’un membre possède le rôle ROLE_SUPER_ADMIN, il possède le ROLE_ADMIN seulement via la hiérarchie définie dans security.yml. Il faut donc injecter le service RoleHierarchy dans notre voter de cette manière :

Et le récupérer dans la classe :

Et voilà, juste une dernière chose. Étant donné que la classe AbstractVoter ne permet pas la récupération du TokenStorage présent dans vote(), j’ai réécris la fonction afin de le récupérer.

Le gros avantage de cette méthode, c’est qu’elle est complètement découplée de la base de données (vous pourriez très bien choisir d’utiliser un stockage local que ça marcherait), contrairement aux ACE qui ont besoin d’être initialisés et stockés.