Ajout page pour Dev API
This commit is contained in:
parent
1ae02d40b1
commit
ae99b0795a
113
docs/DevAPIPermissions.md
Normal file
113
docs/DevAPIPermissions.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Contrôle d'accès des fonctions de l'API
|
||||
|
||||
## Rappels
|
||||
|
||||
Dans ScoDoc, les permissions sont liées à des rôles. Un utilisateur a un ou plusieurs
|
||||
rôles, dans un ou tous les départements (si le département est null, on
|
||||
considère que le rôle est donnés dans tous les départements).
|
||||
|
||||
Dans ScoDoc Web, toutes les routes sont liées à des départements
|
||||
|
||||
/ScoDoc/<str:dept_acronym>/Scolarite/
|
||||
|
||||
sauf la page d'accueil (`/ScoDoc/index`), les pages de configuration générale
|
||||
(`ScoDoc/configuration`) et les pages "entreprises" (`ScoDoc/entreprises/`).
|
||||
|
||||
L'API manipule des objets (formsemestres, modules, notes...) identifiés par leur
|
||||
`id` qui est unique dans la base (note: les code INE et NIP ne sont pas des id,
|
||||
et peuvent se retrouver dans plusieurs départements, notamment en cas de
|
||||
transfert d'un étudiant d'un département à un autre).
|
||||
|
||||
## Contrôle des permissions par l'API et départements
|
||||
Ainsi, une route API comme `/partition/<int:partition_id>` n'est pas ambigüe.
|
||||
Toutefois, ScoDoc doit déterminer si l'utilisateur a le droit d'accéder (ou de
|
||||
modifier) cet objet. Pour cela, ScoDoc a besoin de connaitre le département.
|
||||
C'est généralement assez simple: dans cet exemple, l'objet `partition` a une
|
||||
relation avec `formsemestre`, lui même lié à son département: on écrit
|
||||
`p.formsemestre.departement.acronym`.
|
||||
|
||||
Cependant, le contrôle de l'accès est plus facile à exprimer (donc plus sûr,
|
||||
moins de risque d'erreurs) avec un décorateur: on écrit typiquement les vues:
|
||||
```
|
||||
@permission_required(Permission.ScoView)
|
||||
def ma_vue( arg ):
|
||||
...
|
||||
```
|
||||
|
||||
Comme nous l'avons dit, pour les vues Web (voir sources dans `app/view/*.py`),
|
||||
le département est dans la route (URL): ainsi le tableau de bord d'un
|
||||
fomsemestre est
|
||||
```
|
||||
ScoDoc/<str:dept_acronym>/Scolarite/Notes/formsemestre_status
|
||||
```
|
||||
La vue s'écrit
|
||||
```
|
||||
@bp.route("/formsemestre_status")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def formsemestre_status(formsemestre_id:int):
|
||||
...
|
||||
```
|
||||
Le décorateur `scodoc` (défini dans `app/scodoc/decorators.py`) récupère le
|
||||
département présent dans la route et affecte deux attributs dans la requête
|
||||
```
|
||||
g.scodoc_dept = "RT" # l'acronyme du dept
|
||||
g.scodoc_dept_id = 5 # l'id du dept
|
||||
```
|
||||
|
||||
Le décorateur suivant, `permission_required` peut ainsi vérifier que la
|
||||
permission est bien accordée dans ce département.
|
||||
|
||||
Pour l'API, on a deux routes:
|
||||
```
|
||||
/ScoDoc/api/partition/<int:partition_id>
|
||||
```
|
||||
Dans ce cas, le décorateur `scodoc` ne récupère pas de département
|
||||
(`g.scodoc_dept`est mis à `None`), et `permission_required`exige alors que la
|
||||
permission soit accordé dans *tous les départements* (`dept`à `None`).
|
||||
|
||||
Lorsque l'API est utilisée depuis une vue web de ScoDoc, donc par un utilisateur
|
||||
ordinaire n'ayant de rôles que dans son (ou ses) départements, ce mécanisme
|
||||
échoue. On propose donc une autre route, de la forme
|
||||
```
|
||||
/ScoDoc/<str:dept_acronym>/api/partition/<int:partition_id>
|
||||
```
|
||||
Les décorateurs fonctionnent alors bien.
|
||||
|
||||
## Écriture d'une vue API
|
||||
Il reste à la charge des fonctions de l'API d'effectuer la vérification que les
|
||||
objets demandés sont bien dans le département donné par la route (point de
|
||||
vigilance: risque de fuite de données si mal codé). Dans la plupart des cas, il
|
||||
faut pour cela ajouter une jointure. par exemple, pour demander une partition,
|
||||
on écrira non pas
|
||||
```
|
||||
p = Partition.query.get(partition_id)
|
||||
```
|
||||
mais plutôt
|
||||
```
|
||||
p = Partition.query.filter_by(id=partition_id).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
```
|
||||
|
||||
Écriture d'une vue de l'API accessible en mode web et API:
|
||||
```
|
||||
@bp.route("/api_function/<int:arg>")
|
||||
@api_web_bp.route("/api_function/<int:arg>")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def api_function(arg: int):
|
||||
"""Une fonction quelconque de l'API"""
|
||||
return jsonify({"current_user": current_user.to_dict(), "dept": g.scodoc_dept})
|
||||
```
|
||||
|
||||
## Fonctionnement interne du contrôle d'accès ScoDoc
|
||||
|
||||
Les accès ScoDoc sont gérés avec `flask-login`. L'authentification est faite
|
||||
soit par un cookie de session (web), soit par un jeton jwt (API).
|
||||
|
||||
Ce décodage/contrôle est fait par la fonction
|
||||
`app.auth.logic.load_user_from_request()`.
|
||||
|
||||
En cas de refus (jeton ou cookie absent ou invalide), on a une redirection vers
|
||||
la page de login (en mode web), ou un message d'erreur JSON 401 pour l'API
|
||||
(voir `app.auth.logic.unauthorized_handler`).
|
Loading…
Reference in New Issue
Block a user