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.
7.6 KiB
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)
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(formusername/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/healthest public. - Rôles :
Admin,Superviseur,Consultant. Les routes/admin/*exigentAdmin(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 :
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 (
Adminen écriture) :POST/PUT/DELETEsur/services,/categories,/contactset/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é, carTABLE_FINALré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 :
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_SECRETdoit ê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é dansRENDU/02_Dump_SQL/). - Journal d'audit alimenté à chaque login + action admin.
Tests
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 versgit.nfteam.ovh/neckfire/datasentinel-api+ notification ntfy. Déclenché sur pushmain. - Déploiement, exploitation, sauvegarde : voir
RUNBOOK.mdet le stack d'hébergement (homelab/dev/datasentinel/). La base tourne sur un SQL Server partagé (dev-mssql), pas dédiée à l'app.