Aller au contenu principal
Retour au blog

Symfony 8.2 role_hierarchy : jokers * et debug:roles

Symfony 8.2 autorise le joker * dans role_hierarchy et ajoute debug:roles pour voir quels rôles un utilisateur obtient vraiment, avec l’option --tree.

Flavien Métivier · 9 min de lecture
Sommaire · 8 parties

La hiérarchie de rôles Symfony (role_hierarchy) définit quels rôles sont inclus dans d'autres, pour ne pas tous les attribuer à chaque utilisateur. Symfony 8.2 y ajoute le joker * et une commande debug:roles, et affiche le graphe de la hiérarchie dans le profiler. Présentées le 22 septembre 2026 sur le blog officiel (PR #52099 de Nicolas Rigaud, #65345, #61786 de Damien Fernandes), ces nouveautés arrivent avec Symfony 8.2, attendu en novembre 2026.

Ouvre le security.yaml d'une application qui a trois ans et compte les entrées de role_hierarchy. À partir d'une vingtaine de rôles, personne dans l'équipe ne sait dire de tête ce qu'obtient réellement un utilisateur ROLE_BLOG_EDITOR. La configuration est correcte et personne ne la relit. Or le contrôle d'accès reste le premier risque du classement OWASP, comme le rappelle notre checklist OWASP pour sécuriser une app Symfony.

Pourquoi une hiérarchie de rôles devient ingérable

Aujourd'hui, si ROLE_BLOG_AUTHOR, ROLE_BLOG_EDITOR et ROLE_BLOG_MODERATOR doivent tous inclure ROLE_BLOG_READER, tu écris trois entrées. Tu en écris une quatrième le jour où naît ROLE_BLOG_TRANSLATOR, et la ligne ROLE_USER se répète sur chaque rôle. Le billet le résume : la configuration grossit vite, et il devient difficile de savoir quels rôles exacts appartiennent à un utilisateur.

Utiliser le joker * dans role_hierarchy

Dans Symfony 8.2, un * dans une clé de role_hierarchy désigne toute une famille de rôles. Configuration du billet :

yaml
# config/packages/security.yaml
security:
    # ...

    role_hierarchy:
        ROLE_*:              ROLE_USER
        ROLE_*_MODERATOR:    ROLE_MODERATOR
        ROLE_BLOG_*:         ROLE_BLOG_READER
        ROLE_BLOG_MODERATOR: [ROLE_BLOG_DELETE_POST, ROLE_BLOG_LOCK_POST]
  • ROLE_*: ROLE_USER : tout rôle de la forme ROLE_… inclut ROLE_USER, plus besoin de le répéter.
  • ROLE_*_MODERATOR: ROLE_MODERATOR : tout rôle ROLE_X_MODERATOR inclut ROLE_MODERATOR.
  • ROLE_BLOG_*: ROLE_BLOG_READER : toute la famille du blog inclut le droit de lecture.
  • ROLE_BLOG_MODERATOR reste une clé classique, avec ses permissions sensibles listées explicitement.

Le point le plus important : un rôle n'a pas besoin d'apparaître dans la hiérarchie pour matcher un joker. Un utilisateur ROLE_BLOG_COMMENTER reçoit ROLE_BLOG_READER et ROLE_USER, alors que ce rôle n'est écrit nulle part. Le nouveau rôle hérite de sa famille dès sa création. D'après la PR #52099, les jokers se résolvent récursivement, et si aucun n'est déclaré, l'ancien chemin de code est conservé : l'application ne paie rien pour la fonctionnalité.

Quand le * est-il vraiment un joker ?

  • ROLE_*_FOO : joker, car le * est encadré par des underscores.
  • ROLE_BAR_* : joker, car le * suit un underscore en fin de nom.
  • ROLE_BLOG* : rôle ordinaire, pas de joker.
  • ROLE_*BLOG : rôle ordinaire, pas de joker.

Le billet a d'ailleurs été corrigé après publication sur ce point : la première formulation sur les jokers « en fin de nom » était imprécise. Conséquence pratique, qui est une déduction de ma part : une faute de frappe sur ROLE_BLOG* ne lève probablement aucune erreur. Tu obtiens un rôle littéral qui ne matche rien, et ta famille de rôles n'hérite de rien, en silence.

Limites : un joker n’est pas un rôle

  • Les jokers n’existent que dans les clés de la hiérarchie : pas de ROLE_SUPER_ADMIN: ROLE_* pour tout inclure.
  • is_granted('ROLE_BLOG_*') cherche un rôle portant exactement ce nom : ce n’est pas un test de famille.
  • getReachableRoleNames() et getParentRoleNames() ne renvoient jamais de joker.

Sur le premier point, un lecteur du billet a demandé le joker côté valeur. Christophe Coevoet a répondu que c'est impossible : vérifier qu'un rôle donné matche un motif ne demande pas de connaître tous les rôles, mais lister tous les rôles qui matchent l'exige, et SecurityBundle n'a pas cette liste puisque la hiérarchie n'est pas tenue de contenir tous les rôles utilisés. La contrainte est architecturale, pas un oubli.

