feat(behandelportal): WP-62 medewerker caller identity + authz seam

Splits backend CallerIdentity into the two ADR-0002 §3 actor kinds
(ZorgverlenerCaller/MedewerkerCaller), a stub X-Medewerker/X-Rollen header
path mirroring WP-53's citizen stub, and Authz.CanBeoordelen as the first
medewerker capability — backend-only, no consumer until WP-64. Also fixes
the backlog README's stale WP-61 status (done, but table said todo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-31 23:47:43 +02:00
co-authored by Claude Sonnet 5
parent e4ea75414b
commit a09c4ed87b
21 changed files with 289 additions and 72 deletions
+16
View File
@@ -229,6 +229,22 @@ CallerIdentity.cs`):
instead of calling `ApplicationStore` directly — the last "reads a static store directly" gap
the ACL caveat below used to flag for a citizen-facing endpoint.
**WP-62 split `CallerIdentity` into the two actor kinds ADR-0002 §3 requires** — a
`ZorgverlenerCaller` (citizen, the WP-53 shape above) or a `MedewerkerCaller` (backoffice
employee: `MedewerkerId` + `Rollen`, no BSN), backend-only, unused by any frontend until WP-64.
`StubIdentityProvider` selects the medewerker kind when `X-Medewerker` is present (its value is
the medewerkerId; `X-Rollen` is a comma-separated rollen list, defaulting to `Behandelaar`) —
takes precedence over `X-Subject`; absent, every request today, falls through to the
zorgverlener path unchanged. `CallerIdentity.SubjectId` (BSN or medewerkerId) is what
`ZgwTokenProvider.Mint` now reads instead of `.Bsn` directly, so the ZGW JWT's `user_id` is
correct for either kind with no further change (WP-66's besluit write mints this for free). The
ownership-scoping seams (`ctx.Zorgverlener()`, `IDocumentSource.Upload`, `IZaakSource
.ListMyCases`) are narrowed to `ZorgverlenerCaller` — a medewerker hitting a citizen-scoped SSP
endpoint is a 500 today (unreachable, since no consumer sends `X-Medewerker` yet; WP-64 upgrades
it to a 403 once real backoffice traffic exists). `Authz.CanBeoordelen(CallerIdentity)` is the
first medewerker capability (rol-based, `MedewerkerRol.Behandelaar`), shipped only as a decision
flag, never a rollen matrix.
## The five ZGW APIs (context for later slices)
| API | Component | Used by |
+12
View File
@@ -32,6 +32,18 @@ Both are wired only under `isDevMode()` — they do not exist in a production bu
Mechanism: `src/app/shared/infrastructure/role.ts` reads the role and the HTTP interceptor stamps it
as an `X-Role` header on role-aware requests; the backend resolves it into a `Principal`.
## Actor kinds (backend, WP-62)
`X-Role`/`Principal` above is a coarse role that applies to **either** of two actor kinds the
backend now models (ADR-0002 §3): a **zorgverlener** (this SSP's citizen — has a BSN) or a
**medewerker** (backoffice employee — no BSN, has `Rollen`). `StubIdentityProvider` picks the
medewerker kind from a dev header, `X-Medewerker` (+ `X-Rollen`), mirroring `X-Role`/`X-Subject`
above — **the SSP's FE never sends either header**; they exist only for the backend's own tests
and for the behandelportal (WP-64+) to use later. `Authz.CanBeoordelen(caller)` is the first
medewerker capability — a rol-based decision flag (`MedewerkerRol.Behandelaar`), not a role
entry on `/me`, since `/me`'s `RoleCapabilities` is keyed on `Principal` and can't see the actor
kind.
## What each role unlocks
Capabilities are resolved server-side (`backend/src/BigRegister.Api/Domain/Authorization/Authz.cs`,