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
94 changes: 94 additions & 0 deletions docs/reference/providers/provider-ad.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
title: Provider Reference - IdLE.Provider.AD (Active Directory)
sidebar_label: Active Directory
Expand Down Expand Up @@ -604,6 +604,100 @@
}
```

### Manager Attribute Handling

The AD provider supports the `Manager` attribute for both `CreateIdentity` and `EnsureAttribute` operations with automatic DN resolution.

**Supported Input Formats:**

The Manager value can be specified in multiple formats, which will be automatically resolved to a Distinguished Name (DN):

- **Distinguished Name (DN)**: Direct DN string (no resolution needed)
- Example: `CN=Jane Smith,OU=Managers,DC=contoso,DC=local`
- **GUID**: User's ObjectGuid (resolved to DN)
- Example: `a1b2c3d4-e5f6-7890-abcd-ef1234567890`
- **UPN**: UserPrincipalName (resolved to DN)
- Example: `jsmith@contoso.local`
- **sAMAccountName**: Simple username (resolved to DN)
- Example: `jsmith`

The provider automatically detects the format and resolves it to the manager's DN. If the manager cannot be found, an error is thrown with a clear message.

**CreateIdentity with Manager (DN):**

```powershell
@{
Name = 'CreateUserWithManager'
Type = 'IdLE.Step.CreateIdentity'
With = @{
Provider = 'Identity'
IdentityKey = 'jdoe'
Attributes = @{
GivenName = 'John'
Surname = 'Doe'
UserPrincipalName = 'jdoe@contoso.local'
Manager = 'CN=Jane Smith,OU=Managers,DC=contoso,DC=local' # DN
}
AuthSessionName = 'ActiveDirectory'
}
}
```

**CreateIdentity with Manager (sAMAccountName):**

```powershell
@{
Name = 'CreateUserWithManagerSam'
Type = 'IdLE.Step.CreateIdentity'
With = @{
Provider = 'Identity'
IdentityKey = 'jdoe'
Attributes = @{
GivenName = 'John'
Surname = 'Doe'
Manager = 'jsmith' # Will be resolved to DN automatically
}
AuthSessionName = 'ActiveDirectory'
}
}
```

**Setting Manager via EnsureAttribute (UPN):**

```powershell
@{
Name = 'SetManager'
Type = 'IdLE.Step.EnsureAttribute'
With = @{
Provider = 'Identity'
IdentityKey = 'jdoe'
Name = 'Manager'
Value = 'jsmith@contoso.local' # UPN - will be resolved to DN
AuthSessionName = 'ActiveDirectory'
}
}
```

**Clearing Manager:**

To clear the Manager attribute, set the value to `$null`:

```powershell
@{
Name = 'ClearManager'
Type = 'IdLE.Step.EnsureAttribute'
With = @{
Provider = 'Identity'
IdentityKey = 'jdoe'
Name = 'Manager'
Value = $null
Comment thread
blindzero marked this conversation as resolved.
AuthSessionName = 'ActiveDirectory'
}
}
```

**Note:** Clearing the Manager attribute using `$null` works correctly in PSD1 workflow files. PowerShell evaluates `$null` at load time.

### Complete example workflows

Complete example workflows are available in the repository:
Expand Down
114 changes: 111 additions & 3 deletions src/IdLE.Provider.AD/Private/New-IdleADAdapter.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,99 @@ function New-IdleADAdapter {
return $escaped
} -Force

# Add Manager DN validation helper
$adapter | Add-Member -MemberType ScriptMethod -Name TestManagerDN -Value {
param(
[Parameter(Mandatory)]
[AllowNull()]
[object] $Value
)

if ($null -eq $Value) {
return $true
}

if ($Value -isnot [string]) {
throw "Manager must be a DistinguishedName (DN) string, but received type: $($Value.GetType().FullName)"
}

if ([string]::IsNullOrWhiteSpace($Value)) {
throw "Manager must be a DistinguishedName (DN) string, but received empty or whitespace-only value."
}

if (-not ($Value -match '=' -and $Value -match ',')) {
throw "Manager must be a DistinguishedName (DN). Expected format: 'CN=Name,OU=Unit,DC=domain,DC=com'. Received: '$Value'"
}

return $true
} -Force

# Add Manager DN resolution helper
# Accepts DN, GUID, UPN, or sAMAccountName and resolves to DN
$adapter | Add-Member -MemberType ScriptMethod -Name ResolveManagerDN -Value {
param(
[Parameter(Mandatory)]
[AllowNull()]
[object] $Value
)

if ($null -eq $Value) {
return $null
}

if ($Value -isnot [string]) {
throw "Manager must be a string (DN, GUID, UPN, or sAMAccountName), but received type: $($Value.GetType().FullName)"
}

if ([string]::IsNullOrWhiteSpace($Value)) {
throw "Manager cannot be an empty or whitespace-only string."
}

# Check if already a DN (contains = and ,)
if ($Value -match '=' -and $Value -match ',') {
# Already a DN, validate and return
$this.TestManagerDN($Value) | Out-Null
return $Value
}

# Try to resolve as GUID, UPN, or sAMAccountName
Write-Verbose "Manager value '$Value' is not a DN. Attempting to resolve to DN..."

# Try GUID format first
$guid = [System.Guid]::Empty
if ([System.Guid]::TryParse($Value, [ref]$guid)) {
try {
$managerUser = $this.GetUserByGuid($guid.ToString())
if ($null -ne $managerUser) {
Write-Verbose "Resolved Manager GUID '$Value' to DN: $($managerUser.DistinguishedName)"
return $managerUser.DistinguishedName
}
}
catch {
Write-Verbose "Failed to resolve Manager GUID '$Value': $_"
}
throw "Manager: Could not find user with GUID '$Value'."
}

# Try UPN format (contains @)
if ($Value -match '@') {
$managerUser = $this.GetUserByUpn($Value)
if ($null -ne $managerUser) {
Write-Verbose "Resolved Manager UPN '$Value' to DN: $($managerUser.DistinguishedName)"
return $managerUser.DistinguishedName
}
throw "Manager: Could not find user with UPN '$Value'."
}

# Fallback to sAMAccountName
$managerUser = $this.GetUserBySam($Value)
if ($null -ne $managerUser) {
Write-Verbose "Resolved Manager sAMAccountName '$Value' to DN: $($managerUser.DistinguishedName)"
return $managerUser.DistinguishedName
}
throw "Manager: Could not find user with sAMAccountName '$Value'."
} -Force

$adapter | Add-Member -MemberType ScriptMethod -Name GetUserByUpn -Value {
param(
[Parameter(Mandatory)]
Expand All @@ -56,7 +149,7 @@ function New-IdleADAdapter {
$escapedUpn = $escapedUpn -replace '''', ''''''
$params = @{
Filter = "UserPrincipalName -eq '$escapedUpn'"
Properties = @('Enabled', 'DistinguishedName', 'ObjectGuid', 'UserPrincipalName', 'sAMAccountName')
Properties = @('Enabled', 'DistinguishedName', 'ObjectGuid', 'UserPrincipalName', 'sAMAccountName', 'Manager')
ErrorAction = 'Stop'
}
if ($null -ne $this.Credential) {
Expand Down Expand Up @@ -85,7 +178,7 @@ function New-IdleADAdapter {

$params = @{
Filter = "sAMAccountName -eq '$escapedSam'"
Properties = @('Enabled', 'DistinguishedName', 'ObjectGuid', 'UserPrincipalName', 'sAMAccountName')
Properties = @('Enabled', 'DistinguishedName', 'ObjectGuid', 'UserPrincipalName', 'sAMAccountName', 'Manager')
ErrorAction = 'Stop'
}
if ($null -ne $this.Credential) {
Expand All @@ -110,7 +203,7 @@ function New-IdleADAdapter {

$params = @{
Identity = $Guid
Properties = @('Enabled', 'DistinguishedName', 'ObjectGuid', 'UserPrincipalName', 'sAMAccountName')
Properties = @('Enabled', 'DistinguishedName', 'ObjectGuid', 'UserPrincipalName', 'sAMAccountName', 'Manager')
ErrorAction = 'Stop'
}
if ($null -ne $this.Credential) {
Expand Down Expand Up @@ -241,6 +334,13 @@ function New-IdleADAdapter {
if ($effectiveAttributes.ContainsKey('EmailAddress')) {
$params['EmailAddress'] = $effectiveAttributes['EmailAddress']
}
if ($effectiveAttributes.ContainsKey('Manager')) {
$managerValue = $effectiveAttributes['Manager']
$resolvedManagerDN = $this.ResolveManagerDN($managerValue)
if ($null -ne $resolvedManagerDN) {
$params['Manager'] = $resolvedManagerDN
}
}

# Password handling: support SecureString, ProtectedString, and explicit PlainText
$hasAccountPassword = $effectiveAttributes.ContainsKey('AccountPassword')
Expand Down Expand Up @@ -346,6 +446,14 @@ function New-IdleADAdapter {
'Title' { $params['Title'] = $Value }
'EmailAddress' { $params['EmailAddress'] = $Value }
'UserPrincipalName' { $params['UserPrincipalName'] = $Value }
'Manager' {
# Expect $Value to be a normalized DN or $null.
if ($null -eq $Value) {
$params['Clear'] = 'manager'
} else {
$params['Manager'] = $Value
}
}
default {
$params['Replace'] = @{ $AttributeName = $Value }
}
Expand Down
Loading
Loading