Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Arise Programming Interface (API)

ARISE Logo

L’API d’ARISE (Administrateurs du Réseau Informatique et des Services aux Étudiants) offre une grande souplesse et permet de définir précisément les données souhaitées. Elle est ouverte aux arisers mais également aux étudiants et associations.

Aperçu

Voici quelques liens rapides pour vous aider à vous familiariser avec l’API :

À propos de GraphQL

Le langage de requête de données GraphQL est :

  • Une spécification. La spécification détermine la validité du schéma sur le serveur API. Le schéma détermine la validité des requêtes du client.
  • Fortement typé. Le schéma définit le système de types d’une API et toutes les relations entre les objets.
  • Introspectif. Un client peut interroger le schéma pour obtenir des détails sur celui-ci.
  • Hiérarchique. La structure d’un appel GraphQL reflète la structure des données JSON qu’il renvoie. Les champs imbriqués vous permettent de rechercher et de recevoir uniquement les données que vous spécifiez en un seul aller-retour.
  • Une couche applicative. GraphQL n’est ni un modèle de stockage ni un langage de requête de base de données. Le graphe fait référence aux structures de graphe définies dans le schéma, où les nœuds définissent des objets et les arêtes définissent les relations entre les objets. L’API parcourt et renvoie les données de l’application en fonction des définitions du schéma, indépendamment de la manière dont les données sont stockées.

Pourquoi ARISE utilise GraphQL

ARISE a choisi GraphQL car cette technologie offre une flexibilité nettement supérieure pour nos utilisateurs et applications internes. La possibilité de définir précisément les données souhaitées (et uniquement celles-ci) constitue un avantage considérable par rapport aux endpoint API REST traditionnels. GraphQL permet de remplacer plusieurs requêtes REST par un seul appel pour récupérer les données spécifiées.

Pour en savoir plus sur les raisons qui ont poussé ARISE à miser sur GraphQL, consultez le README du projet.

Cas d’usage

  • Modifier ses données personnelles [Work in Progress]
  • Récupérer les informations d’un utilisateur via OAuth
  • Obtenir une donnée en particulier sur l’ensemble des utilisateurs
    • Nécessite une autorisation préalable d’ARISE

Guides

Ces guides présentent comment utiliser l’API GraphQL. Bien que personnalisé pour ARISE, ces guides restent génériques et présentent surtout le standard GraphQL.

Introduction à GraphQL

Terminologie et concepts utiles pour utiliser l’API GraphQL d’ARISE.

Schéma

Un schéma définit le système de types d’une API GraphQL. Il décrit l’ensemble complet des données possibles (objets, champs, relations, etc.) auxquelles un client peut accéder. Les requêtes émises par le client sont validées et exécutées par rapport au schéma. Un client peut obtenir des informations sur le schéma par introspection. Un schéma réside sur le serveur de l’API GraphQL.

Champ

Un champ est une unité de données que vous pouvez extraire d’un objet. Comme l’indique la documentation officielle de GraphQL : “Le langage de requête GraphQL consiste essentiellement à sélectionner des champs sur des objets.”

La spécification officielle précise également à propos des champs :

Toutes les opérations GraphQL doivent spécifier leurs sélections jusqu’au niveau des champs qui renvoient des valeurs scalaires afin de garantir une réponse sans ambiguïté.

Cela signifie que si vous essayez de renvoyer un champ qui n’est pas un scalaire, la validation du schéma générera une erreur. Vous devez ajouter des sous-champs imbriqués jusqu’à ce que tous les champs renvoient des scalaires.

Note

Un scalaire désigne un type “simple” comme un string, un booléen ou un entier. Il n’est pas possible de demander à retourner implicitement tous les champs d’un objet. Il faut lister explicitement tous les champs scalaires.

Argument

Un argument est un ensemble de paires clé-valeur associées à un champ spécifique. Certains champs nécessitent un argument. Les mutations nécessitent un objet d’entrée en tant qu’argument.

Héritage

Un schéma GraphQL peut utiliser le terme implements pour définir comment un objet hérite d’une interface.

Voici un exemple fictif de schéma définissant l’interface X et l’objet Y :

interface X {
  unChamp: String!
  unAutreChamp: String!
}

type Y implements X {
  unChamp: String!
  unAutreChamp: String!
  nouveauChamp: String!
}

Cela signifie que l’objet Y nécessite les mêmes champs/arguments/types de retour que l’interface X, tout en ajoutant de nouveaux champs spécifiques à l’objet Y. (Le signe ! indique que le champ est obligatoire.)

Connexion (Connection)

Les connexions vous permettent d’interroger des objets associés dans le cadre d’un même appel. Grâce aux connexions, vous pouvez utiliser un seul appel GraphQL là où vous auriez dû effectuer plusieurs appels vers une API REST.

Il est utile de le visualiser comme un graphe : des points reliés par des lignes. Les points sont des nœuds, les lignes sont des arêtes. Une connexion définit une relation entre des nœuds.

Arête (Edge)

Les arêtes représentent les connexions entre les nœuds. Lorsque vous interrogez une connexion, vous parcourez ses arêtes pour accéder à ses nœuds. Chaque champ edges comporte un champ node et un champ cursor. Les curseurs sont utilisés pour la pagination. Pour plus d’informations, consultez la page sur la Pagination.

Nœud (Node)

