mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-11 12:29:24 +00:00
api key doc for developers
This commit is contained in:
215
docs/tutorial/api/connect.md
Normal file
215
docs/tutorial/api/connect.md
Normal file
@ -0,0 +1,215 @@
|
||||
La connexion à l'API du site AE peut se faire par deux moyens :
|
||||
|
||||
- par le cookie de session du site ; si vous accédez à l'API depuis le sith
|
||||
en étant connecté, cette méthode fonctionne par défaut
|
||||
- par clef d'API ; si vous accédez à l'API depuis une application externe,
|
||||
vous devez passer par cette méthode.
|
||||
|
||||
Comme la méthode par cookie de session ne devrait pas être utilisée
|
||||
en dehors du cadre interne au site et qu'elle marche par défaut
|
||||
dans le cadre de ce dernier, nous ne décrirons pas outre mesure la manière
|
||||
de l'utiliser.
|
||||
|
||||
## Obtenir une clef d'API
|
||||
|
||||
Il n'y a, à l'heure actuelle, pas d'interface accessible sur le site
|
||||
pour obtenir une clef d'API.
|
||||
Si vous désirez en obtenir une, demandez directement au respo info.
|
||||
|
||||
!!!danger
|
||||
|
||||
Votre clef d'API doit rester secrète.
|
||||
Ne la transmettez à personne, ne l'inscrivez pas en dur dans votre code.
|
||||
|
||||
Si votre clef a fuité, ou que vous soupçonnez qu'elle ait pu fuiter,
|
||||
informez-en immédiatement l'équipe informatique !
|
||||
|
||||
## L'interface Swagger
|
||||
|
||||
Avant de commencer à utiliser l'API du site, vous pouvez explorer
|
||||
les différentes routes qu'elle met à disposition,
|
||||
avec les schémas de données attendus en requête et en réponse.
|
||||
|
||||
Pour cela, vous pouvez vous rendre sur
|
||||
[https://ae.utbm.fr/api/docs](https://ae.utbm.fr/api/docs).
|
||||
|
||||
Toutes les routes, à de rares exceptions près, y sont recensées.
|
||||
Vous pouvez les utiliser dans les limites
|
||||
de ce à quoi vos permissions vous donnent droit
|
||||
et de la méthode d'authentification.
|
||||
|
||||
Vous pouvez vous connecter directement sur l'interface Swagger,
|
||||
en cliquant sur ce bouton, en haut à droite :
|
||||
|
||||

|
||||
/// caption
|
||||
Bouton d'autorisation sur Swagger
|
||||
///
|
||||
|
||||
Puis rentrez votre clef d'API dans le champ prévu à cet effet,
|
||||
et cliquez sur authorize :
|
||||
|
||||
|
||||

|
||||
/// caption
|
||||
Saisie de la clef d'API
|
||||
///
|
||||
|
||||
Les routes accessibles avec une clef d'API seront alors marquées par
|
||||
une icône de cadenas fermé, sur la droite.
|
||||
|
||||
!!!warning "Authentification et permissions"
|
||||
|
||||
L'icône de cadenas signifie que la route accepte l'authentification
|
||||
basée sur les clefs d'API, mais pas forcément que vous avez les
|
||||
permissions nécessaires.
|
||||
|
||||
Si une route vous renvoie une erreur 403,
|
||||
référez-en à l'équipe info, pour qu'elle puisse vous donner
|
||||
les permissions nécessaires.
|
||||
|
||||
## Utiliser la clef d'API
|
||||
|
||||
### `X-APIKey`
|
||||
|
||||
Maintenant que vous avez la clef d'API,
|
||||
il faut l'utiliser pour authentifier votre application
|
||||
lorsqu'elle effectue des requêtes au site.
|
||||
|
||||
Pour cela, vous devez le fournir dans vos requêtes
|
||||
à travers le header `X-APIKey`.
|
||||
|
||||
Par exemple :
|
||||
|
||||
```shell
|
||||
curl "https://ae.utbm.fr/api/club/1" \
|
||||
-H "X-APIKey: <votre clef d'API>"
|
||||
```
|
||||
|
||||
Comme votre clef d'API doit rester absolument secrète,
|
||||
vous ne devez en aucun cas la mettre dans votre code.
|
||||
À la place, vous pouvez créer un fichier (par exemple, un `.env`)
|
||||
qui contiendra votre clef et qui sera gitignoré.
|
||||
|
||||
```dotenv title=".env"
|
||||
API_KEY="<votre clef d'API>"
|
||||
```
|
||||
|
||||
Vous fournirez alors la clef d'API en la chargeant depuis votre environnement.
|
||||
Notez que c'est une bonne pratique à double-titre,
|
||||
puisque vous pouvez ainsi aisément changer votre clef d'API.
|
||||
|
||||
### Connexion persistante
|
||||
|
||||
La plupart des librairies permettant d'effectuer des requêtes
|
||||
HTTP incluent une prise en charge des sessions persistantes.
|
||||
Nous vous recommandons fortement d'utiliser ces fonctionnalités,
|
||||
puisqu'elles permettent de rendre votre code plus simple
|
||||
(vous n'aurez à renseigner votre clef d'API qu'une seule fois)
|
||||
et plus efficace (réutiliser la même connexion plutôt que d'en créer
|
||||
une nouvelle à chaque requête peut résulter en un gain de performance significatif ;
|
||||
cf. [HTTP persistant connection (wikipedia)](https://en.wikipedia.org/wiki/HTTP_persistent_connection))
|
||||
|
||||
Voici quelques exemples :
|
||||
|
||||
=== "Python (requests)"
|
||||
|
||||
Dépendances :
|
||||
|
||||
- `requests` (>=2.32)
|
||||
- `environs` (>=14.1)
|
||||
|
||||
```python
|
||||
import requests
|
||||
from environs import Env
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
with requests.Session() as session:
|
||||
session.headers["X-APIKey"] = env.str("API_KEY")
|
||||
response = session.get("https://ae.utbm.fr/api/club/1")
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
=== "Python (aiohttp)"
|
||||
|
||||
Dépendances :
|
||||
|
||||
- `aiohttp` (>=3.11)
|
||||
- `environs` (>=14.1)
|
||||
|
||||
```python
|
||||
import aiohttp
|
||||
import asyncio
|
||||
from environs import Env
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
async def main():
|
||||
async with aiohttp.ClientSession(
|
||||
base_url="https://ae.utbm.fr/api/",
|
||||
headers={"X-APIKey": env.str("API_KEY")}
|
||||
) as session:
|
||||
async with session.get("club/1") as res:
|
||||
print(await res.json())
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
=== "Javascript (axios)"
|
||||
|
||||
Dépendances :
|
||||
|
||||
- `axios` (>=1.9)
|
||||
- `dotenv` (>=16.5)
|
||||
|
||||
```javascript
|
||||
import { axios } from "axios";
|
||||
import { config } from "dotenv";
|
||||
|
||||
config();
|
||||
|
||||
const instance = axios.create({
|
||||
baseUrl: "https://ae.utbm.fr/api/",
|
||||
headers: { "X-APIKey": process.env.API_KEY }
|
||||
});
|
||||
console.log(await instance.get("club/1").json());
|
||||
```
|
||||
|
||||
=== "Rust (reqwest)"
|
||||
|
||||
Dépendances :
|
||||
|
||||
- `reqwest` (>= 0.12, features `json` et `gzip`)
|
||||
- `tokio` (>= 1.44, feature `derive`)
|
||||
- `dotenvy` (>= 0.15)
|
||||
|
||||
```rust
|
||||
use reqwest::Client;
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use dotenvy::EnvLoader;
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let env = EnvLoader::new().load()?;
|
||||
let mut headers = HeaderMap::new();
|
||||
let mut api_key = HeaderValue::from_str(env.var("API_KEY")?.as_str());
|
||||
api_key.set_sensitive(true);
|
||||
headers.insert("X-APIKey", api_key);
|
||||
let client = Client::builder()
|
||||
.default_headers(headers)
|
||||
.gzip(true)
|
||||
.build()?;
|
||||
let resp = client
|
||||
.get("https://ae.utbm.fr/api/club/1")
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
println!("{resp:#?}");
|
||||
Ok(())
|
||||
}
|
||||
```
|
132
docs/tutorial/api/dev.md
Normal file
132
docs/tutorial/api/dev.md
Normal file
@ -0,0 +1,132 @@
|
||||
|
||||
Pour l'API, nous utilisons `django-ninja` et sa surcouche `django-ninja-extra`.
|
||||
Ce sont des librairies relativement simples et qui présentent
|
||||
l'immense avantage d'offrir des mécanismes de validation et de sérialisation
|
||||
de données à la fois simples et expressifs.
|
||||
|
||||
## Schéma de données
|
||||
|
||||
Le cœur de django-ninja étant sa validation de données grâce à Pydantic,
|
||||
le développement de l'API commence par l'écriture de ses schémas de données.
|
||||
|
||||
Pour en comprendre le fonctionnement, veuillez consulter
|
||||
[la doc de django-ninja](https://django-ninja.dev/guides/response/).
|
||||
|
||||
Il est également important de consulter
|
||||
[la doc de pydantic](https://docs.pydantic.dev/latest/).
|
||||
|
||||
Notre surcouche par-dessus les schémas de django-ninja est relativement mince.
|
||||
Elle ne comprend que [UploadedImage][core.schemas.UploadedImage], qui hérite de
|
||||
[`UploadedFile`](https://django-ninja.dev/guides/input/file-params/?h=upl)
|
||||
pour le restreindre uniquement aux images.
|
||||
|
||||
## Authentification et permissions
|
||||
|
||||
### Authentification
|
||||
|
||||
Notre API offre deux moyens d'authentification :
|
||||
|
||||
- par cookie de session (la méthode par défaut de django)
|
||||
- par clef d'API
|
||||
|
||||
La plus grande partie des routes de l'API utilisent la méthode par cookie de session.
|
||||
|
||||
Pour placer une route d'API derrière l'une de ces méthodes (ou bien les deux),
|
||||
utilisez l'attribut `auth` et les classes `SessionAuth` et
|
||||
[`ApiKeyAuth`][apikey.auth.ApiKeyAuth].
|
||||
|
||||
!!!example
|
||||
|
||||
```python
|
||||
@api_controller("/foo")
|
||||
class FooController(ControllerBase):
|
||||
# Cette route sera accessible uniquement avec l'authentification
|
||||
# par cookie de session
|
||||
@route.get("", auth=[SessionAuth()])
|
||||
def fetch_foo(self, club_id: int): ...
|
||||
|
||||
# Et celle-ci sera accessible peut importe la méthode d'authentification
|
||||
@route.get("/bar", auth=[SessionAuth(), ApiKeyAuth()])
|
||||
def fetch_bar(self, club_id: int): ...
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
Si l'utilisateur est connecté, ça ne veut pas dire pour autant qu'il a accès à tout.
|
||||
Une fois qu'il est authentifié, il faut donc vérifier ses permissions.
|
||||
|
||||
Pour cela, nous utilisons une surcouche
|
||||
par-dessus `django-ninja`, le système de permissions de django
|
||||
et notre propre système.
|
||||
Cette dernière est documentée [ici](../perms.md).
|
||||
|
||||
### Limites des clefs d'API
|
||||
|
||||
Le système des clefs d'API est apparu très tard dans l'histoire du site
|
||||
(en P25, 10 ans après le début du développement).
|
||||
Il s'agit ni plus ni moins qu'un système d'authentification parallèle fait maison,
|
||||
devant interagir avec un système de permissions ayant connu lui-même
|
||||
une histoire assez chaotique.
|
||||
|
||||
Assez logiquement, on ne peut pas tout faire :
|
||||
il n'est pas possible que toutes les routes acceptent
|
||||
l'authentification par clef d'API.
|
||||
|
||||
Cette impossibilité provient majoritairement d'une incompatibilité
|
||||
entre cette méthode d'authentification et le système de permissions
|
||||
(qui n'a pas été prévu pour l'implémentation d'un client d'API).
|
||||
Les principaux points de friction sont :
|
||||
|
||||
- `CanView` et `CanEdit`, qui se basent `User.can_view` et `User.can_edit`,
|
||||
qui peuvent eux-mêmes se baser sur les méthodes `can_be_viewed_by`
|
||||
et `can_be_edited_by` des différents modèles.
|
||||
Or, ces dernières testent spécifiquement la relation entre l'objet et un `User`.
|
||||
Ce comportement est possiblement changeable, mais au prix d'un certain travail
|
||||
et au risque de transformer encore plus notre système de permissions
|
||||
en usine à gaz.
|
||||
- `IsSubscriber` et `OldSubscriber`, qui vérifient qu'un utilisateur est ou
|
||||
a été cotisant.
|
||||
Or, une clef d'API est liée à un client d'API, pas à un utilisateur.
|
||||
Par définition, un client d'API ne peut pas être cotisant.
|
||||
- `IsLoggedInCounter`, qui utilise encore un autre système
|
||||
d'authentification maison et qui n'est pas fait pour être utilisé en dehors du site.
|
||||
|
||||
## Créer un client et une clef d'API
|
||||
|
||||
Le site n'a actuellement pas d'interface permettant à ses utilisateurs
|
||||
de créer une application et des clefs d'API.
|
||||
|
||||
C'est volontaire : tant que le système ne sera pas suffisamment mature,
|
||||
toute attribution de clef d'API doit passer par le pôle info.
|
||||
|
||||
Cette opération se fait au travers de l'interface admin.
|
||||
|
||||
Pour commencer, créez un client d'API, en renseignant son nom,
|
||||
son propriétaire (l'utilisateur qui vous a demandé de le créer)
|
||||
et les groupes qui lui sont attribués.
|
||||
Ces groupes sont les mêmes que ceux qui sont attribués aux utilisateurs,
|
||||
ce qui permet de réutiliser une partie du système d'authentification.
|
||||
|
||||
!!!warning
|
||||
|
||||
N'attribuez pas les groupes "anciens cotisants" et "cotisants"
|
||||
aux clients d'API.
|
||||
Un client d'API géré comme un cotisant, ça n'a aucun sens.
|
||||
|
||||
Evitez également de donner à des clients d'API des droits
|
||||
autres que ceux de lecture sur le site.
|
||||
|
||||
Et surtout, n'attribuez jamais le group Root à un client d'API.
|
||||
|
||||
Une fois le client d'API créé, créez-lui une clef d'API.
|
||||
Renseignez uniquement son nom et le client d'API auquel elle est lié.
|
||||
La valeur de cette clef d'API est automatiquement générée
|
||||
et affichée en haut de la page une fois la création complétée.
|
||||
|
||||
Notez bien la valeur de la clef d'API et transmettez-la à la personne
|
||||
qui en a besoin.
|
||||
Dites-lui bien de garder cette clef en lieu sûr !
|
||||
Si la clef est perdue, il n'y a pas moyen de la récupérer,
|
||||
vous devrez en recréer une.
|
||||
|
||||
|
Reference in New Issue
Block a user