Goal
Provide a production-usable Microsoft Entra ID provider module so IdLE can run real Joiner/Mover/Leaver workflows out of the box.
Scope
New module
- Create a new provider module:
IdLE.Provider.EntraID
- Backend: Microsoft Graph
- Recommended baseline: direct REST calls via an internal adapter (portable, testable).
- Alternative acceptable implementation: Microsoft Graph PowerShell SDK, but it must be documented and wrapped behind the same adapter contract for unit testing.
Capabilities (MVP)
The provider MUST publish capabilities via GetCapabilities().
IdLE.Identity.Read
IdLE.Identity.List (provider API only, no built-in step)
IdLE.Identity.Create
IdLE.Identity.Attribute.Ensure
IdLE.Identity.Disable
IdLE.Identity.Enable
IdLE.Entitlement.List (Groups)
IdLE.Entitlement.Grant (Groups)
IdLE.Entitlement.Revoke (Groups)
IdLE.Identity.Delete (opt-in gated, see safety section)
Identity addressing
- Support lookup by:
- objectId (GUID string)
- UPN
- mail
- Document resolution rules and canonical identity key.
- Canonical identity key for outputs MUST be the user objectId (GUID string).
Group entitlement model
- Entitlement object format follows IdLE convention:
Entitlement.Kind = 'Group'
Entitlement.Id = <group objectId GUID string> (canonical)
Entitlement.DisplayName optional
Entitlement.Mail optional
- Provider MAY accept
Entitlement.Id as displayName for convenience, but MUST resolve to objectId deterministically:
- no match -> throw
- multiple matches -> throw (no best-effort)
Paging, throttling, transient failures
- List/Search operations MUST handle Graph paging (nextLink).
- Provider/adapter MUST classify transient failures for retry policies:
- 429, 5xx, network timeouts -> throw an exception marked transient via:
Exception.Data['Idle.IsTransient'] = $true
- Include relevant, non-secret metadata in the exception message (HTTP status, request id if available).
Idempotency guarantees (required for retries and re-runs)
- Create:
- if the user already exists (by objectId or UPN), treat as success and return
Changed = $false.
- Delete:
- if the user is already gone, treat as success and return
Changed = $false.
- Disable/Enable:
- if already in desired state, return
Changed = $false.
- EnsureAttribute:
- if attribute already matches desired value, return
Changed = $false.
- Group Grant/Revoke:
- membership already in desired state must be a no-op success (
Changed = $false).
Hard constraints (from #77 and #91)
Authentication is host-owned (AuthSessionBroker)
- The host injects an
AuthSessionBroker via Providers.AuthSessionBroker.
- Steps and providers acquire sessions only via
Context.AcquireAuthSession(Name, Options).
- Workflows/plans/step metadata MUST remain data-only:
- no secrets
- no ScriptBlocks (including nested values in
AuthSessionOptions)
- Providers MUST NOT start authentication flows (no interactive login, no device code prompts, no
Connect-* patterns that trigger auth).
- Delegated vs. app-only authentication is a host concern.
- use latest
New-IdleAuthSessionBroker CmdLet for simple auth cases
Multi-auth-context per step (data-only routing)
The provider MUST support selecting different auth contexts per step using existing step metadata keys:
With.AuthSessionName (string)
- For this provider, workflows SHOULD use:
MicrosoftGraph
With.AuthSessionOptions (hashtable, data-only)
- Example:
@{ Role = 'Tier0' } vs. @{ Role = 'Admin' }
The provider MUST accept an optional AuthSession parameter for all callable methods that may need auth.
Safety: Delete must be explicit opt-in (align with #46)
IdLE.Identity.Delete MUST be gated by provider configuration.
- Provider constructor MUST default to AllowDelete = $false.
- Provider advertises
IdLE.Identity.Delete only when AllowDelete = $true.
- Example workflows MUST demonstrate this gate by requiring explicit opt-in for delete.
Provider contract (methods)
The provider object returned by the factory function MUST implement these script methods.
All methods MUST be idempotent and MUST accept an optional AuthSession parameter.
Required
GetCapabilities() -> string[]
Identity
GetIdentity(IdentityKey, [AuthSession]) -> object { IdentityKey, Enabled, Attributes }
ListIdentities([hashtable] Filter, [AuthSession]) -> string[]
- Filter handling is optional; if supported, document supported keys (e.g.
Filter.Search).
CreateIdentity(IdentityKey, Attributes, [AuthSession]) -> object { IdentityKey, Changed }
EnsureAttribute(IdentityKey, Name, Value, [AuthSession]) -> object { IdentityKey, Changed }
DisableIdentity(IdentityKey, [AuthSession]) -> object { IdentityKey, Changed }
EnableIdentity(IdentityKey, [AuthSession]) -> object { IdentityKey, Changed }
DeleteIdentity(IdentityKey, [AuthSession]) -> object { IdentityKey, Changed }
- Only required when
AllowDelete = $true and only then advertised.
Entitlements (Groups)
ListEntitlements(IdentityKey, [AuthSession]) -> IdLE.Entitlement[]
GrantEntitlement(IdentityKey, Entitlement, [AuthSession]) -> object { IdentityKey, Changed }
RevokeEntitlement(IdentityKey, Entitlement, [AuthSession]) -> object { IdentityKey, Changed }
Factory function (public API)
Create a public factory function similar to New-IdleADIdentityProvider:
New-IdleEntraIDIdentityProvider
- Parameters:
-AllowDelete (switch, default: off)
-Adapter (object, internal/test injection; default constructed)
Notes:
- The provider instance MAY implement both identity and entitlement contracts (as the AD provider does) so hosts can pass a single provider object under
Providers.Identity.
PSTypeName SHOULD follow the module naming pattern, e.g. IdLE.Provider.EntraIDIdentityProvider.
AuthSession expectations (provider-side)
The provider MUST treat the auth session as opaque input. To remain flexible for hosts, the provider SHOULD support at least these shapes:
string access token (Bearer token)
- object with property
AccessToken
- object with ScriptMethod
GetAccessToken() returning a string
- (optional) object with property
HttpClient (host-managed) if the host wants to fully own HTTP setup
The provider MUST NOT log secrets or tokens and MUST NOT emit them into events or plan exports.
Implementation approach (testable, portable)
Adapter layer (required)
Implement a small internal adapter that performs the actual Graph operations.
The provider MUST call the adapter only (no direct REST/SDK calls outside the adapter), enabling unit tests to inject a fake adapter.
Recommended internal adapter function:
New-IdleEntraIDAdapter (internal)
Minimal adapter methods (MVP):
GetUserById(objectId, authContext)
GetUserByUpn(upn, authContext)
CreateUser(payload, authContext)
PatchUser(objectId, patchPayload, authContext)
DeleteUser(objectId, authContext)
ListUsers(filter, authContext) (handles paging)
GetGroupById(groupId, authContext)
GetGroupByDisplayName(displayName, authContext) (must detect ambiguity)
ListUserGroups(objectId, authContext) (handles paging)
AddGroupMember(groupObjectId, userObjectId, authContext)
RemoveGroupMember(groupObjectId, userObjectId, authContext)
authContext is whatever normalized structure the provider derives from AuthSession (e.g., access token string).
REST baseline
If using REST:
- Use
https://graph.microsoft.com/v1.0 endpoints (document if beta endpoints are used; beta should be avoided for MVP).
- Implement paging via
@odata.nextLink.
- For transient HTTP failures (429/5xx), throw exceptions marked transient via
Exception.Data['Idle.IsTransient'] = $true.
- Respect
Retry-After if present when mapping to transient errors (include it in error metadata; host controls retry timing).
Built-in steps coverage
The provider MUST be usable with built-in steps in IdLE.Steps.Common:
IdLE.Step.CreateIdentity -> CreateIdentity
IdLE.Step.EnsureAttribute -> EnsureAttribute
IdLE.Step.DisableIdentity -> DisableIdentity
IdLE.Step.EnableIdentity -> EnableIdentity
IdLE.Step.DeleteIdentity -> DeleteIdentity (opt-in gated)
IdLE.Step.EnsureEntitlement -> ListEntitlements, GrantEntitlement, RevokeEntitlement
Workflows MAY specify With.Provider to select provider alias; default alias is Identity.
Docs / Examples (part of DoD)
Documentation
Add a provider reference page:
docs/reference/provider-entraID.md
It MUST document:
- Required host prerequisites for both auth modes (delegated vs. app-only) at a conceptual level.
- Because auth is host-owned via AuthSessionBroker, the doc must focus on what the broker must return for this provider.
- Required Graph permissions/scopes (high-level guidance).
- Identity resolution rules (UPN vs. objectId) and canonical keys.
- Entitlement model (groups; canonical id = group objectId).
- Safety gate for delete (
-AllowDelete).
Example workflows
Provide at least one workflow per LifecycleEvent under examples/workflows/:
- Joiner
- CreateIdentity + EnsureAttribute + EnsureEntitlement (group grants)
- Mover
- EnsureAttribute changes + group delta
- Leaver
- DisableIdentity + optional DeleteIdentity
- Delete MUST require explicit opt-in and capability requirement
Demo runner integration
Integrate with the single demo runner:
- Update
examples/Invoke-IdleDemo.ps1 to support -Provider EntraID (and keep Mock as default).
- The demo must show how to supply:
Providers.Identity = New-IdleEntraIDIdentityProvider
Providers.AuthSessionBroker = <host-created broker>
The demo MUST NOT prompt for credentials inside the provider module. Any interactive auth must be isolated to the demo runner (host role).
Tests (part of DoD)
Unit tests (no live Entra ID)
- Add Pester unit tests for
IdLE.Provider.EntraID using a fake adapter injected via -Adapter.
- Tests MUST cover:
- Capability list (including Delete gating)
- Idempotency rules for all methods
- Group resolution ambiguity handling (if displayName resolution is supported)
- Transient error marking behavior (adapter throws with
Idle.IsTransient = $true for 429/5xx/timeouts)
Contract tests
- Add provider contract tests similar in spirit to existing provider tests:
- Validate required method presence when capability is advertised.
- Validate
AuthSession parameter is supported where applicable.
Acceptance criteria
- Provider module
IdLE.Provider.EntraID loads and advertises capabilities.
- MVP capabilities are implemented and covered by unit tests (mocked; no live Entra ID required).
- Idempotency rules are enforced and test-covered.
- Example workflows run via the demo runner using
-Provider EntraID (documented commands).
Definition of done
- Pester: green
- PSScriptAnalyzer: green
- Documentation added/updated (provider reference + example usage)
- Examples added and demo runner updated
- No secrets/tokens introduced into workflow files, plan exports, or event streams
References
#46
#77
#91
Goal
Provide a production-usable Microsoft Entra ID provider module so IdLE can run real Joiner/Mover/Leaver workflows out of the box.
Scope
New module
IdLE.Provider.EntraIDCapabilities (MVP)
The provider MUST publish capabilities via
GetCapabilities().IdLE.Identity.ReadIdLE.Identity.List(provider API only, no built-in step)IdLE.Identity.CreateIdLE.Identity.Attribute.EnsureIdLE.Identity.DisableIdLE.Identity.EnableIdLE.Entitlement.List(Groups)IdLE.Entitlement.Grant(Groups)IdLE.Entitlement.Revoke(Groups)IdLE.Identity.Delete(opt-in gated, see safety section)Identity addressing
Group entitlement model
Entitlement.Kind = 'Group'Entitlement.Id = <group objectId GUID string>(canonical)Entitlement.DisplayNameoptionalEntitlement.MailoptionalEntitlement.Idas displayName for convenience, but MUST resolve to objectId deterministically:Paging, throttling, transient failures
Exception.Data['Idle.IsTransient'] = $trueIdempotency guarantees (required for retries and re-runs)
Changed = $false.Changed = $false.Changed = $false.Changed = $false.Changed = $false).Hard constraints (from #77 and #91)
Authentication is host-owned (AuthSessionBroker)
AuthSessionBrokerviaProviders.AuthSessionBroker.Context.AcquireAuthSession(Name, Options).AuthSessionOptions)Connect-*patterns that trigger auth).New-IdleAuthSessionBrokerCmdLet for simple auth casesMulti-auth-context per step (data-only routing)
The provider MUST support selecting different auth contexts per step using existing step metadata keys:
With.AuthSessionName(string)MicrosoftGraphWith.AuthSessionOptions(hashtable, data-only)@{ Role = 'Tier0' }vs.@{ Role = 'Admin' }The provider MUST accept an optional
AuthSessionparameter for all callable methods that may need auth.Safety: Delete must be explicit opt-in (align with #46)
IdLE.Identity.DeleteMUST be gated by provider configuration.IdLE.Identity.Deleteonly whenAllowDelete = $true.Provider contract (methods)
The provider object returned by the factory function MUST implement these script methods.
All methods MUST be idempotent and MUST accept an optional
AuthSessionparameter.Required
GetCapabilities()->string[]Identity
GetIdentity(IdentityKey, [AuthSession])-> object{ IdentityKey, Enabled, Attributes }ListIdentities([hashtable] Filter, [AuthSession])->string[]Filter.Search).CreateIdentity(IdentityKey, Attributes, [AuthSession])-> object{ IdentityKey, Changed }EnsureAttribute(IdentityKey, Name, Value, [AuthSession])-> object{ IdentityKey, Changed }DisableIdentity(IdentityKey, [AuthSession])-> object{ IdentityKey, Changed }EnableIdentity(IdentityKey, [AuthSession])-> object{ IdentityKey, Changed }DeleteIdentity(IdentityKey, [AuthSession])-> object{ IdentityKey, Changed }AllowDelete = $trueand only then advertised.Entitlements (Groups)
ListEntitlements(IdentityKey, [AuthSession])->IdLE.Entitlement[]GrantEntitlement(IdentityKey, Entitlement, [AuthSession])-> object{ IdentityKey, Changed }RevokeEntitlement(IdentityKey, Entitlement, [AuthSession])-> object{ IdentityKey, Changed }Factory function (public API)
Create a public factory function similar to
New-IdleADIdentityProvider:New-IdleEntraIDIdentityProvider-AllowDelete(switch, default: off)-Adapter(object, internal/test injection; default constructed)Notes:
Providers.Identity.PSTypeNameSHOULD follow the module naming pattern, e.g.IdLE.Provider.EntraIDIdentityProvider.AuthSession expectations (provider-side)
The provider MUST treat the auth session as opaque input. To remain flexible for hosts, the provider SHOULD support at least these shapes:
stringaccess token (Bearer token)AccessTokenGetAccessToken()returning a stringHttpClient(host-managed) if the host wants to fully own HTTP setupThe provider MUST NOT log secrets or tokens and MUST NOT emit them into events or plan exports.
Implementation approach (testable, portable)
Adapter layer (required)
Implement a small internal adapter that performs the actual Graph operations.
The provider MUST call the adapter only (no direct REST/SDK calls outside the adapter), enabling unit tests to inject a fake adapter.
Recommended internal adapter function:
New-IdleEntraIDAdapter(internal)Minimal adapter methods (MVP):
GetUserById(objectId, authContext)GetUserByUpn(upn, authContext)CreateUser(payload, authContext)PatchUser(objectId, patchPayload, authContext)DeleteUser(objectId, authContext)ListUsers(filter, authContext)(handles paging)GetGroupById(groupId, authContext)GetGroupByDisplayName(displayName, authContext)(must detect ambiguity)ListUserGroups(objectId, authContext)(handles paging)AddGroupMember(groupObjectId, userObjectId, authContext)RemoveGroupMember(groupObjectId, userObjectId, authContext)authContextis whatever normalized structure the provider derives fromAuthSession(e.g., access token string).REST baseline
If using REST:
https://graph.microsoft.com/v1.0endpoints (document if beta endpoints are used; beta should be avoided for MVP).@odata.nextLink.Exception.Data['Idle.IsTransient'] = $true.Retry-Afterif present when mapping to transient errors (include it in error metadata; host controls retry timing).Built-in steps coverage
The provider MUST be usable with built-in steps in
IdLE.Steps.Common:IdLE.Step.CreateIdentity->CreateIdentityIdLE.Step.EnsureAttribute->EnsureAttributeIdLE.Step.DisableIdentity->DisableIdentityIdLE.Step.EnableIdentity->EnableIdentityIdLE.Step.DeleteIdentity->DeleteIdentity(opt-in gated)IdLE.Step.EnsureEntitlement->ListEntitlements,GrantEntitlement,RevokeEntitlementWorkflows MAY specify
With.Providerto select provider alias; default alias isIdentity.Docs / Examples (part of DoD)
Documentation
Add a provider reference page:
docs/reference/provider-entraID.mdIt MUST document:
-AllowDelete).Example workflows
Provide at least one workflow per LifecycleEvent under
examples/workflows/:Demo runner integration
Integrate with the single demo runner:
examples/Invoke-IdleDemo.ps1to support-Provider EntraID(and keep Mock as default).Providers.Identity = New-IdleEntraIDIdentityProviderProviders.AuthSessionBroker = <host-created broker>The demo MUST NOT prompt for credentials inside the provider module. Any interactive auth must be isolated to the demo runner (host role).
Tests (part of DoD)
Unit tests (no live Entra ID)
IdLE.Provider.EntraIDusing a fake adapter injected via-Adapter.Idle.IsTransient = $truefor 429/5xx/timeouts)Contract tests
AuthSessionparameter is supported where applicable.Acceptance criteria
IdLE.Provider.EntraIDloads and advertises capabilities.-Provider EntraID(documented commands).Definition of done
References
#46
#77
#91