Skip to content

How-to: Ressourcenbuchungssystem

Übersicht

Diese Anleitung behandelt den Aufbau einer Ressourcenbuchungs-API mit NENE2. Funktionen umfassen Kapazitätsdurchsetzung, Doppelbuchungsprävention, IDOR-Isolation pro Benutzer und Admin-Stornierung.

Referenzimplementierung: ../NENE2-FT/bookinglog/


Schema-Design

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)
);

Schlüsselconstraints:

  • UNIQUE (resource_id, user_id, slot_date, slot_hour) — eine Buchung pro Benutzer pro Slot.
  • cancelled Soft-Delete-Flag — Geschichte beibehalten während Wiederbuchungen ermöglicht werden.
  • Kapazität wird zur Abfragezeit geprüft (aktive Buchungen zählen vs. resource.capacity).

Routentabelle

MethodePfadAuthBeschreibung
GET/resourcesKeineAlle Ressourcen auflisten
POST/resourcesAdminRessource erstellen
POST/bookingsBenutzerSlot buchen
GET/bookingsBenutzerEigene Buchungen auflisten
GET/bookings/{id}BenutzerEine Buchung abrufen
DELETE/bookings/{id}Benutzer/AdminBuchung stornieren

Doppelbuchungsprävention

Zuerst prüfen, ob der Benutzer diesen Slot bereits hat (Anwendungsebene):

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';
}

Dann Kapazität prüfen:

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

IDOR-Isolation

Benutzer können nur ihre eigenen Buchungen lesen/stornieren. 404 (nicht 403) zurückgeben, um die Existenz nicht preiszugeben:

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

Admin storniert ohne X-User-Id

Admin kann jede Buchung ohne eigene Benutzer-ID stornieren:

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

Validierungsregeln

FeldRegel
resource_idis_int() + positiv
slot_dateRegex /\A\d{4}-\d{2}-\d{2}\z/
slot_houris_int() + 0–23
capacityis_int() + positiv
namenicht-leerer String

HTTP-Statuscodes

SituationStatus
Ressource erstellt201
Buchung bestätigt201
Buchung gefunden / Liste200
Kein X-User-Id400
Ungültiger Feldtyp422
Ungültiges Datumsformat422
slot_hour außerhalb 0–23422
Ressource nicht gefunden404
Buchung nicht gefunden404
Kein Admin-Key403
Eigene Buchung stornieren200
Buchung eines anderen stornieren403
Doppelbuchung409
Kapazität voll409

Behandelte VULN-Muster

VULNMusterAbwehr
AIDOR: Benutzer sieht andere BuchungWHERE user_id = :uid + 404
BNegative resource_idis_int() + > 0-Prüfung
Cnull slot_hour (Mitternacht)Bereich 0-23 erlaubt 0
DSQL-Injection in slot_dateRegex-Validierung + parametrisierte Abfrage
EString resource_id Typ-Jonglageis_int() strenge Prüfung
FDoppelbuchungExistenzprüfung vor INSERT
GKapazitätsüberlaufCOUNT vs. Kapazitätsprüfung
HKein X-User-Id400 mit Meldung
IBuchung eines anderen Benutzers stornierenuser_id-Eigentümerprüfung → 403
JListe gibt Daten anderer Benutzer preisWHERE user_id = :uid
KAdmin storniert beliebige BuchungisAdmin umgeht Eigentümerschaft
Lslot_hour = 24 (außerhalb des Bereichs)$hour > 23 → 422

Veröffentlicht unter der MIT-Lizenz.