AutoMate Technisch Help

Access Control V2

Access Control V2 is de opvolger van de Persona-gebaseerde resource-toewijzing. Het koppelt medewerkers automatisch én handmatig aan Microsoft 365 resources — shared mailboxes, enterprise apps, Teams-groepen en security groups — op basis van organisatieattributen uit het OrgModel.

Het belangrijkste architecturele verschil met Persona's: Access Control V2 gebruikt inheritance (overerving langs de boomstructuur van root naar blad), terwijl Persona's het deepest-match-wins principe hanteren. Dit maakt cumulatieve resource-toewijzing over meerdere niveaus van een organisatiehiërarchie mogelijk.

Architectuur

Drie strategieën in één designer

Access Control V2 ondersteunt drie toewijzingsstrategieën, die allemaal samenkomen in één visuele boomdesigner:

Strategie

Mechanisme

Beschrijving

ABAC

Condition nodes

Attributen uit Entra (Department, JobTitle, City, etc.) bepalen matching. Resources op elke laag erven over naar alle onderliggende niveaus.

TBAC

Task nodes

Handmatige toewijzing van medewerkers aan taken of rollen (bijv. BHV, EHBO). Assignees staan direct op de node.

RBAC

Via ABAC

Impliciet ondersteund doordat condition nodes op functie/rol kunnen filteren.

Inheritance model

De rule tree is een gerichte boom van AccessControlRuleNode-objecten, verbonden via AccessControlRuleEdge-paren (sourceId → targetId). De sync engine loopt van root naar bladeren en verzamelt resources op elk gematchte niveau — er is geen vroeg stoppen zoals bij Persona's.

Root (Access Control Rules) ├── Resources: [TeamsGroup: "Alle Medewerkers"] ← geldt voor IEDEREEN │ └── UserType = "In dienst" ├── Resources: [SharedMailbox: "info@"] ← alle actieve medewerkers │ ├── Department = "IT" │ ├── Resources: [EnterpriseApp: "Jira"] ← IT-afdeling │ │ │ └── JobTitle = "Manager" │ └── Resources: [EnterpriseApp: "Azure"] ← IT-managers │ └── [Taak: "BHV"] ├── Assignees: [jan@, piet@] └── Resources: [TeamsGroup: "BHV Team"] ← handmatige toewijzing

Voorbeeld — Jan (IT, Manager, BHV-medewerker): TeamsGroup "Alle Medewerkers" + SharedMailbox "info@" + EnterpriseApp "Jira" + EnterpriseApp "Azure" + TeamsGroup "BHV Team" = 5 resources

Vier resource types

Type

Provisioning

Graph/Exchange API

SharedMailbox

Full Access + optioneel Send As

Exchange Admin API via IEntraUserService

EnterpriseApp

App Role Assignment

POST /v1.0/servicePrincipals/{id}/appRoleAssignedTo

TeamsGroup

M365 Group membership

POST /v1.0/groups/{id}/members/$ref

SecurityGroup

Entra Security Group membership

POST /v1.0/groups/{id}/members/$ref

Datamodel

Domain objecten

// De volledige rule tree — serialized als JSON in AccessControl_RuleVersions.Payload public sealed class AccessControlRuleTree { public List<AccessControlRuleNode> Nodes { get; set; } = []; public List<AccessControlRuleEdge> Edges { get; set; } = []; public AccessControlRuleNode? GetRootNode() => Nodes.FirstOrDefault(n => n.Type == "root"); public IReadOnlyList<AccessControlRuleNode> GetChildren(Guid nodeId) { ... } } // Eén node in de boom — condition, task of root public sealed class AccessControlRuleNode { public Guid Id { get; set; } public Guid? ParentId { get; set; } public string Type { get; set; } = "condition"; // "root" | "condition" | "task" public int? AttributeTypeId { get; set; } // OrgModel attribuut type ID public string? AttributeTypeName { get; set; } // e.g. "UserType", "Department" public string? AttributeValue { get; set; } // e.g. "In dienst", "IT" public List<Guid> PolicyIds { get; set; } = []; public List<NodeResource> Resources { get; set; } = []; // single source of truth public string? TaskName { get; set; } // voor task nodes public List<NodeAssignee>? Assignees { get; set; } public NodePosition Position { get; set; } = new(); } // Resource gekoppeld aan een node public sealed class NodeResource { public string ResourceType { get; set; } = string.Empty; // SharedMailbox | EnterpriseApp | TeamsGroup | SecurityGroup public string ResourceId { get; set; } = string.Empty; public string ResourceDisplayName { get; set; } = string.Empty; public string? AppRoleId { get; set; } public bool FullAccess { get; set; } = true; public bool SendAs { get; set; } }

Database tabellen (UserTypeDb)

