Le composant a ses propres règles d’URL, identiques option pour option à celles de security.access_control. Elles n’en sont pas un doublon, et la différence coûte un après-midi si vous la rencontrez du mauvais côté.
access_control:
rules:
- { path: ^/admin, roles: [ROLE_ADMIN] }
- { path: ^/profile, roles: [IS_AUTHENTICATED_FULLY] }
- { path: ^/api, requires_channel: https }
Elles sont comparées dans l’ordre de déclaration, la première correspondance l’emportant, avec les mêmes normalisations que celles de Security, si bien qu’une règle se transpose sans être réécrite. La clé est rules plutôt qu’access_control, qui sous la racine de ce composant se serait lue access_control.access_control.
Les portées diffèrent
Les règles de Security alimentent security.access_map, consommé par un écouteur qui est un maillon de la chaîne de chaque pare-feu. Elles s’appliquent à l’intérieur d’un pare-feu, et là seulement. L’écouteur de ce composant n’appartient à aucun pare-feu, donc ses règles s’appliquent partout.
Ce qui veut dire qu’un chemin hors de tout pare-feu ne peut être gardé qu’ici. C’est le cas que les gens découvrent en général à leurs dépens : une sonde de santé, un point de terminaison de webhook, un chemin d’administration statique que quelqu’un a exclu du motif de pare-feu il y a des années.
Quand les deux couvrent le même chemin
Les deux s’appliquent. Le pare-feu passe en premier, à la priorité 8 de kernel.request, et l’écouteur de ce composant à 7. Le résultat est l’intersection des permissions.
Une règle ajoutée ici ne peut donc que resserrer, jamais relâcher. C’est fermer par défaut, ce n’est pas dangereux, mais c’est silencieux, et cela produit un accident bien précis : déplacer une règle d’une clé à l’autre sans supprimer l’originale vous en laisse deux, et les permissions d’aucune des deux.
Déclarer des règles sur les deux clés à la fois lève à la compilation, et c’est le bon cas. Ce qui ne lève pas, c’est le cas ci-dessus, où une règle est déplacée plutôt que dupliquée. Déplacez, ne copiez pas, et relisez le diff.
requires_channel est appliqué une seule fois
- Avec un pare-feu qui couvre le chemin, c’est le pare-feu qui l’applique.
- Sans pare-feu, c’est l’écouteur de canal de ce composant.
- Jamais les deux, et quand aucune règle ne déclare
requires_channel, cet écouteur n’est même pas enregistré.
roles prend des attributs, pas seulement des rôles
Le nom vient de Security, conservé pour qu’un security.yaml se transpose sans changement. Ce qu’on y met est tout ce qu’un votant comprend : un rôle, un état d’authentification, ou un attribut à vous auquel un votant répond.
access_control:
rules:
- { path: ^/admin, roles: [ROLE_ADMIN] }
- { path: ^/beta, roles: [FEATURE_BETA] }
- { path: ^/reports, allow_if: "is_granted('VIEW_REPORTS') and request.getClientIp() matches '/^10\\./'" }
Un drapeau de fonctionnalité comme règle d’accès en est un usage légitime : un votant lit votre magasin de drapeaux, et la règle se lit comme le reste de votre autorisation. roles et allow_if donnés ensemble sont combinés en AtLeastOneOf : l’un ou l’autre qui accorde laisse passer la requête.
Ce qui lève au lieu d’être ignoré
Nommer request_matcher à côté de path, host, port, ips, methods, attributes ou route lève, au lieu d’ignorer l’un d’eux en silence. Une règle vide lève aussi, ce qui attrape le tiret égaré du YAML qui, autrement, correspondrait à toutes les requêtes.
Ces deux cas sont le même principe appliqué à la configuration : une règle qui ne fait pas ce qu’elle dit est pire qu’une compilation qui s’arrête. La page des règles d’URL liste toutes les options.