Rollen: wie ziet welke PII
Een rol is een naam plus een verzameling PII-types. Verder niets.
De scope — dit document of alles — zit niet in de rol maar in de grant, dus je kiest hem bij het toekennen. Dezelfde rol kan aan de een gegeven worden voor één document en aan de ander voor alles. Zat de scope in de rol, dan waren "HR voor dit dossier" en "HR voor alles" twee rollen die morgen uit elkaar lopen.
Waarom een rol een ding is en geen sjabloon
Het verschil zit in wat er gebeurt als je hem uitzet.
Een sjabloon zou bij het toekennen de types naar de grant kopiëren. Die grant leeft daarna zijn eigen leven. Een rol uitzetten is dan een opruimactie — elke uitgegeven grant terugvinden en intrekken, met een tijdvenster erin, precies op het moment dat je er geen wilt. En een rol inperken verandert niets aan wat al is uitgegeven: je denkt dat je iets hebt dichtgezet en dat is niet zo.
Als ding wordt de rol opgelost op het moment dat authorize() beslist. Uit
is uit, onmiddellijk, en er verandert geen enkele grant. Dat laatste staat in
test_switching_a_role_off_stops_a_reveal_that_was_working: eerst onthult hij,
dan gaat de rol uit, dan geeft dezelfde grant 403 — en de grant staat nog op
ACTIVE.
Een rol maken
In de console: rollen → naam invullen → types aanvinken → Maak rol. De
aanvinklijst komt uit het typeregister (pii_categories), met de AVG-grondslag
erbij — Art. 6, 9 en 10 zijn niet dezelfde beslissing.
Of over de API:
curl -XPOST $API/roles -H 'content-type: application/json' \
-d '{"name":"HR","allowed_types":["PERSON","EMAIL"]}'
Een rol toekennen
Een grant noemt precies één bron voor zijn types: een eigen lijst, een PPL, of een rol. Twee bronnen voor één antwoord is hoe autorisatiefouten ontstaan.
# per document
curl -XPOST $API/grants -H 'content-type: application/json' \
-d '{"recipient":"iemand@gemeente.nl","role":"HR","document_id":"<uuid>"}'
# globaal — alleen op naam van de beheerdersrol
curl -XPOST $API/grants -H 'content-type: application/json' \
-d '{"recipient":"iemand@gemeente.nl","role":"beheerder"}'
De ongescopete grant draagt een naam
Een grant zonder document_id onthult op elk document.
WORDSWORTH_ALLOW_GLOBAL_GRANTS weigert die, en dat blijft zo. De uitzondering
is de beheerdersrol: een ongescopete grant mag wanneer hij die rol noemt.
De twee alternatieven waren slechter. De vlag omzetten opent hem voor élke
ongescopete grant, voor iedereen — de hardening terugdraaien om hem voor één
iemand te openen. Per document een grant uitgeven is 791 stuks, en morgen meer.
Nu draagt de uitzondering de naam van wie hem krijgt, en zegt het auditspoor
achteraf dát er een uitzondering gold (global_by_role) en onder welke rol.
Breakglass
curl -XPOST $API/roles/HR/deactivate -H 'content-type: application/json' \
-d '{"reason":"sleutel gelekt"}'
Een reden is verplicht. Een noodrem zonder reden is een schakelaar waarvan
niemand later kan navertellen waarom hij overging, en dit is precies het moment
waarop dat uitmaakt. Terugzetten (/activate) vraagt er ook een: terugzetten is
net zo goed een besluit.
Elke wijziging aan een rol — aanmaken, inperken, uitzetten, aanzetten — komt in
het sleutel-levensloopspoor (key_lifecycle), waar grants en
sleutelrotaties ook staan. Niet in de document-hashketen: die is de
toestandsmachine van één document, en een rol raakt er duizend. De stand ná de
wijziging staat erbij, zodat uit de stroom zelf te reconstrueren is wat een rol
op enig moment toestond. De console schrijft hetzelfde record als de API — twee
wegen naar dezelfde handeling met één spoor eronder is hoe een spoor gaten
krijgt.
Wat er eerder onthuld is, verandert hierdoor niet. Dat is gebeurd en staat in het spoor. "Vanaf nu niet meer" is iets anders dan "is nooit gezien" — de console zegt dat er met zoveel woorden bij.
De beheerdersrol
wordsworth-init maakt hem aan bij de installatie, met alle types die het
register kent. Mark, 2026-09-17: "er is altijd 1 (super)admin bij het
aanmaken/installeren van de app, die bepaalt de RBAC." Die eerste rol komt dus
niet uit het rollenstelsel — zoals een root-account niet door een
gebruikersbeheerder wordt aangemaakt.
Een bestaande beheerdersrol wordt niet overschreven. Zou elke uitrol hem terugzetten op "alles", dan was een ingeperkte of uitgezette beheerdersrol één deploy lang geldig — het tegenovergestelde van een breakglass.
"Alles zien" is daarmee een rol en geen pad langs authorize(). Elke onthulling
door een beheerder is een gewone, geauditeerde onthulling, en uitzetten werkt
ook op hem. Een bijzondere route voor beheerders is de tweede deur die dit
project nergens heeft — en het is de deur die het meest gebruikt wordt en het
minst bekeken.
Wat hier niet woont
Wie iemand is. Dat komt uit de aanmelding: nu Cloudflare Access (het geverifieerde e-mailadres is het callerlabel), later mogelijk Keycloak. Deze laag kent alleen namen die daarvandaan komen, zodat de dag dat rollidmaatschap ergens anders vandaan komt, alléén dat punt verandert en niet de beslissing.
Zie ook grants, api-auth en deleting documents — die laatste leunt op precies deze rol om te bepalen wie mag verwijderen.