Contrôle d'accès personnalisé
Le contrôle d'accès dans le fichier de configuration security.yaml peut nous permettre de restreindre des sections entières à un rôle particulier, une méthode HTTP, ou tout un tas d'autres options.
Mais si on veut un contrôle d'accès plus fin, au niveau d'un contrôleur par exemple, la configuration ne suffira plus.
Dans un contrôleur
Dans un contrôleur, on dispose de méthodes fournies par l'AbstractController.
denyAccessUnlessGranted
denyAccessUnlessGranted($attribute, $subject = null, string $message = 'Access Denied.') permet de générer automatiquement une erreur 403 Forbidden si l'utilisateur connecté n'a pas le droit d'accéder à la ressource (le subject) pour effectuer une action (l'attribute).
On peut aussi passer un rôle dans $attribute :
$this->denyAccessUnlessGranted('ROLE_ADMIN');
isGranted
isGranted($attribute, $subject = null), quant à elle, renvoie un booléen pour indiquer si l'utilisateur a le droit d'accéder à la ressource pour effectuer une action. On n'aura pas une 403 générée automatiquement, seulement un booléen en retour.
On peut également l'utiliser avec un rôle, pour vérifier si l'utilisateur a bien le rôle indiqué.
Il est recommandé de ne pas utiliser directement la méthode getRoles() sur un utilisateur pour vérifier ses rôles.
À la place, il faut utiliser denyAccessUnlessGranted ou isGranted
Récupérer l'utilisateur
Depuis un contrôleur
Si on veut récupérer l'utilisateur depuis un contrôleur, on pourra utiliser la méthode utilitaire $this->getUser(). Le type de retour indiqué est bien sûr UserInterface|null.
Depuis un service
Si on ne se trouve pas dans un contrôleur, on pourra injecter directement le type Security :
class MyService
{
public function __construct(
private Security $security
) {
}
public function myMethod()
{
$user = $this->security->getUser();
}
}
Le service Security présente la méthode isGranted pour vérifier si un utilisateur a un rôle ou bien peut effectuer une action. Encore une fois, il n'est pas nécessaire de récupérer l'utilisateur et ensuite vérifier ses rôles avec getRoles() : on utilisera directement isGranted sur le service Security
Utiliser un Voter
Pour implémenter un contrôle d'accès plus fin, on peut également écrire un Voter personnalisé.
Par exemple, mettons que des articles peuvent être édités ou supprimés seulement si l'utilisateur connecté en est l'auteur, ou bien qu'il est administrateur.
Depuis un contrôleur, on peut vérifier si l'utilisateur a le droit de modifier l'article :
$this->denyAccessUnlessGranted('ARTICLE_EDIT', $article);
La logique de vérification peut quant à elle être écrite dans une classe héritant de la classe abstraite Voter :
class ArticleVoter extends Voter
{
public const EDIT = 'ARTICLE_EDIT';
public const DELETE = 'ARTICLE_DELETE';
public function __construct(
private AccessDecisionManager $accessDecisionManager
) {
}
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE])
&& $subject instanceof Article;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface || !$subject instanceof Article) {
return false;
}
if ($this->accessDecisionManager->decide($token, ['ROLE_ADMIN']) || $subject->getAuthor() === $user) {
return true;
}
return false;
}
}
Utilisez l'AccessDecisionManager pour vérifier les rôles au sein d'un Voter (documentation)
On peut créer un voter à l'aide du maker : php bin/console make:voter
À partir du moment ou notre voter hériter de la classe abstraite Voter, Symfony va détecter automatiquement notre service et l'enregistrer automatiquement pour la vérification d'un contrôle d'accès.
Ce voter sera donc appelé automatiquement lorsqu'on appellera denyAccessUnlessGranted ou isGranted avec un attribut et un sujet.
Dans un premier temps, Symfony interrogera chaque voter enregistré en appelant leur méthode supports. Cette méthode retourne un booléen pour indiquer si elle est capable de fournir une décision pour l'attribut et le sujet.
Si supports renvoie true, alors Symfony appelle automatiquement la méthode voteOnAttribute du voter.