Autorisatie, permissions en server-side contextcontrole
5.1 Doel van dit hoofdstuk
Dit hoofdstuk beschrijft hoe OefenHub autorisatie technisch afdwingt na succesvolle authenticatie en interne accountinitialisatie. Vanaf Feature 11-5 is de primaire autorisatiebeslissing permission-based RBAC: rollen groeperen permissions, maar controles op routes, schermen, menu-items, frontpageblokken, services en commands vragen om expliciete permission-codes.
Autorisatie is in OefenHub geen frontendmechanisme. Zichtbare knoppen, menu-items, frontpageblokken, routeparameters, browserstate, querystringwaarden of eerder geladen schermcontexten zijn nooit voldoende om toegang te verlenen. Iedere raadpleeg-, wijzig-, export-, live-, beheer- en backgroundactie moet server-side opnieuw worden gecontroleerd tegen de actuele account-, permission-, relatie-, feature- en objectcontext.
Dit hoofdstuk vult hoofdstuk 04 aan. Hoofdstuk 04 beschrijft identiteit, authenticatie, provisioning en sessieopbouw. Dit hoofdstuk beschrijft wat er daarna gebeurt: welke permissions effectief zijn, welke applicatiecontext geldig is en of de gebruiker een specifieke actie op een specifiek object mag uitvoeren.
5.2 Scope en afbakening
| Onderwerp | In dit hoofdstuk |
|---|---|
| Authenticatie bij Keycloak of externe identity provider | Nee, zie hoofdstuk 04 |
| Interne accountstatus en provisioning | Alleen als input voor autorisatie |
| Rollen als bundels voor permissions | Ja |
| Permissions en RolePermissions | Ja |
| Permissioncache en invalidatie | Ja |
| Declaratieve permission metadata en guards | Ja |
| ASP.NET Core policies | Alleen optioneel implementatiedetail; niet het functionele autorisatiemodel |
| Object- en relatieautorisatie | Ja, als domein-specifieke tweede laag |
| Queryscoping en IDOR-preventie | Ja |
| Frontend-zichtbaarheid van acties | Alleen als afgeleide van server-side permissionchecks |
| Databaseconstraints en soft links | Alleen waar relevant voor autorisatie |
| Logging van geweigerde toegang | Ja, in samenhang met hoofdstuk 19 |
| Beheerinterface voor rollen/permissies | Nee; placeholder Feature 17 story 091 |
5.3 Belangrijke inputbronnen
- Functioneel Ontwerp: rollen, permissies, context en autorisatie
- Ontwerpbron: RBAC-permissieregister
- Ontwerpbron: autorisatiematrix
- Functioneel Ontwerp: functionele beslisregels en uitgangspunten
- Software Requirements Specification: functionele requirements
- Database-informatie: identiteit en autorisatie
- Technisch Ontwerp: identiteit, authenticatie en rolcontext
- Technisch Ontwerp: rolflows technisch
5.4 Ontwerpprincipes
| Principe | Regel |
|---|---|
| Permission-first | Routes, pages, components, menu-items, frontpageblokken en commands controleren tegen expliciete permission-codes. |
| Rollen zijn bundels | Rollen geven rechten alleen via RolePermissions; directe rolchecks voor autorisatie zijn niet toegestaan. |
| Server-side autorisatie | Iedere actie wordt server-side gecontroleerd. UI-state is nooit autoriserend. |
| Least privilege | Een gebruiker krijgt alleen toegang tot de minimale actie en dataset die op dat moment nodig is. |
| Context is expliciet | Autorisatie gebruikt expliciete permission-, relatie-, object- en actiecontext. |
| Objecttoegang boven permission alleen | Een permission geeft nooit automatisch toegang tot alle objecten van dat type. |
| Routes zijn onbetrouwbaar | Routeparameters en querystrings worden beschouwd als gebruikersinput. |
| Queryscoping vóór uitlezen | Data wordt bij voorkeur al in de query beperkt tot de toegestane dataset. |
| Geen gedeeltelijke datalekken | Bij weigering wordt geen naam, antwoorddata, payload, runinhoud of technisch detail gelekt. |
| Hercontrole per actie | Detail, filter, export, live-start en mutatie herhalen autorisatie. |
| Modulegrenzen blijven intact | Autorisatie gebruikt publieke modulecontracts, niet elkaars interne DbContexts of entities. |
| Loggen zonder inhoudslek | Geweigerde toegang wordt herleidbaar gelogd zonder gevoelige payloads. |
5.5 Eigenaarschap en projectafbakening
Autorisatie wordt verdeeld over meerdere technische onderdelen, maar de centrale rollen-, permission-, cache- en contextlogica hoort bij OefenHub.Authorization.
| Project | Verantwoordelijkheid |
|---|---|
OefenHub.Web | Route guards, permission metadata, UI-compositie, zichtbare acties en doorgeven van gebruikersacties aan modulecontracts. Geen autorisatiebron. |
OefenHub.Identity | Interne accountstatus, account lifecycle, provisioningstatus en accountidentiteit als input voor autorisatie. |
OefenHub.Authorization | Rollen, UserRoles, Permissions, RolePermissions, permissioncache, permissionchecks, cache-invalidatie en autorisatiecontracts. |
| Domeinmodules | Objectspecifieke toegang binnen het eigen domein via publieke contracts. |
OefenHub.Scheduling | Autorisatie van jobuitvoering en technische jobcontext, niet de businessregels van domeinacties. |
OefenHub.Infrastructure | Technische integraties en middlewareondersteuning waar nodig. |
OefenHub.Authorization gebruikt schema authorization. Verwijzingen naar gebruikers uit identity worden als cross-module soft links behandeld, tenzij dit via een expliciet Technisch Ontwerp-besluit anders wordt vastgelegd.
5.6 Projectstructuur van OefenHub.Authorization
OefenHub.Authorization/
Contracts/
IOefenHubPermissionService.cs
IOefenHubPermissionCache.cs
IOefenHubPermissionCacheInvalidator.cs
IOefenHubAuthorizationDecisionService.cs
IPermissionMetadataRegistry.cs
Models/
Enums/
Data/
AuthorizationDbContext.cs
Entities/
Role.cs
UserRole.cs
Permission.cs
RolePermission.cs
Configurations/
Migrations/
Models/
Commands/
Queries/
ReadModels/
Enums/
Services/
Interfaces/
Guards/
Metadata/
Events/
Extensions/
| Map | Doel |
|---|---|
Contracts | Publieke autorisatie-ingangen voor Web en andere modules. |
Data | Autorisatie-eigen DbContext, entities, EF-configuraties en migrations. |
Guards | Permission guards voor route/page/component-integratie waar deze niet in Web thuishoren. |
Metadata | Declaratieve permissionmetadata en startup-/testvalidatie. |
Services | Interne implementatie van permission lookup, cache, invalidatie en beslislogica. |
Models/ReadModels | Module-eigen readmodels voor autorisatiebeheer en contextoverzichten. |
Extensions | Dependency-injectionregistratie zoals AddOefenHubAuthorization. |
Implementatieclasses, entities en DbContext zijn standaard internal, tenzij zij expliciet onderdeel zijn van het publieke contract. Andere modules gebruiken alleen types uit Contracts.
5.7 Autorisatielagen
Autorisatie wordt niet als één enkele controle uitgevoerd. Iedere actie doorloopt meerdere lagen.
| Laag | Vraag | Voorbeeld |
|---|---|---|
| Authenticatielaag | Is er een geldige geauthenticeerde gebruiker? | Providerlogin is geslaagd. |
| Accountlaag | Bestaat het interne account en is het actief? | Users.IsActive = true. |
| Permissionlaag | Heeft de gebruiker de vereiste permission? | student-results.read.assigned. |
| Featurelaag | Is de functie beschikbaar? | Vrienden/delen staat aan voor leerling. |
| Objectlaag | Mag deze gebruiker dit object benaderen? | Docent mag alleen eigen niveaucontext zien. |
| Actielaag | Mag deze actie op dit object? | Bekijken wel, wijzigen niet. |
| Datasetlaag | Welke records mogen worden getoond? | Alleen gekoppelde kinderen of eigen leerlingen. |
| Workflowlaag | Mag deze samengestelde actie in deze toestand? | Ticket kan niet rechtstreeks vanuit New worden gesloten. |
Een actie is pas toegestaan wanneer alle relevante lagen slagen.
5.8 Rollen, permissions en samengestelde UI
Een gebruiker kan meerdere rollen hebben, behalve de rol Leerling. De effectieve permissions worden server-side bepaald als distinct union van alle actieve permissions uit alle actieve rollen.
| Situatie | Regel |
|---|---|
| Alleen leerling | Student-permissionbundel actief; leerlingrol exclusief. |
| Leerling gecombineerd met andere rollen | Niet toegestaan; autorisatiecontext ongeldig. |
| Beheerder + docent + ouder/voogd | Alle gekoppelde permissions zijn effectief; frontpage toont blokken in vaste volgorde Beheerder, Docent, Ouder/voogd. |
| Docent + ouder/voogd | Beide blokken/menu's kunnen zichtbaar zijn; objectscope blijft gescheiden. |
| Niet-publieke rol | Alleen via expliciete beheer-/adminflow toegekend of ingetrokken. |
| Geen permissions | Beperkte context of veilige blokkade volgens hoofdstuk 04. |
Rolcontext is geen UI-keuze die blind vertrouwd wordt. Wanneer de frontend een route of blok gebruikt, wordt deze server-side herleid naar de permission- en domeincontexten van het interne account.
5.9 Permission metadata en guards
OefenHub gebruikt eigen declaratieve permissionmetadata als primaire koppeling tussen ingang en autorisatie.
Voorbeelden:
endpoints.MapPost("/teacher/students/{studentId}/results", ...)
.RequireAuthorization()
.RequirePermission("student-results.read.assigned");
<PermissionView Permission="frontpage.view.teacher">
<TeacherFrontPageBlock />
</PermissionView>
<OefenHubPermissionGuard Permission="practice-results.read.own">
...
</OefenHubPermissionGuard>
Regels:
RequireAuthorization()blijft baseline authenticatie afdwingen.RequirePermission(...)of vergelijkbare metadata declareert de OefenHub-permission.- ASP.NET Core policies mogen onder water gebruikt worden, maar zijn niet de primaire functionele bron.
- Iedere gedeclareerde permission-code moet via startup-/testvalidatie bestaan in
Permissions.Codeof seeddata. - Er komt geen grote centrale C#-permissiecatalogus als tweede waarheid.
5.10 Permission service en cache
Permissionchecks lopen via publieke contracts.
public interface IOefenHubPermissionService
{
Task<bool> HasPermissionAsync(
Guid userId,
string permission,
CancellationToken cancellationToken);
Task RequirePermissionAsync(
Guid userId,
string permission,
CancellationToken cancellationToken);
}
public interface IOefenHubPermissionCache
{
Task<IReadOnlySet<string>> GetPermissionsAsync(
Guid userId,
CancellationToken cancellationToken);
}
public interface IOefenHubPermissionCacheInvalidator
{
Task InvalidateUserAsync(Guid userId, CancellationToken cancellationToken);
Task InvalidateUsersInRoleAsync(Guid roleId, CancellationToken cancellationToken);
}
Cachegedrag:
- Bij login probeert OefenHub de effectieve permissions vooraf op te halen en te cachen.
- Iedere check kijkt eerst in
IMemoryCache. - Bij cache miss worden alle actieve permissions uit alle actieve rollen opgehaald.
- Cachewaarde is een distinct set van permission-codes per gebruiker.
- Standaard TTL komt uit
Authorization:PermissionCacheDurationMinutesen is functioneel 60 minuten. IMemoryCacheis voorlopig voldoende; bij multi-instance deployment moetIDistributedCacheof gedeelde invalidatie worden toegevoegd.
Cache-invalidatie is verplicht bij:
UserRolestoevoegen/intrekken/deactiveren;- publieke rolkeuze of publieke rolwijziging;
- account deactiveren, heractiveren of anonimiseren;
RolePermissionstoevoegen/intrekken/deactiveren;Permissionsdeactiveren;- expliciete systeem- of beheeractie om usercache te legen.
5.11 Resource-based authorization en domeinchecks
Permissions zijn onvoldoende om objecttoegang te bepalen. Daarom gebruikt OefenHub na de permissioncheck domein-specifieke objectchecks voor objecten zoals kinderen, leerlingen, niveaus, oefeningen, runs, berichten, tickets en live-sessies.
Conceptueel patroon:
await permissionService.RequirePermissionAsync(
currentUser.UserId,
"student-results.read.assigned",
cancellationToken);
var decision = await teacherStudentAccessService.AuthorizeResultReadAsync(
teacherUserId: currentUser.UserId,
studentUserId: requestedStudentId,
runId: requestedRunId,
cancellationToken);
if (!decision.Allowed)
{
return AccessDeniedResult.From(decision);
}
De domeinservice mag voor objectdata alleen publieke contracts gebruiken van de eigenaarmodule. Een autorisatieservice mag bijvoorbeeld niet rechtstreeks PracticeDbContext of RelationshipsDbContext gebruiken wanneer hij buiten die module leeft.
| Voorbeeld | Toegestane technische route |
|---|---|
| Controleren of een kind actief gekoppeld is aan ouder | Relationships contract. |
| Controleren of een run afgerond is en bij kind hoort | Practice contract. |
| Controleren of docent niveau heeft geautoriseerd | Catalog/Authorization of eigenaarcontract volgens implementatiekeuze. |
| Controleren of ticket van huidige gebruiker is | Support contract. |
5.12 Queryscoping en IDOR-preventie
IDOR-risico's ontstaan wanneer een gebruiker een technisch object-id in een route of request kan aanpassen. Daarom gelden de volgende regels.
| Regel | Toelichting |
|---|---|
| Object-id uit route is alleen input | Het id bewijst geen toegang. |
| Query's worden gescoped | Query bevat direct gebruiker-, permission-, relatie- of contextfilter. |
| Geen eerst ophalen en later verbergen | Bij voorkeur wordt ongeautoriseerde data nooit uit de database geladen. |
| Geen technische ids tonen waar niet nodig | Gebruikers zien functionele context, niet GUID's als herkenningsmiddel. |
| Export en detail hercontroleren | Een eerder getoond overzicht geeft geen blijvende toegang. |
| Oude bookmarks zijn veilig | Oude routes tonen geen gedeeltelijke data wanneer toegang is vervallen. |
5.13 Autorisatie per domein
| Domein | Primaire autorisatiebron |
|---|---|
| Identity | Eigen accountcontext, accountstatus en ondersteunde beheerpermissions. |
| Authorization | Rollen, UserRoles, Permissions, RolePermissions, permissioncache en invalidatie. |
| Relationships | Permissions voor ingang plus actieve relatie, relatietype, uitnodigingsstatus en actorcontext. |
| Catalog | Permissions voor ingang plus niveau-eigendom, collaboratorstatus, docentcontext en actieve oefeningstatus. |
| Practice | Permissions voor ingang plus leerlingcontext, run-eigenaarschap, niveaucontext, relatie- of docentcontext. |
| Communication | Permissions voor ingang plus mailboxparticipant, systeemberichtontvanger, threaddeelnemer, templatebeheer. |
| Support | Permissions voor ingang plus ticketmelder, beheerdercontext, assignment/behandelcontext. |
| LiveMonitoring | Permissions voor ingang plus actieve docent- of ouder-/voogdrelatie, actieve run en livebeschikbaarheid. |
| Admin | Beheerpermissions, expliciete supportcontext en beheeractie. |
| Reporting | Geautoriseerde historische brondata, exportactie en viewercontext. |
| Scheduling | Jobtype, technische jobcontext en domeincontract voor uitvoering. |
5.14 Domeinspecifieke autorisatie
Docent-, ouder-/voogd-, leerling-, beheer-, communicatie-, support-, live- en schedulingautorisatie blijven domeinspecifiek. De permissioncheck is de ingang; de domeincheck bepaalt de concrete scope.
Voorbeelden:
| Flow | Permission | Domeincheck |
|---|---|---|
| Leerlingresultaat lezen door leerling | practice-results.read.own | Run is eigen afgeronde run. |
| Leerlingresultaat lezen door docent | student-results.read.assigned | Docent-leerlingrelatie + niveauautorisatie + run valt binnen scope. |
| Leerlingresultaat lezen door ouder/voogd | student-results.read.children | Actieve GuardianStudent-relatie + run hoort bij kind. |
| Niveau wijzigen door eigenaar | teacher-levels.update.own | Actor is eigenaar van niveau. |
| Niveau wijzigen door collaborator | teacher-levels.update.collaborating | Actor is actieve collaborator met bewerkrecht. |
| Live meekijken door docent | live-monitoring.start.assigned | Actieve run + docent-leerlingrelatie + niveauautorisatie. |
| Live meekijken door ouder/voogd | live-monitoring.start.children | Actieve run + actieve GuardianStudent-relatie. |
| Ticket als beheerder oplossen | tickets.resolve.all | Actieve behandelcontext en statusovergang toegestaan. |
5.15 Zichtbare acties in Web
Web mag zichtbare acties afleiden uit server-side readmodels en permissionchecks, maar zichtbaarheid is nooit de autorisatie zelf.
| UI-situatie | Technische regel |
|---|---|
| Knop verborgen | Gebruiksvriendelijkheid, geen beveiligingsmaatregel. |
| Knop zichtbaar | Command autoriseert opnieuw. |
| Frontpageblok zichtbaar | Route/detailactie autoriseert opnieuw. |
| Menu-item zichtbaar | Page/endpoint autoriseert opnieuw. |
| Link uit oud bericht | Vervolgactie autoriseert opnieuw. |
| Route rechtstreeks geopend | Page composition vraagt geautoriseerd readmodel op. |
| Browser terug/vooruit | Oude clientstate mag toegang niet herstellen. |
| Filterwaarden gewijzigd | Server valideert filters tegen toegestane dataset. |
5.16 Veilige afwijzing
Bij ontbrekende toegang wordt veilig geweigerd. De respons mag geen gevoelige gegevens of technische details lekken.
| Situatie | Responsrichting |
|---|---|
| Niet ingelogd | Redirect naar login of beperkte publieke context. |
| Account niet actief | Veilige accountfout of contactroute. |
| Geen vereiste permission | Generieke toegang-geweigerdafhandeling. |
| Geen objecttoegang | Generieke toegang-geweigerdafhandeling. |
| Object bestaat niet of niet toegankelijk | Geen onderscheid lekken tenzij functioneel veilig. |
| Oude route/bookmark | Geen gedeeltelijke data; terug naar veilige context. |
| Export niet toegestaan | Geen bestand genereren, generieke fout. |
| Live niet beschikbaar | Geen live-data, veilige beschikbaarheidsmelding. |
5.17 Logging van autorisatiebeslissingen
Niet iedere succesvolle permissioncheck hoeft als losse logregel te worden opgeslagen. Geweigerde, verdachte of beheerrelevante beslissingen moeten wel herleidbaar zijn.
Minimale velden bij geweigerde toegang:
| Veld | Doel |
|---|---|
CorrelationId | Koppeling met request, job of workflow. |
UserId | Interne gebruiker als soft reference of geanonimiseerde waarde. |
Permission | Gevraagde permission-code. |
Action | Gevraagde actie wanneer afwijkend van permission. |
ResourceType | Type object, bijvoorbeeld ExerciseRun of Ticket. |
ResourceIdHash | Indien nodig gehasht of beperkt, niet altijd ruwe identifier. |
DecisionCode | Gestandaardiseerde reden, zonder inhoudslek. |
UtcTimestamp | Herleidbaarheid. |
Verboden in autorisatielogs:
- wachtwoorden, tokens of providerclaims met gevoelige inhoud;
- antwoorden of vraagpayloads;
- volledige privéberichtinhoud;
- ticketbeschrijvingen of interne discussie-inhoud;
- kindnamen of runinhoud wanneer toegang ontbreekt;
- volledige PDF- of exportinhoud.
5.18 Publieke contracts
Voorbeelden van publieke autorisatiecontracts:
public interface IOefenHubPermissionService
{
Task<bool> HasPermissionAsync(Guid userId, string permission, CancellationToken cancellationToken);
Task RequirePermissionAsync(Guid userId, string permission, CancellationToken cancellationToken);
}
public interface IOefenHubPermissionCache
{
Task<IReadOnlySet<string>> GetPermissionsAsync(Guid userId, CancellationToken cancellationToken);
}
public interface IOefenHubPermissionCacheInvalidator
{
Task InvalidateUserAsync(Guid userId, CancellationToken cancellationToken);
Task InvalidateUsersInRoleAsync(Guid roleId, CancellationToken cancellationToken);
}
public interface IOefenHubAuthorizationDecisionService
{
Task<OefenHubAccessDecision> AuthorizeAsync(
OefenHubAuthorizationRequest request,
CancellationToken cancellationToken);
}
De exacte interfacevorm mag tijdens implementatie wijzigen, maar de contractuele bedoeling blijft:
- permission server-side bepalen;
- actie en resource expliciet meegeven wanneer domeincontext nodig is;
- geen module-interne entities lekken;
- beslisreden technisch vastleggen zonder gevoelige inhoud;
- Web en domeinmodules alleen publieke contracts laten gebruiken;
- cache-invalidatie beschikbaar stellen voor rol-/permissionmutaties.
5.19 Tests
Autorisatie krijgt eigen testdekking op meerdere niveaus.
| Testtype | Voorbeelden |
|---|---|
| Unit tests | Permissionlookup, cache hit/miss, invalidatie, combinatierollen, student-exclusiviteit. |
| Module integration tests | AuthorizationDbContext, Permissions, RolePermissions, UserRoles, seeddata. |
| Cross-module integration tests | Guardianresultaat, docentgeschiedenis, tickettoegang, live-start. |
| Web/componenttests | Knoppen/blokken zichtbaar/verborgen op basis van permissions. |
| Security tests | IDOR-pogingen, oude bookmarks, route-id manipulatie, onbekende permissionmetadata. |
| Architecture tests | Web geen DbContext, modules alleen Contracts, geen interne entities over grens, geen directe rolchecks voor autorisatie. |
| Regression tests | Intrekken rol/relatie/autorisatie blokkeert vervolgacties direct na cache-invalidatie. |
Minimale scenario's:
| Scenario | Verwachting |
|---|---|
| Login met meerdere rollen | Eén gecachete distinct permissionset. |
| Permissioncache verlopen | Volgende check laadt opnieuw uit DB. |
| UserRole ingetrokken | Cache voor gebruiker wordt geleegd en toegang vervalt. |
| RolePermission later gewijzigd | Cache van gebruikers in die rol wordt geleegd. |
| Endpoint declareert onbekende permission | Startup-/testvalidatie faalt. |
| Ouder verliest relatie en opent oude runlink | Geen resultaatdata. |
| Docent opent run buiten eigen niveauautorisatie | Toegang geweigerd. |
| Beheerder probeert live mee te kijken | Niet toegestaan als reguliere livefunctie. |
| PDF-export via oude link | Opnieuw autoriseren, anders geen bestand. |
| SignalR reconnect na ingetrokken toegang | Livegroep verlaten of geen updates. |
5.20 Implementatiechecklist
Bij iedere nieuwe route, command, query, export of job moet minimaal worden gecontroleerd:
- welke permission vereist is;
- of de permission in het RBAC-permissieregister en seeddata bestaat;
- welk object of welke dataset wordt benaderd;
- welke module eigenaar is van de domeincheck;
- welk publiek contract de controle uitvoert;
- of routeparameters opnieuw worden gevalideerd;
- of queryscoping vóór uitlezen gebeurt;
- of oude clientstate/bookmarks veilig falen;
- of geweigerde toegang geen inhoud lekt;
- of logging een correlation-id bevat;
- of cache-invalidatie nodig is bij mutatie;
- of tests bestaan voor toegestane én geweigerde toegang;
- of Software Requirements Specification, Functioneel Ontwerp, permissionregister en database-informatie aangepast moeten worden bij nieuwe functionele regels.
5.21 Implementatieverificaties
| Punt | Toelichting |
|---|---|
| Permissionmetadata | Exacte helpernamen zoals RequirePermission, RequiresPermission, PermissionView en OefenHubPermissionGuard vastleggen bij implementatie. |
| AuthorizationDbContext-inhoud | Permissions en RolePermissions toevoegen, inclusief constraints, seeddata en migraties. |
| Claimsmapping | Providerclaims blijven geen bron voor permissions; interne Users.Id is ingang voor permissionlookup. |
| Resource-id logging | Bepalen wanneer ruwe ids, hashes of beperkte referenties gelogd worden. |
| Featuretoggle-integratie | Exacte technische koppeling tussen featuretoggles en permissionbeslissingen bepalen. |
| SignalR authorization | Hub- en group-autorisatie opnieuw testen met permissions. |
| TickerQ technische actor | Representatie van scheduler/system actor vastleggen. |
| Access denied storage | Bepalen of bepaalde securityevents alleen in technische logs of ook persistent worden opgeslagen. |
| Rollen-/permissionbeheer UI | Buiten scope; placeholder Feature 17 story 091. |