Migratie 083_Create_AccessControlV2_Tables_UserTypeDb.cs (versienummer 20260417120000) maakt 9 tabellen aan:

Tabel

Doel

AccessControl_Policies

Policy definities per tenant

AccessControl_RuleVersions

Versioned JSON payloads (draft/active semantiek)

AccessControl_RoleMappings

RBAC platte mappings (legacy, sync leest uit tree)

AccessControl_TaskDefinitions

TBAC taakdefinities (legacy, sync leest uit tree)

AccessControl_TaskAssignments

TBAC handmatige toewijzingen (legacy, sync leest uit tree)

AccessControl_ResourceLinks

Resource-koppelingen aan policies (legacy, sync leest uit tree)

AccessControl_Assignments

Berekende toewijzingen per medewerker per policy

AccessControl_ResourceState

Actueel geprovisioneerde staat per medewerker per resource

AccessControl_SyncRuns

Audit trail van sync-uitvoeringen

Belangrijk: De sync engine leest resources uitsluitend uit de JSON payload in AccessControl_RuleVersions. De AccessControl_ResourceLinks, AccessControl_RoleMappings en AccessControl_TaskAssignments tabellen zijn achtergelaten als legacy structuur. De tree is de single source of truth.

AccessControl_ResourceState gebruikt twee gefilterde unique indexes om de combinatie (TenantId, EmployeeUpn, ResourceType, ResourceId, AppRoleId) uniek te houden — één voor rijen met AppRoleId IS NOT NULL en één voor rijen met AppRoleId IS NULL, omdat SQL Server geen standaard unique constraint met nullable kolommen ondersteunt.

Rule matching engine

AccessControlRuleMatchingService implementeert de inheritance walk. De centrale methode is ComputeEffectiveResources:

public EffectiveResourceResult ComputeEffectiveResources( AccessControlRuleTree ruleTree, JsonElement employeeData, string employeeUpn)

Algoritme:

ja

ja

nee

nee

nee

ja

nee

ja

meer children

klaar

Start bij root node

Verzamel resources van root node

Itereer over child nodes

Type = task?

Medewerker in assignees?

Verzamel resources van task node

NextChild

attributeTypeName + attributeValue aanwezig?

Medewerker attribuut matcht?

Verzamel resources van condition node

Recursief verder in de boom

Dedupliceer op ResourceType + ResourceId + AppRoleId

EffectiveResourceResult

Attribuutmatching is case-insensitive. De waarde van het OrgModel-attribuut wordt opgehaald uit het Entra-gebruikersobject via property name matching (bijv. "Department"department veld in Graph API response).

Sync engine

Bestanden

Bestand

Verantwoordelijkheid

Services/AccessControlResourceSyncService.cs

Core sync loop: desired state berekenen, diff uitvoeren, provisioners aanroepen

Services/AccessControlRuleMatchingService.cs

Rule tree walk met inheritance

Services/ResourceProvisioners/

Vier concrete provisioners plus IResourceProvisioner interface

Sync flow (tenant-brede sync)

ja

nee

ja

nee

meer medewerkers

klaar

AccessControlTenantSyncJob.RunAsync

Laad actieve RuleVersion uit DB

Deserializeer JSON payload naar AccessControlRuleTree

Haal alle actieve medewerkers op via Graph API\n/v1.0/users?$top=999&accountEnabled eq true

Bereken desired state per medewerker\nComputeDesiredStateFromTree

Laad current state uit AccessControl_ResourceState

Diff: desired vs current per medewerker

Resources toe te voegen?

IResourceProvisioner.GrantAccessAsync

UpsertResourceStateAsync

Resources te verwijderen?

IResourceProvisioner.RevokeAccessAsync

DeleteResourceStateAsync

NextEmployee

Medewerkers in current state maar niet meer in Entra\nDeprovisioner alle resources

Update AccessControl_SyncRuns met statistieken

Klaar

Resource provisioners

Alle provisioners implementeren IResourceProvisioner:

public interface IResourceProvisioner { string ResourceType { get; } Task GrantAccessAsync(Guid tenantId, string employeeObjectId, string resourceId, string? appRoleId, CancellationToken ct); Task RevokeAccessAsync(Guid tenantId, string employeeObjectId, string resourceId, string? appRoleId, CancellationToken ct); }

Provisioners zijn idempotent: een 409 Conflict (al toegewezen) bij Grant en een 404 Not Found (al verwijderd) bij Revoke worden stil genegeerd en als succes beschouwd.

EnterpriseAppProvisioner — Graph API:

  • Grant: POST /v1.0/servicePrincipals/{resourceId}/appRoleAssignedTo met { principalId, resourceId, appRoleId }

  • Revoke: zoek eerst de assignment op via filter, dan DELETE /v1.0/servicePrincipals/{resourceId}/appRoleAssignedTo/{assignmentId}

  • Wanneer appRoleId null is, wordt 00000000-0000-0000-0000-000000000000 gebruikt (default app role)

