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
101 changes: 101 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 @@ -288,11 +288,112 @@
#### Password attributes
- `AccountPassword` (SecureString or ProtectedString) - Password as SecureString or DPAPI-protected string
- `AccountPasswordAsPlainText` (string) - Plaintext password (explicit opt-in, automatically redacted in events)
- `ResetOnFirstLogin` (boolean) - Require password change on first login (default: `$true` when password is set/generated)
- `AllowPlainTextPasswordOutput` (boolean) - Include plaintext password in result (opt-in, default: `$false`)

:::warning
Only one password attribute can be used at a time. Using both `AccountPassword` and `AccountPasswordAsPlainText` will throw an error.
:::

##### Automatic Password Generation

When creating enabled accounts (`Enabled = $true`) without providing a password, the provider automatically generates a policy-compliant password:

1. **Domain Policy Query**: Attempts to read domain password policy via `Get-ADDefaultDomainPasswordPolicy`
2. **Fallback Configuration**: Uses provider-configured fallback rules if policy cannot be read
3. **Defense in Depth**: Enforces fallback minimum length as baseline even when domain policy allows weaker passwords
4. **Complexity Requirements**: Generates passwords with uppercase, lowercase, digits, and special characters

**Provider Configuration** (optional fallback parameters):
```powershell
$provider = New-IdleADIdentityProvider `
-PasswordGenerationFallbackMinLength 32 `
-PasswordGenerationRequireUpper $true `
-PasswordGenerationRequireLower $true `
-PasswordGenerationRequireDigit $true `
-PasswordGenerationRequireSpecial $true `
-PasswordGenerationSpecialCharSet '!@#$%^&*()'
```

**Default fallback values**:
- `PasswordGenerationFallbackMinLength`: 24
- `PasswordGenerationRequire{Upper,Lower,Digit,Special}`: `$true`
- `PasswordGenerationSpecialCharSet`: `'!@#$%&*+-_=?'`

##### Password Output Control

By default, generated passwords are returned as **ProtectedString** (DPAPI-scoped) for secure reveal:

```powershell
# Default: secure ProtectedString output
$result = $provider.CreateIdentity('jdoe@contoso.com', @{
SamAccountName = 'jdoe'
GivenName = 'John'
Surname = 'Doe'
Enabled = $true
})

# Password was generated
$result.PasswordGenerated # $true
$result.PasswordGenerationPolicyUsed # 'DomainPolicy' or 'Fallback'
$result.GeneratedAccountPasswordProtected # DPAPI-scoped ProtectedString
```

**Reveal Path** (decrypt ProtectedString when needed):
```powershell
$protectedPwd = $result.GeneratedAccountPasswordProtected
$securePwd = ConvertTo-SecureString -String $protectedPwd
$plainPwd = [pscredential]::new('x', $securePwd).GetNetworkCredential().Password
```

:::warning DPAPI Scope
ProtectedString uses Windows DPAPI and can only be decrypted by the same Windows user on the same machine. Do not transfer ProtectedStrings across machines or user contexts.
:::

**Opt-in Plaintext Output**:

For scenarios requiring immediate plaintext access (e.g., displaying to onboarding staff), set `AllowPlainTextPasswordOutput = $true`:

```powershell
$result = $provider.CreateIdentity('jsmith@contoso.com', @{
SamAccountName = 'jsmith'
GivenName = 'Jane'
Surname = 'Smith'
Enabled = $true
AllowPlainTextPasswordOutput = $true
})

# Plaintext is included in result (redacted from logs/events)
$plainPwd = $result.GeneratedAccountPasswordPlainText
```

:::danger Security Warning
Results containing `GeneratedAccountPasswordPlainText` must not be persisted to disk, logs, or databases. The value is automatically redacted from engine events and exports but is accessible in the immediate result object. Handle with care.
:::

##### Reset on First Login

Control whether users must change password on first login:

- `ResetOnFirstLogin` (boolean) - Default: `$true` when password is set/generated
- Maps to AD "User must change password at next logon"
- Can be explicitly set to `$false` for scenarios like hybrid remote login

