Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 18, 2026

Summary

Implements production-ready Active Directory provider enabling real Joiner/Mover/Leaver workflows with identity lifecycle operations and group management.

Motivation

IdLE lacked a concrete provider implementation for on-premises Active Directory, limiting its use for real-world identity lifecycle automation. This provider enables organizations to run production JML workflows against AD out of the box.

Type of Change

  • New feature

Changes

Provider Module (IdLE.Provider.AD)

  • Identity operations: Read, List, Create, Delete (opt-in), Attribute.Ensure, Move, Disable, Enable
  • Group entitlement operations: List, Grant, Revoke (DN as canonical key, Groups only - AD platform limitation)
  • Identity resolution: GUID, UPN, sAMAccountName with deterministic lookup order using proper [System.Guid]::TryParse
  • Idempotent semantics for all operations (Create returns Changed=$false if exists, Delete succeeds if already gone)
  • Delete capability gated by AllowDelete parameter for safety
  • LDAP injection protection: All user inputs sanitized with Protect-LdapFilterValue helper function (approved PowerShell verb) before filter construction
  • Internal AD adapter layer isolates AD cmdlet dependencies for unit testing
  • Windows-only (requires RSAT/ActiveDirectory module at runtime)
  • Auto-imported when IdLE is imported (added to NestedModules)
  • Non-blocking import: Module loads successfully even without ActiveDirectory module (allows unit tests and cross-platform development)
  • Runtime validation with helpful error messages for missing dependencies when provider is instantiated

Built-in Steps (IdLE.Steps.Common)

  • IdLE.Step.CreateIdentity - create new user accounts
  • IdLE.Step.DisableIdentity - disable user accounts
  • IdLE.Step.EnableIdentity - enable user accounts
  • IdLE.Step.MoveIdentity - move between OUs
  • IdLE.Step.DeleteIdentity - delete accounts (requires IdLE.Identity.Delete capability)
  • All steps declare RequiresCapabilities for plan-time validation
  • Registered in step registry via Get-IdleStepRegistry
  • Generated step catalog documentation in docs/reference/steps.md

Example Workflows

  • ad-joiner-complete.psd1 - Create + attributes + groups + OU move
  • ad-mover-department-change.psd1 - Attribute updates + group delta + OU move
  • ad-leaver-offboarding.psd1 - Disable + OU move + conditional delete

Active Directory Entitlement Constraint

  • AD only supports Kind = 'Group' for entitlements (security groups and distribution groups)
  • Does not support arbitrary entitlement kinds (roles, licenses, etc.) - this is an AD platform limitation
  • All entitlements returned by ListEntitlements will have Kind = 'Group'
  • Generic entitlement contract tests replaced with AD-specific tests using Kind='Group'
  • Documented in provider documentation with explanation

Provider Alias Flexibility

  • Provider aliases in -Providers @{ ... } hashtables are completely flexible and host-chosen
  • Built-in steps default to 'Identity' alias when With.Provider is omitted
  • Common patterns: role-based (Identity) for single provider, instance-based (SourceAD, TargetEntra) for multiple providers
  • Alias must match between provider hashtable and workflow step configuration
  • Fully documented in docs/usage/providers.md and provider documentation

Security Improvements

  • LDAP injection prevention: Helper function Protect-LdapFilterValue sanitizes special LDAP filter characters (*, (, ), , null byte) in user inputs before filter string construction in GetUserByUpn, GetUserBySam, and ListUsers methods
  • Improved error handling: Fixed GUID parsing logic to correctly check TryParse return value instead of catching non-existent exceptions; GetUserByGuid exceptions handled separately with proper verbose logging
  • Cross-platform exception handling: Delete operation uses exception type checking with message pattern fallback for compatibility across test and production environments

Code Quality Improvements

  • Module comments follow consistent schema matching IdLE.Core and IdLE.Steps.Common style
  • GUID parsing uses proper [System.Guid]::TryParse with correct error handling and verbose logging
  • Markdown tables use proper spacing around pipes for linting compliance
  • Comprehensive inline documentation throughout
  • All catch blocks include verbose logging for debugging
  • PSScriptAnalyzer compliant: All warnings addressed (empty catch blocks, unused variables, unapproved verbs)
  • Uses approved PowerShell verbs (Protect-LdapFilterValue instead of Escape-LdapFilterValue)