SharedMailboxProvisioner — Exchange Admin API via IEntraUserService:

  • Grant: IEntraUserService.GrantMailboxDelegateAccessAsync

  • Revoke: IEntraUserService.RevokeMailboxDelegateAccessAsync

  • Requires Exchange Administrator role op de service principal

TeamsGroupProvisioner / SecurityGroupProvisioner — Graph API:

  • Grant: POST /v1.0/groups/{groupId}/members/$ref met { "@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/{userId}" }

  • Revoke: DELETE /v1.0/groups/{groupId}/members/{userId}/$ref

  • Teams-groepen staan geen nesting van security groups toe — individuele users worden toegevoegd

DI-registratie

// AccessControlSetup.cs services.AddScoped<IAccessControlRepository, AccessControlRepository>(); services.AddScoped<IAccessControlPolicyService, AccessControlPolicyService>(); services.AddScoped<IAccessControlRuleMatchingService, AccessControlRuleMatchingService>(); services.AddScoped<IAccessControlRuleVersionsService, AccessControlRuleVersionsService>(); services.AddScoped<IAccessControlResourceSyncService, AccessControlResourceSyncService>(); services.AddScoped<IAccessControlAuditService, AccessControlAuditService>(); // Provisioners — IEnumerable<IResourceProvisioner> in AccessControlResourceSyncService services.AddScoped<IResourceProvisioner, SharedMailboxProvisioner>(); services.AddScoped<IResourceProvisioner, EnterpriseAppProvisioner>(); services.AddScoped<IResourceProvisioner, TeamsGroupProvisioner>(); services.AddScoped<IResourceProvisioner, SecurityGroupProvisioner>();

Hangfire jobs

Job

Display name

Schedule / Trigger

AccessControlSyncDispatcherJob

Access Control Sync Dispatcher

Dagelijks 07:00 + 19:00 UTC (0 7,19 * * *)

AccessControlTenantSyncJob

Access Control Tenant Sync - ()

Per tenant, ge-enqueued door dispatcher, manual trigger, publish, HR sync

AccessControlSingleEmployeeSyncJob

Per medewerker, ge-enqueued door UpdateEmployeeCommandHandler en CreateEmployeeCommandHandler

AccessControlChangedWorkflowTriggerJob

Na resource-wijzigingen (nu stub, nog niet geïmplementeerd)

Registratie in Program.cs:

recurring.AddOrUpdate<AccessControlSyncDispatcherJob>( "accesscontrol-sync-dispatcher", x => x.RunAsync(), "0 7,19 * * *", recurringJobOptions);

Dispatcher flow:

per tenant

Cron: 07:00 / 19:00 UTC

AccessControlSyncDispatcherJob

AccessControlTenantSyncJob\ntriggerSource: Scheduled

POST /usertypes/accesscontrol/sync

AccessControlTenantSyncJob\ntriggerSource: Manual

POST /usertypes/accesscontrol/rules/publish

AccessControlTenantSyncJob\ntriggerSource: Publish

IamEmployeeTriggerWorkflowsJob

AccessControlTenantSyncJob\ntriggerSource: HrSync

UpdateEmployeeCommandHandler\nCreateEmployeeCommandHandler

AccessControlSingleEmployeeSyncJob

AccessControlTenantSyncJob heeft een ingebouwde timeout van 30 minuten. De job gebruikt multiTenantService.SetTenantId(tenantId) om de tenant context in te stellen voor de duur van de uitvoering.

API endpoints

Base path: /usertypes/accesscontrol

Alle endpoints verkrijgen de tenant ID via de X-Tenant-Id header via IMultiTenantService.GetTenantId(). Het tenant ID staat nooit in de request body.

Policies

Methode

Pad

Beschrijving

GET

/policies

Alle policies voor de tenant

POST

/policies

Policy aanmaken

GET

/policies/{id}

Enkele policy

PUT

/policies/{id}

Policy bijwerken

DELETE

/policies/{id}

Policy verwijderen

Resources (Graph API lookups + koppelen)

Methode

Pad

Beschrijving

GET

/shared-mailboxes

Shared mailboxes ophalen via Graph

GET

/enterprise-apps

Enterprise apps met app roles

GET

/teams-groups

Teams / M365 groepen

GET

/security-groups

Entra security groups (securityEnabled=true, mailEnabled=false)

GET

/policies/{id}/resources

Resources gekoppeld aan een policy

POST

/policies/{id}/resources

Resource koppelen aan policy

DELETE

/policies/{id}/resources/{linkId}

