OrganisationRegistry · Sprint Review

Permissie-gebaseerde autorisatie

Sprint28 aug – 3 sep 2026
EpicOR-3114PJ758 — Rollen en rechten
TeamKoen · Egon · Yusuf (halftijds) · Jan (halftijds)
Deze sprint (Jira-status)

Hoofdstory OR-3296: In Test

TicketTitelStatus
OR-3296Als frontend wil ik via /v1/me kunnen zien of ik organisaties mag aanmakenIn Test
OR-3398Permissietests voor SleutelsIn Progress

30+ commits deze sprint op OR-3296/OR-3398. Details op de volgende slides.

Openstaand probleem — te melden aan Cegeka

ElasticSearch-projecties liepen vast op staging (AGO)

  • Wat gebeurde er: ElasticSearch-projecties liepen vast op de staging-omgeving.
  • Grondoorzaak: events kwamen binnen met lege essentiële velden — de validators die dit hadden moeten tegenhouden, werken blijkbaar niet correct.
  • Waarom dit nu pas opvalt: tot nu toe beschermde de (oude) UI hiertegen — een gebruiker kon deze lege velden nooit versturen. Met de nieuwe UI valt die bescherming weg, en komen de onderliggende validatie-gaten aan het licht.

Actie: dit is geen permissie-bug maar een datavalidatie-bug in het command-/domeinlaag — orthogonaal aan dit werk maar ontdekt tijdens het testen ervan. Er moet een nieuw bug-ticket op Jira komen zodat dit niet stil in de sprint-marge verdwijnt.

Beveiliging

Centraal permissiemodel + samenstelbare restrictie-architectuur

Voor deze sprint

  • Autorisatielogica: deels frontend, deels backend, per rol hardcoded.
  • Rol-checks verspreid over losse policy-klassen, geen centrale bron van waarheid.

Na deze sprint

  • Eén centraal Permission-model: elke rol krijgt expliciet toegewezen rechten.
  • Restrictie-architectuur: een generieke, samenstelbare IRestriction-algebra (AllowList, NotAllowList, CompositeAnd, RequireUnderVlimpersManagement, ...), elk apart unit-getest.
Hoe werkt het nu, architecturaal

Twee lagen: poort (permissie) en context (restrictie)

Gebruiker logt in  →  Laag 1 — Controller-poort: heeft deze rol de permissie?  →  Laag 2 — Policy-restrictie: geldt de permissie ook voor déze organisatie/dit record?  →  Actie toegestaan of 403

Voorbeeld: een VlimpersBeheerder heeft CanManageKeys — maar enkel als restrictie: enkel als de organisatie onder Vlimpers-beheer staat, en enkel voor toegelaten sleuteltypes. Beide lagen moeten "ja" zeggen.

Concreet — de vertaaltabellen

RolePermissionMap & ScopePermissionMap

9
rollen in RolePermissionMap
4
M2M-scopes in ScopePermissionMap
21
permissies totaal in het Permission-enum
59
permissies toegekend via rollen (RolePermissionMap, incl. restricted grants)
21
permissies toegekend via scopes (ScopePermissionMap)

Onbekende rol of scope → PermissionSet.Empty, fail-closed. Nooit toevallig toegang door een ontbrekende regel.

Concreet — de vertaaltabel in code

RolePermissionMap: elke rol expliciet, niets impliciet


public static class RolePermissionMap
{
    private static readonly IReadOnlyDictionary<Role, PermissionSet> Map =
        new Dictionary<Role, PermissionSet>
        {
            [Role.AlgemeenBeheerder] = PermissionSet.Of(
                Permission.CanManageChildren,
                Permission.CanManageContacts,
                Permission.CanManageFunctions,
                Permission.CanManageCapacities,
                Permission.CanManageKeys,
                Permission.CanManageBodies,
                Permission.CanImport,
                Permission.CanReadConfiguration),

            [Role.VlimpersBeheerder] = PermissionSet.Of(
                Permission.CanManageChildren,
                Permission.CanManageLabels,
                Permission.CanEditOrganisationLabels),

            [Role.AutomatedTask] = PermissionSet.Of(
                Permission.CanRunScheduledJobs),
        };
      
Afgewerkt voorbeeld — Sleutels (Keys)

Wat is er concreet gebouwd voor Sleutels