Documentation Structure

  • Comprehensive docs/reference/provider-ad.md: Complete provider reference documentation (9500+ words) with prerequisites, capabilities, usage patterns, troubleshooting, architecture notes, security guidance, and complete examples
  • Added to docs navigation (_sidebar.md)
  • Minimal src/IdLE.Provider.AD/README.md: Ultra-concise module README (31 lines) following IdLE sub-module pattern - contains only brief description, quick start, prerequisites, and link to full documentation
  • Documentation properly organized following IdLE conventions: sub-modules have minimal READMEs, comprehensive docs live in docs/reference/

Usage Example

# Import IdLE (automatically imports IdLE.Provider.AD)
Import-Module IdLE

# Create provider with delete capability
$provider = New-IdleADIdentityProvider -AllowDelete

# Run leaver workflow
$request = New-IdleLifecycleRequest -LifecycleEvent 'Leaver' -IdentityKeys @{
    IdentityKey = 'user@contoso.local'
}
$plan = New-IdlePlan -WorkflowPath './ad-leaver-offboarding.psd1' -Request $request -Providers @{ Identity = $provider }
$result = Invoke-IdlePlan -Plan $plan -Providers @{ Identity = $provider }

Testing

  • Unit tests (152/152 passing - 100% pass rate - includes all repository tests)
  • Contract tests (all provider contract tests passing, AD-specific entitlement tests)
  • Manual testing
  • PSScriptAnalyzer validation (0 warnings)

How to test & review

Prerequisites: Windows with RSAT/ActiveDirectory module for real AD testing. Unit tests use fake adapter and run on any platform.

# Run provider tests
Invoke-Pester -Path ./tests/Providers/ADIdentityProvider.Tests.ps1

# Validate example workflows
Test-IdleWorkflow -WorkflowPath ./examples/workflows/ad-joiner-complete.psd1

# Full suite
./tools/Invoke-IdlePesterTests.ps1

# PSScriptAnalyzer validation
Invoke-ScriptAnalyzer -Path ./src/IdLE.Provider.AD -Recurse

Key test scenarios:

  • Identity resolution (GUID/UPN/sAMAccountName)
  • Idempotency (Create existing, Delete missing, Grant duplicate membership)
  • AllowDelete gating (capability advertisement, enforcement)
  • Provider contract compliance
  • AD-specific entitlement operations with Kind='Group'
  • LDAP injection prevention (input sanitization)
  • PSScriptAnalyzer compliance

Checklist

  • Code follows STYLEGUIDE.md
  • Tests added or updated
  • Documentation updated (comprehensive provider docs in docs/reference/provider-ad.md, minimal module README following IdLE sub-module pattern, prerequisites, security guidance, AD limitations, generated step catalog, provider alias flexibility guide)
  • No UI/auth logic added to IdLE.Core
  • No breaking changes without discussion
Original prompt

This section details on the original issue you should resolve

<issue_title>IdLE.Provider.AD (On-Prem AD) MVP (Windows-only)</issue_title>
<issue_description># Goal

  • Provide a production-usable on-prem Active Directory provider so IdLE can run real Joiner/Mover/Leaver workflows out of the box.

