Quatre algorithmes de combinaison, sous leurs noms XACML

Plusieurs votants répondent à une même question. L’algorithme de combinaison transforme leurs résultats en une décision, et son nom dans ce composant est celui que lui a donné XACML, pas celui de Symfony Security.

Il y en a quatre, et la correspondance avec Security est exacte : un security.yaml se transpose sans changer de sens.

  • permit_overrides, que Security appelle affirmative : une seule autorisation emporte la décision, quoi qu’aient dit les autres.
  • deny_overrides, que Security appelle unanimous : un seul refus lie la décision, quoi qu’aient dit les autres.
  • majority, que Security appelle consensus : le côté le plus lourd l’emporte, abstentions exclues.
  • first_applicable, que Security appelle priority : le premier votant qui ne s’abstient pas tranche.

permit_overrides est le défaut, comme affirmative l’est dans Security.

Unanimous n’est pas l’unanimité

Celui sur lequel il faut s’arrêter est deny_overrides. Il refuse dès qu’un votant refuse, et accorde si au moins un a accordé et qu’aucun n’a refusé. Malgré le nom que Security lui donne, ce n’est pas l’unanimité de tous les votants : les abstentions ne bloquent pas.

C’est l’unanimité des votants qui se sont exprimés, ce qu’exigent les modèles de contrôle d’accès obligatoire : une règle qu’on peut outrepasser n’est pas obligatoire. Lire le nom XACML plutôt que celui de Security rend l’erreur plus difficile, et c’est une bonne part de la raison pour laquelle les noms ont bougé.

Où le choix s’applique

Une fois, pour toute l’application :

access_control:
    default_strategy: deny_overrides

Ou pour une question, ce qui passe outre le défaut :

$accessControlManager->decide($accessRequest, 'deny_overrides');

Ou pour une politique d’accès, sur l’attribut qui la déclare :

#[AccessPolicy('EDIT', new Argument('post'), strategy: 'deny_overrides')]

Trois façons de dire « A et B »

Elles marchent toutes les trois, et choisir au hasard vous coûtera plus tard. Elles diffèrent par ce qu’elles savent exprimer, et par ce qu’elles vous disent quand elles refusent.

  • deny_overrides sur les votants, quand la conjonction est une propriété de tout votre modèle d’autorisation : chaque règle doit être d’accord, toujours. C’est un réglage, donc un lecteur du contrôleur ne le voit pas.
  • L’attribut All sur les politiques, quand c’est ce point d’entrée qui exige que deux questions distinctes reçoivent une réponse positive, et pas le reste de l’application. C’est visible là où l’exigence vit, et le profileur montre l’arbre plutôt qu’une liste plate.
  • Une expression, quand une des deux moitiés n’est pas une question mais une condition : quelque chose sur le sujet, sur la requête, ou un calcul qu’aucun attribut ne nomme.

La règle empirique : si vous savez nommer les deux moitiés en attributs, prenez All ; si l’une est une condition plutôt qu’une question, prenez une expression ; et ne changez default_strategy que si vous l’entendez pour l’application entière.

Écrire le vôtre

Implémentez StrategyInterface, dont le getName() est le nom qu’une requête demande et dont l’evaluate() transforme les votes en décision. L’autoconfiguration l’étiquette access_control.strategy et il peut dès lors être nommé partout où le sont ceux qui sont fournis.

Deux stratégies enregistrées sous le même nom lèvent à la construction du gestionnaire, plutôt que de laisser l’une gagner en silence ; nommer une default_strategy que rien n’enregistre fait de même. La page des algorithmes de combinaison donne les détails, et un article plus loin dans cette série en écrit un de zéro.