  • Permission.CanManageKeys toegevoegd aan het permissie-enum en aan RolePermissionMap
  • KeyContext + KeyRestrictions: restrictie-context met Vlimpers-status en keytype-allowlist
  • KeyPolicy herbouwd op de IRestriction-algebra i.p.v. hardcoded rol-checks
  • Controllers (OrganisationKeyController, OrganisationKeyCommandController) gebruiken OrganisationRegistryAuthorize met de nieuwe permissie
  • Command handlers voor aanmaken/wijzigen/verwijderen van sleutels autoriseren via KeyPolicy
  • CanSelect/CanEdit velden toegevoegd aan de sleutel-list respons
  • Volledige testdekking: unit, API-integratie, SQL-integratie — alles groen
Controllers — waar staan de permissie-checks al

76 van 142 controllers hebben een autorisatie-check

76 / 142
controllers met een autorisatie-check: 71 met het nieuwe OrganisationRegistryAuthorize-attribuut + 5 met het oudere [Authorize(..., Policy=...)] uit de legacy Edit-API
7 / 76
daarvan met ook een restrictie-check op de read (GET)-actie zelf (KeyPolicy, LabelPolicy, CapacityPolicy, RegulationPolicy, OrganisationClassificationTypePolicy) — de restrictie op de write-kant zit in de command handler, niet in de command-controller

Wél afgedekt: 49 command-controllers (mutaties) + 22 niet-command controllers op het nieuwe attribuut (bv. KboController, VlimpersController, SecurityController, ImportOrganisationsController) + 5 legacy Edit-API-controllers (bv. OrganisationBankAccountController, OrganisationContactsController) op het oudere schema.

Poort wél, restrictie nog niet: de overige 69 van de 76 checken enkel of de rol/permissie aanwezig is (laag 1) — een per-record restrictie (laag 2) is enkel al toegepast bij Sleutels, Labels, Capaciteiten, Regelgeving en OrganisatieClassificaties.

Nog niet afgedekt (66 controllers): de read-only query/list/report-controllers (bv. OrganisationListController, BodyListController, de Report-controllers, SearchController) — deze muteren niets.

Command handlers — waar staat het patroon al

14 van 130 commands op het nieuwe permissiemodel

Alles hiervan is deze sprint gerealiseerd. Vóór deze sprint (op main): 0 permission-based checks — het Permission-enum, RolePermissionMap, ScopePermissionMap, alle restricties en RequiresPermission bestonden nog niet. KeyPolicy en FormalFrameworkPolicy bestonden wél al, maar checkten intern op rollen — ze zijn deze sprint omgebouwd naar het permissiemodel. Daarnaast: 57 nieuwe PermissionMatrix-integratietests, ~11.700 regels toegevoegd over 212 bestanden.

14 / 130
command-handle-methodes op het nieuwe permissiemodel (Permission + IRestriction) — direct via RequiresPermission(...) of via KeyPolicy/FormalFrameworkPolicy: Capaciteiten (3), Toepassingsgebieden (5), Functietypes (2), Locaties (2), Sleutels (2)
116 / 130
nog te migreren: 100 op rol-checks, 16 zonder enige check in de handler

Geteld per Handle(ICommandEnvelope<...>)-methode (= per command), over 101 handler-bestanden — bestanden als CapacityCommandHandlers bevatten meerdere commands. Let op: veel van die 100 zitten al in een eigen Policy-klasse (bv. LabelPolicy, VlimpersPolicy, CapacityPolicy) — maar die checken vandaag nog rechtstreeks op Role (user.IsInAnyOf(...)), niet op Permission. De "Policy"-naam is dus geen garantie dat het al het nieuwe model is.

Nog geen enkele check in de handler (16 commands): o.a. AssignBodyNumber, UpdateMainLocation, UpdateMainBuilding, CreateOrganisationsFromImport, RebuildProjection, en de 4 KBO-commands (die zijn wel afgeschermd op controller-niveau via OrganisationKboCommandController).

Tijdlijn — sinds maandag 28 augustus

Wat de commits echt laten zien

  • 28/08: startpunt — Permission-enum + eerste versie van RolePermissionMap/ScopePermissionMap.
  • 31/08 – 01/09: restrictie-primitieven, Sleutels end-to-end (permissiemodel-referentie), CanEditAll verwijderd, eerste controller-gates.
  • 02/09: piekdag — 10+ commits: CanSelect/CanEdit op keys, restrictions op meerdere contexten, permissietests voor keys, handover- en cookbook-documentatie.
  • 03/09: permissiechecks op meer controllers + tests, 403 i.p.v. 400 bij een verboden actie, scope-based permissions (M2M) afgewerkt.

Infrastructuur: fout wachtwoord in de AWS/OpenSearch-configuratie tijdens de sprint, ondertussen opgelost.

Deze tijdlijn is gebaseerd op de commit-historie, niet op tasks.md.

Functioneel — wat staat er al

Stand van zaken per bouwsteen

Rol → permissie-vertaling (RolePermissionMap / ScopePermissionMap)100%
Sleutels (Keys) — volledig incl. restricties (permissiemodel-referentie)100%
Controller-poorten op 12 resource-types~85%
Restrictie-architectuur (IRestriction-algebra, samenstelbaar en getest)100%
Uitrol van restricties naar alle 12 resource-types (Vlimpers/OVO)~25%
Rechten teruggeven aan frontend per veld (canEdit/canSelect)~15%
Functioneel — wat staat er nog open

10 van 12 resource-types wachten nog op het permissiemodel-patroon

OnderdeelStatusNog te doen
Sleutels (Keys)KlaarDient als referentiepatroon
Overige 10 policies (Vlimpers, Label, Capacity, Body, Regulation, ...)Oud patroonNog hardcoded rol-checks, niet omgezet naar IRestriction
/v1/me — globale rechten endpointIn testOR-3296
Per-veld rechten teruggeven aan frontendNog te doenNiet ingepland
AGO-validatiebug (lege velden in events)Nieuw op te lossenNieuw Jira bug-ticket nodig
Wie deed wat — deze sprint

Bijdrages 28 aug – 3 sep

Koen
20 commits — fundament + permissiemodel (Sleutels-MVP)
  • Permission-enum, RolePermissionMap, ScopePermissionMap opgezet
  • Restriction-algebra + Sleutels end-to-end
  • Twee handover-documenten + cookbook geschreven
Egon
6 commits — uitrol naar overige resource-types
  • Permissiecontroles op 9 controllers in één keer
  • 57-bestanden testsuite (PermissionMatrix)
  • Orgaan (Body) sub-controllers gerechtigd
  • Scope-gebaseerde rechten (M2M) + referentiedata-handlers naar RequiresPermission
Samengevat

Permissiemodel en restrictie-architectuur staan; uitrol naar overige resources loopt

Eén actiepunt: AGO-validatiebug krijgt een eigen Jira-ticket.

EpicOR-3114