Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions docs/reference/providers/provider-entraID.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import EntraLeaver from '@site/../examples/workflows/templates/entraid-leaver.ps
## Summary

- **Module:** `IdLE.Provider.EntraID`
- **What it’s for:** Entra ID user lifecycle + group entitlements (Microsoft Graph API)
- **What it’s for:** Entra ID user lifecycle + group and Administrative Unit entitlements (Microsoft Graph API)
- **Targets:** Microsoft Entra ID (formerly Azure AD) via Microsoft Graph (v1.0)

## When to use

Use this provider when your workflow needs to manage **Entra ID user accounts**, for example:

- **Joiner:** create or update a user, set baseline attributes, assign baseline groups
- **Joiner:** create or update a user, set baseline attributes, assign baseline groups and Administrative Units
- **Mover:** update org attributes and managed groups (covered as *optional patterns* inside the Joiner template)
- **Leaver:** disable account, revoke sessions, optional cleanup (groups, delete)
- **Leaver:** disable account, revoke sessions, optional cleanup (groups, Administrative Units, delete)

Non-goals:

Expand All @@ -32,7 +32,7 @@ Non-goals:
### Requirements

- Your runtime must be able to supply a **Microsoft Graph auth session** (token/session object) to IdLE
- Graph permissions must allow the actions you intend to run (users + groups)
- Graph permissions must allow the actions you intend to run (users, groups, Administrative Units)

### Install (PowerShell Gallery)