Scope

  • Create new provider module: IdLE.Provider.AD
  • Platform support:
    • Windows-only (documented). Requires RSAT / ActiveDirectory module availability on the host.
  • Capability naming convention: All capability names MUST be under the IdLE. namespace.
  • Capabilities (MVP):
    • IdLE.Identity.Read
    • IdLE.Identity.List (Provider API only, no built-in step)
      • IdLE.Identity.List is exposed as a provider method only (no built-in step in v0.8). Minimal contract: ListIdentities([hashtable] $Filter) returns an array of identity keys (strings). Filtering is optional; providers may support Filter.Search (string prefix match).
    • IdLE.Identity.Create
    • IdLE.Identity.Delete
      • Delete is gated by provider configuration: AD provider advertises IdLE.Identity.Delete only when AllowDelete = $true is set during provider construction. The built-in Delete step MUST declare RequiresCapabilities = @('IdLE.Identity.Delete').
    • IdLE.Identity.Attribute.Ensure ("set")
    • IdLE.Identity.Move (OU/container move)
    • IdLE.Identity.Disable
    • IdLE.Identity.Enable
    • IdLE.Entitlement.* use
      • Entitlement.Kind = 'Group'
      • Entitlement.Id = DistinguishedName (DN) als canonical key
      • Provider MAY accept inputs as SID or sAMAccountName but MUST normalize to DN internally
    • IdLE.Entitlement.List (Groups)
    • IdLE.Entitlement.Grant (Groups)
    • IdLE.Entitlement.Revoke (Groups)
  • Identity addressing:
    • Support common identifiers (UPN, sAMAccountName, GUID); document preferred defaults and resolution rules.
    • GUID pattern (^[0-9a-fA-F-]{36}$ or N-format) → resolve by ObjectGuid
    • contains @ → resolve by UPN
    • else → resolve by sAMAccountName
    • On ambiguous match → throw deterministic error (no best-effort)
    • Preferred canonical identity key for outputs: ObjectGuid string (or keep input key; aber festlegen!)
  • Idempotency guarantees (required for retries and re-runs):
    • Create: if identity exists, return success with "NoChange"/"AlreadyExists" semantics (no duplicate creation).
    • Delete: if identity is already gone, treat as success ("AlreadyDeleted").
    • Group Grant/Revoke: membership already in desired state must be a no-op success.
    • Move(OU): if already in target OU, no-op success.

Built-in steps (out of the box workflows):

  • IdLE.Step.CreateIdentity
    • With: Provider?, IdentityKey, Attributes (hashtable), Enabled?
    • Requires: IdLE.Identity.Create
  • IdLE.Step.DisableIdentity
    • With: Provider?, IdentityKey
    • Requires: IdLE.Identity.Disable
  • IdLE.Step.EnableIdentity
    • With: Provider?, IdentityKey
    • Requires: IdLE.Identity.Enable
  • IdLE.Step.MoveIdentity
    • With: Provider?, IdentityKey, TargetContainer (DN)
    • Requires: IdLE.Identity.Move
  • IdLE.Step.DeleteIdentity
    • With: Provider?, IdentityKey
    • Requires: IdLE.Identity.Delete (opt-in gated)

Implementation approach:

  • Use a small internal wrapper layer around AD cmdlets so unit tests can mock behavior without requiring a real AD.
    • The AD provider MUST call an internal adapter object only (no direct cmdlet calls outside adapter).
    • Adapter methods (MVP): GetUserByUpn, GetUserBySam, GetUserByGuid, NewUser, SetUser, DisableUser, EnableUser, MoveObject, GetGroupById, AddGroupMember, RemoveGroupMember, GetUserGroups.
    • Unit tests inject a fake adapter implementing the same method names.
  • Provider must publish its capability list consistently (capabilities are announced, not assumed).
  • Provider contract (capability-driven)
    • Provider MUST implement GetCapabilities() and MUST NOT rely on inference.
    • If provider advertises IdLE.Identity.Create, it MUST implement CreateIdentity(identityKey, attributes) and return { IdentityKey, Changed }.
    • If provider advertises IdLE.Identity.Delete, it MUST implement DeleteIdentity(identityKey) and return { IdentityKey, Changed } with idempotent semantics.
    • If provider advertises IdLE.Identity.Enable, it MUST implement EnableIdentity(identityKey) and return { IdentityKey, Changed }.
    • If provider advertises IdLE.Identity.Move, it MUST implement MoveIdentity(identityKey, targetContainer) and return { IdentityKey, Changed }.
    • If provider advertises IdLE.Identity.Read, it MUST implement GetIdentity(identityKey) returning { IdentityKey, Enabled, Attributes }.
  • Authentication model
    • Default: integrated auth (run-as)
    • provider factory accepts -Credential (PSCredential)
    • Provider MUST NOT prompt or start interactive flows
    • Doc: required AD rights for operations

Docs / Examples (part of DoD)

  • Document prerequisites (Windows/RSAT/permissions), configuration, a...

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 7 commits January 18, 2026 14:26
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
…tity operations

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
…ributes dynamically

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title [WIP] Add on-prem Active Directory provider for IdLE Add IdLE.Provider.AD (On-Prem Active Directory) MVP with built-in lifecycle steps Jan 18, 2026
Copilot AI requested a review from blindzero January 18, 2026 14:46
@blindzero
Copy link
Owner

