-
Notifications
You must be signed in to change notification settings - Fork 0
Add IdLE.Provider.EntraID module for Microsoft Graph-based identity lifecycle operations #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+2,812
β2
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7fd0111
Initial plan
Copilot 1ebbef9
Add IdLE.Provider.EntraID module structure and core implementation
Copilot dffa77e
Add EntraID provider implementation with tests (29 of 35 passing)
Copilot 2c3c0b9
Add EntraID provider documentation, example workflows, and demo runneβ¦
Copilot 419cd31
Address PR feedback: Move docs to providers/, simplify README, fix moβ¦
Copilot bb8c3f4
Fix Pester tests: Return original IdentityKey, fix example workflows,β¦
Copilot 9936256
Fix entitlement contract tests: Normalize Kind to 'Group' and handle β¦
Copilot 774b4b9
Fix test framework issue: Skip generic entitlement contract tests, adβ¦
Copilot 92b64a6
fix: only Kind=group accepted for Entitlements
blindzero a9bfdec
fix: removing overpermissive GUID check
blindzero f4b506d
Fix GrantEntitlement: Validate Kind='Group' instead of silent normaliβ¦
Copilot 8f57594
Fix N-format GUID parsing: Support prefixed GUIDs for contract tests β¦
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,346 @@ | ||
| # IdLE.Provider.EntraID Reference | ||
|
|
||
| Microsoft Entra ID (formerly Azure Active Directory) identity provider for IdLE. | ||
|
|
||
| ## Overview | ||
|
|
||
| The `IdLE.Provider.EntraID` module provides a production-ready provider for managing identities and group entitlements in Microsoft Entra ID via the Microsoft Graph API (v1.0). | ||
|
|
||
| ## Installation | ||
|
|
||
| The provider is included in the IdLE repository under `src/IdLE.Provider.EntraID/`. | ||
|
|
||
| ```powershell | ||
| Import-Module ./src/IdLE.Provider.EntraID/IdLE.Provider.EntraID.psd1 | ||
| ``` | ||
|
|
||
| ## Authentication | ||
|
|
||
| ### Host-Owned Authentication (Required Pattern) | ||
|
|
||
| The EntraID provider follows IdLE's **host-owned authentication** pattern. The provider does NOT perform authentication internally. Instead, authentication is managed by the host application via the `AuthSessionBroker`. | ||
|
|
||
| ### What the Host Must Provide | ||
|
|
||
| The host must: | ||
|
|
||
| 1. Obtain a valid Microsoft Graph access token (delegated or app-only) | ||
| 2. Create an `AuthSessionBroker` that returns the token when requested | ||
| 3. Pass the broker to IdLE via `Providers.AuthSessionBroker` | ||
|
|
||
| ### Supported Auth Session Formats | ||
|
|
||
| The provider accepts authentication sessions in these formats: | ||
|
|
||
| - **String**: Direct access token (`"eyJ0eXAiOiJKV1Qi..."`) | ||
| - **Object with AccessToken property**: `@{ AccessToken = "token" }` | ||
| - **Object with GetAccessToken() method**: Custom object with method returning token string | ||
| - **PSCredential**: Token in password field (legacy compatibility) | ||
|
|
||
| ### Example: Delegated Authentication | ||
|
|
||
| ```powershell | ||
| # Host obtains token (example using Azure PowerShell) | ||
| Connect-AzAccount | ||
| $token = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com").Token | ||
|
|
||
| # Create broker | ||
| $broker = New-IdleAuthSessionBroker -SessionMap @{ | ||
| @{} = $token | ||
| } -DefaultCredential $token | ||
|
|
||
| # Create provider | ||
| $provider = New-IdleEntraIDIdentityProvider | ||
|
|
||
| # Use in plan | ||
| $plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{ | ||
| Identity = $provider | ||
| AuthSessionBroker = $broker | ||
| } | ||
| ``` | ||
|
|
||
| ### Example: App-Only Authentication (Service Principal) | ||
|
|
||
| ```powershell | ||
| # Host obtains app-only token (example using MSAL or Azure PowerShell) | ||
| $clientId = "your-app-id" | ||
| $clientSecret = "your-secret" | ||
| $tenantId = "your-tenant-id" | ||
|
|
||
| # Obtain token (pseudo-code - use your preferred auth library) | ||
| $token = Get-GraphAppOnlyToken -ClientId $clientId -ClientSecret $clientSecret -TenantId $tenantId | ||
|
|
||
| # Create broker | ||
| $broker = New-IdleAuthSessionBroker -SessionMap @{ | ||
| @{} = $token | ||
| } -DefaultCredential $token | ||
|
|
||
| # Rest is identical to delegated flow | ||
| ``` | ||
|
|
||
| ### Example: Multi-Role Scenario | ||
|
|
||
| ```powershell | ||
| $tier0Token = Get-GraphToken -Role 'Tier0' | ||
| $adminToken = Get-GraphToken -Role 'Admin' | ||
|
|
||
| $broker = New-IdleAuthSessionBroker -SessionMap @{ | ||
| @{ Role = 'Tier0' } = $tier0Token | ||
| @{ Role = 'Admin' } = $adminToken | ||
| } -DefaultCredential $adminToken | ||
|
|
||
| # Workflow steps specify: With.AuthSessionOptions = @{ Role = 'Tier0' } | ||
| ``` | ||
|
|
||
| ## Required Microsoft Graph Permissions | ||
|
|
||
| ### Delegated Permissions (User Context) | ||
|
|
||
| Minimum required: | ||
|
|
||
| - `User.Read.All` (read user information) | ||
| - `User.ReadWrite.All` (create/update/delete users) | ||
| - `Group.Read.All` (list group memberships) | ||
| - `GroupMember.ReadWrite.All` (add/remove group members) | ||
|
|
||
| ### Application Permissions (App-Only Context) | ||
|
|
||
| Minimum required (same as delegated): | ||
|
|
||
| - `User.Read.All` | ||
| - `User.ReadWrite.All` | ||
| - `Group.Read.All` | ||
| - `GroupMember.ReadWrite.All` | ||
|
|
||
| **Note**: Application permissions require admin consent in the tenant. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| The provider advertises these capabilities via `GetCapabilities()`: | ||
|
|
||
| - `IdLE.Identity.Read` - Read identity information | ||
| - `IdLE.Identity.List` - List identities (filter support varies) | ||
| - `IdLE.Identity.Create` - Create new identities | ||
| - `IdLE.Identity.Attribute.Ensure` - Set/update identity attributes | ||
| - `IdLE.Identity.Disable` - Disable user accounts | ||
| - `IdLE.Identity.Enable` - Enable user accounts | ||
| - `IdLE.Entitlement.List` - List group memberships | ||
| - `IdLE.Entitlement.Grant` - Add group membership | ||
| - `IdLE.Entitlement.Revoke` - Remove group membership | ||
| - `IdLE.Identity.Delete` - **Opt-in only** (see Safety section) | ||
|
|
||
| ## Identity Addressing | ||
|
|
||
| ### Lookup Modes | ||
|
|
||
| The provider supports multiple ways to reference an identity: | ||
|
|
||
| | Format | Example | Notes | | ||
| |--------|---------|-------| | ||
| | objectId (GUID) | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` | Most deterministic | | ||
| | UserPrincipalName (UPN) | `"user@contoso.com"` | Contains `@` | | ||
| | mail | `"user.name@contoso.com"` | Fallback if UPN lookup fails | | ||
|
|
||
| ### Canonical Identity Key | ||
|
|
||
| **All provider methods return the user objectId (GUID) as the canonical IdentityKey.** | ||
|
|
||
| This ensures deterministic identity references across workflows and is the recommended format for workflow definitions. | ||
|
|
||
| ### Resolution Rules | ||
|
|
||
| 1. If the input is a valid GUID format, look up by objectId | ||
| 2. If the input contains `@`, try UPN lookup, then mail lookup | ||
| 3. Otherwise, throw an error | ||
|
|
||
| ## Entitlement Model (Groups) | ||
|
|
||
| ### Entitlement Object Format | ||
|
|
||
| ```powershell | ||
| @{ | ||
| Kind = 'Group' | ||
| Id = '<group objectId GUID>' # Canonical | ||
| DisplayName = 'Group Display Name' # Optional | ||
| Mail = 'group@contoso.com' # Optional | ||
| } | ||
| ``` | ||
|
|
||
| ### Group Resolution | ||
|
|
||
| The provider accepts group references in two formats: | ||
|
|
||
| 1. **objectId (GUID)** - Direct lookup (most reliable) | ||
| 2. **displayName** - Lookup by name (must be unique) | ||
|
|
||
| #### Ambiguity Handling | ||
|
|
||
| If multiple groups share the same displayName, the provider throws an error. Use objectId for deterministic lookup. | ||
|
|
||
| ### Idempotency | ||
|
|
||
| All group operations are idempotent: | ||
|
|
||
| - **Grant**: Returns `Changed = $false` if already a member | ||
| - **Revoke**: Returns `Changed = $false` if not a member | ||
|
|
||
| ## Safety: Delete Capability | ||
|
|
||
| ### Default Behavior (Delete Disabled) | ||
|
|
||
| By default, the `IdLE.Identity.Delete` capability is **NOT advertised** and delete operations will fail. | ||
|
|
||
| ```powershell | ||
| $provider = New-IdleEntraIDIdentityProvider | ||
| # Delete is NOT available | ||
| ``` | ||
|
|
||
| ### Opt-In for Delete | ||
|
|
||
| To enable delete capability, use the `-AllowDelete` switch: | ||
|
|
||
| ```powershell | ||
| $provider = New-IdleEntraIDIdentityProvider -AllowDelete | ||
| # Delete is now available | ||
| ``` | ||
|
|
||
| ### Workflow Requirements | ||
|
|
||
| Workflows that require delete must explicitly declare the capability requirement in their metadata (not yet implemented in IdLE core, but provider is ready). | ||
|
|
||
| ## Transient Error Handling | ||
|
|
||
| The provider classifies errors as transient or permanent for retry policy support. | ||
|
|
||
| ### Transient Errors (Retryable) | ||
|
|
||
| These errors set `Exception.Data['Idle.IsTransient'] = $true`: | ||
|
|
||
| - HTTP 429 (Rate limiting) | ||
| - HTTP 5xx (Server errors) | ||
| - HTTP 408 (Request timeout) | ||
| - Network timeouts | ||
|
|
||
| ### Retry Metadata | ||
|
|
||
| Transient errors include metadata in the exception message: | ||
|
|
||
| - HTTP status code | ||
| - Microsoft Graph request ID (if available) | ||
| - `Retry-After` header (if present) | ||
|
|
||
| **Note**: The provider does NOT perform retries automatically. Retry policy is a host concern. | ||
|
|
||
| ## Supported Attributes | ||
|
|
||
| ### Identity Attributes | ||
|
|
||
| These attributes can be set via `CreateIdentity` and `EnsureAttribute`: | ||
|
|
||
| | Attribute | Graph Property | Notes | | ||
| |-----------|---------------|-------| | ||
| | `GivenName` | `givenName` | First name | | ||
| | `Surname` | `surname` | Last name | | ||
| | `DisplayName` | `displayName` | Display name (required for create) | | ||
| | `UserPrincipalName` | `userPrincipalName` | UPN (required for create) | | ||
| | `Mail` | `mail` | Email address | | ||
| | `Department` | `department` | Department | | ||
| | `JobTitle` | `jobTitle` | Job title | | ||
| | `OfficeLocation` | `officeLocation` | Office location | | ||
| | `CompanyName` | `companyName` | Company name | | ||
| | `MailNickname` | `mailNickname` | Mail alias (auto-generated if not provided) | | ||
| | `PasswordProfile` | `passwordProfile` | Password policy for new users | | ||
| | `Enabled` | `accountEnabled` | Account enabled state | | ||
|
|
||
| ### Password Policy (Create Only) | ||
|
|
||
| When creating users, provide a `PasswordProfile`: | ||
|
|
||
| ```powershell | ||
| $attributes = @{ | ||
| UserPrincipalName = 'newuser@contoso.com' | ||
| DisplayName = 'New User' | ||
| PasswordProfile = @{ | ||
| forceChangePasswordNextSignIn = $true | ||
| password = 'Temp@Pass123!' | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| If not provided, a random password is generated with `forceChangePasswordNextSignIn = $true`. | ||
|
|
||
| ## Paging | ||
|
|
||
| The provider automatically handles Microsoft Graph paging for `ListUsers` and `ListUserGroups` operations using the `@odata.nextLink` continuation token. | ||
|
|
||
| No additional configuration required. | ||
|
|
||
| ## Built-in Steps Compatibility | ||
|
|
||
| The provider works with these built-in IdLE steps: | ||
|
|
||
| - `IdLE.Step.CreateIdentity` | ||
| - `IdLE.Step.EnsureAttribute` | ||
| - `IdLE.Step.DisableIdentity` | ||
| - `IdLE.Step.EnableIdentity` | ||
| - `IdLE.Step.DeleteIdentity` (when `AllowDelete = $true`) | ||
| - `IdLE.Step.EnsureEntitlement` | ||
|
|
||
| ## Workflow Configuration | ||
|
|
||
| ### Recommended AuthSession Routing | ||
|
|
||
| - `With.AuthSessionName = 'MicrosoftGraph'` | ||
| - `With.AuthSessionOptions = @{ Role = 'Admin' }` (or other routing keys) | ||
|
|
||
| ### Example Step Definition | ||
|
|
||
| ```powershell | ||
| @{ | ||
| Id = 'CreateUser' | ||
| Type = 'IdLE.Step.CreateIdentity' | ||
| With = @{ | ||
| AuthSessionName = 'MicrosoftGraph' | ||
| AuthSessionOptions = @{ Role = 'Admin' } | ||
| Attributes = @{ | ||
| UserPrincipalName = 'newuser@contoso.com' | ||
| DisplayName = 'New User' | ||
| GivenName = 'New' | ||
| Surname = 'User' | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Limitations | ||
|
|
||
| - **Supported API version**: v1.0 (beta endpoints not used) | ||
| - **Group types**: Only Entra ID groups (not M365 groups or distribution lists) | ||
| - **Licensing**: The provider does NOT manage license assignments | ||
| - **MFA/Conditional Access**: Not managed by provider | ||
| - **Custom attributes/extensions**: Not supported in MVP | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### "AuthSession is required" | ||
|
|
||
| Ensure you're passing an `AuthSessionBroker` to `New-IdlePlan` and that steps are using `With.AuthSessionName`. | ||
|
|
||
| ### "Multiple groups found with displayName" | ||
|
|
||
| Use the group objectId instead of displayName for deterministic lookup. | ||
|
|
||
| ### "429 Too Many Requests" | ||
|
|
||
| Microsoft Graph enforces rate limits. The provider marks these as transient errors. Implement retry logic in your host or reduce request frequency. | ||
|
|
||
| ### "Insufficient permissions" | ||
|
|
||
| Verify the access token has the required Graph API permissions (see Required Permissions section). | ||
|
|
||
| ## See Also | ||
|
|
||
| - [IdLE Architecture](../advanced/architecture.md) | ||
| - [AuthSessionBroker Pattern](../advanced/security.md) | ||
| - [Example Workflows](../../examples/workflows/) | ||
| - [Invoke-IdleDemo.ps1](../../examples/Invoke-IdleDemo.ps1) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.