Files
Api-DataSentinel/README.md
T
Antho 138bc3c87c test(api): étend la couverture de 16 à 82 tests
Trois domaines n'étaient pas couverts : l'historique et les courbes
d'évolution, les contrôles de sécurité, et le CRUD des référentiels.

- test_historique.py : bornes de dates passées en paramètres, rejet d'un
  format de date invalide, agrégation des séries par monitoring et par
  service à partir de lignes à plat.
- test_securite.py : protection 401 vérifiée route par route, RBAC 403 pour
  Superviseur et Consultant sur chaque écriture, rejet d'un jeton signé avec
  une autre clé, en-têtes de sécurité, rate-limit 429 à la 6e tentative,
  refus d'un compte désactivé, absence du hachage dans la réponse de login,
  anonymisation RGPD sans suppression de ligne.
- test_referentiels.py : CRUD catégories et contacts, refus 409 sur
  rattachement, 404 sur enregistrement inexistant, modification partielle
  limitée aux champs fournis, réactivation d'un monitoring, plafonnement de
  limit, terme de recherche transmis en paramètre et non concaténé.

Corrige au passage un défaut d'isolation révélé par la suite complète : le
limiteur de débit est un état global, les tests de connexion se
comptabilisaient entre eux et un test échouait selon l'ordre d'exécution
tout en passant fichier par fichier. La fixture limiteur_vierge le remet à
zéro, et un test dédié vérifie désormais explicitement le seuil.

Suite vérifiée stable sur 3 exécutions consécutives et fichier par fichier.
2026-08-15 15:01:42 +02:00

154 lines
7.6 KiB
Markdown