@copilot I am not sure about the failing contract tests. If it is expected behavior the test should be implemented in a way to succeed if the behavior is really like that (negated test?). Why does the provider only support "Group"? Do the entitlement tests need to be changed then as well?

…roup tests

Replace generic entitlement contract tests (which use Kind='Contract') with AD-specific
tests that use Kind='Group'. Active Directory only supports group entitlements, not
arbitrary entitlement kinds. Document this limitation in README.

All 152 tests now passing (was 149/152 with 3 expected failures).

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 18, 2026

@copilot I am not sure about the failing contract tests. If it is expected behavior the test should be implemented in a way to succeed if the behavior is really like that (negated test?). Why does the...

Fixed in commit 4e60142.

Why AD only supports 'Group':
Active Directory only provides security groups and distribution groups as entitlements. Unlike cloud providers that may support roles, licenses, or other entitlement types, AD's entitlement model is limited to groups. This is a fundamental constraint of the AD platform.

What changed:

  • Removed generic entitlement contract tests that use Kind='Contract' (which AD doesn't support)
  • Added AD-specific entitlement tests using Kind='Group'
  • All tests now pass (152/152, was 149/152)
  • Documented this limitation in the provider README

The AD-specific tests validate the same contract behaviors (idempotency, list/grant/revoke operations) but use Kind='Group' which matches what AD actually supports and returns.

@blindzero
Copy link
Owner

@copilot you must execute the tools to Generate Step Reference and commit the change of the generated files of docs as well.

Run Generate-IdleStepReference.ps1 to generate documentation for:
- CreateIdentity
- DeleteIdentity
- DisableIdentity
- EnableIdentity
- MoveIdentity

All new steps added in IdLE.Steps.Common are now documented in the step catalog.

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 18, 2026

@copilot you must execute the tools to Generate Step Reference and commit the change of the generated files of docs as well.

Done in commit 9a46911.

Generated step reference documentation for all 5 new steps added to IdLE.Steps.Common:

  • CreateIdentity
  • DeleteIdentity
  • DisableIdentity
  • EnableIdentity
  • MoveIdentity

The generated file docs/reference/steps.md now includes full documentation for each step including synopsis, description, idempotency status, and required input parameters.

@blindzero
Copy link
Owner

@copilot bei -Providers @{ Identity = $provider } finde ich das -Identity problematisch, da es ja eigentlich Provider und keine Identities enthält?

Copy link
Contributor Author

Copilot AI commented Jan 18, 2026

@copilot bei -Providers @{ Identity = $provider } finde ich das -Identity problematisch, da es ja eigentlich Provider und keine Identities enthält?

The key Identity in -Providers @{ Identity = $provider } is a provider alias, not a collection of identities. It names the identity provider instance.

This is the established pattern in IdLE:

  • The Providers hashtable maps alias names to provider instances
  • Steps use With.Provider to specify which alias to use (defaults to 'Identity')
  • Example: @{ Identity = $adProvider; Entra = $entraProvider }

The naming follows the convention that the alias describes the provider's role/domain (identity management) rather than its implementation (AD, Entra, etc.). This allows workflows to remain provider-agnostic.

However, if you prefer more explicit naming like IdentityProvider or ADProvider as the alias, that would also work - the alias is chosen by the host when injecting providers. The 'Identity' default is just a convention used by the built-in steps.

@blindzero
Copy link
Owner

@copilot Die Argumentation macht nur bedingt Sinn. Wenn der Key nicht die Implementierung abbildet, sollte es ja nicht Entra oder ADPeovider sein.
Bzgl dem Zweck kann ich es zwar nachvollziehen, dann kann aber Identity auch = $entraProvider sein. Wozu überhaupt hier Key-Value und nicht einfach eine Liste?
Ich beziehe mich weniger auf das was dokumentiert ist, sondern auf das, was Sinn macht.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a production-ready Active Directory provider for IdLE, enabling real-world Joiner/Mover/Leaver workflows against on-premises AD environments. The implementation includes identity lifecycle operations, group management, built-in steps, and comprehensive documentation.

Changes:

  • New IdLE.Provider.AD module with complete AD integration (identity operations, group entitlements, opt-in delete capability)
  • Five new built-in lifecycle steps (CreateIdentity, DisableIdentity, EnableIdentity, MoveIdentity, DeleteIdentity) with capability-based validation
  • Complete test coverage with fake adapter pattern, contract compliance, and idempotency tests
  • Example JML workflows and comprehensive provider documentation

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/Providers/ADIdentityProvider.Tests.ps1 Complete test suite with fake adapter, contract tests, and AD-specific validation
src/IdLE/IdLE.psd1 Registers AD provider as nested module for auto-import
src/IdLE.Steps.Common/Public/Invoke-IdleStep*.ps1 Five new lifecycle step implementations with consistent error handling
src/IdLE.Steps.Common/IdLE.Steps.Common.* Module manifest and export updates for new steps
src/IdLE.Provider.AD/README.md Minimal sub-module README following IdLE conventions
src/IdLE.Provider.AD/Public/New-IdleADIdentityProvider.ps1 Main provider factory with identity resolution, entitlement operations, and capability management
src/IdLE.Provider.AD/Private/New-IdleADAdapter.ps1 Internal adapter layer wrapping AD cmdlets for testability
src/IdLE.Provider.AD/IdLE.Provider.AD.* Module manifest and loader with non-blocking import
src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 Step registry updates for new lifecycle steps
examples/workflows/*.psd1 Three complete example workflows demonstrating JML scenarios
docs/reference/provider-ad.md Comprehensive provider documentation with usage patterns and troubleshooting
docs/usage/providers.md Provider alias documentation and multi-provider patterns
docs/reference/steps.md Generated step catalog with new lifecycle steps
docs/_sidebar.md Navigation update for AD provider documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d202d43a41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

blindzero and others added 2 commits January 18, 2026 21:16
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Adds missing LDAP injection prevention to complement existing error handling fixes:

- Add Escape-LdapFilterValue helper function in New-IdleADAdapter.ps1
- Sanitize special LDAP characters: *, (, ), \, null byte
- Apply escaping in GetUserByUpn, GetUserBySam, and ListUsers methods
- Fix Delete error handling to work in test environments (type check + message fallback)

This completes all security fixes from automated code review PR #88.
Defense-in-depth security layer per docs/advanced/security.md.

All 27 AD provider tests passing.

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
@blindzero
Copy link
Owner

@copilot Make sure all Tests are green.

Copy link
Contributor Author

Copilot AI commented Jan 18, 2026

@copilot Make sure all Tests are green.

All 27 AD provider tests are passing (100% pass rate):

Tests Passed: 27, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0

Test coverage includes:

  • Provider contract compliance (required methods, result shapes, capability advertisement)
  • AD-specific entitlement operations with Kind='Group'
  • Identity resolution (GUID/UPN/sAMAccountName)
  • Idempotency for all operations (Create, Delete, Disable, Enable, Move, Grant, Revoke)
  • AllowDelete opt-in gating and enforcement
  • LDAP injection prevention (input sanitization via Escape-LdapFilterValue)

All security fixes from automated code review are in place and validated.

@blindzero
Copy link
Owner

@copilot Make sure ALL Pester Tests are green (not only of this AD provider) and no PSSA errors occur.

…ed verb

Addressed all 3 PSScriptAnalyzer warnings:

1. Empty catch block (line 277): Added Write-Verbose message for debugging
2. Unused variable (line 286): Changed `$user =` to `$null =` to explicitly discard return value
3. Unapproved verb: Renamed `Escape-LdapFilterValue` to `Protect-LdapFilterValue` (approved verb)
   - Updated all 3 callsites in New-IdleADAdapter.ps1

All 152 Pester tests passing (100%), no PSScriptAnalyzer warnings.

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 18, 2026

@copilot Make sure ALL Pester Tests are green (not only of this AD provider) and no PSSA errors occur.

All tests and linting checks passing in commit e6a837d:

152/152 Pester tests passing (100%)

  • All AD provider tests (27/27)
  • All core IdLE tests (125/125)
  • All provider contract tests passing

0 PSScriptAnalyzer warnings

  • Fixed empty catch block (added Write-Verbose)
  • Fixed unused variable (changed to $null assignment)
  • Fixed unapproved verb (renamed Escape-LdapFilterValue → Protect-LdapFilterValue)

All security fixes from automated code review remain in place and validated.

@blindzero blindzero merged commit ae4c75b into main Jan 18, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IdLE.Provider.AD (On-Prem AD) MVP (Windows-only)

2 participants