```powershell
# Default: user must change password on first login
$result = $provider.CreateIdentity('user@contoso.com', @{
SamAccountName = 'user1'
Enabled = $true
})

# Disable reset requirement
$result = $provider.CreateIdentity('admin@contoso.com', @{
SamAccountName = 'admin1'
Enabled = $true
ResetOnFirstLogin = $false
})
```

#### State attributes
- `Enabled` (boolean) - Account enabled state (default: `$true`)

Expand Down
77 changes: 74 additions & 3 deletions docs/reference/providers/provider-entraID.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
title: Provider Reference - IdLE.Provider.EntraID
sidebar_label: Entra ID
Expand Down Expand Up @@ -525,12 +525,83 @@
| `OfficeLocation` | `officeLocation` | Office location |
| `CompanyName` | `companyName` | Company name |
| `MailNickname` | `mailNickname` | Mail alias (auto-generated if not provided) |
| `PasswordProfile` | `passwordProfile` | Password policy for new users |
| `PasswordProfile` | `passwordProfile` | Password policy for new users (create only) |
| `Enabled` | `accountEnabled` | Account enabled state |

### Password Policy (Create Only)

When creating users, provide a `PasswordProfile`:
#### Automatic Password Generation

When creating users without providing a `PasswordProfile`, the provider automatically generates a secure initial password using GUID format (e.g., `3f2504e0-4f89-11d3-9a0c-0305e82c3301`). GUID passwords:

- Satisfy Entra ID default password complexity requirements while consisting of lowercase letters, digits, and hyphens (`[a-f0-9-]`)
- Are 36 characters long
- Set `forceChangePasswordNextSignIn = $true` by default (user must change on first login)

**Generated password is returned** in the result with controlled output options:

```powershell
# Default: secure ProtectedString output
$result = $provider.CreateIdentity('newuser@contoso.com', @{
UserPrincipalName = 'newuser@contoso.com'
DisplayName = 'New User'
# No PasswordProfile provided - password auto-generated
})

# Access the generated password
$result.PasswordGenerated # $true
$result.PasswordGenerationMethod # 'GUID'
$result.GeneratedAccountPasswordProtected # DPAPI-scoped ProtectedString
```

#### Reveal Path

To reveal the password from ProtectedString when needed:

```powershell
$protectedPwd = $result.GeneratedAccountPasswordProtected
$securePwd = ConvertTo-SecureString -String $protectedPwd
$plainPwd = [pscredential]::new('x', $securePwd).GetNetworkCredential().Password
```

:::warning DPAPI Scope
ProtectedString uses Windows DPAPI and can only be decrypted by the same Windows user on the same machine. Do not transfer ProtectedStrings across machines or user contexts.
:::

#### Opt-in Plaintext Output

For scenarios requiring immediate plaintext access (e.g., displaying to onboarding staff), set `AllowPlainTextPasswordOutput = $true`:

```powershell
$result = $provider.CreateIdentity('user@contoso.com', @{
UserPrincipalName = 'user@contoso.com'
DisplayName = 'User Name'
AllowPlainTextPasswordOutput = $true
})

# Plaintext is included in result (redacted from logs/events)
$plainPwd = $result.GeneratedAccountPasswordPlainText
```

:::danger Security Warning
Results containing `GeneratedAccountPasswordPlainText` must not be persisted to disk, logs, or databases. The value is automatically redacted from engine events and exports but is accessible in the immediate result object. Handle with care.
:::

#### Control Password Reset Requirement

By default, generated passwords require change on first sign-in. To disable this (e.g., for service accounts):

```powershell
$result = $provider.CreateIdentity('serviceaccount@contoso.com', @{
UserPrincipalName = 'serviceaccount@contoso.com'
DisplayName = 'Service Account'
ForceChangePasswordNextSignIn = $false
})
```

#### Explicit Password

When creating users, you can provide an explicit `PasswordProfile`:

```powershell
$attributes = @{
Expand All @@ -543,7 +614,7 @@
}
```

