# BONUS - Résistance aux attaques ## Scénario 1 - Accès direct à un secret via l'URL (IDOR) **Description** Un utilisateur authentifié tente d'accéder à un secret appartenant à une équipe dont il n'est **pas** membre, en devinant ou en réutilisant un identifiant de secret dans l'URL. **Procédure de test** 1. Se connecter en tant qu'**alice** (membre de `devops` uniquement). 2. Identifier l'identifiant du secret `Mailchimp API` (appartenant à `marketing`) - il est visible en parcourant la base de données ou en étant connecté en tant que BOB. 3. En tant qu'alice, accéder à `http://127.0.0.1:8000/secrets/`. **Résultat attendu** HTTP 403 - page d'erreur « Accès refusé. » **Résultat obtenu** HTTP 403 - la route `GET /secrets/{secret_id}` délègue à `services.reveal_secret()`, qui appelle `check_team_membership(user, secret.team_id)`. Alice n'étant pas membre de `marketing`, une `AccessDeniedError` est levée dans la couche domaine, et la route retourne la page d'erreur 403 sans révéler aucune information sur le secret. ## Scénario 2 - Création d'un secret pour une équipe étrangère **Description** Un utilisateur authentifié soumet un formulaire de création de secret en falsifiant le champ `team_id` pour cibler une équipe dont il n'est **pas** membre. **Procédure de test** 1. Se connecter en tant qu'**alice** (membre de `devops` uniquement). 2. Ouvrir les outils développeur du navigateur ou utiliser `curl` / Postman. 3. Envoyer une requête `POST /secrets` avec `name=test&value=hack&team_id=`. **Résultat attendu** HTTP 403 - accès refusé, secret non créé. **Résultat obtenu** HTTP 403 - la route `POST /secrets` délègue à `services.create_secret()`, qui appelle `check_team_membership(user, team_id)` avant tout chiffrement ou écriture en base. La règle métier bloque la création et la route renvoie la page d'erreur 403. ## Scénario 3 - Enumération d'identifiants (ID enumeration) **Description** Un attaquant authentifié tente de lister tous les secrets en parcourant séquentiellement les identifiants (`/secrets/1`, `/secrets/2`, `/secrets/3`…). **Procédure de test** 1. Se connecter en tant qu'**alice** (membre de `devops` uniquement). 2. Parcourir l'URL `http://127.0.0.1:8000/secrets/1`, `/secrets/2`, à partir de 1 en incrémentant. 3. L'ID 1 appartient à `devops` → alice peut le voir. 4. L'ID 2 appartient à `marketing` → alice ne devrait pas y avoir accès. **Résultat attendu** - `/secrets/1` → HTTP 200, valeur déchiffrée affichée (alice est membre de devops). - `/secrets/2` → HTTP 403, page d'erreur « Accès refusé. » **Résultat obtenu** Conforme aux attentes. La protection n'est pas basée sur l'obscurcissement des identifiants mais sur le contrôle d'accès systématique dans `services.reveal_secret()` : chaque accès vérifie l'appartenance à l'équipe propriétaire du secret, quelle que soit la valeur de l'identifiant. ## Bonus - Conflit de rotation concurrente (Optimistic Locking) **Description** Deux utilisateurs membres d'une même équipe ouvrent simultanément le formulaire de rotation d'un secret et soumettent tous les deux leurs modifications. **Procédure de test** 1. Se connecter en tant que **bob** dans deux onglets/navigateurs différents. 2. Dans les deux onglets, ouvrir le formulaire de rotation du secret `AWS root key` (équipe devops). 3. Soumettre la rotation dans le premier onglet → succès, la version passe à 2. 4. Soumettre la rotation dans le second onglet (qui contient toujours `expected_version=1`). **Résultat attendu** Le deuxième envoi est rejeté avec un message de conflit (HTTP 409). **Résultat obtenu** `services.rotate_secret()` compare `expected_version` avec la version courante en base. Si elles diffèrent, une `ConflictError` est levée avant toute modification. En base, la requête `UPDATE … WHERE id = ? AND version = ?` ne met à jour aucune ligne si la version a changé (`rowcount == 0`), ce qui constitue un second filet de sécurité. L'utilisateur voit le formulaire rechargé avec le message d'avertissement et la version actuelle du secret.