Makina Blog
Access Control, bibliothèque PHP pour gérer des droits d’accès
Suite à un projet de gestion métier opérationnel dont la durée de vie et la maintenance sont à long termes, nous avons expérimenté un passage de celui-ci sur l’architecture hexagonale et la clean architecture.
Dans cet article, ce qui nous intéresse est la structure en couches applicatives aussi parfois appelée structure en oignon, avec au centre le Domaine où est implémenté le métier du client, indépendant de tout code externe, et plus à l’extérieur la couche Infrastructure, qui porte les implémentations concrètes des interfaces du domaine, en utilisant alors des bibliothèques tierces. Nous avons récemment abouti un projet de gestion métier opérationnel, dont la durée de vie et la maintenance sont planifiées pour de nombreuses années. Dans ce contexte, nous avons expérimenté un passage de celui-ci sur l’architecture hexagonale et la clean architecture.
Dans ce contexte, nous avions besoin d’une méthode pour implémenter les vérifications de droits d’accès dans notre domaine métier sans le coupler à du code extérieur. La solution que nous avons développée est une bibliothèque de vérification des droits d’accès qui propose une API en AOP pour Aspect Orientend Programming, utilisant les Attribute
de PHP 8.0.
L’idée globale est de s’assurer que toute dépendance externe, y compris le framework Symfony lui-même, soit des composants discrets au sein du code métier, et puisse être remplacée sans aucune implication, ni contrainte.
Une courte introduction
Le problème des droits d’accès
Le premier problème est de vérifier les droits d’accès en plusieurs points stratégiques que nous appelons des PEP ou Policy Enforcement Point :
- Lorsqu’une requête HTTP arrive, avant d’exécuter le contrôleur par exemple.
- Lorsqu’une commande s’apprête à être exécutée dans le bus de message, avant d’exécuter son handler.
- À différents autres endroits plus marginaux, selon les choix d’architecture qui ont été faits.
Pour ce faire, nous avons plusieurs modèles de vérification des droits d’accès :
- RBAC pour Role Based Access Control, ce que traditionnellement la plupart des gens utilisent en première intention sur un projet Symfony via la méthode
AbstractController::isGranted()
. Par exemple, lorsque la gestion des droits d’accès reste simple. Nous restons ici en dehors du domaine métier. - ABAC pour Attribute Based Access Control, qui dénomine des méthodes de vérification des droits d’accès en utilisant des valeurs présentes dans les entités ciblées par cette vérification de droits d’accès. Lorsque nous utilisons cette méthode, nous pénétrons franchement dans le domaine métier.
La liste n’est pas exhaustive, mais ces deux exemples illustrent un fait : les vérifications de droits d’accès peuvent se baser sur des règles dérivées de l’identité de la personne et donc en dehors du domaine, mais aussi sur des règles métier couplées à l’état du domaine.
Traditionnellement, pour des vérifications de droit d’accès, dans une application Symfony, nous allons utiliser les méthodes que nous offrent le framework. La plus parlante est AbstractController::isGranted()
et souvent l’implémentation de Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
. Mais dans notre domaine métier découplé, le but est de nous dissocier du framework : nous devons donc nous débarrasser des concepts du framework tels que les Voter
.
Un peu de vocabulaire
Tout d’abord, pour lire la suite de cet article, vous devriez prendre connaissance de ce glossaire :
- Quand on parle d’Access Control, on parle plus généralement de la vérification de droits d’accès, à ne pas confondre avec les ACL pour Access Control List qui représente une méthode particulière de validation de droits d’accès.
- Un modèle, ici, est une méthode particulière de vérification de droits d’accès, comme RBAC pour Role Based Access Control, LBAC pour Lattice Based Access Control ou encore PBAC pour Permission Based Access Control. Il existe de nombreux autres modèles que ceux qui viennent d’être cités.
- Le PDP pour Policy Decision Point est un composant logiciel qui à partir d’une ou plusieurs policies et un contexte applicatif va répondre tout simplement oui / allow ou non / deny.
- Le PEP pour Policy Enforcement Point est un endroit particulier dans le code d’un logiciel où on appelle le PDP. Généralement dans une application web on trouve des PEP sur l’arrivée d’une requête, au début de l’exécution d’un contrôleur, en entrée d’un bus de message, etc…
- Nous n’en parlerons pas dans cet article, mais il est intéressant que certaines applications ou systèmes d’information plus complexes disposent en sus d’un PAP pour Policy Administration Point, ainsi que le PRP pour Policy Retrieval Point, composants logiciels, parfois externes à l’application qui les utilise, qui permettent à la fois la configuration des policies et leur stockage déporté en dehors de l’application opérationnelle.
- Nous vérifions toujours les droits d’accès à une resource, elle peut être une entité métier, une section d’un site, une page, ou tout autre concept auquel un utilisateur peut accéder.
- Nous vérifions toujours les droits d’accès pour un subjet, représentant une identité, un utilisateur ou un acteur, qui peut être une personne physique ou application tierce, qui se connecte à l’application protégée pour accéder à ses données ou fonctionnalités.
Aspect Oriented Programming
Comme expliqué plus haut, nous voulons trouver un moyen de :
- Supprimer les dépendances explicites au framework pour la gestion des droits d’accès.
- Malgré tout, continuer à utiliser l’outillage qu’il nous offre, car il implémente déjà des choses dont nous avons besoin.
- Aller au-delà du framework et s’assurer qu’on peut remplacer son implémentation si on devait en changer plus tard.
- Pouvoir définir et implémenter nos propres PEP de façon simple.
- Pour aller plus loin, conserver explicitement la définition de ces droits d’accès dans le code du domaine, pour éviter des allers-retours incessants entre de la configuration du framework et notre domaine, et garder tout le métier… dans la couche métier.
Pour répondre à tous ces points, nous avons décidé d’implémenter les vérifications de droits d’accès par Aspect Oriented Programming. Ce paradigme consiste non pas à écrire du code dans le domaine, mais à décorer le code existant.
Jusqu’à PHP 7.4, le seul moyen que nous avions pour implémenter de l’AOP était d’utiliser doctrine/annotations
, mais depuis PHP 8.0, nous pouvons utiliser les Attributes désormais intégrés au langage.
Parlons peu, parlons bien, à la suite découvrez quelques exemples.
Exemple dans un contrôleur
Les contrôleurs dépendent directement du framework pour lesquels on les écrit. Par conséquent ils vivent, en théorie, dans la couche Infrastructure.
Voici un exemple de contrôleur utilisant l’API de Symfony directement :
<?php
declare (strict_types=1);
namespace Vendor\App\UserInterface\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FooController exteds AbstractController
{
public function doSomething(Request $request): Response
{
$this->denyAccessUnlessGranted('Gestionnaire');
// Ici le code de votre contrôleur.
}
}
Il existe bien entendu plusieurs autres moyens d’aboutir à ce résultat, comme utiliser les firewalls de Symfony par exemple.
Voici son alter-ego utilisant notre API de vérification de droits d’accès :
<?php
declare (strict_types=1);
namespace Vendor\App\UserInterface\Controller;
use MakinaCorpus\AccessControl\AccessRole;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FooController
{
#[AccessRole("Gestionnaire")]
public function doSomething(Request $request): Response
{
// Ici le code de votre contrôleur.
}
}
Vous remarquerez que malgré un certain nombre d’efforts, le contrôleur reste couplé au composant symfony/http-foundation
, mais désormais plus au framework lui-même, ce qui est déjà un premier pas.
L’utilisation de cet attribut, nous permet d’annoter notre contrôleur et de nous débarrasser de la dépendance à Symfony\Bundle\FrameworkBundle\Controller\AbstractController
et de sa méthode isGranted()
et donc de se découpler du framework.
Exemple de handler de commande
Notre projet utilise un bus de message, qui nous sert à exécuter du code de façon asynchrone. Pour aller plus loin l’intégralité du code, qui effectue des écritures dans le domaine, est implémentée sous la forme de commande et handler, ce qui rend le projet, sur le papier, complètement asynchrone.
Les commandes et handlers appartiennent au domaine métier, par conséquent ils vivent dans la couche Domaine. Il devient alors critique qu’aucun couplage avec du code extérieur ne puisse exister dans ces objets.
Note importante : n’importe quel composant logiciel de l’infrastructure peut envoyer des messages dans le bus, y compris des applications front, ce qui implique que le bus ait un endpoint ouvert pour recevoir des messages. Dans ce contexte, n’importe qui peut envoyer n’importe quoi dans notre bus, d’où l’absolue nécessité de forcer toutes les commandes du bus à être protégées, et donc d’avoir un PEP qui décore le bus.
Nous pouvons écrire un handler pour le bus de message de la façon suivante :
<?php
declare (strict_types=1);
use MakinaCorpus\CoreBus\Attr\CommandHandler;
class FooHandler
{
#[CommandHandler]
public function doSomething(SomethingCommand $command): void
{
// Ici le code de votre handler.
}
}
Et le message associé de la façon suivante :
<?php
declare (strict_types=1);
namespace Vendor\App\Domain\Command;
use MakinaCorpus\AccessControl\AccessRole;
#[AccessRole("Client")]
class SomethingCommand
{
// Vos attributs, constructeur et méthodes
}
Veuillez noter que dans le cadre de ce projet, nous n’utilisons pas symfony/messenger
car les choix d’architecture pré-datent la version de Symfony où ce dernier a été officiellement considéré comme stable.
Ici, la dépendance au code externe MakinaCorpus\AccessControl\AccessRole
existe toujours. Cependant, un des aspects intéressant des Attribute
de PHP est que le code n’est pas chargé explicitement tant que ce n’est pas demandé explicitement par du code d’auto-configuration. Par conséquent, si vous n’installez pas la dépendance, le code reste fonctionnel.
Fonctionnalités
Je ne vais pas détailler l’ensemble des fonctionnalités, mais les plus importantes seulement.
Policies
Une policy est l’instance d’une règle métier unitaire, définie par le développeur. Chaque Resource peut être décorée d’autant de policies que désirées. Si plusieurs entre en concurrence sur une-même Resource, le résultat des vérifications de droits d’accès est un ou logique entre toutes ces règles.
Notre library permet de définir les policies avec les modèles suivants :
MakinaCorpus\AccessControl\AccessAllOrNothing
: indique que toutes les autres policies doivent aboutir à un allow où le résultat global sera deny.MakinaCorpus\AccessControl\AccessAllow
: indique que le résultat sera arbitrairement toujours allow et vous permet de spécifier une raison.MakinaCorpus\AccessControl\AccessDelegate
: vous permet d’indiquer le nom d’une autre classe PHP sur laquelle charger les policies.MakinaCorpus\AccessControl\AccessDeny
: indique que le résultat sera arbitrairement toujours deny et vous permet de spécifier une raison. Ceci est utile par exemple pour protéger du code qui est prévu pour être exécuté programmatiquement par une autre API plutôt qu’être utilisé directement par l’utilisateur.MakinaCorpus\AccessControl\AccessMethod
: vous permet d’indiquer le nom d’une méthode de l’objet resource à exécuter pour déterminer le droit d’accès. On peut utiliser ça pour implémenter le modèle ABAC pour Attribute Based Access Control et ainsi implémenter la vérification d’accès dans notre couche Domaine.MakinaCorpus\AccessControl\AccessPermission
PBAC ou Permission Based Access Control: vérifie que le subject dispose d’une certaine permission. Ce qu’est le subject et comment sont déterminées les permissions qui dépendent alors de l’intégration que vous avez écrite via un permission checker. Cette policy est l’une des rares qui ne dispose pas d’implémentation par défaut.MakinaCorpus\AccessControl\AccessResource
: permet d’indiquer un type et un identifiant permettant de substituer la classe qui porte cet attribut par un objet chargé par un resource loader en tant que resource pour les vérifications d’accès.MakinaCorpus\AccessControl\AccessRole
RBAC ou Role Based Access Control : vérifie que le subject dispose d’un certain rôle. Ce qu’est le subject et comment sont déterminés ses rôles dépendent alors de l’intégration que vous avez écrite via un role checker. Il existe une implémentation par défaut dans l’intégration Symfony qui utilise les rôles de l’utilisateur connecté.MakinaCorpus\AccessControl\AccessService
permet d’exécuter la méthode d’un service comme procédure de vérification d’accès. On peut utiliser ça pour implémenter le modèle ABAC pour Attribute Based Access Control et ainsi implémenter la vérification d’accès dans notre couche Domaine.
Méthode et service
À un moment donné lors de la conception de cette API, nous avons eu besoin de déléguer des vérifications d’accès au domaine métier. Le modèle qui se rapproche le plus étant l’ABAC pour Attribute Based Access Control, où l’identité de la personne n’est plus un facteur pour donner ou non l’accès à une resource, mais une ou des règles métiers qui dépendent de l’état du domaine. Par exemple, autoriser l’accès à un article de blog que s’il est publié.
Écrire un équivalent à symfony/expression-language
semblait être une voie complexe, et semée d’embûches pour une première version. Raison pour laquelle nous avons implémenté un modèle plus plat, et plus direct au travers des policies AccessMethod
et AccessService
pour déléguer l’exécution de la vérification d’accès au domaine.
AccessMethod
Les deux se présentent sous la forme de l’écriture d’un appel de méthode dans un chaîne de caractères, comme par exemple :
<?php
declare (strict_types=1);
namespace App\Domain\Blog\Entity;
use MakinaCorpus\AccessControl\AccessMethod;
use Symfony\Component\Security\Core\User\UserInterface;
#[AccessMethod("isVisibleFor(subject)")]
class BlogArticle
{
private bool $published;
private string $ownerUserId;
public function isVisibleFor(UserInterface $user): bool
{
return $this->published || $user->getUserIdentifier() === $this->ownerUserId;
}
}
Comme vous pouvez le constater AccessMethod
délègue la vérification à une méthode présente sur la resource elle-même.
Considérons le code suivant dans notre PEP dans le contexte d’une application Symfony :
<?php
declare (strict_types=1);
namespace App\Infra\Blog\AccessControl;
use App\Domain\Blog\Entity\BlogArticle;
use MakinaCorpus\AccessControl\Authorization;
class SomeServiceAccessControlDecorator implements SomeService
{
public function __construct(
private SomeService $decorated
private Authorization $authorization
) {
}
/**
* {@inheritdoc}
*
* Method from SomeService
*/
public function doSomethingWithArticle(BlogArticle $article): mixed
{
if (!$this->authorization->isGranted($article)) {
throw new \DomainException("You shall not pass.");
}
return $this->decorated->doSomethingWithArticle($article);
}
}
La méthode isGranted()
va successivement :
- Trouver notre policy
AccessMethod
. - Chercher les
subjects
dans l’environnement : comme nous sommes dans Symfony, elle trouvera au moins une instance deSymfony\Component\Security\Core\User\UserInterface
. - Valider la syntaxe de la chaîne
isVisibleFor(subject)
et jeter une exception si elle est invalide. - Puis vérifier que la méthode
isVisibleFor()
peut être appelée sur l’instance deBlogArticle
. - Itérer sur tous les
subjects
et appeler la méthode avec le premier dont le typage correspond.
Notez que lever les ambiguïtés, vous pouvez préciser le nom des paramètres de la méthode appelée. Ici, nous passons le Subject du contexte au paramètre $user
de la méthode isVisibleFor()
; vous pouvez écrire :
#[AccessMethod("isVisible(user: subject)")]
Notez que vous pouvez passer arbitrairement des attributs de l’objet subject
ou resource
en paramètre, imaginons que nous écrivions la méthode isVisibleFor()
de la sorte pour découpler le framework Symfony de votre domaine métier :
public function isVisibleFor(string $username): bool
{
return $this->published || $username === $this->ownerUserId;
}
Vous auriez pu alors écrire :
#[AccessMethod("isVisible(username: subject.id)")]
Ou encore :
#[AccessMethod("isVisible(username: subject.getId)")]
Attention, la résolution des attributs ressemble beaucoup à celle de Twig, mais un peu différente :
- Elle cherche si une méthode
public
,protected
ouprivate
avec un nom identique existe. - Si cette méthode existe, et n’a aucun paramètre ou que des paramètres optionnels, elle l’appelle et retourne le résultat.
- Si l’appel échoue, ou si la méthode n’existe pas, elle cherche si une propriété
public
,protected
ouprivate
de la classe existe avec ce nom, et retourne sa valeur. - Si rien n’est trouvé, le PDP va lancer une exception.
La différence fondamentale est que ce composant ne cherche pas de getter
ou hasser
en tentant de convertir id
en getId
par exemple.
AccessService
Pour utiliser un service au lieu d’une méthode, le fonctionnement est similaire, mais vous devez commencer par écrire le service qui contient la méthode à appeler, de la sorte :
<?php
declare (strict_types=1);
namespace App\Domain\Blog\AccessService;
use App\Domain\Blog\Entity\BlogArticle;
class BlogArticleAccessService
{
public function isVisibleFor(BlogArticle $article, string $username): bool
{
return $article->isPublished() || $article->getOwnerUserId() === $username;
}
}
Ensuite, si vous utilisez Symfony, enregistrez simplement ce service dans le container en lui apposant le tag access_control.service
.
Vous pourrez ensuite réécrire la classe BlogArticle
de la sorte :
<?php
declare (strict_types=1);
namespace App\Infra\Blog\Entity;
use MakinaCorpus\AccessControl\AccessService;
#[AccessMethod("BlogArticleAccessService.isVisibleFor(article: resource, username: user.getId)")]
class BlogArticle
{
private bool $published;
private string $ownerUserId;
}
Notez ici que :
- On rajoute un paramètre, le paramètre
resource
. Dans cette API, le paramètre nomméresource
sera toujours l’objet passé à la méthodeisGranted()
, celui dont la classe porte lesAttribute
qui définissent nos policies. - Pour vous simplifier la vie, par défaut l’intégration à Symfony vous permet d’appeler le service en utilisant son class local name, soit ici
BlogArticleAccessService
, mais vous pouvez cependant utiliser son FQCN pour Fully Qualified Class Name, soit iciApp\Domain\Blog\AccessService\BlogArticleAccessService
. - La syntaxe change un peu, vous devez préfixer le nom de la méthode par le nom du service, par exemple :
SomeService.checkThisOrThat(foo: subject, bar: resource)
Intégration dans votre projet
Cette bibliothèque a été conçue pour être facilement intégrée et utilisée dans votre propre code, votre propre projet. C’est d’ailleurs pour cette raison que l’intégration Symfony est volontairement pauvre.
Pour créer un PEP sur un bus de message, vous pouvez procéder de la sorte en utilisant le pattern décorateur.
Imaginons que vous utilisez un bus de message dont l’interface est la suivante :
<?php
declare(strict_types=1);
namespace SomeUberCommandBus;
interface CommandBus
{
public function dispatchCommand(object $command): mixed;
}
Vous pouvez écrire un décorateur de la sorte :
<?php
declare(strict_types=1);
namespace Vendor\App\Infra\CommandBus;
use MakinaCorpus\AccessControl\Authorization;
use SomeUberCommandBus\CommandBus;
class AccessControlCommandBusDecorator implements CommandBus
{
public function __construct(
private Authorization $authorization,
private CommandBus $decorated
) {
}
/**
* {@inheritdoc}
*/
public function dispatchCommand(object $command): mixed
{
if (!$this->authorization->isGranted($command)) {
throw new \Exception("Vous ne passerez pas.");
}
return $this->decorated->dispatchCommand();
}
}
Vous pouvez utiliser le service MakinaCorpus\AccessControl\Authorization
où vous le souhaitez, et déterminer quels sont les PEP qui vous concernent dans votre application.
Bundle Symfony
Le paquet dispose d’un bundle symfony compatible avec Symfony 5.4 et 6.0, qui apporte les fonctionnalités suivantes :
- Auto-configuration des composants.
- Si les composants
symfony/security-*
sont installés et configurés, un subject locator et un role checker utilisant les utilisateurs et leurs rôles de Symfony sont activés. - La vérification des droits d’accès est enregistrée automatiquement sur les contrôleurs.
- Un service
MakinaCorpus\AccessControl\Authorization
utilisable dans votre propre code.
Design logiciel
Nous n’avons pas encore répondu à une des problématiques exprimée plus haut, qui est : comment découpler le code du domaine du framework, tout en utilisant l’outillage qu’il nous fournit ?
Ou plus concrètement : comment utiliser la gestion des rôles de Symfony alors que nous n’utilisons plus son API directement ?
La réponse à cette question devient triviale dès lors que vous avez compris comment fonctionne cette bibliothèque, et c’est que nous allons décrire maintenant.
Il est important de noter que bien qu’étant fournie avec une intégration Symfony, cette bibliothèque peut fonctionner dans une standalone setup et ne requière aucune dépendance à l’exception de makinacorpus/profiling
et psr/log
, qui sont là pour instrumenter.
Concepts
Pour fonctionner, ce composant logiciel définit un certain nombre de concepts qu’il va ensuite manipuler pour vérifier les droits d’accès.
Policy et Policy Loader
Une policy est une directive, qui contient la règle − métier ou non − exprimée par le développeur pour mener la vérification de droits d’accès.
On retrouve touts les modèles de policies possibles décrits plus haut dans les fonctionnalités, elles sont matérialisées par les attributs PHP 8.0 décrits. Une policy est une instance d’un de ces attributs, qui porte avec elle en mémoire les données métiers pour faire la vérification.
Subject et Subject Locator
Un subject représente l’utilisateur connecté, une de ses identités, ou n’importe quel objet arbitraire. Il reste volontairement dénué de type et de définition stricts, car il peut varier selon le contexte d’utilisation. Nous considérons toujours que plusieurs subjects coexistent : celui utilisé pour les vérifications des droits d’accès sera déterminé par le typage demandé de la policy exécutée. Dans le cas où le typage attendu ne peut être déterminé, tous seront testés.
Pour trouver ces subjects dans le contexte d’exécution, un composant appelé le subject locator existe. C’est une interface simpliste qui est triviale à implémenter pour votre framework ou projet.
Cette bibliothèque va toujours considérer que les identités peuvent être multiples, et par conséquent travaillera toujours sur une collection de subjects.
Resource et Resource Locator
La resource représente l’entité à laquelle le subject accède, qui peut être n’importe quel objet arbitraire.
La resource est par défaut l’instance sur laquelle notre PDP va chercher les Attribute PHP. Cependant, par un jeu de configuration un peu plus avancé en utilisant l’attribut AccessResource
, celle-ci peut être un tout autre objet, cas dans lequel il fera appel à une chaîne de resource locators qui peut être implémentée très facilement.
Role Checker
Un rôle est une simple chaîne de caractères qui représente… un rôle. Cette API ne donne pas d’autre définition au mot rôle qu’une simple chaîne de caractères.
L’appartenance ou non à un ou des rôles est déterminée par les implémentations de role checker. C’est une interface simpliste extrêmement facile à implémenter pour votre framework ou projet.
Permission Checker
Une permission est une simple chaîne de caractères qui représente… une permission. Cette API ne donne pas d’autre définition au mot permission qu’une simple chaîne de caractères.
La détention ou non d’une ou plusieurs permissions est déterminée par les implémentations de permission checker. C’est une interface simpliste extrêmement facile à implémenter pour votre framework ou projet.
Le permission checker est le seul composant de cette API qui ne dispose pas d’implémentation.
Service locator
Un service est une instance qui dispose de méthodes qui peuvent être appelées. Un service peut être vraiment n’importe quel objet, ou classe portant des méthodes statiques. Pour cette API, un service est une chaîne de caractères qui sert à identifier une instance.
Le service locator est un composant de cette API qui à partir de l’identifiant va chercher à trouver ou créer l’instance du service. Une implémentation par défaut existe et utilise le service container de Symfony pour trouver ces services.
Method executor
Ce composant est un détail interne de l’implémentation et n’est jamais exposé dans l’API, il s’agit d’un composant discret, mais important. Lorsque vous définissez une policy utilisant AccessMethod
ou AccessService
, ce composant va, à partir du service trouvé ou de la resource vérifiée :
- Parser et vérifier la chaîne fournie en entrée.
- Chercher une méthode publique, qui peut être exécutée sur l’objet en question, dont le nom correspond à celui donné dans la chaîne fournie en entrée.
- Avec les informations de contexte, les paramètres additionnels passés à la méthode
isGranted()
et autres informations aggrégés (lesubject
et laresource
) va construire une table de correspondance entre les paramètres du contexte et les paramètres de la méthode trouvée. Ce tableau de correspondance tient compte du nom, mais aussi du typePHP
des paramètres. - Pour ensuite aboutir à l’exécution de la méthode, ou à une erreur si le tableau de correspondance des paramètres n’a pas pu être complété.
Articulation
Premièrement, tout le métier de vérification même des droits d’accès est intégré à un unique composant séggrégé derrière l’interface MakinaCorpus\AccessControl\Authorization
. Une implémentation unique existe, il a été fait le choix de l’abstraire via une interface pour masquer cette implémentation au développeur qui l’utilise. Cet objet est le PDP pour Policy Decision Point, c’est à dire l’object qui évalue concrètement les policies dans le contexte d’exécution et émet un allow ou un deny.
Ce composant dispose de références vers chacun des composants suivants:
- policy loader
- subject locator,
- resource locator,
- permission checker
- role checker
Et chacune de ces références est une chaîne d’implémentations qui elles-mêmes vont toutes être interrogées jusqu’à ce que l’une réponde.
Ce composant va, dans l’ordre:
- Charger toutes les policies de l’objet passé en paramètre en utilisant le policy loader.
- Trouver le sujet, en utilisant le subject locator, s’il existe.
- Si c’est explicité par une policy, essayer de charger une resource différente en utilisant le resource locator.
- Interpréter une à une les policies jusqu’à ce qu’une réponde allow.
La réalité est un petit peu plus complexe que cela, car un certain nombre de décisions sont prises par configuration, telle que le comportement en cas d’absence de policies, ou quel conscensus doit être adopté si il y a plus de Deny que de Allow. Mais aller plus en détails n’est pas le sujet de cet article.
Conclusion
Ce petit micro-composant était au départ conçu pour rester dans le projet dont il est issu. Après plus de deux ans de développement, bien des choses sont arrivées, y compris de nouvelles fonctionnalités de plus en plus avancées, et de nouveaux projets ayant le même besoin. Sa ré-utilisation en interne a contribué à sa stabilisation, au point que nous ayons décidé d’en faire une bibliothèque PHP Open Source.
Pour résumer, ce composant est l’implémentation d’un PDP pour Policy Decision Point fourni avec une interconnexion minimale à Symfony via un PEP pour Policy Enforcement Point qui par défaut est simplement branché sur l’exécution des contrôleurs. Il se veut léger, indépendant de tout framework mais surtout facile à étendre.
Nous utilisons à ce jour ce composant dans trois divers projets sans surprises.
Si vous souhaitez essayer, rendez vous sur https://packagist.org/packages/makinacorpus/access-control.
Si vous souhaitez regarder à quoi ça ressemble à l’intérieur, ou remonter des bugs, nous vous accueillerons avec plaisir sur https://github.com/makinacorpus/php-access-control.
Formations associées
Formations Outils et bases de données
Formation sécurité web
Paris Du 25 au 27 février 2025
Voir la formationActualités en lien
DbToolsBundle : sortie de la version 1.2
Découvrez les nouveautés de cette nouvelle version ainsi que les fonctionnalités à venir de la prochaine version majeure.
Itérations vers le DDD et la clean architecture avec Symfony (1/2)
Pourquoi et comment avons nous fait le choix de faire évoluer la conception de nos projets Symfony, et quelles erreurs avons-nous faites ?