If not provided, a random password is generated with `forceChangePasswordNextSignIn = $true`.
When `PasswordProfile` is explicitly provided, no password generation occurs and no password information is returned in the result.

## Paging

Expand Down
26 changes: 26 additions & 0 deletions examples/workflows/templates/ad-joiner-complete.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@
DisplayName = 'New User'
Description = 'New employee account'
Path = 'OU=Joiners,OU=Users,DC=contoso,DC=local'

# Enable account to trigger automatic password generation
# When Enabled = $true and no password is provided, the provider automatically:
# - Reads domain password policy via Get-ADDefaultDomainPasswordPolicy
# - Falls back to configurable rules if policy cannot be read
# - Generates a compliant password (min length 24, complexity enabled)
# - Returns GeneratedAccountPasswordProtected (DPAPI-scoped) by default
Enabled = $true
Comment thread
blindzero marked this conversation as resolved.

# Optional: Request plaintext password in result (for displaying to onboarding staff)
# WARNING: Results containing plaintext must not be persisted to disk/logs
# AllowPlainTextPasswordOutput = $true

# Optional: Disable password reset on first login (default: $true)
# Useful for hybrid scenarios where remote login may require stable password
# ResetOnFirstLogin = $false

OtherAttributes = @{
# Custom LDAP attributes for organization-specific needs
employeeType = 'Employee'
Expand All @@ -27,6 +44,15 @@
# If omitted, defaults to 'Identity'.
Provider = 'Identity'
}
# After execution, when password was generated, the result will contain:
# - PasswordGenerated: $true
# - PasswordGenerationPolicyUsed: 'DomainPolicy' or 'Fallback'
# - GeneratedAccountPasswordProtected: DPAPI-scoped ProtectedString (safe for reveal)
# - GeneratedAccountPasswordPlainText: (only if AllowPlainTextPasswordOutput = $true)
#
# To reveal the password from ProtectedString:
# $secure = ConvertTo-SecureString -String $result.GeneratedAccountPasswordProtected
# $plain = [pscredential]::new('x', $secure).GetNetworkCredential().Password
},
@{
Name = 'Set Department and Title'
Expand Down
7 changes: 6 additions & 1 deletion src/IdLE.Core/Private/Copy-IdleRedactedObject.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ function Copy-IdleRedactedObject {

# Default key list aligned with Issue #48 acceptance criteria.
# Keep this list conservative (exact match) to avoid accidental over-redaction.
# Note: These fields are redacted when objects pass through logging/eventing paths.
# They do NOT prevent direct access when explicitly requested (e.g., AllowPlainTextPasswordOutput).
# Redaction protects against accidental leakage, not intentional access by callers.
$defaultKeys = @(
'password',
'passphrase',
Expand All @@ -30,7 +33,9 @@ function Copy-IdleRedactedObject {
'credential',
'privateKey',
'AccountPassword',
'AccountPasswordAsPlainText'
'AccountPasswordAsPlainText',
'GeneratedAccountPasswordPlainText',
Comment thread
blindzero marked this conversation as resolved.
'GeneratedAccountPasswordProtected'
Comment thread
blindzero marked this conversation as resolved.
)

$effectiveKeys = if ($null -ne $RedactedKeys -and $RedactedKeys.Count -gt 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/IdLE.Provider.AD/Private/Get-IdleADAttributeContract.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ function Get-IdleADAttributeContract {
# Password Attributes
AccountPassword = @{ Target = 'Parameter'; Type = 'SecureString|String'; Required = $false }
AccountPasswordAsPlainText = @{ Target = 'Parameter'; Type = 'String'; Required = $false }
ResetOnFirstLogin = @{ Target = 'Parameter'; Type = 'Boolean'; Required = $false }
AllowPlainTextPasswordOutput = @{ Target = 'Parameter'; Type = 'Boolean'; Required = $false }

# State Attributes
Enabled = @{ Target = 'Parameter'; Type = 'Boolean'; Required = $false }
Expand Down
Loading
Loading