diff --git a/backend/openzaak/README.md b/backend/openzaak/README.md index a3c6664..5e2177c 100644 --- a/backend/openzaak/README.md +++ b/backend/openzaak/README.md @@ -107,14 +107,24 @@ app change. **rolls back the whole create**) on any notified resource — see the compose file's comment. - `setup_configuration/data.yaml` — the declarative, scripted alternative to clicking through the Django admin (upstream's own documented `setup_configuration` CLI mechanism): creates the - one `bigregister-test` client (`heeft_alle_autorisaties: true` — this instance never exists - for anything but this harness, so there's no least-privilege boundary worth modeling). + one `bigregister-test` client with `heeft_alle_autorisaties: false` — this YAML mechanism + (`vng_api_common`'s `ApplicatieConfigurationModel`) has no field for granular scopes at all, + so the client starts with zero Autorisaties; `bootstrap-catalogus.sh` grants the exact ones + it needs (WP-57). - `bootstrap-catalogus.sh` — the business content (catalogus/zaaktype/zaak/…) `setup_configuration` has no YAML for; every field value here was checked against OpenZaak's own OpenAPI spec and a live run of this exact script, not guessed (two OpenZaak quirks it works around: a zaaktype needs ≥1 resultaattype and 2 statustypen before it can be published, and its `selectielijstklasse` and the zaaktype's `selectielijstProcestype` must reference the same `procesType` on the public VNG selectielijst API). Idempotent (WP-56) — see "Bring it up" above. + Also grants `bigregister-test`'s Autorisaties via `manage.py shell` (WP-57, see the script's + top comment): `ztc` scopes (`catalogi.lezen`/`catalogi.schrijven`, this script's own + content-creation needs) up front, `zrc` scopes (`zaken.aanmaken`/`zaken.bijwerken`/ + `zaken.lezen`, scoped to the one zaaktype the BFF and this script both use) once that + zaaktype exists. No `documenten`/DRC grant — `Zgw:InformatieobjecttypeUrls` is empty in this + harness's `appsettings.json`, so `OpenZaakDocumentSource` isn't reachable here yet; add the + grant (scoped to a real `informatieobjecttype`, which this script would also need to seed) + when a later WP wires DRC content into this harness. - **Not here**: Documenten (DRC) / Notificaties (NRC) content — add if a later WP needs to prove those round-trips against a live instance too (WP-51/52 are fixture-tested today). - `docker-compose.openzaak.prod.yml` (WP-55) — production overrides layered on top of diff --git a/backend/openzaak/bootstrap-catalogus.sh b/backend/openzaak/bootstrap-catalogus.sh index eee6526..22a40f0 100755 --- a/backend/openzaak/bootstrap-catalogus.sh +++ b/backend/openzaak/bootstrap-catalogus.sh @@ -16,6 +16,16 @@ # repeatedly against a long-lived instance, not just once per fresh volume. Prints the seeded # zaak's `identificatie` + `url` on success; also writes them to seeded.env (repo-ignored) for # OpenZaakIntegrationTests.cs to assert against. +# +# WP-57: `bigregister-test` starts with ZERO Autorisaties (data.yaml sets +# heeft_alle_autorisaties: false) — the setup_configuration YAML has no field for granular +# scopes at all (confirmed from vng_api_common's own ApplicatieConfigurationModel), so this +# script grants them itself via `manage.py shell` (Django ORM, inside the `web` container) at +# the two points they become grantable: ztc scopes up front (no zaaktype dependency), zrc +# scopes once `zaaktype_url` exists below. Going through the ORM instead of the +# JWT-authenticated Autorisaties REST API sidesteps a real chicken-and-egg: a client with zero +# scopes cannot grant itself any scope over that API. Re-running this script re-grants the same +# scopes (idempotent, like everything else here). set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" @@ -61,6 +71,27 @@ oz() { echo "$json" } +# Grant (replace) an Autorisatie for $CLIENT_ID directly via the ORM (see the WP-57 note up +# top for why this bypasses the REST Autorisaties API). $1 = component, $2 = python list +# literal of scopes, $3.. = extra `Autorisatie(...)` kwargs as `name=value` (value already a +# valid Python literal, e.g. a quoted URL). +grant_scopes() { + local component="$1" scopes="$2" + shift 2 + local extra="" kv + for kv in "$@"; do extra+=" $kv,"$'\n'; done + docker compose -f docker-compose.openzaak.yml exec -T --workdir /app/src web python manage.py shell <`, `max_vertrouwelijkheidaanduiding=openbaar` — both fields are + *required* by OpenZaak's `AutorisatieValidator` for any `zaken.*` scope) — granted once + `zaaktype_url` is known, right after the zaaktype is created/resolved. + +Reading the actual `RolViewSet`/`StatusViewSet`/`ZaakInformatieObjectViewSet` +`required_scopes` (not just the scope docstrings, which are aspirational/descriptive) showed +the decision text's "statussen (aanmaken), rollen (aanmaken)" don't map to separate OpenZaak +scopes — `zaken.aanmaken` alone (OR'd against alternatives) already covers the first status +and the initiator rol; there is no `rollen.aanmaken` scope. `documenten`/`zaakinformatieobjecten` +scope was **not** granted: `Zgw:InformatieobjecttypeUrls` is empty in `appsettings.json`, so +`OpenZaakDocumentSource.Upload` can't function in this harness regardless of scope (throws +before any HTTP call) — nothing to scope precisely to yet. Left as a documented follow-up +(the script would also need to seed an `informatieobjecttype` to have something concrete to +scope `documenten.aanmaken` to). + +Verified for real: `down -v` fresh volume → `up -d` → `bootstrap-catalogus.sh` (all +"created", scopes granted, `heeft_alle_autorisaties: False` confirmed via `manage.py shell`) +→ `dotnet test --filter Category=Integration` green → reran `bootstrap-catalogus.sh` again +under the now-narrowed client (all "exists", scopes re-granted idempotently, no 403s) → +confirmed the narrowing is real, not just untested, by DELETEing the seeded zaak with a +hand-rolled JWT for this client: 403 `permission_denied` (zaak deletion needs +`zaken.verwijderen`/`zaken.geforceerd-bijwerken`, neither granted). ## Verification