Aller au contenu

Session 27 — Enforcement des droits d'accès par service + correction admin-users

Durée

~4h


Contexte

Deux bugs distincts traités dans cette session :

  1. Accès non contrôlé par service — un compte créé avec uniquement canAccessJmeter = true pouvait s'authentifier et accéder à tous les services sans restriction. Les droits étaient stockés en base et retournés au login (Session 26), mais aucun service ne les vérifiait.

  2. Modification des droits impossible — dans chaos-admin/admin-users.html, il était possible de créer un compte avec des droits, mais aucun bouton ni modal ne permettait de modifier ces droits après coup. L'endpoint PUT /api/admin/accounts/{id}/rights existait côté Java depuis la Session 26 mais n'était jamais appelé depuis l'UI.


Cause racine — bug 1

L'endpoint /api/admin/login est partagé par tous les services. Il retourne les droits dans la réponse JSON (canAccessChaos, canAccessJmeter, etc.) depuis la Session 26, mais la vérification d'autorisation était absente côté service :

  • Scripts UI et JMeter UI ont leur propre backend Node.js qui proxy le login → vérification possible côté serveur avant création de session.
  • Chaos Admin, Admin backoffice, Monitoring → pas de backend propre, login direct via fetch côté client → vérification possible uniquement côté client JS.

Cause racine — bug 2

La page admin-users.html ne présentait que deux boutons par ligne : 🔑 Mdp et 🗑 Supprimer. Le modal de modification des droits et la fonction saveRights() n'existaient pas côté frontend.


Corrections appliquées

1. scripts-ui/src/server.js — canAccessScripts (serveur)

const data = await resp.json();
if (!data.isSuperAdmin && !data.canAccessScripts) {
  return res.status(403).json({ error: "Accès refusé — Scripts UI." });
}
req.session.authenticated = true;

2. jmeter-ui/src/server.js — canAccessJmeter (serveur)

Même pattern. HTTP 403 si canAccessJmeter = false et isSuperAdmin = false.

3. chaos-admin/public/login.html — canAccessChaos (client)

const data = await r.json();
if (!r.ok) { /* afficher data.error */ return; }
if (!data.isSuperAdmin && !data.canAccessChaos) {
  /* bloquer avant redirect */ return;
}
sessionStorage.setItem('chaos_can_access_jmeter', data.canAccessJmeter ? 'true' : 'false');
sessionStorage.setItem('chaos_can_access_scripts', data.canAccessScripts ? 'true' : 'false');
// + les 3 droits existants
window.location.href = 'index.html';

Bonus : les 5 droits sont maintenant tous stockés en session (y compris canAccessJmeter et canAccessScripts ajoutés en Session 26).

4. admin/index.html — canAccessAdmin (client)

if (!d.isSuperAdmin && !d.canAccessAdmin) {
  showLoginError("Accès refusé — backoffice.");
  return;
}

Auto-reconnect INIT supprimé : /api/admin/status ne retourne que { authenticated } sans les droits. Un cookie de session valide ne peut donc plus court-circuiter le guard.

5. monitoring/public/admin/index.html — canAccessMonitoring (client)

if (!data.isSuperAdmin && !data.canAccessMonitoring) {
  /* bloquer avant redirect vers monitoring.html */ return;
}

6. chaos-admin/public/admin-users.html — modal modification des droits

Ajouts :

  • Bouton 🛡 Droits dans chaque ligne du tableau (à côté de 🔑 Mdp et 🗑 Supprimer)
  • Modal modal-rights avec les 5 cases à cocher pré-remplies avec les droits actuels
  • Fonction openRightsModal(id, account) — lit les droits depuis le registre mémoire accountsById
  • Fonction saveRights() — appelle PUT /api/admin/accounts/{id}/rights puis loadAccounts()
  • Variable currentRightsTargetId — remise à null à la fermeture du modal
  • CSS .btn-rights — couleur ambre pour distinguer visuellement du bouton Mdp (bleu)
// Ouverture du modal — pré-remplissage depuis le registre mémoire (pas du DOM)
function openRightsModal(id, account) {
  currentRightsTargetId = id;
  document.getElementById('modal-rights-email').textContent = account.email;
  document.getElementById('edit-can-chaos').checked      = !!account.canAccessChaos;
  document.getElementById('edit-can-monitoring').checked = !!account.canAccessMonitoring;
  document.getElementById('edit-can-admin').checked      = !!account.canAccessAdmin;
  document.getElementById('edit-can-jmeter').checked     = !!account.canAccessJmeter;
  document.getElementById('edit-can-scripts').checked    = !!account.canAccessScripts;
  document.getElementById('modal-rights').classList.add('show');
}

