Files
Metamorph/tasks/testing-m3.md

103 lines
4.9 KiB
Markdown
Raw Permalink Normal View History

feat(m3): RBAC — atomic perms, groups, users, admin SPA pages Permission catalogue (services/permissions_seed.py) - 31 atomic codes across 10 families: user.*, group.*, invitation.*, test_template.*, scenario_template.*, mission.* (incl. mission.write_red_fields + mission.write_blue_fields), detection_level.{read,update}, setting.{read,update}, mitre.sync. - Default bindings: admin = all 31; redteam = 8 (catalogue read + mission. {read,create,update,archive,write_red_fields} + detection_level.read); blueteam = 5 (catalogue read + mission.{read,write_blue_fields} + detection_level.read). - Seed runs at boot AND after /setup so a freshly truncated DB (via /diag/reset) gets the bindings back via the bootstrap path. Idempotent + additive (never removes a perm from a system group). Users admin (services/users.py + api/users.py) - list (q + is_active filter + pagination), get, patch (display_name / locale / is_active with tri-state sentinel for clear-vs-unset), soft-delete, set groups. - Last-admin protection on update (deactivate), delete, and group-strip (refusing to remove the admin group from the last active admin). Groups admin (services/groups.py + api/groups.py) - Full CRUD with system-group protection (no rename, no delete on admin/redteam/blueteam). - PUT /groups/{id}/permissions sets the perm list. - Admin system group's perm set is locked to the full catalogue (SystemGroupProtected → 409) — preserves the bypass invariant even if a future refactor moves to perm-based checks. Permissions read-only (api/permissions.py) - GET /permissions returns the catalogue (admin or group.read holders). /diag/reset extension - After truncate + token mint, the limiter is also reset (limiter.reset()) so the Playwright suite doesn't hit 10/min budgets across spec files. Guarded by limiter.enabled to no-op in APP_ENV=test. Rate-limit scope (core/rate_limit.py) - enabled = APP_ENV in ("prod", "staging"). A staging deployment serves humans, so it gets the limits too. Dev/test stay unthrottled for Playwright ergonomics. Spec §6 NF-security is an operator-facing requirement. Frontend chrome - components/RequireAdmin.tsx + ui/Modal.tsx (reusable centered dialog with accessible name + Escape + backdrop-click). - Layout.tsx shows Admin nav links only when is_admin === true. Server remains the arbiter — non-admins hitting /admin/* get redirected to /. Frontend pages - pages/AdminUsersPage.tsx, AdminGroupsPage.tsx, AdminInvitationsPage.tsx with edit modals using TanStack Query mutations + multi-select for perms grouped by family + copy-once invitation URL display. - lib/admin.ts: shared types + query keys + groupPermsByFamily helper. - lib/api.ts: apiPatch / apiPut / apiDelete added. Playwright config (e2e/playwright.config.ts) - workers: 1 + fullyParallel: false: spec files share the live Postgres, so concurrent /diag/reset calls clobber each other. Intra-file order preserved via test.describe.configure({ mode: 'serial' }). Testing - backend/tests/test_rbac.py: 15 integration tests (39 backend total — 1 health + 8 schema + 15 auth + 15 RBAC). - e2e/tests/m3-rbac.spec.ts: 8 Playwright tests covering DoD §10 #2/#3 (28 e2e total — 8 M0 + 4 M1 + 8 M2 + 8 M3). - tasks/testing-m3.md. DoD: make test-api → 39 passed, make e2e → 28 passed. Spec-reviewer pass applied (admin perm invariant + staging rate-limit scope). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 06:17:07 +02:00
---
type: testing
milestone: M3
date: "2026-05-11"
project: Metamorph
---
# Testing M3 — RBAC, groups, users, invitations
## 1. Lancement de la stack
```bash
make clean # reset complet si une stack tournait déjà
make up # build + start db/api/front
make migrate # applique le schéma (head)
```
DoD réussie quand :
- `http://localhost:8080` répond et la home indique « M3 milestone (RBAC) »
- `make logs-api | grep INSTALL` montre le bandeau du token d'install (1ʳᵉ fois)
- `make logs-api | grep metamorph.permissions.seeded` confirme `perms_total: 31` au boot
## 2. Tests automatisés
```bash
make test-api # 39 tests pytest, dont 15 nouveaux sur RBAC
make e2e # 28 tests Playwright, dont 8 M3
make e2e-report # ouvre le rapport HTML
```
Le rapport JUnit est dans `e2e/playwright-report/junit.xml`.
## 3. Procédure manuelle (smoke navigateur)
### Pré-requis
1. Stack up via `make up`.
2. `make print-install-token` → noter le token (ou `make logs-api` pour le bandeau).
### 3.1 Bootstrap admin
1. Visiter `http://localhost:8080/setup` → coller le token, créer `admin@metamorph.local` / `AdminPass1234!`.
2. Redirection auto vers `/login` après ~1.5 s.
3. Se connecter avec les mêmes identifiants.
4. La barre de nav fait apparaître les liens **Users / Groups / Invitations** (visibles uniquement quand `is_admin === true`).
### 3.2 Page Groups (`/admin/groups`)
1. Cliquer **+ New group** → modale `new group`.
2. Nom : `pentest-2026-Q2`, description libre.
3. Cocher uniquement `mission.read` et `mission.write_red_fields` dans la grille de permissions.
4. **Create group** → la carte apparaît dans la liste avec les badges `2 perms`.
5. Ouvrir le groupe `admin` (système) — le champ `Name` doit être désactivé et la mention « System groups cannot be renamed. » visible.
6. Tenter de supprimer le groupe `admin` → bouton **Delete** invisible (les groupes système sont protégés côté UI **et** serveur).
### 3.3 Page Invitations (`/admin/invitations`)
1. Cliquer **+ New invitation** → modale `new invitation`.
2. Email hint : `alice@metamorph.local`. Cocher `pentest-2026-Q2`.
3. **Generate link** → modale `invitation link` montrant l'URL one-shot ; bouton **Copy** disponible.
4. Coller l'URL dans un onglet privé → page `/register` pré-remplie avec l'email hint. Compléter le mot de passe, valider.
### 3.4 Page Users (`/admin/users`)
1. Liste les comptes existants. Chercher `alice` dans le champ **Search**.
2. Cliquer **Edit** sur Alice → modale `alice@metamorph.local`. Vérifier ses groupes (cochés : `pentest-2026-Q2`).
3. Décocher `pentest-2026-Q2`, cocher `redteam`. **Save changes**.
4. Recharger la page → Alice a maintenant le badge `redteam`.
### 3.5 Last-admin protection
1. Tenter de soft-delete l'admin courant via l'API :
```bash
ACCESS=$(curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@metamorph.local","password":"AdminPass1234!"}' | jq -r .access_token)
MY_ID=$(curl -s http://localhost:8080/api/v1/auth/me -H "Authorization: Bearer $ACCESS" | jq -r .id)
curl -i -X DELETE http://localhost:8080/api/v1/users/$MY_ID -H "Authorization: Bearer $ACCESS"
```
Réponse : **409** `{"error":"last_admin_protected"}`.
### 3.6 Vérification RBAC côté serveur (non-admin)
1. Se connecter en tant qu'Alice (membre uniquement de `redteam` après l'étape 3.4) :
```bash
ACCESS=$(curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"alice@metamorph.local","password":"<son password>"}' | jq -r .access_token)
```
2. `/users`**403** : `redteam` n'a pas `user.read`.
3. `POST /groups`**403** : `redteam` n'a pas `group.create`.
4. Naviguer dans le browser sur `/admin/users` → redirige vers `/` (RequireAdmin client-side).
### 3.7 Catalogue permissions
```bash
curl -s http://localhost:8080/api/v1/permissions -H "Authorization: Bearer $ACCESS_ADMIN" | jq '.items | length'
```
Doit retourner `31` (cf. `app/services/permissions_seed.py:PERMISSION_CATALOGUE`).
## 4. Points de contrôle critiques
- [x] Les 3 groupes système (`admin`, `redteam`, `blueteam`) existent avec `is_system=true` et leurs perms bind par défaut.
- [x] Le seed est idempotent : booter, `make migrate`, rebooter → toujours `perms_total: 31`, `perms_created: 0`.
- [x] Un non-admin reçoit 403 sur tout endpoint protégé par `@require_perm` qu'il ne couvre pas.
- [x] L'admin bypass est effectif (`is_admin` → toujours OK même sans perm explicite).
- [x] Les noms et perms des groupes système ne sont pas modifiables côté UI (champs disabled, bouton Delete masqué).
- [x] Le dernier admin ne peut pas être désactivé / supprimé / retiré du groupe `admin`.
- [x] `/admin/users`, `/admin/groups`, `/admin/invitations` masqués pour les non-admin (nav + RequireAdmin).
- [x] `/diag/reset` réinitialise aussi les compteurs rate-limit (utile entre tests Playwright).