Nœud est un terme générique désignant un objet. Vous pouvez rechercher un nœud directement ou accéder à des nœuds associés via une connexion. Si vous spécifiez un node qui ne renvoie pas de scalaire, vous devez inclure des sous-champs jusqu’à ce que tous les champs renvoient des scalaires.

Introspection

GraphQL est introspectif. Cela signifie que vous pouvez interroger un schéma GraphQL pour obtenir des informations le concernant.

  • Interrogez __schema pour répertorier tous les types définis dans le schéma et obtenir des détails sur chacun d’entre eux :
query {
  __schema {
    types {
      name
      kind
      description
      fields {
        name
      }
    }
  }
}
  • Interrogez __type pour obtenir des détails sur n’importe quel type :
query {
  __type(name: "User") {
    name
    kind
    description
    fields {
      name
    }
  }
}

Effectuer des appels avec GraphQL

Comment vous authentifier auprès de l’API GraphQL, comment créer et exécuter des requêtes et des mutations.

L’endpoint GraphQL

Une API REST comporte de nombreux points de terminaison. Avec l’API GraphQL, le point de terminaison reste le même, quelle que soit l’opération effectuée. Pour la version en production, cet endpoint est :

https://api.iiens.net/graphql/v1

Important

Le standard GraphQL est pensé pour ne pas avoir de changement cassant. Mais si un jour une nouvelle version majeure de l’API devait être publiée, le suffixe /v1 garanti que votre application ne sera pas impactée.

Communication avec GraphQL

Les opérations GraphQL étant constituées de JSON sur plusieurs lignes, ARISE recommande d’utiliser des clients GraphQL pour effectuer des appels GraphQL. Vous pouvez également utiliser curl ou toute autre bibliothèque prenant en charge le protocole HTTP.

En REST, les verbes HTTP déterminent l’opération effectuée. En GraphQL, vous fournissez un corps encodé en JSON, que vous effectuiez une requête ou une mutation, le verbe HTTP est donc POST.

Pour interroger GraphQL dans une commande curl, effectuez une requête POST avec des données JSON. Les données doivent contenir une chaîne appelée query :

curl -H "Authorization: bearer TOKEN" -X POST --data " \
  { \
    \"query\": \"query { profile { id }}\" \
  } \
" https://api.iiens.net/graphql/v1

Note

La valeur de la chaîne "query" doit échapper les retour à la ligne, sinon le schéma ne l’analysera pas correctement. Pour le corps de la requête POST, utilisez des doubles guillemets à l’extérieur et des doubles guillemets échappés à l’intérieur.

Requêtes et mutations

Les deux types d’opérations autorisées dans l’API GraphQL d’ARISE sont les requêtes (queries) et les mutations. Si l’on compare GraphQL à REST, les requêtes fonctionnent comme des requêtes GET, tandis que les mutations fonctionnent comme des requêtes POST/PATCH/DELETE. Le nom de la mutation détermine la modification qui est exécutée.

Les requêtes et les mutations ont une structure similaire, mais présentent quelques différences importantes.

Requêtes (queries)

Les requêtes GraphQL renvoient uniquement les données que vous spécifiez. Pour formuler une requête, vous devez spécifier des champs à l’intérieur d’autres champs (également appelés sous-champs imbriqués) jusqu’à ce que vous n’obteniez plus que des valeurs scalaires.

Les requêtes sont structurées comme suit :

query {
  OBJET-JSON-À-RENVOYER
}

Pour un exemple concret, consultez la section Exemple de requête.

Mutations

Pour créer une mutation, vous devez spécifier trois éléments :

  1. Nom de la mutation. Le type de modification que vous souhaitez effectuer.
  2. Objet d’entrée. Les données que vous souhaitez envoyer au serveur, composées de champs d’entrée. Transmettez-le en tant qu’argument au nom de la mutation.
  3. Objet de charge utile (payload). Les données que vous souhaitez recevoir du serveur, composées de champs de retour. Transmettez-le en tant que corps du nom de la mutation.

Les mutations sont structurées comme suit :

mutation {
  MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) {
    MUTATION-NAME-PAYLOAD
  }
}

Dans cet exemple, l’objet d’entrée est MutationNameInput et l’objet de charge utile est MutationNamePayload.

Pour un exemple concret, consultez Exemple de mutation.

Utilisation des variables

Les variables permettent de rendre les requêtes plus dynamiques et plus puissantes, et elles peuvent réduire la complexité lors du passage d’objets d’entrée de mutation.

Voici un exemple de requête avec une seule variable :

query Profile($numberOfGroups: Int!) {
  profile {
    name
    groups(last: $numberOfGroups) {
      nodes {
        group {
          name
        }
      }
    }
  }
}
{
  "numberOfGroups": 10
}

L’utilisation des variables se déroule en trois étapes :

  1. Définissez la variable en dehors de l’opération dans un objet variables :
{
  "numberOfGroups": 10
}

L’objet doit être au format JSON valide. Cet exemple montre un type de variable Int simple, mais il est possible de définir des types de variables plus complexes, tels que des objets d’entrée. Vous pouvez également définir plusieurs variables ici.

  1. Transmettez la variable à l’opération en tant qu’argument :