Expand Down Expand Up @@ -127,14 +127,22 @@ Writes to scoped path: `Request.Context.Providers.<ProviderAlias>.<AuthSessionKe
Engine-defined View: `Request.Context.Views.Identity.Entitlements`
Type: `object[]` (array of `PSCustomObject`, `PSTypeName = 'IdLE.Entitlement'`)

Each element represents one Entra ID group membership:
Each element represents one entitlement (group membership or Administrative Unit membership):

**Group entitlements (`Kind = 'Group'`):**

| Property | Type | Notes |
| --- | --- | --- |
| `PSTypeName` | `string` | Always `IdLE.Entitlement`. |
| `Kind` | `string` | Always `Group`. |
| `Id` | `string` | Entra group `id`. |
| `Mail` | `string` or `$null` | Group `mail` (if returned by the adapter). |
| `Id` | `string` | Entra group object ID (GUID). |
| `Mail` | `string` or `$null` | Group `mail` (if returned by Graph). |

**Administrative Unit entitlements (`Kind = 'AdministrativeUnit'`):**

| Property | Type | Notes |
| --- | --- | --- |
| `Kind` | `string` | Always `AdministrativeUnit`. |
| `Id` | `string` | Entra Administrative Unit object ID (GUID). |

Notes:
- The output paths are fixed by the engine and cannot be changed.
Expand All @@ -159,10 +167,20 @@ This provider has **no provider-specific option bag**. Configuration is done thr

At minimum, you typically need:
- **Users:** read/write (create/update/disable/delete if enabled)
- **Groups:** read/write memberships (if you use entitlement steps)
- **Groups:** read/write memberships (if you use group entitlement steps)
- **Administrative Units:** read/write memberships — only required when using `Kind = 'AdministrativeUnit'` in entitlement steps. Group-only workflows do not need these permissions because `ListEntitlements` skips AU Graph calls when `Kind = 'Group'` is specified.

Exact permission names depend on your auth model (delegated vs application) and what operations you enable.

| Capability | Permission (Application) |
| --- | --- |
| List/Read users | `User.Read.All` |
| Create/update/disable users | `User.ReadWrite.All` |
| List group memberships | `Group.Read.All` |
| Grant/revoke group memberships | `GroupMember.ReadWrite.All` |
| List AU memberships (`Kind = 'AdministrativeUnit'`) | `AdministrativeUnit.Read.All` |
| Grant/revoke AU memberships (`Kind = 'AdministrativeUnit'`) | `AdministrativeUnit.ReadWrite.All` |

## Examples (canonical templates)

These are the **two** canonical Entra ID templates, intended to be embedded directly in documentation.
Expand All @@ -178,3 +196,5 @@ Mover scenarios are integrated as **optional patterns** in the Joiner template.
- **Auth session not found**: check `AuthSessionName` matches your runtime/broker configuration.
- **Delete doesn’t work**: deletion is opt-in. Create the provider with `-AllowDelete` and only use delete with a privileged auth role.
- **Group cleanup is disruptive**: only enable revoke/remove operations when you fully understand the impact (prefer managed allow-lists).
- **Administrative Unit not found**: the AU must exist in Entra before the workflow runs. When referencing by objectId, confirm the GUID is correct. When referencing by displayName, confirm the name matches exactly and `AdministrativeUnit.Read.All` permission is granted.
- **Multiple AUs match displayName**: AU display names are not unique in Entra. If multiple AUs share the same name, use the objectId (GUID) instead to ensure deterministic lookup.
Comment thread
ntt-matthias-fleschuetz marked this conversation as resolved.
138 changes: 101 additions & 37 deletions examples/workflows/templates/entraid-joiner.psd1
Comment thread
ntt-matthias-fleschuetz marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@{
Name = 'EntraID Joiner - Complete Onboarding'
LifecycleEvent = 'Joiner'
Description = 'Creates or updates an Entra ID user with baseline attributes and group memberships. Includes optional mover patterns.'
Description = 'Creates or updates an Entra ID user with baseline attributes, group memberships, and Administrative Unit assignments. Includes optional mover patterns.'

Steps = @(
@{
Expand All @@ -10,9 +10,8 @@
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }

# Using UPN keeps it human-friendly in templates.
IdentityKey = '{{Request.Intent.UserPrincipalName}}'

IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'

Attributes = @{
UserPrincipalName = '{{Request.Intent.UserPrincipalName}}'
Expand All @@ -37,29 +36,51 @@
}
}

# Baseline groups: add one EnsureEntitlement step per group.
@{
Name = 'AddToBaselineGroups'
Name = 'AddToAllEmployeesGroup'
Type = 'IdLE.Step.EnsureEntitlement'
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
Entitlement = @{
Kind = 'Group'
Id = '{{Request.Intent.AllEmployeesGroupId}}'
}
State = 'Present'
}
}

# Using UPN keeps it human-friendly in templates.
IdentityKey = '{{Request.Intent.UserPrincipalName}}'
@{
Name = 'AddToDepartmentGroup'
Type = 'IdLE.Step.EnsureEntitlement'
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
Entitlement = @{
Kind = 'Group'
Id = '{{Request.Intent.DepartmentGroupId}}'
}
State = 'Present'
}
}

# Baseline groups should be explicit and driven by request input (no hardcoding).
Desired = @(
@{
Kind = 'Group'
Id = '{{Request.Intent.AllEmployeesGroupId}}'
DisplayName = '{{Request.Intent.AllEmployeesGroupName}}'
}
@{
Kind = 'Group'
Id = '{{Request.Intent.DepartmentGroupId}}'
DisplayName = '{{Request.Intent.DepartmentGroupName}}'
}
)
# Baseline Administrative Unit: controls which scoped admins can manage this user.
# AUs can be referenced by their GUID objectId or by displayName (tenant-unique names only).
@{
Name = 'AddToDepartmentAdministrativeUnit'
Type = 'IdLE.Step.EnsureEntitlement'
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
Entitlement = @{
Kind = 'AdministrativeUnit'
Id = '{{Request.Intent.DepartmentAdministrativeUnitId}}'
Comment thread
ntt-matthias-fleschuetz marked this conversation as resolved.
}
State = 'Present'
}
}

Expand All @@ -69,7 +90,7 @@
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.Intent.UserPrincipalName}}'
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
}
}

Expand All @@ -95,19 +116,20 @@
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.Intent.UserPrincipalName}}'
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'

Attributes = @{
Department = '{{Request.Intent.NewDepartment}}'
JobTitle = '{{Request.Intent.NewJobTitle}}'
OfficeLocation = '{{Request.Intent.NewOfficeLocation}}'
Manager = '{{Request.Intent.NewManagerObjectId}}'
JobTitle = '{{Request.Intent.NewJobTitle}}'
OfficeLocation = '{{Request.Intent.NewOfficeLocation}}'
Manager = '{{Request.Intent.NewManagerObjectId}}'
}
}
}

# Add one EnsureEntitlement step per group change required on a mover.
@{
Name = 'Mover_AdjustManagedGroups'
Name = 'Mover_AddToDepartmentGroup'
Type = 'IdLE.Step.EnsureEntitlement'
Condition = @{
All = @(
Expand All @@ -122,30 +144,72 @@
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.Intent.UserPrincipalName}}'
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
Entitlement = @{
Kind = 'Group'
Id = '{{Request.Intent.DepartmentGroupId}}'
}
State = 'Present'
}
}

# Optional: add department/project groups as part of a move.
Desired = @(
@{
Name = 'Mover_AddToProjectGroup'
Type = 'IdLE.Step.EnsureEntitlement'
Condition = @{
All = @(
@{
Kind = 'Group'
Id = '{{Request.Intent.DepartmentGroupId}}'
DisplayName = '{{Request.Intent.DepartmentGroupName}}'
Equals = @{
Path = 'Request.Intent.IsMover'
Value = $true
}
}
)
}
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
Entitlement = @{
Kind = 'Group'
Id = '{{Request.Intent.ProjectGroupId}}'
}
State = 'Present'
}
}

# Reassign to the new department's Administrative Unit on a move.
@{
Name = 'Mover_AddToNewDepartmentAdministrativeUnit'
Type = 'IdLE.Step.EnsureEntitlement'
Condition = @{
All = @(
@{
Kind = 'Group'
Id = '{{Request.Intent.ProjectGroupId}}'
DisplayName = '{{Request.Intent.ProjectGroupName}}'
Equals = @{
Path = 'Request.Intent.IsMover'
Value = $true
}
}
)
}
With = @{
AuthSessionName = 'MicrosoftGraph'
AuthSessionOptions = @{ Role = 'Admin' }
IdentityKey = '{{Request.IdentityKeys.UserPrincipalName}}'
Entitlement = @{
Kind = 'AdministrativeUnit'
Id = '{{Request.Intent.NewDepartmentAdministrativeUnitId}}'
}
State = 'Present'
}
}

@{
Name = 'EmitCompletionEvent'
Type = 'IdLE.Step.EmitEvent'
With = @{
Message = 'EntraID user {{Request.Intent.UserPrincipalName}} created/updated successfully.'
Message = 'EntraID user {{Request.IdentityKeys.UserPrincipalName}} created/updated successfully.'
}
}
)
}
}
Loading
Loading