IFTTT via les directives
Gato GraphQL offre la possibilité d'implémenter des stratégies IFTTT (If This Then That, « si ceci alors cela ») via des directives. Ces directives sont ajoutées dynamiquement à la requête chaque fois qu'un champ ou une directive spécifique est présent dans la requête.
En général, les IFTTT sont des règles qui déclenchent des actions chaque fois qu'un événement spécifié se produit. Dans notre cas, les paires événement/action sont :
- Si « le champ X est trouvé dans la requête » alors « attacher la directive Y au champ X »
- Si « la directive Z est trouvée dans la requête » alors « exécuter la directive Y avant/après la directive Z »
L'ajout dynamique de directives IFTTT au schéma est un processus récursif : une telle directive peut, elle-même, avoir son propre ensemble de directives IFTTT configurées, qui sont également ajoutées à la chaîne de directives.
Où est-ce utilisé
En coulisses, les clients dans Gato GraphQL utilisent ce mécanisme pour configurer le schéma GraphQL.
Par exemple, Access Control nous permet de sélectionner les règles de contrôle d'accès à appliquer aux opérations, aux champs et aux directives. C'est grâce aux IFTTT que ces règles sont appliquées à ces éléments du schéma GraphQL.

En général, voici quelques cas d'utilisation :
Définir le max-age du cache control champ par champ
Attacher une directive @CacheControl à tous les champs, en personnalisant la valeur du paramètre maxAge : 1 an pour le champ url du Post, et 1 heure pour le champ title.
Configurer le contrôle d'accès
Attacher une directive @validateDoesLoggedInUserHaveAnyRole au champ email du type User, afin que seuls les administrateurs puissent interroger l'adresse e-mail de l'utilisateur.
Synchroniser le contrôle d'accès avec le cache control
En enchaînant les directives, nous pouvons nous assurer que, chaque fois que l'on valide si l'utilisateur peut accéder à un champ/directive, la réponse ne sera pas mise en cache. Par exemple :
- Attacher la directive
@validateIsUserLoggedInau champme - Attacher la directive
@CacheControlavec la valeur0pour l'argumentmaxAgeà la directive@validateIsUserLoggedIn.
Renforcer la sécurité
Attacher une directive @validateIsUserLoggedIn à la directive @translate, pour éviter que des acteurs malveillants exécutent des requêtes contre le service GraphQL susceptibles de faire tomber le serveur et de faire exploser sa facture (dans ce cas, @translate est basé sur Google Translate et facture des frais pour l'utilisation de ce service)
Comment ça fonctionne
Comment ajouter des directives au schéma via IFTTT ? Supposons, par exemple, que nous voulions créer une directive personnalisée @authorize(role: String!), pour valider que l'utilisateur exécutant le champ myPosts possède le rôle attendu author, ou afficher une erreur dans le cas contraire.
Si nous avions créé le schéma en utilisant le SDL, cela ressemblerait à ceci :
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}La règle IFTTT définit la même intention que celle déclarée par le SDL ci-dessus : chaque fois que le champ myPosts est demandé, exécuter la directive @authorize(role: "author") sur ce champ. Ainsi, chaque fois que le champ myPosts est trouvé dans la requête, le moteur attachera automatiquement @authorize(role: 'author') à ce champ dans la requête exécutable.
Les règles IFTTT peuvent également être déclenchées lors de la rencontre d'une directive, pas seulement d'un champ. Par exemple, nous pouvons configurer la règle « Chaque fois que la directive @translate est trouvée dans la requête, exécuter la directive @cache(time: 3600) sur ce champ ».
L'ajout de directives IFTTT à la requête est un processus récursif : il déclenchera un nouvel événement à traiter par les règles IFTTT, attachant potentiellement d'autres directives à la requête, et ainsi de suite.
Par exemple, la règle « Chaque fois que la directive @cache est trouvée, exécuter la directive @log » enregistrerait une entrée sur l'exécution du champ, puis déclencherait un nouvel événement concernant cette directive nouvellement ajoutée.
Configuration via du code PHP
Le type User possède les champs roles et capabilities, qui peuvent être considérés comme des informations sensibles, et ne devraient donc pas être accessibles par n'importe quel utilisateur.
Nous pouvons donc attacher la directive @validateDoesLoggedInUserHaveAnyRole à ces deux champs, configurée pour valider que seul un utilisateur ayant un rôle donné (configuré via une variable d'environnement) peut y accéder. La configuration est fournie via un CompilerPass :
$accessControlManagerDefinition = $containerBuilderWrapper->getDefinition(AccessControlManagerInterface::class);
if ($roles = Environment::anyRoleLoggedInUserMustHaveToAccessRolesFields()) {
$accessControlManagerDefinition->addMethodCall(
'addEntriesForFields',
[
UserRolesAccessControlGroups::ROLES,
[
[RootObjectTypeResolver::class, 'roles', $roles],
[UserObjectTypeResolver::class, 'roles', $roles],
[RootObjectTypeResolver::class, 'capabilities', $roles],
[UserObjectTypeResolver::class, 'capabilities', $roles],
]
]
);
}
if ($capabilities = Environment::anyCapabilityLoggedInUserMustHaveToAccessRolesFields()) {
$accessControlManagerDefinition->addMethodCall(
'addEntriesForFields',
[
UserCapabilitiesAccessControlGroups::CAPABILITIES,
[
[RootObjectTypeResolver::class, 'roles', $capabilities],
[UserObjectTypeResolver::class, 'roles', $capabilities],
[RootObjectTypeResolver::class, 'capabilities', $capabilities],
[UserObjectTypeResolver::class, 'capabilities', $capabilities],
]
]
);
}Lors de l'exécution de la requête, les utilisateurs non connectés et les utilisateurs sans les rôles requis ne pourront pas accéder à ces champs.