query Profile($numberOfGroups: Int!) {

L’argument est une paire clé-valeur, où la clé est le nom commençant par $ (par exemple, $numberOfGroups), et la valeur est le type (par exemple, Int). Ajoutez un ! pour indiquer si le type est obligatoire. Si vous avez défini plusieurs variables, incluez-les ici en tant qu’arguments multiples.

  1. Utilisez la variable dans l’opération :
groups(last: $numberOfGroups) {

Dans cet exemple, nous remplaçons le nombre de référentiels à récupérer par la variable. Nous spécifions un type à l’étape 2 car GraphQL impose un typage fort.

Ce processus rend l’argument de la requête dynamique. Nous pouvons désormais simplement modifier la valeur dans l’objet variables tout en conservant le reste de la requête inchangé.

L’utilisation de variables comme arguments vous permet de mettre à jour dynamiquement les valeurs dans l’objet variables sans modifier la requête.

Exemple de requête

Examinons une requête plus complexe afin de replacer ces informations dans leur contexte.

La requête suivante interroge le groupe arise, son nom et son site web, trouve les 20 membres les plus récents et renvoie nom complet ainsi que l’année (1A, 2A, etc.) de chaque membre :

query {
  group(group: {id: "arise"}) {
    name
    website
    members(last: 20, filter: {role: {is: ADMIN}}) {
      nodes {
        user {
          name
          schoolYear
        }
        role {
          value
        }
      }
    }
  }
}

Examinons le code ligne par ligne :

  • query {

    Comme nous souhaitons lire des données sur le serveur sans les modifier, l’opération query est l’opération racine. (Si vous ne spécifiez pas d’opération, query est également l’opération par défaut.)

  • group(group: {id: "arise"}) {

    Pour débuter la requête, nous voulons trouver un objet group. La validation du schéma indique que cet objet nécessite un argument group.

  • name website

    Nous récupérons les champs name et website de l’objet Group.

  • members(last: 20, filter: {role: {is: ADMIN}}) {

    Pour prendre en compte tous les membres du groupe, nous appelons l’objet members. (Nous pourrions interroger un seul membre d’un groupe, mais cela nous obligerait à connaître l’identifiant du membre que nous voulons renvoyer et à le fournir en tant qu’argument.)

    Quelques détails concernant l’objet members :

    • La documentation indique que cet objet est de type GroupMembershipConnection.
    • La validation du schéma indique que cet objet nécessite un nombre de résultats (first ou last) en tant qu’argument ; nous fournissons donc 20.
    • La documentation indique également que cet objet accepte un argument filter, qui est de type UserMemberFilter. Pour trouver uniquement les admins, nous attribuons à la clé role la valeur {is: ADMIN}.
  • nodes {

    Nous savons que members est une connexion car il est de type GroupMembershipConnection. Pour récupérer des données sur des membres individuels, nous devons accéder au nœud via nodes.

    La documentation sur GroupMembershipConnection indique que le nœud à l’extrémité de cette connexion est un objet GroupMembership.

  • Nous savons désormais que nous récupérons un objet GroupMembership, nous pouvons consulter la documentation et spécifier les champs que nous souhaitons renvoyer :

    user {
      name
      schoolYear
    }
    role {
      value
    }
    

    Ici, nous spécifions les champs name, schoolYear sur l’objet User et value sur l’object CurrentGroupMembershipInheritance.

    Pourquoi renvoyer la value de role ? Les rôles étant hérité, un OWNER est ADMIN au sens du filtre dans l’objet members. Pour ne récupérer que les admins, il aurait fallu spécifier {strict: true}.

Exemple de mutation

Warning

Work In Progress

Les modification ne sont pas persistante sur l’API. Il faut passer par le LDAP.

Annexe : HTTP

Le serveur GraphQL prend en charge la méthode HTTP POST pour les opérations de requête et de mutation, et accepte également la méthode GET pour les opérations de type query.

Requête POST et corps de la requête

Une requête POST GraphQL standard doit définir application/json comme en-tête Content-Type et inclure un corps encodé en JSON sous la forme suivante :

{
  "query": "...",
  "operationName": "...",
  "variables": { "myVariable": "someValue", ... },
  "extensions": { "myExtension": "someValue", ... }
}

Le paramètre query est obligatoire et contient le texte source d’un document GraphQL. Notez que le terme query est ici trompeur : le document peut contenir n’importe quelle opération GraphQL valide (mutation par exemple).

Les paramètres operationName, variables et extensions sont facultatifs. Le paramètre operationName n’est obligatoire que si le document contient plusieurs opérations query.

Notez que si l’en-tête Content-type est absent de la requête du client, le serveur répondra avec un code d’état 4xx. Comme pour l’en-tête Accept, l’encodage utf-8 est supposé pour un corps de requête de type application/json lorsque cette information n’est pas explicitement fournie.

Requête GET et paramètres

Note

Bien que la spécification GraphQL rende optionnel le support de la méthode GET, l’API d’ARISE la prend en charge.

Lors de la réception d’une requête HTTP GET, le document GraphQL doit être fourni dans le paramètre query de la chaîne de requête (query string). Par exemple, si nous souhaitons exécuter la requête GraphQL suivante :

{
  buildInfo {
    version
  }
}

Cette requête peut être envoyée via une requête HTTP GET comme suit :

https://api.iiens.net/graphql/v1?query={buildInfo{version}}

Warning

Certains clients comme cURL ne pourront pas exécuter la requête sans modification préalable. Il faut encoder la chaîne de caractère pour qu’elle soit “URL safe”.

curl "https://api.iiens.net/graphql/v1?query=%7BbuildInfo%7Bversion%7D%7D"

Les variables de requête peuvent être envoyées sous forme de chaîne encodée en JSON dans un paramètre de requête supplémentaire appelé variables. Si la requête contient plusieurs opérations nommées, un paramètre de requête operationName peut être utilisé pour déterminer laquelle doit être exécutée.

Choix d’une méthode HTTP

Lors du choix d’une méthode HTTP pour une requête GraphQL, plusieurs points doivent être pris en compte.

La méthode HTTP GET ne peut être utilisée que pour les opérations de requête ; ainsi, si un client demande l’exécution d’une opération de mutation, il doit utiliser la méthode POST à la place.

Les clients sont encouragés à exploiter la méthode GET pour faciliter la mise en cache HTTP. Cependant, comme les chaînes de caractères des documents GraphQL peuvent être assez longues pour les opérations complexes, les paramètres de requête peuvent dépasser les limites imposées par les navigateurs et les CDN sur la longueur des URL.

Explorateur GraphQL

L’explorateur GraphQL utilise le client GraphiQL. Pour commencer, rendez vous sur explore.api.iiens.net.

Note

D’autres client peuvent être utilisés pour réaliser vos requêtes, comme la commande curl par exemple. L’avantage de l’explorateur est l’authentification automatique via AriseID Connect.

Important

L’explorateur vous authentifie via OAuth avec le scope api. Le jeton possède les mêmes permissions que votre utilisateur, sans restriction. Par exemple, un admin ARISE pourra accéder aux champs sensibles des utilisateurs.

Un client non authentifié est accessible à l’URL https://api.iiens.net/graphql.

Tip

Des explications plus poussées sur la syntaxe GraphQL sont retrouvables sur la page Introduction à GraphQL

Une fois connecté avec AriseID Connect, vous devriez vous retrouver sur une page similaire à la capture ci-dessus. À gauche, le contenu de la requête. Cela ressemble à du JSON et c’est normal car c’est la forme des données qui nous seront retournées. En bas l’onglet Variables permet d’ajouter des paramètres à notre requête pour la rendre réutilisable. L’onglet Headers permet entre autres d’ajouter les entêtes d’authentification à notre requête, ce qui n’est pas nécessaire sur l’explorateur comme les requêtes sont automatiquement authentifiées avec l’utilisateur courant.

La requête par défaut récupère via profile (profil de l’utilisateur courant) des informations précises (id, nickname, schoolYear, isFipa).

Le bouton rose ▶ (ou Ctrl+Enter) exécute la requête :

On reçoit alors la réponse en JSON à droite, de la forme demandée.

L’avantage majeur de GraphQL est son système de schéma, qui décrit l’entièreté des requêtes possibles ainsi que leur attributs. Le tout documenté avec des commentaires. En cliquant sur le libre à gauche vous pourrez parcourir le schéma.

D’autres requêtes sont possibles, par exemple sur groups en récupérant les données de façon paginée.

Utilisation des clients GraphQL

Vous pouvez effectuer des requêtes sur des données ARISE réelles à l’aide de divers clients et bibliothèques GraphQL.

Utilisation d’IDE clients GraphQL

Il existe de nombreux IDE clients GraphQL open source que vous pouvez utiliser pour accéder à l’API GraphQL d’ARISE.

Consultez la section Effectuer des appels avec GraphQL pour obtenir des informations détaillées sur les méthodes HTTP, l’authentification et la structure des appels GraphQL.

Commencez par choisir un client. Parmi les options courantes, on trouve GraphiQL, Insomnia et Altair (version bureau/web/extension). Vous pouvez consulter la liste complète des clients dans le répertoire des outils de l’organisation GraphQL.

Les instructions génériques suivantes fonctionnent avec la plupart des clients GraphQL :

  1. Pointez le client vers l’endpoint GraphQL : https://api.iiens.net/graphql/v1.
  2. Ajoutez un en-tête Authorization : Authorization: Bearer TOKEN (remplacez TOKEN par votre jeton d’accès personnel ARISE. Pour plus d’informations, consultez la section Authentification avec un jeton d’accès personnel OAuth).
  3. Définissez la méthode de requête sur POST ou, si c’est possible, utilisez le mode GraphQL fourni par le client.
  4. Saisissez votre requête ou votre mutation dans l’éditeur et, si nécessaire, fournissez des variables dans le panneau “Variables”. Exemple :
query {
  profile {
    id
  } 
}
  1. Si votre client nécessite d’un schéma pour le rendu de la documentation ou la saisie semi-automatique, récupérez-le via une requête d’introspection GraphQL. De nombreux clients peuvent le faire automatiquement à partir du panneau “Docs”. Requête d’introspection minimale :
query IntrospectionQuery {
  __schema {
    types {
      name
    }
  }
}
  1. Exécutez la requête et examinez la réponse JSON. La requête de l’exemple devrait renvoyer l’identifiant associé au jeton d’accès personnel ARISE avec lequel vous vous êtes authentifié.

Utilisez l’interface utilisateur du client pour explorer la documentation, exécuter des requêtes et enregistrer des requêtes selon vos besoins.

Recommendations de clients par langage

Work In Progress

Utilisation de la pagination dans l’API GraphQL

Découvrez comment parcourir des ensembles de données à l’aide de la pagination par curseur avec l’API GraphQL.

À propos de la pagination

L’API GraphQL d’ARISE limite le nombre d’éléments que vous pouvez récupérer en une seule requête afin de se prémunir contre les requêtes excessives ou abusives adressées à nos serveurs. Lorsque vous utilisez l’API GraphQL, vous devez fournir un argument first ou last pour chaque requête paginée. La valeur de ces arguments doit être comprise entre 1 et 100. L’API GraphQL renverra le nombre d’éléments spécifié par l’argument first ou last.

Si les données auxquelles vous accédez comportent plus de connexions que le nombre d’éléments spécifié par l’argument first ou last, la réponse est divisée en “pages” plus petites de la taille spécifiée. Ces pages peuvent être récupérées une par une jusqu’à ce que l’ensemble des données ait été récupéré. Chaque page contient le nombre d’éléments spécifié par l’argument first ou last, sauf s’il s’agit de la dernière page, qui peut contenir un nombre d’éléments inférieur.

Ce guide explique comment demander des pages supplémentaires de résultats pour les réponses paginées, comment modifier le nombre de résultats renvoyés sur chaque page et comment écrire un script pour récupérer plusieurs pages de résultats.

Tip

Le standard utilisé est le GraphQL Cursor Connections, créé par Relay. Bon nombre de clients GraphQL comprennent cette spécification et proposent des méthodes simplifiées pour itérer sur toutes les pages d’une requête.

Demander un curseur dans votre requête

Lorsque vous utilisez l’API GraphQL, vous utilisez des curseurs pour parcourir un ensemble de données paginées. Le curseur représente une position spécifique dans l’ensemble de données. Vous pouvez obtenir le premier et le dernier curseur d’une page en interrogeant l’objet pageInfo. Par exemple :

query {
  groups(first: 100, after: null) {
    nodes {
      id
      name
      createdAt
    }
    pageInfo {
      endCursor
      startCursor
      hasNextPage
      hasPreviousPage
    }
  }
}

Dans cet exemple, pageInfo.startCursor indique le curseur du premier élément de la page. pageInfo.endCursor indique le curseur du dernier élément de la page. pageInfo.hasNextPage et pageInfo.hasPreviousPage indiquent s’il existe une page avant et après la page renvoyée.

Changer le nombre d’éléments par page

Les arguments first et last déterminent le nombre d’éléments renvoyés. Le nombre maximal d’éléments que vous pouvez récupérer à l’aide de ces arguments est de 100. Si votre requête porte sur un volume important de données, vous devrez peut-être limiter le nombre d’éléments à moins de 100 afin d’éviter d’atteindre une limite de débit ou une limite de nœuds. Pour plus d’informations, consultez la section Limites pour l’API GraphQL.

Parcourir l’ensemble de données à l’aide de la pagination

Une fois que vous avez obtenu un curseur à la suite d’une requête, vous pouvez l’utiliser pour demander la page suivante de résultats. Pour ce faire, utilisez l’argument after ou before ainsi que le curseur.

Par exemple, en supposant que la valeur pageInfo.endCursor de l’exemple précédent était InVybjp1dWlkOjAxOWNmMTJhLTRmMTAtNzExZi04MDRhLWY4ODg3Y2U3ZWRlMSI, vous pouvez utiliser cette requête pour demander la page suivante de résultats :

query {
  groups(first: 1, after: "InVybjp1dWlkOjAxOWNmMTJhLTRmMTAtNzExZi04MDRhLWY4ODg3Y2U3ZWRlMSI") {
    nodes {
      id
      name
      createdAt
    }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}

Vous pouvez continuer à envoyer des requêtes en utilisant la nouvelle valeur de pageInfo.endCursor renvoyée dans la réponse jusqu’à ce qu’il n’y ait plus de pages à parcourir, ce qui est indiqué par la valeur false renvoyée par pageInfo.hasNextPage.

Important

Le contenu des curseurs doit être considéré comme opaque, il peut changer à tout moment. Il n’est donc pas recommandé de stocker les curseurs sur une longue période. Voir la spécification

Si vous avez spécifié l’argument last au lieu de first, la dernière page de résultats sera renvoyée en premier. Dans ce cas, vous utiliserez la valeur pageInfo.startCursor et l’argument before pour obtenir la page de résultats précédente. Lorsque pageInfo.hasPreviousPage renvoie false, vous avez atteint la dernière page. Par exemple :

query {
  groups(last: 1, after: "InVybjp1dWlkOjAxOWNmMTJhLTRmMTAtNzExZi04MDRhLWY4ODg3Y2U3ZWRlMSI") {
    nodes {
      id
      name
      createdAt
    }
    pageInfo {
      startCursor
      hasPreviousPage
    }
  }
}

Référence

Cette section se concentre sur les spécificités de l’API.

Authentification

Vous pouvez vous authentifier auprès de l’API GraphQL à l’aide d’un jeton d’accès personnel OAuth, d’une application AriseID Connect ou d’un compte de service.

Authentification avec une application OAuth

Warning

AriseID Connect est encore en beta et cette section pourrait être amenée à changer.

Pour vous authentifier avec un jeton OAuth provenant d’une application AriseID Connect, vous devez d’abord autoriser votre application OAuth à l’aide d’un “Authorization Code Flow” ou d’un “Device Authorization Flow”. Vous pouvez ensuite utiliser le jeton d’accès (access token) que vous avez reçu pour accéder à l’API.

Les jeton d’accès (access token) s’utilisent avec une authentification bearer.

Authorization: Bearer <TOKEN>

Exemple avec cURL :

curl 'https://api.iiens.net/graphql/v1' \
  -X POST \
  -H 'Authorization: Bearer ory_at_JGhESDjKfHMQ8Wcy0cC3.hIQxGmX37ydn8WmKAnlD3U' \
  -H 'content-type: application/json' \
  --data '{
    "query": "{ profile { id } }"
  }'

Authentification avec un jeton d’accès personnel OAuth

Le Client Credentials Flow est utilisé lorsqu’une application doit accéder à ses propres ressources, plutôt qu’à celles d’un utilisateur. Dans ce flux, l’application envoie son identifiant client et sa clé secrète client au serveur d’autorisation, et reçoit en retour un jeton d’accès qui peut être utilisé pour accéder aux ressources protégées.

Warning

AriseID Connect est encore en beta et cette section pourrait être amenée à changer.

Pour vous authentifier avec un jeton d’accès personnel (personal access token ou “PAT”), suivez les étapes décrites dans la documentation AriseID Connect (TODO). Les données que vous demandez détermineront le scope ou les autorisations dont vous aurez besoin.

Par exemple, sélectionnez l’autorisation profile pour lire les données nécessaire pour afficher le profil de l’utilisateur.

Si votre jeton ne dispose pas du scope ou des autorisations requis pour accéder à une ressource, l’API renverra un message d’erreur indiquant que la requête ou le champ ne sont pas accessibles.

Authentification basique (client_secret_basic)

Cette méthode est appropriée pour les clients enregistrés en Token Endpoint Auth Method = client_secret_basic.

const clientID = "the-client-id"
const clientSecret = "the-secret-id"
const basicAuth = base64_encode(url_encode(clientID) + ":" + url_encode(clientSecret))

const requestOptions = {
  method: "POST",
  headers: {
    Authorization: "Basic " + basicAuth,
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: "grant_type=client_credentials&scope=read",
}

fetch("https://oidc.iiens.net/oauth2/token", requestOptions)
  .then((response) => response.json())
  .then((data) => console.log(data))

Exemple d’output :

ory_at_JGhESDjKfHMQ8Wcy0cC3.hIQxGmX37ydn8WmKAnlD3U

Authentification dans le body (client_secret_post)

Cette méthode est appropriée pour les clients enregistrés en Token Endpoint Auth Method = client_secret_post.

const clientID = "the-client-id"
const clientSecret = "the-secret-id"

const qs = new URLSearchParams()
qs.set("grant_type", "client_credentials")
qs.set("client_id", clientID)
qs.set("client_secret", clientSecret)
qs.set("scope", read)

const requestOptions = {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: qs.toString(),
}

fetch("https://oidc.iiens.net/oauth2/token", requestOptions)
  .then((response) => response.json())
  .then((data) => console.log(data))

Exemple d’output :

ory_at_JGhESDjKfHMQ8Wcy0cC3.hIQxGmX37ydn8WmKAnlD3U

Requête avec le PAT

Une fois votre Personal Access Token obtenu, vous pouvez effectuer des requêtes à l’API.

Les PAT s’utilisent avec une authentification bearer.

Authorization: Bearer <TOKEN>

Exemple avec cURL :

curl 'https://api.iiens.net/graphql/v1' \
  -X POST \
  -H 'Authorization: Bearer ory_at_JGhESDjKfHMQ8Wcy0cC3.hIQxGmX37ydn8WmKAnlD3U' \
  -H 'content-type: application/json' \
  --data '{
    "query": "{ profile { id } }"
  }'

Authentification avec un compte de service

Les comptes de service ou “Service Account” permettent un accès plus large aux données, par exemple à l’ensemble des utilisateurs. Ces comptes sont utilisés en interne à ARISE mais peuvent aussi être délivrés aux utilisateurs ou associations qui en font la demande.

Caution

L’utilisation de ces jetons expose potentiellement des données sensibles. En utilisant un compte de service, vous vous engagez à respecter les normes RGPD et à protéger votre site par authentification.

Les jetons (tokens) de compte de service s’utilisent avec une authentification basique.

Authorization: Basic <TOKEN>

Exemple avec cURL :

curl 'https://api.iiens.net/graphql/v1' \
  -X POST \
  -H 'Authorization: Basic Y29tcHRlMTpsbCRWQmk2NTMjMExZeEEqaGFGJE13UDg=' \
  -H 'content-type: application/json' \
  --data '{
    "query": "{ currentServiceAccount { id } }"
  }'

Permissions

Par sécurité, aucune donnée n’est accessible sans être authentifié.

Données utilisateur

Liste des utilisateurs

En étant authentifié, les données accessibles sur les utilisateurs contiennent le nécessaire pour afficher un profil, sans données sensibles.

Important

Sont définis comme sensible :

  • Le genre
  • La photo de trombinoscope
  • L’email
  • Le numéro de téléphone
  • Les réseaux sociaux

Profil personnel

Un utilisateur authentifié pourra accéder à ses propres données, y compris les données sensibles.

OAuth

Un utilisateur authentifié via OAuth ne verra aucune données utilisateur par défaut.

Warning

L’accès aux données des autres utilisateurs n’est pas possible avec OAuth.

L’accès aux données propres de l’utilisateur se fait via le système de scope, pour autoriser l’application à accéder à un sous ensemble de valeurs.

Liste non exhaustive de chaines valides :

  • profile
  • email
  • groups

Erreurs

Qu’est-ce qu’une erreur GraphQL ?

Lorsqu’un problème survient en GraphQL, le système ne s’arrête pas toujours complètement. Au lieu de cela, il renvoie :

  • Certaines données (si possible) dans data
  • Une section d’erreur expliquant ce qui n’a pas fonctionné dans errors

Même si le statut HTTP est 200 OK, votre requête peut tout de même contenir des erreurs.

Les erreurs GraphQL comprennent :

  • message : une explication claire de ce qui a échoué.
  • path : la partie de la requête qui a échoué.
  • locations : la ligne et la colonne où le problème s’est produit.
  • extensions : un objet structuré exploitable par une machine. Il contient toujours :
    • code : un code décrivant le type d’erreur. La liste exhaustive est disponible plus bas.
    • request_id : l’ID de la requête à fournir à ARISE lorsque l’erreur est un bug.

Important

Un code 5XX ou 4XX signifie que le serveur web n’a pas pû traiter votre requête ou qu’elle n’est pas arrivée jusqu’au serveur GraphQL. Un code 200 signifie que la requête a été correctement traitée, mais pas forcément sans erreur !

Exemple de requête / réponse :

query {
  profile {
    id
  }
}
{
  "data": null,
  "extensions": {
    "analyzer": {
      "complexity": 3,
      "depth": 2
    }
  },
  "errors": [
    {
      "message": "L'accès à la requête (profile) n'est pas autorisé avec les autorisations actuelles.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "profile"
      ],
      "extensions": {
        "code": "FORBIDDEN",
        "query": "profile",
        "request_id": "8fb550b6-cc63-469b-b4d7-40eb684c5a1c"
      }
    }
  ]
}

Erreurs client

UNAUTHENTICATED

Cause : La requête a fourni des identifiants invalides.

EXPIRED_CREDENTIALS

Cause : Les identifiants fournis ne sont plus valables.

Dans le cas d’identifiants OAuth (Bearer), il faut rafraîchir le jeton.

BAD_REQUEST

Cause : Le client a fourni des paramètres incorrects dans la requête.

ParamètreOptionnelDescription
kindOuiType d’erreur
parameterOuiLe paramètre ayant causé l’erreur
detailsOuiDétails de l’erreur sous forme texte

Les types d’erreur possible sont :

TOO_MUCH_PAGINATION_ARGUMENTS

Une requête paginée doit avoir un paramètre first ou last mais ils sont exclusifs.

NO_PAGINATION_LIMIT_PROVIDED

Une requête paginée doit avoir au minimum un paramètre first ou last.

INVALID_CURSOR

Le curseur fourni n’est pas reconnu. Le paramètre parameter spécifie quel argument fourni a causé l’erreur.

NOT_FOUND

Note

Cette erreur n’est pas renvoyée par les requêtes de recherche d’une seule entité (user, group, etc.). À la place, ces requêtes renvoient null si l’entité n’est pas trouvée.

Lorsqu’une entité est fournie en paramètre (ajout d’un role de groupe par exemple), mais que cette entité n’existe pas dans le système alors NOT_FOUND est renvoyé.

ParamètreDescription
entityLe type de l’entité introuvable
urnSon Uniform Resource Name, permettant de l’identifier

CHECK_VIOLATION

Si une contrainte n’est pas respectée, cette exception est levée. Si l’origine de l’erreur viens de la base de donnée, la table et constraint seront spécifiés. Sinon, json_details expliquera l’origine de l’erreur.

ParamètreOptionnelDescription
tableOuiTable SQL concernée
constraintOuiContrainte impliquée dans l’erreur
json_detailsOuiDétails de l’erreur sous forme JSON

UNIQUE_VIOLATION

Cette erreur est provoquée par un conflit de base de donnée. Deux entitées possèdent les mêmes clés primaires ou un attribut unique est partagé.

ParamètreDescription
tableTable SQL concernée
constraintContrainte impliquée dans l’erreur

Erreurs serveur

INTERNAL_SERVER_ERROR

Important

Cette erreur signifie qu’un comportement imprévu est apparu au sein de l’API. Il s’agit certainement d’un bug, merci de contacter ARISE en fournissant le request_id !

Erreurs internes

ID_GENERATION_FAILURE

Le compte utilisateur n’a pas pu être créé car la liste des ID valables est épuisée. Cela arrive généralement quand le nom de famille est très court et que plusieurs élèves portant ce nom de famille arrivent la même année.

ParamètreDescription
attemptsLa liste des ID précédemment générés

UNIX_UID_GENERATION_FAILURE

Le compte utilisateur n’a pas pu être créé car la liste des UID valables est épuisée.

ParamètreDescription
attemptsLa liste des UID précédemment générés

GLOBAL_STATE_ERROR

L’état global (table globals) des données est incohérent. Le paramètre kind précise la source de l’erreur.

TypeDescription
CURRENT_YEARL’année scolaire ne peut pas être mise à jour. L’année courante est déjà la bonne
ODD_SEMESTER_STILL_ACTIVELe semestre pair ne peut être activé qu’à la nouvelle année civile

Rate limits et limites de requêtes pour l’API GraphQL

L’API GraphQL d’ARISE est soumise à des restrictions visant à empêcher les appels excessifs ou abusifs vers nos serveurs.

Rate limits

Un rate limit représente une limitation du nombre de requête réalisable en un temps imparti.

Note

ARISE n’implémente actuellement pas de rate limit. Toutefois, en cas d’utilisation abusive de l’API, ARISE se réserve le droit d’imposer des sanctions.

Analyse d’une requête

Chaque requête est accompagnée d’une partie extensions et plus particulièrement analyzer.

{
  "data": {
    "profile": {
      "id": "acier2020",
      "nickname": "Steel",
      "schoolYear": 6,
      "isFipa": null
    }
  },
  "extensions": {
    "analyzer": {
      "complexity": 42,
      "depth": 4
    }
  }
}
AttributLimiteDescription
complexity\(2048\)Complexité totale de la requête, calculée en parcourant l’arbre syntaxique. Le coût est proportionnel au nombre direct d’attribut enfants, il est donc facile d’obtenir une complexité exponentielle.
depth\(16\)Profondeur maximale de la requête

Si une des deux limites est atteinte, la réponse sera :

{
  "data": null,
  "errors": [
    {
      "message": "Query is too complex."
    }
  ]
}

Formule de complexité

Un champ simple aura pour complexité \( 0 \).

Toute requête faisant un appel en base de donnée aura pour complexité :

\[ Complexité_{défaut} = 3 \]

Tout attribut faisant un appel en base de donnée groupé (batch) aura pour complexité :

\[ Complexité_{batch} = Complexité_{défaut} + Complexité_{attribut} + 1 \]

Toute requête paginée d’une taille \( N \) aura pour complexité :

\[ Complexité_{pagination}(N) = Complexité_{défaut} + \sum_{i=0}^{N} (Complexité_{attribut_i} + 1) \]

Enfin, les requêtes non paginées mais retournant un nombre inconnu d’élément (par exemple, allPlatforms) auront pour complexité :

\[ Complexité_{inconnue} = 50 \]

Warning

La limite de complexité a pour but de limiter les usages abusifs et les dénis de service (DOS). ARISE se réserve le droit de changer le calcul de cette limite.

Exemple

query Profile {
  profile {
    id
    nickname
    schoolYear
    isFipa: group(group: {id: "fisa"}) {
      role {
        value
      }
    }
  }
}
profile = 3 + (
  id = 0
  nickname = 0
  schoolYear = batch(1) = 3 + 1 + 1 = 5
  group = batch(
    role = 0 + (
      value = 0
    )
  ) = 3 + 0 + 1
)

Endpoints REST

L’API propose également certains endpoints REST en addition de GraphQL.

GET /_health

Cet endpoint renvoie toujours 200 OK. Une réponse différente indique que le serveur web n’est pas accessible.

Tip

L’endpoint /_readiness est préférable pour s’assurer que l’API est fonctionnelle

GET /_readiness

Cet endpoint vérifie que l’application est prête en envoyant une requête ping à la base de donnée afin de s’assurer que la connexion est active.

GET /photo/v1/{id}

Le champ photo du type User ne renvoie pas le contenu de la photo utilisateur directement, mais une URL pointant sur cet endpoint.

Ces URLs sont signées et valides pour une durée de 1h. Elles peuvent donc être inclues dans une page web, tant que l’accès est authentifié.

Important

Il n’est pas possible d’appeler l’endpoint directement pour obtenir la photo d’un utilisateur. Il faut passer par le type User GraphQL pour obtenir une URL paramétrée.

Exemple d’URL :

https://api.iiens.net/photo/v1/mcfly1985?expires=1777071680&signature=ebd68c315e9810714c6ff648c55284ef999b15a96800dd5f1cba573def300b0a

GET /picture/v1/{id}

Cet endpoint est équivalent à /photo/v1/{id}, mais pour les images de profil utilisateur.

Changelog

Tous les changements sur le schéma ou le fonctionnement de l’API sont répertoriés dans ce document. Les changements techniques sont décrit dans ce document.

Politique de versionnement

Les besoins évoluent au fil du temps, et GraphQL permet à une API de s’adapter à ces besoins sans avoir à gérer différentes versions de l’API. Par exemple, si une nouvelle fonctionnalité nécessite la mise à disposition de valeurs de nom plus spécifiques, le type User pourrait être mis à jour comme suit :

type User {
  fullName: String
  nickname: String
  name: String @deprecated(reason: "Utilisez `fullName`.")
}

Les outils client peuvent alors encourager les développeurs à utiliser les nouveaux champs et à supprimer l’utilisation du champ name obsolète. Le champ peut être supprimé une fois établi qu’il n’est plus utilisé ; en attendant, GraphQL continuera à fournir ses données comme prévu.

Les API GraphQL ne sont usuellement pas versionnées. Cependant, ARISE a fait le choix d’ajouter un préfixe /v1 à l’endpoint GraphQL. Malgré les mécanismes décrit précédemment, il n’est pas simple de réussir le design d’une API du premier coup. Le passage de la version 0 à la version 1 s’est déroulé avec beaucoup de “breaking changes”, en maitrisant la version majeure nous avons pû réaliser la transition en douceur.

Si un jour une réécriture complète de l’API est à prévoir, ce préfixe permettra à nouveau une transition plus douce pour les sites associatifs ou même ceux d’ARISE.

1.0.0 - 2026-04-12

Mise en production de l’API