API — Administration¶
Cette page documente AdminController et son helper AdminAuth, montés sous /api/admin. Tous les endpoints de cette page requièrent une authentification admin — soit par cookie de session (après POST /api/admin/login), soit par header X-Admin-Token.
Contrôleurs couverts
AdminController→ login/logout admin, gestion produits, utilisateurs, commandes, comptes admin, upload imageAdminAuth(helper statique) → centralise la vérification d'auth admin (isAdmin(session, token))
Le portail admin caché (/api/admin/portal/*) est documenté à part dans admin-portal.md.
Mécanisme d'authentification admin¶
Tous les endpoints de AdminController (hors login et status) sont protégés par la même vérification :
// Vérifie : session cookie OU header X-Admin-Token
if (!isAdmin(session, token)) return unauthorized();
Session cookie :
POST /api/admin/login→ stockeadmin_logged_in = truedans laHttpSession- Requêtes suivantes → Spring transmet automatiquement
JSESSIONID AdminAuth.isAdmin()lit la clé de session
Header X-Admin-Token :
POST /api/admin/login→ retourne aussi unadminTokenUUID dans le body- Le token est stocké en mémoire dans
AdminController.VALID_ADMIN_TOKENS(ConcurrentHashMap) - Requêtes suivantes → le client envoie
X-Admin-Token: <uuid> AdminAuth.isAdmin()vérifie la présence du token dans la map
Tokens non persistants
La map VALID_ADMIN_TOKENS est réinitialisée à chaque redémarrage du backend. C'est intentionnel — comportement pédagogique pour les démonstrations de chaos mémoire (heap dump). En environnement de formation, une coupure du backend force tous les admins à se reconnecter.
SuperAdmin vs Admin ordinaire¶
PerfShop distingue deux niveaux d'administrateur :
- Superadmin (flag
isSuperAdmin = truedansadmin_users) : accès complet, y compris la gestion des comptes admin. Un seul superadmin existe par défaut, créé par le seed initial. - Admin ordinaire : accès limité selon ses droits granulaires (
canAccessChaos,canAccessMonitoring,canAccessAdmin,canAccessJmeter,canAccessScripts).
Les endpoints marqués 👑 dans les tableaux ci-dessous sont réservés au superadmin — ils retournent 403 Forbidden avec le message Réservé au superadmin. pour un admin ordinaire.
Vue d'ensemble¶
Authentification¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/admin/login |
Aucune | Login admin (BCrypt sur admin_users) |
POST |
/api/admin/logout |
Session ou token | Détruit la session et invalide le token |
GET |
/api/admin/status |
Aucune (lecture) | Statut d'authentification |
Profil admin¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/accounts/me |
Admin | Profil du compte connecté |
Gestion des comptes admin¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/accounts |
Superadmin 👑 | Liste de tous les comptes admin |
POST |
/api/admin/accounts |
Superadmin 👑 | Créer un compte admin |
DELETE |
/api/admin/accounts/{id} |
Superadmin 👑 | Supprimer un compte admin |
PUT |
/api/admin/accounts/{id}/password |
Admin (soi-même) ou Superadmin 👑 | Changer un mot de passe |
PUT |
/api/admin/accounts/{id}/rights |
Superadmin 👑 | Modifier les droits d'accès |
Produits¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/products |
Admin | Liste des produits non pédagogiques |
POST |
/api/admin/products |
Admin | Créer un produit |
PUT |
/api/admin/products/{id} |
Admin | Modifier un produit |
DELETE |
/api/admin/products/{id} |
Admin | Supprimer un produit |
POST |
/api/admin/products/{id}/image |
Admin | Upload d'image produit (multipart) |
Utilisateurs¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/users |
Admin | Liste des utilisateurs avec compteur de commandes |
POST |
/api/admin/users |
Admin | Créer un utilisateur |
DELETE |
/api/admin/users/{id} |
Admin | Supprimer un utilisateur (et ses commandes) |
Commandes¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/orders |
Admin | Liste de toutes les commandes |
DELETE |
/api/admin/orders/{id} |
Admin | Supprimer une commande |
Authentification admin¶
POST /api/admin/login¶
Auth : aucune (c'est l'entrée)
Service : AdminUserService.authenticate() (BCrypt contre table admin_users)
Requête¶
Réponse — 200 OK¶
{
"success": true,
"adminToken": "550e8400-e29b-41d4-a716-446655440000",
"email": "admin@perfshop.fr",
"isSuperAdmin": true,
"canAccessChaos": true,
"canAccessMonitoring": true,
"canAccessAdmin": true,
"canAccessJmeter": true,
"canAccessScripts": true
}
Les flags canAccess* sont utilisés par le frontend pour adapter l'affichage (masquer les onglets interdits). Le adminToken est le même UUID stocké dans VALID_ADMIN_TOKENS — il peut être utilisé à la place du cookie.
Codes d'erreur¶
| Code | Corps | Cause |
|---|---|---|
| 401 | {"error": "Identifiants incorrects"} |
Clé auth.error.credentials |
POST /api/admin/logout¶
Détruit la session et retire le token de VALID_ADMIN_TOKENS.
Auth : session ou token
Réponse — 200 OK¶
Pas d'erreur — l'endpoint est idempotent (un double logout retourne toujours 200).
GET /api/admin/status¶
Retourne l'état d'authentification admin, sans générer d'erreur.
Auth : optionnelle
Réponse — 200 OK¶
Profil admin¶
GET /api/admin/accounts/me¶
Retourne le profil du compte admin connecté. Accessible à tout admin (pas seulement superadmin).
Auth : admin
Réponse — 200 OK¶
{
"id": 1,
"email": "admin@perfshop.fr",
"canAccessChaos": true,
"canAccessMonitoring": true,
"canAccessAdmin": true,
"canAccessJmeter": true,
"canAccessScripts": true,
"isSuperAdmin": true,
"createdAt": "2024-01-01T00:00:00Z"
}
Le champ passwordHash n'est jamais exposé dans ce DTO. Même en chaos sécurité, seul le portail caché admin-portal peut exposer ce champ.
Gestion des comptes admin¶
GET /api/admin/accounts¶
Liste tous les comptes admin.
Auth : superadmin uniquement 👑
Réponse — 200 OK¶
Tableau d'objets identiques au DTO de /accounts/me :
[
{ "id": 1, "email": "admin@perfshop.fr", "isSuperAdmin": true, "canAccessChaos": true, "..." },
{ "id": 2, "email": "trainer@perfshop.fr", "isSuperAdmin": false, "canAccessChaos": true, "..." }
]
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 403 | Admin ordinaire (clé admin.error.reserved_superadmin) |
POST /api/admin/accounts¶
Crée un nouveau compte admin.
Auth : superadmin 👑
Requête¶
{
"email": "trainer@perfshop.fr",
"password": "Minimum6",
"canAccessChaos": true,
"canAccessMonitoring": true,
"canAccessAdmin": false,
"canAccessJmeter": false,
"canAccessScripts": false
}
| Champ | Type | Requis | Contrainte |
|---|---|---|---|
email |
string | oui | Non vide, unique |
password |
string | oui | ≥ 6 caractères |
canAccess* |
boolean | non (défaut false) |
Booléen |
Réponse — 201 Created¶
Le compte créé sérialisé (même structure que GET /accounts/me).
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Email ou mot de passe manquant (admin.error.email_password_required_dot) |
| 400 | Mot de passe trop court (admin.service.password_min_length) |
| 400 | Email déjà utilisé (admin.service.email_exists) |
| 401 | Non authentifié |
| 403 | Non superadmin |
DELETE /api/admin/accounts/{id}¶
Supprime un compte admin.
Auth : superadmin 👑
Protection : le superadmin lui-même ne peut pas être supprimé (admin.service.superadmin_no_delete).
Réponse — 200 OK¶
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 403 | Non superadmin, ou tentative de suppression du superadmin |
| 404 | Compte introuvable |
PUT /api/admin/accounts/{id}/password¶
Change le mot de passe d'un compte admin.
Auth :
- Superadmin : peut changer n'importe quel compte
- Admin ordinaire : peut uniquement changer son propre mot de passe (vérification via id == me.id)
Requête¶
Réponse — 200 OK¶
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Mot de passe vide ou trop court |
| 401 | Non authentifié |
| 403 | Tentative de changer un autre compte en tant qu'admin ordinaire (admin.error.own_password_only) |
| 404 | Compte introuvable |
Détection 400 vs 404
Le service AdminUserService.updatePassword() lance IllegalArgumentException pour deux raisons distinctes : mot de passe trop court (→ 400) et compte introuvable (→ 404). Le contrôleur distingue les deux en interrogeant findById() séparément : si le compte existe, c'est le mot de passe qui est en cause.
PUT /api/admin/accounts/{id}/rights¶
Modifie les droits d'accès d'un compte admin ordinaire.
Auth : superadmin 👑
Protection : les droits du superadmin sont fixes et non modifiables (admin.service.superadmin_rights_fixed).
Requête¶
{
"canAccessChaos": true,
"canAccessMonitoring": true,
"canAccessAdmin": false,
"canAccessJmeter": false,
"canAccessScripts": false
}
Réponse — 200 OK¶
Le compte mis à jour sérialisé.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 403 | Non superadmin, ou tentative de modifier le superadmin |
| 404 | Compte introuvable |
Gestion des produits¶
GET /api/admin/products¶
Liste tous les produits non pédagogiques (les produits avec is_pedagogique = true sont masqués).
Auth : admin Tri : par catégorie puis par nom
Réponse — 200 OK¶
[
{
"id": 1,
"name": "Casque audio Bluetooth",
"description": "...",
"price": 149.99,
"stock": 42,
"category": "Audio",
"imageUrl": "/images/products/1.jpg",
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-03-20T14:30:00Z"
}
]
Produits pédagogiques masqués
Certains produits sont créés exclusivement pour le parcours pédagogique (agents, objets narratifs). Ils portent le flag is_pedagogique = true et sont toujours filtrés des listes admin — ils ne peuvent être ni modifiés ni supprimés via /api/admin/products/*. Une tentative retourne 403 Forbidden avec le message admin.error.pedagogique_protected.
POST /api/admin/products¶
Crée un produit.
Auth : admin
Requête¶
{
"name": "Nouveau produit",
"description": "Description détaillée",
"price": 29.99,
"stock": 100,
"category": "Informatique",
"imageUrl": "/images/products/default.jpg"
}
Tous les champs sont obligatoires sauf imageUrl.
Réponse — 201 Created¶
Le produit créé sérialisé.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Champ obligatoire manquant ou format invalide (admin.error.data_invalid) |
| 401 | Non authentifié |
PUT /api/admin/products/{id}¶
Met à jour un produit existant. Tous les champs du body sont optionnels — seuls ceux transmis sont mis à jour.
Auth : admin
Requête¶
Réponse — 200 OK¶
Le produit mis à jour.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Données invalides |
| 401 | Non authentifié |
| 403 | Produit pédagogique protégé (admin.error.pedagogique_protected) |
| 404 | Produit introuvable |
DELETE /api/admin/products/{id}¶
Supprime un produit.
Auth : admin
Réponse — 200 OK¶
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 403 | Produit pédagogique protégé |
| 404 | Produit introuvable |
POST /api/admin/products/{id}/image¶
Upload une image pour un produit. Le fichier est écrit sur le volume Docker monté et l'URL est mise à jour en base.
Auth : admin
Content-Type : multipart/form-data
Champ attendu : file
Formats acceptés : image/jpeg, image/png, image/webp, image/gif
Requête¶
curl -H "X-Admin-Token: $TOKEN" \
-F "file=@/path/to/image.png" \
http://localhost:9080/api/admin/products/42/image
Réponse — 200 OK¶
Le nom de fichier est normalisé côté serveur : custom_<productId>.<ext>. L'extension est choisie selon le Content-Type reçu.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Content-Type ne commence pas par image/ (admin.error.image_required) |
| 401 | Non authentifié |
| 404 | Produit introuvable |
| 500 | Échec d'écriture disque (admin.error.file_write_failed) |
Où sont stockées les images
Le répertoire cible est contrôlé par la propriété Spring perfshop.images.upload-path (défaut : /images-data/products). Dans Docker Compose, ce chemin est un volume monté qui permet au conteneur nginx de servir les images via le préfixe d'URL /images/products/.
Gestion des utilisateurs¶
GET /api/admin/users¶
Liste les utilisateurs finaux non pédagogiques avec leur nombre de commandes.
Auth : admin
Réponse — 200 OK¶
[
{
"id": 42,
"email": "alice@example.com",
"firstName": "Alice",
"lastName": "Durand",
"createdAt": "2025-02-01T10:00:00Z",
"orderCount": 7
}
]
Les comptes avec is_pedagogique = true (les agents narratifs du parcours BAC5) sont filtrés — ils n'apparaissent pas dans cette liste et ne peuvent pas être supprimés via l'admin.
POST /api/admin/users¶
Crée un utilisateur final. Utilisé typiquement pour les démonstrations ou pour dépanner un étudiant.
Auth : admin
Requête¶
Réponse — 201 Created¶
Le mot de passe est haché avec BCrypt via AuthService.hashPassword() avant insertion.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Email ou mot de passe manquant (admin.error.email_password_required) |
| 400 | Email déjà utilisé (admin.error.email_exists) |
| 401 | Non authentifié |
DELETE /api/admin/users/{id}¶
Supprime un utilisateur et toutes ses commandes (via orderRepository.deleteByUserId() en amont).
Auth : admin
Protection : les comptes avec is_pedagogique = true ne peuvent pas être supprimés (admin.error.user_pedagogique_protected).
Réponse — 200 OK¶
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 403 | Compte pédagogique protégé |
| 404 | Utilisateur introuvable |
Gestion des commandes¶
GET /api/admin/orders¶
Liste toutes les commandes tous utilisateurs confondus.
Auth : admin
Paramètres¶
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
includeTestData (query) |
boolean | false |
Si true, inclut les commandes is_test_data = true des agents pédagogiques |
Réponse — 200 OK¶
[
{
"id": 789,
"orderNumber": "PS-2026-000123",
"totalAmount": 299.98,
"status": "CONFIRMED",
"createdAt": "2026-04-08T10:30:00Z",
"shippingMethod": "express",
"shippingAddress": "12 rue de la Paix, 75001 Paris",
"userId": 42,
"userEmail": "alice@example.com",
"itemCount": 1,
"isTestData": false
}
]
Tri : les plus récentes en premier (createdAt DESC).
DELETE /api/admin/orders/{id}¶
Supprime une commande.
Auth : admin
Réponse — 200 OK¶
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 404 | Commande introuvable |
Suppression destructive
Cette opération est définitive — elle supprime la commande et ses order_items par cascade JPA. Elle ne recrédite pas le stock. C'est un outil de nettoyage administratif (ex. purge des commandes de test), pas un remplaçant de POST /api/orders/{id}/cancel.
Exemple curl — workflow admin complet¶
# 1. Login admin
TOKEN=$(curl -s -H "Content-Type: application/json" \
-d '{"email":"admin@perfshop.fr","password":"admin"}' \
http://localhost:9080/api/admin/login | jq -r '.adminToken')
# 2. Lister les produits
curl -H "X-Admin-Token: $TOKEN" http://localhost:9080/api/admin/products
# 3. Créer un produit
curl -H "X-Admin-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test","price":9.99,"stock":10,"category":"Divers"}' \
http://localhost:9080/api/admin/products
# 4. Upload image
curl -H "X-Admin-Token: $TOKEN" \
-F "file=@/tmp/logo.png" \
http://localhost:9080/api/admin/products/42/image
# 5. Créer un compte admin ordinaire (superadmin requis)
curl -H "X-Admin-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"trainer@perfshop.fr","password":"trainer123","canAccessChaos":true}' \
http://localhost:9080/api/admin/accounts
Points d'attention¶
Ce contrôleur n'est PAS vulnérable
Contrairement au portail admin caché (admin-portal.md), AdminController est sécurisé par construction :
- BCrypt pour la comparaison de mot de passe
- Pas de SQLi (requêtes paramétrées via JPA)
- Pas d'IDOR (vérifications isSuperAdmin systématiques)
- Pas de mass assignment (DTOs explicites)
Les failles pédagogiques sont isolées dans AdminPortalController qui n'est activé qu'en Chaos Sécurité niveau 4. Cette séparation garantit qu'un chaos mal configuré ne compromet jamais le chemin d'administration légitime.
Pas de pagination
Les listes admin (/products, /users, /orders, /accounts) ne sont pas paginées. C'est acceptable car PerfShop est une plateforme pédagogique avec des volumes de données limités (quelques centaines de lignes max). En production avec des volumes élevés, il faudrait ajouter une pagination Spring Data comme dans ProductController.
Liens associés¶
admin-portal.md— portail caché S10-S12 (niveau Master)chaos-admin.md— pilotage chaos par le formateur- Authentification admin — détails du helper
AdminAuth