Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ad19ed089 |
@@ -22,6 +22,17 @@ ORIGINE_VERIFIEE = {
|
|||||||
("POST", "/api/v1/auth/password"),
|
("POST", "/api/v1/auth/password"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Toute route derrière `require_role` (LecteurDep, OperateurDep, AdminDep) peut rendre 403 pour
|
||||||
|
# `password_change_required`, pas seulement les routes `admin`.
|
||||||
|
ROUTES_A_ROLE = {
|
||||||
|
("GET", "/api/v1/users"),
|
||||||
|
("POST", "/api/v1/users"),
|
||||||
|
("PATCH", "/api/v1/users/{id}"),
|
||||||
|
("POST", "/api/v1/users/{id}/password-reset"),
|
||||||
|
("GET", "/api/v1/sites"),
|
||||||
|
("GET", "/api/v1/sites/{site_id}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def schema() -> dict[str, Any]:
|
def schema() -> dict[str, Any]:
|
||||||
@@ -55,11 +66,11 @@ def test_every_route_demanding_an_identity_says_how_it_refuses(schema: dict[str,
|
|||||||
assert muettes == []
|
assert muettes == []
|
||||||
|
|
||||||
|
|
||||||
def test_every_administration_route_documents_the_role_refusal(schema: dict[str, Any]) -> None:
|
def test_every_role_guarded_route_documents_the_role_refusal(schema: dict[str, Any]) -> None:
|
||||||
sans_403 = [
|
sans_403 = [
|
||||||
(methode, chemin)
|
(methode, chemin)
|
||||||
for methode, chemin, operation in operations(schema)
|
for methode, chemin, operation in operations(schema)
|
||||||
if "users" in operation.get("tags", []) and "403" not in operation["responses"]
|
if (methode, chemin) in ROUTES_A_ROLE and "403" not in operation["responses"]
|
||||||
]
|
]
|
||||||
|
|
||||||
assert sans_403 == []
|
assert sans_403 == []
|
||||||
|
|||||||
@@ -235,6 +235,20 @@ Les modèles de `app/schemas/errors.py` décrivent ce que les gestionnaires renv
|
|||||||
`loc` n'apparaît dans aucune réponse de cette API : `validation_error_handler()` rend `champ` et
|
`loc` n'apparaît dans aucune réponse de cette API : `validation_error_handler()` rend `champ` et
|
||||||
`type`. Renommer un champ là-bas sans le faire ici rend la documentation fausse en silence.
|
`type`. Renommer un champ là-bas sans le faire ici rend la documentation fausse en silence.
|
||||||
|
|
||||||
|
**Ajouter une route métier** (`sites` est le gabarit, `reading`/`dataset`/`prediction`/`alert`/
|
||||||
|
`recommendation` suivront) :
|
||||||
|
|
||||||
|
1. Composer ses `responses=` depuis `app/api/openapi.py` : `REPONSES_LECTEUR` ou `REPONSES_ADMIN`
|
||||||
|
au niveau de l'`include_router` dans `app/api/v1/router.py` (401 et le 403 propre au rôle),
|
||||||
|
`REPONSE_VALIDATION` et les codes locaux (404, 409, ...) sur l'endpoint lui-même s'il a un
|
||||||
|
corps, un paramètre ou peut échouer par identifiant.
|
||||||
|
2. Décrire son tag dans `TAGS` (`app/api/openapi.py`).
|
||||||
|
3. Si elle passe par `require_role`, l'ajouter à `ROUTES_A_ROLE`
|
||||||
|
(`tests/api/test_openapi.py`) ; si elle passe par `require_trusted_origin`, l'ajouter à
|
||||||
|
`ORIGINE_VERIFIEE`. Ces deux listes sont maintenues à la main, pas déduites automatiquement du
|
||||||
|
code : une route protégée qui n'y figure pas ne sera pas détectée par les tests.
|
||||||
|
4. `make openapi`, puis `pytest tests/api/test_openapi.py`.
|
||||||
|
|
||||||
## Sécurité
|
## Sécurité
|
||||||
|
|
||||||
Voir la vue consolidée dans [00-vue-ensemble.md](00-vue-ensemble.md) et les décisions dans les
|
Voir la vue consolidée dans [00-vue-ensemble.md](00-vue-ensemble.md) et les décisions dans les
|
||||||
|
|||||||
Reference in New Issue
Block a user