Resource ontkoppelen

Rules (draft/publish lifecycle)

Methode

Pad

Beschrijving

POST

/rules/draft

Nieuwe draft aanmaken

GET

/rules/draft

Huidige draft ophalen

PUT

/rules/draft

Draft payload opslaan (tree JSON)

DELETE

/rules/draft

Draft verwijderen

POST

/rules/publish

Draft publiceren → activeert + triggert sync

GET

/rules/active

Actieve versie ophalen

GET

/rules/versions

Versiehistorie

Draft/publish semantiek: Per tenant bestaat maximaal één draft. Bij publicatie wordt de draft gemarkeerd als IsActive = true, de vorige actieve versie wordt gedeactiveerd, en AccessControlTenantSyncJob wordt ge-enqueued met triggerSource: "Publish".

RBAC / TBAC

Methode

Pad

Beschrijving

GET

/policies/{id}/role-mappings

Rol mappings ophalen

PUT

/policies/{id}/role-mappings

Rol mappings opslaan

GET

/policies/{id}/task-assignments

Taak-toewijzingen ophalen

POST

/policies/{id}/task-assignments

Medewerker toewijzen aan taak

DELETE

/policies/{id}/task-assignments/{assignmentId}

Toewijzing verwijderen

Sync & monitoring

Methode

Pad

Beschrijving

POST

/sync

Handmatige sync triggeren

GET

/sync-runs

Recente sync-runs ophalen

GET

/assignments

Alle berekende toewijzingen

GET

/policies/{id}/assignments

Toewijzingen per policy

Frontend designer

Route: /usertypes/accesscontrol-v2/rules

De designer is gebouwd met Blazor.Diagrams en bestaat uit drie zones:

Zone

Component

Beschrijving

Sidebar (links)

AccessControlDesignerPage

Twee drag-and-drop blokken: Eigenschap conditie en Taak/Rol

Canvas (midden)

Blazor.Diagrams canvas

Interactief diagram met nodes, edges, zoom en auto-layout

Config panel (rechts)

Inline in AccessControlDesignerPage

Contextgevoelig configuratiepaneel per geselecteerde node

Node widgets:

Component

Nodetyp

Beschrijving

AcRootNodeWidget.razor

root

De wortelknoop — niet verwijderbaar

AcConditionNodeWidget.razor

condition

Attribuutgebaseerde condities (ABAC)

AcTaskNodeWidget.razor

task

Handmatige toewijzingsnodes (TBAC)

Resource picker dialogs:

Dialog

Gebruik

SharedMailboxPickerDialog.razor

Twee-staps: mailbox kiezen + Full Access / Send As permissie

EnterpriseAppPickerDialog.razor

Twee-staps: eerst app kiezen, dan app role selecteren

ResourcePickerDialog.razor

Generiek voor Teams-groepen, security groups en medewerkers (assignees)

Overgeerfde resources worden in het config panel visueel onderscheiden: grayed-out stijl, slotpictogram, gestippelde rand. Ze zijn niet aanpasbaar via de huidige node.

Graph API permissions

De service principal van de tenant heeft de volgende permissions nodig:

Permission

Type

Gebruik

User.Read.All

Application

Medewerkerdata ophalen uit Entra

Group.ReadWrite.All

Application

Security / Teams group membership beheren

AppRoleAssignment.ReadWrite.All

Application

Enterprise app role assignments

Application.Read.All

Application

Enterprise apps en rollen ophalen

Exchange Administrator

Directory role

Shared mailbox permissions via Exchange Admin API

Bekende beperkingen en TODO's

Item

Status

Beschrijving

EvaluateImpact endpoint

Stub (directory aanwezig)

Impact preview vóór publicatie — berekent hoeveel medewerkers resources krijgen of verliezen

AccessControlChangedWorkflowTriggerJob

Stub

Moet na elke sync een workflow triggeren als resources zijn gewijzigd

Publish validatie

Ontbreekt

Valideer tree structuur en controleer of alle nodes geconfigureerd zijn vóór publicatie

Graph API pagination

Hardcoded $top=999

Bij tenants met meer dan 999 gebruikers worden niet alle medewerkers gesynchroniseerd

Vergelijking met Persona's

Aspect

Persona's

Access Control V2

Toewijzingsmodel

Deepest match wins

Inheritance (alle matched niveaus)

Resources

Assets, licenties, templates

M365 resources (mailbox, app, groep)

Handmatige toewijzing

Niet ondersteund

Task nodes met assignees

Provisioning

Workflow-gebaseerd

Direct via Graph/Exchange API

Versioning

Versioned OrgModel tree

Versioned rule tree (draft/active)

Designer

Blazor.Diagrams

Blazor.Diagrams (zelfde component library)

Last modified: 23 April 2026