232 lines
12 KiB
Markdown
232 lines
12 KiB
Markdown
# Diagramme de sequence securite - Annulation de commande avec PIN (CANCEL_ORDER)
|
|
|
|
**Phase UML** : P1 - Conception, complement UML (passe security-by-design)
|
|
**Statut** : v0.2 - flux sensible PIN-gate + audit_log (controle anti-fraude interne)
|
|
**Date** : 2026-06-12
|
|
**Branche** : `feat/p1-conception`
|
|
**Auteur methodologie** : BYAN
|
|
|
|
---
|
|
|
|
## 1. Objet du document
|
|
|
|
Ce document decrit le **flux temporel securise** de l'annulation d'une commande
|
|
en back-office (`CANCEL_ORDER`). L'annulation est une action de **manipulation
|
|
d'argent** : annuler une commande deja `paid` peut servir a masquer un
|
|
detournement d'especes (l'equipier encaisse, annule, garde le cash). Le flux
|
|
ci-dessous materialise le controle qui adresse ce risque : une **re-authentification
|
|
par PIN par equipier** (`RG-T13`) avant l'execution, et l'ecriture d'une ligne
|
|
**`audit_log` immuable** dans la meme transaction que l'effet (`RG-T14`), de sorte
|
|
que chaque annulation est rattachee a une personne meme sur un poste partage.
|
|
|
|
Le diagramme reste au niveau conceptuel / logique. Il nomme les echanges entre
|
|
participants sans detailler l'implementation PHP ni le SQL exact. Il complete
|
|
l'operation `CANCEL_ORDER` du `docs/merise/mlt.md` (7.1), la transition T5 de
|
|
`docs/uml/state-commande.md` (`paid -> cancelled`, re-credit du stock) et le cas
|
|
d'utilisation "Annuler une commande" de `docs/uml/use-cases.md` (UC13).
|
|
|
|
**Sources** :
|
|
- `docs/merise/mlt.md` 7.1 `CANCEL_ORDER` (PRE-1..PRE-3, RG-1..RG-6, POST, ERR)
|
|
- `docs/merise/mlt.md` section 2 : `RG-T13` (PIN sensible), `RG-T14` (audit_log), `RG-T11` (re-credit dans la meme transaction), `RG-T07` (garde de concurrence), `RG-T08` (transaction atomique)
|
|
- `docs/merise/dictionary.md` 3.14 (`user.pin_hash`, argon2id) et 3.20 (`audit_log`)
|
|
- `docs/uml/state-commande.md` T5 (`paid -> cancelled`, `stock_movement` type `cancellation`)
|
|
|
|
---
|
|
|
|
## 2. Participants
|
|
|
|
| Participant | Role | Couche |
|
|
|---|---|---|
|
|
| **Equipier** | Counter / Drive / Admin titulaire de `order.cancel`, sur poste partage | Acteur |
|
|
| **Admin UI** | Interface back-office (Bloc 3, formulaire d'annulation + saisie PIN) | Presentation |
|
|
| **Controleur** | Back-end REST sous `/api/*` (Bloc 2), orchestre la transaction | Application |
|
|
| **Verif PIN** | Service de verification du PIN (`password_verify` argon2id) | Application |
|
|
| **BDD** | Base de donnees MariaDB | Persistance |
|
|
|
|
La session est **partagee par poste de travail** pour le routine 95% ; le PIN
|
|
re-introduit une attribution individuelle sur le sous-ensemble sensible
|
|
(`mlt.md` `RG-T13`). Le PIN n'est pas une session : il est verifie a chaque action
|
|
sensible et sert a capturer le `user_id` qui sera ecrit dans `audit_log`.
|
|
|
|
---
|
|
|
|
## 3. Diagramme de sequence
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
actor Equipier
|
|
participant AdminUI as Admin UI
|
|
participant Ctrl as Controleur
|
|
participant PIN as Verif PIN
|
|
participant BDD
|
|
|
|
Note over Equipier,BDD: Phase 1 - Demande et controle de permission
|
|
|
|
Equipier->>AdminUI: demander l'annulation d'une commande
|
|
AdminUI->>Ctrl: POST /api/orders/{id}/cancel
|
|
Ctrl->>Ctrl: verifier session active + is_active = 1 (RG-T02)
|
|
Ctrl->>BDD: lire role_permission (permission order.cancel ?) (RG-T03, PRE-1)
|
|
BDD-->>Ctrl: permission presente
|
|
Ctrl->>BDD: lire customer_order (existe ? statut ?) (PRE-2, PRE-3)
|
|
BDD-->>Ctrl: status courant
|
|
|
|
alt status hors ['pending_payment','paid'] (delivered ou cancelled)
|
|
Ctrl-->>AdminUI: 422 CANNOT_CANCEL_IN_STATE {current_status}
|
|
AdminUI-->>Equipier: refus, aucun changement d'etat (ERR-1)
|
|
else status annulable
|
|
Note over Equipier,BDD: Phase 2 - Re-authentification PIN (RG-T13)
|
|
|
|
Ctrl-->>AdminUI: demander la saisie du PIN
|
|
Equipier->>AdminUI: saisir le PIN
|
|
AdminUI->>Ctrl: soumettre le PIN (re-auth action sensible)
|
|
Ctrl->>PIN: verifier le PIN soumis
|
|
PIN->>BDD: lire user.pin_hash de l'equipier
|
|
BDD-->>PIN: pin_hash (argon2id)
|
|
PIN->>PIN: password_verify(pin, pin_hash)
|
|
|
|
alt PIN incorrect
|
|
PIN-->>Ctrl: echec
|
|
Ctrl-->>AdminUI: refus du PIN, action rejetee
|
|
AdminUI-->>Equipier: PIN incorrect, aucun changement d'etat
|
|
else PIN correct
|
|
PIN-->>Ctrl: succes, acting user_id capture (RG-T13)
|
|
|
|
Note over Equipier,BDD: Phase 3 - Transaction atomique (RG-T08, RG-T11)
|
|
|
|
Ctrl->>BDD: BEGIN transaction
|
|
Ctrl->>BDD: UPDATE customer_order SET status = 'cancelled', cancelled_at = NOW() WHERE id = :id AND status IN ('pending_payment','paid') (RG-1, RG-T07)
|
|
BDD-->>Ctrl: lignes affectees
|
|
|
|
alt 0 ligne affectee (annulation concurrente)
|
|
Ctrl->>BDD: ROLLBACK
|
|
Ctrl-->>AdminUI: 409 INVALID_TRANSITION (ERR-2)
|
|
AdminUI-->>Equipier: deja annulee par un autre poste
|
|
else 1 ligne affectee
|
|
opt statut anterieur = paid (re-credit du stock)
|
|
Ctrl->>BDD: UPDATE ingredient SET stock_quantity = stock_quantity + :units (par ingredient consomme) (RG-3)
|
|
Ctrl->>BDD: INSERT stock_movement (type cancellation, delta +units, order_id, user_id de l'equipier) (RG-3)
|
|
end
|
|
Ctrl->>BDD: INSERT audit_log (action_code order.cancel, actor_user_id, actor_role_id, entity_type customer_order, entity_id, summary [statut anterieur + montant re-credite]) (RG-6, RG-T14)
|
|
Ctrl->>BDD: COMMIT
|
|
BDD-->>Ctrl: transaction validee
|
|
Ctrl-->>AdminUI: 200 annulation confirmee (OUT-1)
|
|
AdminUI-->>Equipier: commande annulee, trace enregistree
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Notes de modelisation : chaque pas et sa regle
|
|
|
|
Le tableau ci-dessous mappe chaque interaction du diagramme a la regle
|
|
`mlt.md` 7.1 ou a la regle transverse correspondante, et a l'entite ecrite.
|
|
|
|
| # | Interaction | Regle (mlt.md) | Entite ecrite / lue |
|
|
|---|---|---|---|
|
|
| 1 | Verifier session active + `is_active = 1` | `RG-T02` | `user` (lecture) |
|
|
| 2 | Verifier `order.cancel` via `role_permission` | `RG-T03`, 7.1 PRE-1 | `role_permission` (lecture) |
|
|
| 3 | Charger la commande et lire son `status` | 7.1 PRE-2, PRE-3 | `customer_order` (lecture) |
|
|
| 4 | Bloquer si `status` est `delivered` ou `cancelled` | 7.1 ERR-1 | aucune ecriture (HTTP 422) |
|
|
| 5 | Demander + verifier le PIN (`password_verify` argon2id) | `RG-T13`, 7.1 RG-6 | `user.pin_hash` (lecture) |
|
|
| 6 | Rejeter si PIN incorrect, sans changement d'etat | `RG-T13` | aucune ecriture |
|
|
| 7 | Capturer l'`acting user_id` pour l'audit | `RG-T13` | (en memoire, sert aux pas 11-12) |
|
|
| 8 | `BEGIN` transaction | `RG-T08` | transaction |
|
|
| 9 | `UPDATE customer_order SET status='cancelled'` avec garde `AND status IN (...)` | 7.1 RG-1, `RG-T07` | `customer_order` (ecriture) |
|
|
| 10 | `ROLLBACK` + 409 si 0 ligne affectee (concurrence) | 7.1 ERR-2, `RG-T07` | aucune ecriture nette |
|
|
| 11 | Re-credit conditionnel du stock si statut anterieur `paid` | 7.1 RG-3, `RG-T11` | `ingredient`, `stock_movement` (type `cancellation`) |
|
|
| 12 | `INSERT audit_log` dans la meme transaction | 7.1 RG-6, `RG-T14` | `audit_log` (ecriture) |
|
|
| 13 | `COMMIT` (tout ou rien) | `RG-T08`, `RG-T11` | transaction |
|
|
| 14 | Reponse 200 de confirmation | 7.1 OUT-1 | aucune ecriture |
|
|
|
|
### 4.1 Re-credit conditionnel du stock (`RG-T11`)
|
|
|
|
Le re-credit ne s'applique que si la commande etait au statut `paid` avant
|
|
l'annulation (7.1 RG-3). Une commande `pending_payment` n'avait pas encore
|
|
decremente le stock (le decrement a lieu a la transition `paid`), donc il n'y a
|
|
rien a re-crediter. Pour chaque `order_item` d'une commande `paid`, les unites
|
|
consommees sont recalculees (format `normal`/`maxi`, ajustees par les
|
|
`order_item_modifier`), `ingredient.stock_quantity` est re-incremente et un
|
|
`stock_movement` de type `cancellation` est insere. `RG-T11` garantit que ce
|
|
re-credit et l'`UPDATE` du statut sont dans la **meme transaction** : il n'y a pas
|
|
de decrement orphelin si une etape echoue.
|
|
|
|
### 4.2 Garde de concurrence (`RG-T07`)
|
|
|
|
L'`UPDATE` porte la clause `AND status IN ('pending_payment','paid')`. Si deux
|
|
postes tentent d'annuler la meme commande au meme instant, seul le premier
|
|
obtient une ligne affectee ; le second recoit 0 ligne et le controleur repond
|
|
409 `INVALID_TRANSITION` apres `ROLLBACK` (7.1 ERR-2). Cette garde optimiste
|
|
reduit le risque d'une double annulation et d'un double re-credit du stock.
|
|
|
|
### 4.3 PIN distinct de la session (`RG-T13`)
|
|
|
|
La session reste **partagee par poste** pour le flux routine. Le PIN est verifie
|
|
a chaque action du sous-ensemble sensible (annulation, prix/VAT, RBAC, gestion
|
|
utilisateur, correction d'inventaire), et c'est lui qui fournit l'`actor_user_id`
|
|
ecrit dans `audit_log`. Le `pin_hash` est un hash argon2id (`dictionary.md` 3.14),
|
|
compare via `password_verify` ; il fait partie des champs RESTRICTED tenus hors
|
|
des logs et des reponses API.
|
|
|
|
---
|
|
|
|
## 5. Menace adressee : repudiation et detournement d'especes
|
|
|
|
L'annulation d'une commande `paid` est le geste qui permet le schema de fraude
|
|
"encaisser puis annuler pour garder le cash" (insider cash-skim). Sans controle,
|
|
sur un poste a session partagee, une annulation ne serait rattachee a personne :
|
|
l'auteur pourrait nier l'avoir faite (repudiation). Le flux ci-dessus reduit le
|
|
risque de ce schema en combinant deux mecanismes concrets :
|
|
|
|
- **PIN par equipier (`RG-T13`)** : l'annulation exige une re-authentification
|
|
individuelle. Sur un poste partage, cela rattache l'acte a une personne et non
|
|
au seul poste. Le PIN tend a dissuader l'usage opportuniste d'une session
|
|
laissee ouverte par un collegue.
|
|
- **`audit_log` immuable (`RG-T14`)** : chaque annulation ecrit une ligne
|
|
`audit_log` (`action_code = order.cancel`, `actor_user_id`, `actor_role_id`,
|
|
`entity_type`, `entity_id`, `summary` avec le statut anterieur et le montant
|
|
re-credite) dans la **meme transaction** que l'`UPDATE` du statut. La table
|
|
n'accepte ni `UPDATE` ni `DELETE` au niveau applicatif (`dictionary.md` 3.20).
|
|
Une annulation ne peut donc pas exister sans sa trace, et la trace ne peut pas
|
|
etre effacee par l'auteur.
|
|
|
|
L'effet combine : un pic d'annulations rattachees a un meme `actor_user_id`
|
|
devient visible et opposable lors d'une revue. Ceci ne supprime pas le risque,
|
|
mais le **reduit** en transformant un acte anonyme et niable en un acte attribue
|
|
et trace. La residualite (collusion, partage de PIN) releve de controles
|
|
organisationnels hors du modele de donnees.
|
|
|
|
> Note : `audit_log` enregistre des **noms de champs** et un `summary`
|
|
> non-personnel (`details` stocke les noms de champs modifies, pas de PII),
|
|
> conformement a `RG-T14` et a la classification de `PROJECT_CONTEXT.md` section 19.
|
|
> L'attribution `stock_movement.user_id` du re-credit complete la trace cote stock
|
|
> sans double journalisation.
|
|
|
|
---
|
|
|
|
## 6. Coherence avec les autres livrables
|
|
|
|
| Verification | Resultat |
|
|
|---|---|
|
|
| Statuts annulables coherents avec `state-commande.md` | Oui : `pending_payment` et `paid` (T3, T5) ; `delivered` non annulable (7.1 ERR-1) |
|
|
| Transition `paid -> cancelled` avec re-credit | Couverte par T5 et 7.1 RG-3 (`stock_movement` type `cancellation`) |
|
|
| Entites ecrites presentes au dictionnaire | `customer_order` (3.10), `ingredient`, `stock_movement`, `audit_log` (3.20) |
|
|
| Regle PIN appliquee | `RG-T13` (sous-ensemble sensible inclut 7.1) ; `user.pin_hash` (3.14) |
|
|
| Regle audit appliquee | `RG-T14` ; colonnes conformes a `audit_log` (3.20) |
|
|
| Atomicite re-credit + statut + audit | `RG-T08` + `RG-T11` (une transaction, `COMMIT` / `ROLLBACK`) |
|
|
| Codes d'erreur | 422 `CANNOT_CANCEL_IN_STATE` (ERR-1), 409 `INVALID_TRANSITION` (ERR-2) |
|
|
|
|
---
|
|
|
|
## 7. Arbitrage tranche
|
|
|
|
Le flux retient la re-authentification **par PIN** plutot qu'une re-saisie du mot
|
|
de passe complet : le PIN couvre le sous-ensemble sensible sans casser le routine
|
|
95% a session partagee (`RG-T13`), tout en fournissant l'attribution
|
|
individuelle. L'`audit_log` est ecrit dans la **meme transaction** que l'effet
|
|
(`RG-T14` + `RG-T08`) : une annulation sans trace ne peut pas etre committee. Le
|
|
re-credit du stock est conditionnel au statut anterieur `paid` (`RG-T11`), ce qui
|
|
ecarte un re-credit indu sur une commande `pending_payment` qui n'a pas ete
|
|
decrementee. Les codes d'erreur (`CANNOT_CANCEL_IN_STATE`, `INVALID_TRANSITION`)
|
|
reprennent ceux de `mlt.md` 7.1, sans en inventer de nouveaux.
|