Voir les rôles réels avec debug:roles et --tree

Votre dette technique s'accumule ?

Audit complet en 10 jours. Recommandations priorisées et actionnables.

Calculer mon audit →

Les jokers raccourcissent la configuration, mais la rendent moins lisible ; le billet le dit lui-même. La commande debug:roles compense :

bash
# displays the whole role hierarchy
$ php bin/console debug:roles

# displays all the roles granted by the given role(s)
$ php bin/console debug:roles ROLE_BLOG_MODERATOR

 * ROLE_BLOG_MODERATOR
 * ROLE_BLOG_DELETE_POST
 * ROLE_BLOG_LOCK_POST
 * ROLE_USER
 * ROLE_MODERATOR
 * ROLE_BLOG_READER

La liste à plat donne le résultat. L’option --tree donne la cause, jokers matchés compris :

bash
$ php bin/console debug:roles ROLE_BLOG_MODERATOR --tree

ROLE_BLOG_MODERATOR
├── ROLE_*
│   └── ROLE_USER
├── ROLE_*_MODERATOR
│   └── ROLE_MODERATOR
├── ROLE_BLOG_*
│   └── ROLE_BLOG_READER
├── ROLE_BLOG_DELETE_POST
└── ROLE_BLOG_LOCK_POST

Chaque droit reçu est rattaché à l'entrée qui le donne. Quand un auditeur te demande pourquoi tel rôle peut verrouiller un article, tu montres la branche au lieu de reconstituer le raisonnement de mémoire. Note que la commande prend des rôles en argument, pas un utilisateur. Selon la PR #65345, si tu utilises ta propre implémentation de RoleHierarchyInterface, --tree est ignoré et l'argument devient obligatoire. Pour un utilisateur SSO, dont les rôles viennent du fournisseur d'identité via le login OpenID Connect natif, c'est l'outil pour vérifier ce que ces rôles donnent réellement.

Le graphe dans le profiler

Symfony 7.4 avait introduit debug:security:role-hierarchy, qui exporte la hiérarchie en diagramme Mermaid. En 8.2, ce graphe s'affiche directement dans le panneau Security du profiler. C'est un confort de développement, utile pour un onboarding ou pour expliquer l'héritage à un product owner. Pour une vérification reproductible, debug:roles --tree reste la référence, parce qu'on peut le lancer en CI et en conserver la sortie.

Pièges de production : sur-attribution et audit

Un joker large est un héritage implicite qui s'applique aussi aux rôles futurs, y compris ceux que quelqu'un ajoutera dans six mois sans relire security.yaml. ROLE_BLOG_*: ROLE_BLOG_EDITOR donnerait les droits d'édition à tout futur rôle du blog, même créé en lecture seule. Ma règle : ne pointe un joker que vers des rôles de moindre privilège. Pour verrouiller ce principe, un garde-fou en CI suffit. Ce script n'est pas tiré du billet, adapte les noms à ton domaine :

bash
# Verrou de non-régression : un rôle de lecture ne doit jamais atteindre un rôle d'écriture
out=$(php bin/console debug:roles ROLE_BLOG_COMMENTER)
if echo "$out" | grep -q 'ROLE_BLOG_DELETE_POST'; then
  echo "ALERTE : ROLE_BLOG_COMMENTER atteint ROLE_BLOG_DELETE_POST"; exit 1
fi

La hiérarchie ne remplace pas non plus les voters pour les droits par objet : elle peut être propre alors que les vérifications sur les ressources manquent. Et un rôle n'est qu'une couche ; pour les actions sensibles, le sudo mode de Symfony 8.2 ajoute une exigence de fraîcheur.

À retenir

  • Le joker * ne vit que dans les clés de role_hierarchy, encadré d’underscores ou après un underscore final.
  • Un rôle absent de la hiérarchie peut quand même hériter d’un joker : c’est la force et le risque de la fonctionnalité.
  • debug:roles --tree indique quel joker a fait matcher chaque rôle ; mets-le dans ta revue de sécurité.
  • Si ta hiérarchie tient en cinq lignes, garde-la telle quelle : les jokers servent à partir de quelques dizaines de rôles.

Une hiérarchie de rôles se relit rarement en entier, et c'est là que les droits en trop s'installent. Si tu veux savoir qui peut réellement faire quoi dans ton application, un Bear Scan passe en revue en dix jours ouvrés la sécurité et la qualité de ton code, contrôle d'accès compris, et te rend des recommandations classées par priorité que ton équipe peut appliquer.

Cet article vous a été utile ? Partagez-le.

Portrait de Flavien Métivier, fondateur de The Bearded Bear : barbe longue, lunettes, sur fond sombre

Écrit par

Flavien Métivier

Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.

Mon parcours

À lire ensuite

Votre dette technique s'accumule ?

Audit complet en 10 jours. Recommandations priorisées et actionnables.

Newsletter Bear Bytes

Veille Claude Code, Symfony et PHP, retours d'expérience terrain et conseils techniques, directement dans votre boîte mail.