-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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.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
- Support lookup by:
- objectId (GUID string)
- UPN
- 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.DisplayNameoptionalEntitlement.Mailoptional
- Provider MAY accept
Entitlement.Idas 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).
- 429, 5xx, network timeouts -> throw an exception marked transient via:
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.
- if the user already exists (by objectId or UPN), treat as success and return
- Delete:
- if the user is already gone, treat as success and return
Changed = $false.
- if the user is already gone, treat as success and return
- Disable/Enable:
- if already in desired state, return
Changed = $false.
- if already in desired state, return
- EnsureAttribute:
- if attribute already matches desired value, return
Changed = $false.
- if attribute already matches desired value, return
- Group Grant/Revoke:
- membership already in desired state must be a no-op success (
Changed = $false).
- membership already in desired state must be a no-op success (
Hard constraints (from #77 and #91)
Authentication is host-owned (AuthSessionBroker)
- The host injects an
AuthSessionBrokerviaProviders.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-IdleAuthSessionBrokerCmdLet 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
- For this provider, workflows SHOULD use:
With.AuthSessionOptions(hashtable, data-only)- Example:
@{ Role = 'Tier0' }vs.@{ Role = 'Admin' }
- Example:
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.DeleteMUST be gated by provider configuration.- Provider constructor MUST default to AllowDelete = $false.
- Provider advertises
IdLE.Identity.Deleteonly whenAllowDelete = $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).
- Filter handling is optional; if supported, document supported keys (e.g.
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 = $trueand only then advertised.
- Only required when
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)
- Parameters:
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. 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)- 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.0endpoints (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-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,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.ps1to support-Provider EntraID(and keep Mock as default). - The demo must show how to supply:
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)
- Add Pester unit tests for
IdLE.Provider.EntraIDusing 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 = $truefor 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
AuthSessionparameter is supported where applicable.
Acceptance criteria
- Provider module
IdLE.Provider.EntraIDloads 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