Skip to content

How-to : Système de réservation de ressources

Vue d'ensemble

Ce guide couvre la construction d'une API de réservation de ressources avec NENE2. Les fonctionnalités incluent l'application de capacité, la prévention des doubles réservations, l'isolation IDOR par utilisateur et l'annulation admin.

Implémentation de référence : ../NENE2-FT/bookinglog/


Conception du schéma

sql
CREATE TABLE IF NOT EXISTS resources (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    name       TEXT    NOT NULL UNIQUE,
    capacity   INTEGER NOT NULL DEFAULT 1,
    created_at TEXT    NOT NULL
);

CREATE TABLE IF NOT EXISTS bookings (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    resource_id INTEGER NOT NULL,
    user_id     INTEGER NOT NULL,
    slot_date   TEXT    NOT NULL,   -- 'YYYY-MM-DD'
    slot_hour   INTEGER NOT NULL,   -- 0-23
    created_at  TEXT    NOT NULL,
    cancelled   INTEGER NOT NULL DEFAULT 0,
    FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE,
    UNIQUE (resource_id, user_id, slot_date, slot_hour)
);

Contraintes clés :

  • UNIQUE (resource_id, user_id, slot_date, slot_hour) — une réservation par utilisateur par créneau.
  • Flag de suppression douce cancelled — préserver l'historique tout en permettant la re-réservation.
  • Capacité vérifiée au moment de la requête (compter les réservations actives vs resource.capacity).

Table des routes

MéthodeCheminAuthDescription
GET/resourcesAucuneLister toutes les ressources
POST/resourcesAdminCréer une ressource
POST/bookingsUtilisateurRéserver un créneau
GET/bookingsUtilisateurLister ses propres réservations
GET/bookings/{id}UtilisateurObtenir une réservation
DELETE/bookings/{id}Utilisateur/AdminAnnuler une réservation

Prévention des doubles réservations

D'abord, vérifier si l'utilisateur a déjà ce créneau (niveau applicatif) :

php
$stmt = $this->pdo->prepare(
    'SELECT id FROM bookings WHERE resource_id = :rid AND user_id = :uid
     AND slot_date = :d AND slot_hour = :h AND cancelled = 0'
);
$stmt->execute([...]);
if ($stmt->fetch() !== false) {
    return 'double_booking';
}

Ensuite vérifier la capacité :

php
$count = $this->countSlotBookings($resourceId, $date, $hour);
if ($count >= (int) $resource['capacity']) {
    return 'capacity_full';
}

Isolation IDOR

Les utilisateurs ne peuvent lire/annuler que leurs propres réservations. Retourner 404 (pas 403) pour éviter de révéler l'existence :

php
if (!$this->isAdmin($req) && (int) $booking['user_id'] !== $uid) {
    return $this->problem(404, 'not-found', 'Booking not found.');
}

Annulation admin sans X-User-Id

L'admin peut annuler n'importe quelle réservation sans fournir son propre ID utilisateur :

php
$isAdmin = $this->isAdmin($req);
$uid     = $this->uid($req);
if ($uid === null && !$isAdmin) {
    return $this->problem(400, 'bad-request', 'X-User-Id required.');
}
$result = $this->repo->cancel($id, $uid ?? 0, $isAdmin);

Règles de validation

ChampRègle
resource_idis_int() + positif
slot_dateregex /\A\d{4}-\d{2}-\d{2}\z/
slot_houris_int() + 0–23
capacityis_int() + positif
namechaîne non vide

Codes de statut HTTP

SituationStatut
Ressource créée201
Réservation confirmée201
Réservation trouvée / liste200
Pas de X-User-Id400
Type de champ invalide422
Format de date invalide422
slot_hour hors 0–23422
Ressource non trouvée404
Réservation non trouvée404
Pas de clé admin403
Annuler sa propre réservation200
Annuler la réservation d'un autre403
Double réservation409
Capacité pleine409

Patterns VULN couverts

VULNPatternDéfense
AIDOR : l'utilisateur voit la réservation d'un autreWHERE user_id = :uid + 404
Bresource_id négatifvérification is_int() + > 0
Cslot_hour zéro (minuit)la plage 0-23 autorise 0
DInjection SQL dans slot_dateValidation regex + requête paramétrée
EJonglage de type resource_id en chaînevérification stricte is_int()
FDouble réservationVérification d'existence avant INSERT
GDébordement de capacitéVérification COUNT vs capacity
HPas de X-User-Id400 avec message
IAnnuler la réservation d'un autre utilisateurvérification de propriété user_id → 403
JLa liste fuit les données d'un autre utilisateurWHERE user_id = :uid
KL'admin annule n'importe quelle réservationcontournement de propriété isAdmin
Lslot_hour = 24 (hors plage)$hour > 23 → 422

Publié sous licence MIT.