La gestion des permissions avec les voters

La gestion des permissions avec les voters

20 March 2022

Afin de gérer les autorisations dans Symfony, comme l'accès à une page ou même l'accès à une action tel que l'édition, la suppression... Les voters est l'outil parfait, centralisant les logiques d'autorisations dans le but de pouvoir les réutiliser partout dans votre code.

Création d'un voter

La création d'un voter passera par le terminal et la commande make :

symfony console make:voter PostVoter

Cette commande va générer un fichier dans le dossier src/Security/Voter contenant :

<?php

namespace App\Security\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class PostVoter extends Voter
{
    public const EDIT = 'POST_EDIT';
    public const VIEW = 'POST_VIEW';

    protected function supports(string $attribute, $subject): bool
    {
        // replace with your own logic
        // https://symfony.com/doc/current/security/voters.html
        return in_array($attribute, [self::EDIT, self::VIEW])
            && $subject instanceof \App\Entity\Post;
    }

    protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        // if the user is anonymous, do not grant access
        if (!$user instanceof UserInterface) {
            return false;
        }

        // ... (check conditions and return true to grant permission) ...
        switch ($attribute) {
            case self::EDIT:
                // logic to determine if the user can EDIT
                // return true or false
                break;
            case self::VIEW:
                // logic to determine if the user can VIEW
                // return true or false
                break;
        }

        return false;
    }
}

Paramétrer le voter

Les constantes

La première partie concerne les constances en début de fichier :

public const EDIT = 'POST_EDIT';
public const VIEW = 'POST_VIEW';

Il s'agit simplement des différentes autorisations que vous souhaitez traiter. Rien ne vous empêche d'en ajouter d'autres si nécessaire.

Le support

La méthode supports() permet de vérifier que le voter sera bien utilisé pour une entité précise et que l'attribut reçu est bien une des permissions que vous avez définies dans les constantes.

Le vote

La méthode voteOnAttribute() contient la logique de vérification des permissions.

Une fois l'étape du support passée, cette méthode retournera un booléen pour accepter ou non l'autorisation.

Dans un premier temps, nous reprenons l'utilisateur actuellement connecté à l'application, mais si celui-ci est anonyme, c'est-à-dire non connecté, le voter retournera false systématiquement, refusant l'accès :

$user = $token->getUser();

if (!$user instanceof UserInterface) {
    return false;
}

Enfin, une instruction switch permet de dispatcher la logique des autorisations selon la condition reçue (POST_EDIT, POST_VIEW...).

La plupart du temps, il s'agit d'appeler une méthode qui effectuera une vérification. Dans la méthode, on passera l'objet $subject qui est l'entité liée au voter, dans notre exemple il s'agit de l'entité Post, ainsi que l'objet $user pour vérifier ses droits.

switch ($attribute) {
    case self::EDIT:
        return $this->canEdit($subject, $user);
        break;
    case self::VIEW:
        return $this->canView($subject, $user);
        break;
}

Reste à effectuer la condition voulue dans la méthode appelée et de retourner un booléen en conséquence :

private function canEdit(Post $post, User $user): bool
{
    return $user === $post->getAuthor();
}

À partir de là, le voter est paramétré et prêt à être utilisé au sein de votre ou vos contrôleurs.

Administrateur

Si vous souhaitez accorder tous les accès à un rôle administrateur, rajoutez le code suivant après la récupération de l'utilisateur connecté :

$user = $token->getUser();

// ...

if ($this->security->isGranted('ROLE_ADMIN')) {
    return true;
}

// ...

Pour que cette condition fonctionne, vous devez ajouter une propriété et le constructeur :

private Security $security;

public function __construct(Security $security)
{
    $this->security = $security;
}

Utiliser le voter

Dans le contrôleur de votre choix, placez-vous en début de méthode et utiliser le code suivant :

/**
 * @Route("/posts/{id}/edit", name="post_edit")
 */
public function edit(Post $post): Response
{
    $this->denyAccessUnlessGranted('POST_EDIT', $post);

    // ...
}

Voilà ! On passe en paramètre de la méthode denyAccessUnlessGranted() deux valeurs. La première concerne l'autorisation que vous voulez vérifier et la seconde, vous passez l'objet de l'entité sur laquelle effectuer la vérification.