// Sauvegarde — appel backend
async function saveRights() {
  const r = await adminFetch(`${API}/api/admin/accounts/${currentRightsTargetId}/rights`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      canAccessChaos:      document.getElementById('edit-can-chaos').checked,
      canAccessMonitoring: document.getElementById('edit-can-monitoring').checked,
      canAccessAdmin:      document.getElementById('edit-can-admin').checked,
      canAccessJmeter:     document.getElementById('edit-can-jmeter').checked,
      canAccessScripts:    document.getElementById('edit-can-scripts').checked
    })
  });
  if (r.ok) { showToast('Droits mis à jour ✓', 'ok'); closeModal('modal-rights'); loadAccounts(); }
  else { const d = await r.json(); showToast(d.error || 'Erreur', 'err'); }
}

Pattern de délégation d'événementsdata-action sur chaque bouton pour router proprement sans passer de données textuelles (email) dans le DOM :

tbody.onclick = e => {
  const btn    = e.target.closest('button[data-id]');
  const id     = Number(btn.dataset.id);
  const account = accountsById.get(id);
  const action  = btn.dataset.action;
  if (action === 'delete') deleteAccount(id, account.email);
  if (action === 'pwd')    openPasswordModal(id, account.email);
  if (action === 'rights') openRightsModal(id, account);   // nouveau
};

Superadmin : les boutons Mdp + Droits sont absents pour le superadmin (affiche "protégé") — comportement inchangé.


Analyse Chaos Sécurité S11 — mécanisme intact ✅

Rappel du scénario S11 :

  1. POST /api/admin/portal/login avec payload SQLi (admin' OR '1'='1' --)
  2. AdminController.addValidToken(token, found.getEmail()) — token injecté dans VALID_ADMIN_TOKENS
  3. L'étudiant utilise ce token via header X-Admin-Token pour accéder à chaos-admin et monitoring

Les guards ajoutés ne s'appliquent que lors du login via le formulaire (/api/admin/login). S11 bypasse ce formulaire et injecte directement dans VALID_ADMIN_TOKENS via AdminPortalController → le token fonctionne côté serveur Spring Boot (via AdminAuth) → mécanisme pédagogique intact.


Matrice d'accès finale

Service Droit requis Méthode de vérification
Scripts UI canAccessScripts Backend Node.js — HTTP 403 avant session
JMeter UI canAccessJmeter Backend Node.js — HTTP 403 avant session
Chaos Admin canAccessChaos Client JS — blocage avant redirect
Backoffice Admin canAccessAdmin Client JS — blocage avant affichage
Monitoring Admin canAccessMonitoring Client JS — blocage avant redirect

Le superadmin (isSuperAdmin = true) bypasse tous les guards — accès total garanti.


Fichiers modifiés

scripts-ui/src/server.js                          (guard canAccessScripts)
jmeter-ui/src/server.js                           (guard canAccessJmeter)
chaos-admin/public/login.html                     (guard canAccessChaos + 5 droits en session)
admin/index.html                                  (guard canAccessAdmin + auto-reconnect supprimé)
monitoring/public/admin/index.html                (guard canAccessMonitoring)
chaos-admin/public/admin-users.html               (modal 🛡 Droits + saveRights())
mkdocs/docs/vibe-coding/session-27.md             (ce fichier)
mkdocs/docs/vibe-coding/session-index.md          (session 27 ajoutée)
mkdocs/mkdocs.yml                                 (session 27 dans la nav)
PROMPT_SESSION_28.md                              (prompt audit pour discussion suivante)

Déploiement NAS

# Scripts UI + JMeter UI — server.js modifié
docker compose restart perfshop-scripts-ui perfshop-jmeter-ui

# Chaos Admin + Admin + Monitoring — fichiers statiques nginx, rebuild nécessaire
docker compose build perfshop-chaos-admin perfshop-admin perfshop-monitoring
docker compose up -d perfshop-chaos-admin perfshop-admin perfshop-monitoring

Erreurs documentées

Aucune erreur Claude dans cette session.

Incident mineur : mkdocs.yml partiellement écrasé par un write_file sur les dernières lignes seulement — restauré immédiatement dans la même session grâce au fichier uploadé par Philippe comme référence.