# Api-DataSentinel
API **FastAPI** de monitoring de la qualité des données — Data Sentinel (XEFI).
Backend en lecture seule sur SQL Server, sécurisé par authentification JWT.
## Stack
- Python 3.12 · FastAPI 0.136 · uvicorn
- SQL Server 2022 via `pyodbc` (ODBC Driver 18)
- Auth : JWT (`python-jose`) + bcrypt (`passlib`), rate-limit (`slowapi`)
## Lancement en local (dev)
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python -m uvicorn main:app --reload --port 8000
```
- Swagger : `http://127.0.0.1:8000/docs` · Santé : `http://127.0.0.1:8000/health`
Sans variables d'environnement, l'API se connecte en authentification Windows
sur `localhost`. Pour une instance nommée, définir `DB_SERVER` (ex.
`DB_SERVER=MonPoste\SQLEXPRESS`, sans `DB_PORT`).
## Configuration (variables d'environnement)
| Variable | Rôle | Défaut |
|----------|------|--------|
| `DB_SERVER` | hôte SQL Server (ou `hôte\instance`) | `localhost` |
| `DB_PORT` / `DB_NAME` | port / base — laisser `DB_PORT` vide pour une instance nommée | — / `DataSentinel` |
| `DB_USER` / `DB_PASSWORD` | compte applicatif ; sa présence active l'authentification SQL | — (sinon auth Windows) |
| `DB_TRUSTED_CONNECTION` | force l'authentification Windows même si `DB_USER` est défini | — |
| `DB_DRIVER` | pilote ODBC | `ODBC Driver 18 for SQL Server` |
| `CORS_ORIGINS` | origines autorisées (séparées par `,`) | `localhost:5173,localhost:3000` |
| `JWT_SECRET` | clé de signature JWT — **obligatoire en déploiement** | clé aléatoire régénérée à chaque démarrage |
| `APP_BUILD` | SHA du commit construit, injecté par la CI et exposé par `/health` | `local` |
| `JWT_ALGORITHM` / `JWT_EXPIRE_MINUTES` | algo / durée du token | `HS256` / `60` |
## Authentification & rôles
- `POST /auth/login` (form `username`/`password`) → `{access_token, token_type, user}`. Rate-limité **5/min**.
- `GET /auth/me` → profil courant.
- **Tous les endpoints de données exigent** `Authorization: Bearer <token>` (401 sinon). Seul `/health` est public.
- Rôles : `Admin`, `Superviseur`, `Consultant`. Les routes `/admin/*` exigent `Admin` (403 sinon).
### Comptes de démo
| Identifiant | Mot de passe | Rôle | Usage |
|-------------|--------------|------|-------|
| `Juré` (`JURE@NEXA.com`) | `123456` | Admin | **Compte de visite / évaluation** — à communiquer aux personnes qui consultent le site |
| `admin` | `Admin2026!` | Admin | Compte d'administration technique |
| `superviseur` | `Super2026!` | Superviseur | Illustration du RBAC (pas d'accès `/admin/*`) |
| `consultant` | `Conseil2026!` | Consultant | Illustration du RBAC (lecture seule) |
Ces comptes sont créés par `sql/data_sentinel_auth.sql`. Pour (re)créer le seul
compte `Juré` sur une base déjà déployée, sans rejouer tout le script d'auth :
```bash
sqlcmd -S <serveur> -d DataSentinel -U <user> -P <mdp> -i sql/create_admin_jure.sql
```
Les mots de passe ci-dessus sont des identifiants de démonstration : ils sont
stockés hachés (bcrypt, coût 12) et doivent être régénérés avant toute mise en
production réelle (`POST /admin/users/{id}/reset-password`).
## Organisation du code
`main.py` ne fait que l'assemblage (configuration, middlewares, montage des
routeurs). Chaque domaine fonctionnel vit dans `routers/` :
| Module | Responsabilité |
|--------|----------------|
| `routers/authentification.py` | `/auth/login` (rate-limité), `/auth/me` |
| `routers/referentiels.py` | services, catégories, contacts — lecture + CRUD Admin |
| `routers/monitorings.py` | nomenclature + données détaillées par table dédiée |
| `routers/dashboard.py` | `VUE_CONSO` : vue consolidée, filtres, KPI |
| `routers/historique.py` | snapshots journaliers (`TABLE_FINAL`) |
| `routers/evolution.py` | `VUE_TABLE_FINAL_CONSO` : courbes global / monitoring / service |
| `routers/admin.py` | comptes utilisateurs et journal d'audit |
| `routers/rgpd.py` | portabilité et droit à l'oubli |
| `domain.py` | enums (`UserRole`, `AuditAction`) et mapping `MONITO_TABLES` |
| `helpers.py` | conversion des lignes pyodbc, écriture du journal d'audit |
## Endpoints (résumé)
- **Données** (protégés) : `/categories`, `/services`, `/contacts`, `/monitorings[...]`,
`/dashboard[...]`, `/historique[...]`, `/evolution/*`.
- **Référentiels** (`Admin` en écriture) : `POST/PUT/DELETE` sur `/services`,
`/categories`, `/contacts` et `/monitorings`. La suppression est refusée (409)
tant que des enregistrements y sont rattachés ; un monitoring est **désactivé**
(`actif = 0`) et jamais supprimé, car `TABLE_FINAL` référence son identifiant.
- **Admin** (`Admin`) : `GET/POST /admin/users`, `PUT/DELETE /admin/users/{id}`,
`POST /admin/users/{id}/reset-password`, `GET /admin/journal`.
- **RGPD** : `GET /me/data-export` (portabilité), `DELETE /me` (droit à l'oubli + anonymisation).
Spécification complète : `GET /openapi.json` (export dans `docs/openapi.json`).
## Vérifier qu'un déploiement a pris
`GET /health` expose le SHA du commit dont l'image a été construite :
```bash
curl -s https://datasentinel-api.nfteam.ovh/health
# {"api":"ok","database":"ok","version":"1.0.0","build":"cb84e20e1f2a",...}
```
Comparer `build` au dernier commit poussé sur `main`. S'ils diffèrent, le
conteneur tourne encore une ancienne image : `docker compose pull` puis
`docker compose up -d` (un `up -d` seul ne retélécharge rien). Le front expose
la même information sur `/version.json`.
## Sécurité
- `JWT_SECRET` doit être défini en déploiement. À défaut, l'API démarre quand
même mais tire une clé aléatoire à chaque lancement (sessions perdues au
redémarrage) : aucun secret de repli n'est écrit dans le dépôt, un secret
public permettrait de forger un jeton d'administrateur.
- En-têtes : `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Strict-Transport-Security`.
- CORS restreint aux origines `CORS_ORIGINS`, tous verbes + credentials.
- Requêtes SQL **paramétrées** (noms de tables/colonnes whitelistés) ; le compte applicatif
n'est pas `sa`. Tables d'auth : `[USER]` + `JOURNAL_AUDIT` (`sql/data_sentinel_auth.sql`,
identique au dump livré dans `RENDU/02_Dump_SQL/`).
- Journal d'audit alimenté à chaque login + action admin.
## Tests
```bash
pip install pytest httpx && pytest -q
```
**82 tests**, curseur SQL simulé (aucune vraie base requise) :
| Fichier | Couverture |
|---------|------------|
| `test_endpoints.py` | santé, référentiels, authentification, filtres du dashboard, rôles par enum |
| `test_historique.py` | bornes de dates, validation du format, agrégation des courbes par monitoring et par service |
| `test_securite.py` | protection 401 de chaque route, RBAC 403, jeton forgé rejeté, en-têtes de sécurité, rate-limit 429, compte désactivé, droits RGPD |
| `test_referentiels.py` | CRUD complet, refus 409 sur rattachement, modification partielle, paramétrage des requêtes de recherche |
Le compteur anti brute-force est remis à zéro entre les tests (fixture
`limiteur_vierge`) : c'est un état global, et sans cela les tests de connexion
se comptabilisent entre eux et échouent selon l'ordre d'exécution.
## Docker & CI/CD
- `Dockerfile` : image Python 3.12 + `msodbcsql18`.
- CI Gitea Actions (`.gitea/workflows/build.yml`) : **build → pytest (dans l'image) → push**
vers `git.nfteam.ovh/neckfire/datasentinel-api` + notification ntfy. Déclenché sur push `main`.
- Déploiement, exploitation, sauvegarde : voir `RUNBOOK.md` et le stack d'hébergement
(`homelab/dev/datasentinel/`). La base tourne sur un **SQL Server partagé** (`dev-mssql`),
pas dédiée à l'app.