IFTTT durch Direktiven
Gato GraphQL bietet die Möglichkeit, IFTTT-Strategien (If This Then That, „wenn dies, dann das") durch Direktiven umzusetzen. Diese Direktiven werden dynamisch zur Query hinzugefügt, sobald ein bestimmtes Feld oder eine bestimmte Direktive in der Query vorhanden ist.
Generell sind IFTTT Regeln, die Aktionen auslösen, sobald ein bestimmtes Ereignis eintritt. In unserem Fall sind die Ereignis/Aktion-Paare folgende:
- Wenn „Feld X in der Query gefunden" dann „füge Direktive Y an Feld X an"
- Wenn „Direktive Z in der Query gefunden" dann „führe Direktive Y vor/nach Direktive Z aus"
Das dynamische Hinzufügen von IFTTT-Direktiven zum Schema ist ein rekursiver Prozess: Eine solche Direktive kann ihrerseits einen eigenen Satz von IFTTT-Direktiven konfiguriert haben, die ebenfalls der Direktiven-Kette hinzugefügt werden.
Wo wird es verwendet
Im Hintergrund nutzen Clients in Gato GraphQL diesen Mechanismus, um das GraphQL-Schema zu konfigurieren.
Zum Beispiel ermöglicht Access Control die Auswahl der Zugriffssteuerungsregeln, die auf Operationen, Felder und Direktiven angewendet werden sollen. Durch IFTTT werden diese Regeln auf die entsprechenden Elemente des GraphQL-Schemas angewendet.

Im Allgemeinen gibt es folgende Anwendungsfälle:
Cache-Control-max-age feldweise festlegen
Eine @CacheControl-Direktive an alle Felder anhängen und dabei den Wert des maxAge-Parameters anpassen: 1 Jahr für das Feld url des Post-Typs und 1 Stunde für das Feld title.
Zugriffssteuerung einrichten
Eine @validateDoesLoggedInUserHaveAnyRole-Direktive an das Feld email des Typs User anhängen, sodass nur Administratoren die E-Mail-Adresse des Benutzers abfragen können.
Zugriffssteuerung mit Cache-Control synchronisieren
Durch das Verketten von Direktiven können wir sicherstellen, dass die Antwort nicht gecacht wird, sobald geprüft wird, ob ein Benutzer auf ein Feld/eine Direktive zugreifen darf. Zum Beispiel:
- Direktive
@validateIsUserLoggedInan das Feldmeanhängen - Direktive
@CacheControlmit dem Wert0für das ArgumentmaxAgean die Direktive@validateIsUserLoggedInanhängen.
Sicherheit erhöhen
Eine @validateIsUserLoggedIn-Direktive an die Direktive @translate anhängen, um zu verhindern, dass böswillige Akteure queries gegen den GraphQL-Dienst ausführen, die den Server zum Absturz bringen und die Kosten in die Höhe treiben könnten (in diesem Fall basiert @translate auf Google Translate und erhebt eine Gebühr für die Nutzung dieses Dienstes)
Wie es funktioniert
Wie fügen wir Direktiven über IFTTT zum Schema hinzu? Nehmen wir zum Beispiel an, wir möchten eine benutzerdefinierte Direktive @authorize(role: String!) erstellen, um zu prüfen, ob der Benutzer, der das Feld myPosts ausführt, die erwartete Rolle author hat, oder andernfalls einen Fehler anzuzeigen.
Hätten wir das Schema mit der SDL erstellt, würde es so aussehen:
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}Die IFTTT-Regel definiert dieselbe Absicht, die die obige SDL deklariert: Immer wenn das Feld myPosts angefordert wird, führe die Direktive @authorize(role: "author") auf diesem Feld aus. Wenn das Feld myPosts also in der Query gefunden wird, hängt die Engine automatisch @authorize(role: 'author') an dieses Feld in der ausführbaren Query an.
IFTTT-Regeln können auch ausgelöst werden, wenn eine Direktive gefunden wird, nicht nur ein Feld. Zum Beispiel können wir die Regel einrichten: „Immer wenn die Direktive @translate in der Query gefunden wird, führe die Direktive @cache(time: 3600) auf diesem Feld aus."
Das Hinzufügen von IFTTT-Direktiven zur Query ist ein rekursiver Prozess: Es löst ein neues Ereignis aus, das durch die IFTTT-Regeln verarbeitet wird, und hängt dabei potenziell weitere Direktiven an die Query an, und so weiter.
Zum Beispiel würde die Regel „Immer wenn die Direktive @cache gefunden wird, führe die Direktive @log aus" einen Eintrag über die Ausführung des Felds protokollieren und dann ein neues Ereignis bezüglich dieser neu hinzugefügten Direktive auslösen.
Einrichtung über PHP-Code
Der Typ User hat die Felder roles und capabilities, die als sensible Informationen angesehen werden können und daher nicht für beliebige Benutzer zugänglich sein sollten.
Wir können daher die Direktive @validateDoesLoggedInUserHaveAnyRole an diese beiden Felder anhängen und sie so konfigurieren, dass nur ein Benutzer mit einer bestimmten Rolle (konfiguriert über eine Umgebungsvariable) darauf zugreifen kann. Die Konfiguration wird über einen CompilerPass bereitgestellt:
$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],
]
]
);
}Bei der Ausführung der Query haben nicht angemeldete Benutzer und Benutzer ohne die erforderlichen Rollen keinen Zugriff auf diese Felder.