Aller au contenu

API — Catalogue produits

Cette page documente ProductController, monté sous /api/products. Tous les endpoints de cette page sont publics (aucune authentification requise).

Contrôleur couvert

ProductControllerGET /, GET /{id}, GET /search, GET /categories

Les endpoints d'administration (création, modification, suppression, upload d'image) vivent sur /api/admin/products/* — voir admin.md.


Vue d'ensemble

Méthode Endpoint Auth Description
GET /api/products Aucune Liste paginée de tous les produits du catalogue
GET /api/products/{id} Aucune Détail d'un produit par identifiant
GET /api/products/search Aucune Recherche multi-critères (texte, catégorie, fourchette de prix)
GET /api/products/categories Aucune Liste des catégories distinctes présentes en base

GET /api/products

Retourne la liste paginée des produits du catalogue public. Utilisé par la page d'accueil frontend et les pages catégories.

Auth : aucune Contrôleur : ProductController.getAllProducts() Service : ProductService.getAllProducts(Pageable)

Paramètres

Paramètre Type Défaut Description
page integer 0 Numéro de page (0-indexé)
size integer 20 Nombre de produits par page
sort string id Colonne de tri — whitelist : id, name, price, category, createdAt, stock

Whitelist de tri

Un paramètre sort non listé dans la whitelist est silencieusement remplacé par id. Ce garde-fou évite une PropertyReferenceException qui se propagerait en 500 si un client envoyait par exemple sort=password.

Réponse — 200 OK (comportement nominal)

{
  "content": [
    {
      "id": 1,
      "name": "Casque audio Bluetooth",
      "description": "Casque sans fil avec réduction de bruit active",
      "price": 149.99,
      "stock": 42,
      "category": "Audio",
      "imageUrl": "/images/products/1.jpg",
      "createdAt": "2025-01-15T10:00:00Z",
      "updatedAt": "2025-03-20T14:30:00Z",
      "pedagogique": false
    }
  ],
  "totalElements": 147,
  "totalPages": 8,
  "number": 0,
  "size": 20,
  "first": true,
  "last": false
}

A13 — Devise incorrecte

En Chaos Métier niveau 4 (Master), la réponse est enveloppée différemment : un wrapper ajoute un champ currency: "USD" alors que les prix restent exprimés en euros dans les autres endpoints. La structure devient :

{
  "content": [ /* ... */ ],
  "totalElements": 147,
  "totalPages": 8,
  "number": 0,
  "size": 20,
  "currency": "USD"
}

Aucune erreur HTTP — le symptôme est visible uniquement par inspection de la réponse. Voir A13.

Codes d'erreur

Aucun en fonctionnement normal — cet endpoint retourne toujours 200 OK (liste vide si le catalogue est vide).


GET /api/products/{id}

Retourne le détail d'un produit par son identifiant.

Auth : aucune Contrôleur : ProductController.getProductById()

Paramètres

Paramètre Type Description
id (path) Long Identifiant du produit

Réponse — 200 OK

Renvoie directement l'entité Product sérialisée :

{
  "id": 1,
  "name": "Casque audio Bluetooth",
  "description": "Casque sans fil avec réduction de bruit active",
  "price": 149.99,
  "stock": 42,
  "category": "Audio",
  "imageUrl": "/images/products/1.jpg",
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-03-20T14:30:00Z",
  "pedagogique": false
}

Codes d'erreur

Code Cause
404 Produit introuvable (corps vide)

F4 — Corruption silencieuse

En Chaos Fonctionnel niveau 4 (Master), cet endpoint retourne HTTP 200 avec des données volontairement corrompues :

  • price multiplié par 1.5
  • stock forcé à 0
  • description tronquée

Aucune exception n'est levée, Tempo reste vert, Grafana n'alerte pas. Le diagnostic nécessite une inspection manuelle du payload. Cette anomalie est particulièrement vicieuse car elle illustre qu'un système peut être « en panne » sans jamais le signaler. Voir F4.


GET /api/products/search

Recherche multi-critères sur le catalogue. Combine un texte libre (q), un filtre catégorie et une fourchette de prix.

Auth : aucune Contrôleur : ProductController.searchProducts()

Paramètres

Paramètre Type Défaut Description
q string null Texte recherché (nom, description)
category string null Filtre catégorie (insensible à la casse)
minPrice decimal null Prix minimum (inclus)
maxPrice decimal null Prix maximum (inclus)
page integer 0 Numéro de page
size integer 20 Taille de page

Comportement nominal (Chaos Sécurité niveau 0)

La recherche utilise ProductService.searchProducts() qui exécute une requête JPQL paramétrée — entièrement immunisée contre l'injection SQL. Le paramètre q est passé comme PreparedStatement à Hibernate.

Réponse — 200 OK

Structure identique à GET /api/products (Page Spring Data).

Codes d'erreur

Aucun en fonctionnement normal — une recherche sans résultat retourne 200 OK avec content: [].

S1 — Injection SQL

En Chaos Sécurité niveau 1 (Junior) et au-delà, le chemin de code bascule vers ProductRepository.slowSearchFullScan(q) qui exécute une requête SQL native non paramétrée utilisant LIKE '%' || q || '%'. Le paramètre q est concaténé directement dans la requête sans passer par un PreparedStatement.

Payloads exploitables :

  • q=' OR '1'='1 → retourne tous les produits (bypass du filtre)
  • q='; SELECT password FROM users-- → erreur ou fuite de données selon le moteur SQL
  • q=' UNION SELECT 1,email,password,1,1,1,1,1,1 FROM users-- → exfiltration

Toute tentative est loggée dans activityLog de SecurityChaosService. Voir S1.


GET /api/products/categories

Retourne la liste de toutes les catégories présentes dans le catalogue.

Auth : aucune Contrôleur : ProductController.getCategories() Service : ProductService.getAllCategories()SELECT DISTINCT category FROM products

Réponse — 200 OK

[
  "Audio",
  "Informatique",
  "Périphériques",
  "Smartphone",
  "Tablette"
]

Retourne directement un tableau JSON (pas d'enveloppe). Pas de pagination — le nombre de catégories est toujours petit (< 20).


Exemple curl

# Liste complète (page 0, 20 produits, triés par prix)
curl "http://localhost:9080/api/products?sort=price&size=20"

# Détail d'un produit
curl "http://localhost:9080/api/products/42"

# Recherche "casque" en Audio entre 50 et 200 €
curl "http://localhost:9080/api/products/search?q=casque&category=Audio&minPrice=50&maxPrice=200"

# Liste des catégories
curl "http://localhost:9080/api/products/categories"

Liens associés