Skip to content

AD Provider: Derive SamAccountName and optionally UPN from IdentityKey in CreateIdentity #152

@blindzero

Description

@blindzero

Problem Statement

When using the provider-agnostic step IdLE.Step.CreateIdentity together with IdLE.Provider.AD, authoring a first simple joiner workflow is unnecessarily error-prone:

  • New-ADUser requires -SamAccountName, but IdLE currently does not derive it from IdentityKey. Users must duplicate the same value in both With.IdentityKey and With.Attributes.SamAccountName.
  • If IdentityKey is provided as a UPN (user@domain) it is intuitive to treat this as the UPN, but IdLE currently does not auto-set Attributes.UserPrincipalName when missing.
  • The AD object CN/RDN name (New-ADUser -Name) should not be coupled to IdentityKey. Users want the directory object name to follow explicit values (e.g., DisplayName or GivenName Surname) without needing AD-specific steps.
  • Path must be reliably passed through to the AD RSAT cmdlets (New-ADUser -Path ...) to avoid default-container placement (and frequent “Access is denied” due to ACL differences).

Goal: Improve usability for AD joiner workflows while keeping IdLE.Step.CreateIdentity provider-agnostic (changes must be in the AD provider/adapter only).

Proposed Solution

Implement AD-specific convenience behavior in IdLE.Provider.AD (and/or its adapter) for CreateIdentity(IdentityKey, Attributes):

1) Derive SamAccountName from IdentityKey (conservative)

If Attributes.SamAccountName is missing/empty:

  • Classify IdentityKey using the same/consistent logic as AD identity resolution (GUID vs UPN vs SamAccountName-like).
  • If IdentityKey is SamAccountName-like:
    • Set Attributes.SamAccountName = IdentityKey.
  • If IdentityKey is a UPN (user@domain):
    • Fail fast (B1) with an actionable error explaining that SamAccountName is mandatory for New-ADUser and must be provided explicitly when IdentityKey is a UPN.

Never override explicitly provided Attributes.SamAccountName.

2) Auto-set UserPrincipalName when IdentityKey is a UPN

If IdentityKey is a UPN and Attributes.UserPrincipalName is missing/empty:

  • Set Attributes.UserPrincipalName = IdentityKey.

Never override explicitly provided Attributes.UserPrincipalName.

3) Derive the AD object CN/RDN -Name independently of IdentityKey

When calling New-ADUser -Name ..., determine the name with this priority order:

  1. Attributes.Name (explicit CN/RDN)
  2. Attributes.DisplayName
  3. If Attributes.GivenName and Attributes.Surname are present: "<GivenName> <Surname>"
  4. Fallback: IdentityKey (with a warning/debug event to highlight the fallback)

This ensures Name != IdentityKey by default in typical joiner workflows, while remaining deterministic.

4) Ensure Path is passed through to RSAT

Continue to accept Attributes.Path and ensure it is consistently passed through to New-ADUser -Path ... (no additional derivation required).

5) Logging / events

When any derivation occurs (SamAccountName, UserPrincipalName, CN/RDN Name):

  • Emit a structured provider/engine event (or debug log) indicating what was derived and why.
  • Avoid logging sensitive values (e.g., do not log full credentials; attribute values may be logged only if safe per existing logging rules).

Alternatives Considered

  • Document-only approach (require users to always specify SamAccountName and manage -Name themselves).

    • Rejected: leads to repetitive workflows and confusing first-time experience.
  • Make IdLE.Step.CreateIdentity AD-aware.

    • Rejected: step must remain provider-agnostic by design.
  • Automatically derive SamAccountName from UPN left-part (truncate/sanitize/collision handling).

    • Rejected for now: introduces org-specific policy decisions and risk of unexpected collisions. The chosen approach is B1 fail-fast for UPN identity keys.

Impact

  • Does this affect existing workflows?

    • Intended to be additive. If workflows already specify Attributes.SamAccountName, Attributes.UserPrincipalName, Attributes.Name, no behavior changes.
    • Workflows that omitted SamAccountName may start working automatically when IdentityKey is samAccountName-like.
  • Any backward compatibility concerns?

    • Low risk: only fill missing/empty attributes; never override explicit values.
    • IdentityKey as UPN and missing SamAccountName will now fail fast with a clearer, earlier error (explicitly guiding users to set SamAccountName).

Additional Context

Acceptance Criteria

  • SamAccountName derivation

    • If Attributes.SamAccountName is missing/empty and IdentityKey is samAccountName-like, SamAccountName is set to IdentityKey.
    • If IdentityKey is a UPN and SamAccountName is missing/empty, creation fails fast with a clear error.
  • UPN convenience

    • If IdentityKey is a UPN and Attributes.UserPrincipalName is missing/empty, UserPrincipalName is set to IdentityKey.
  • CN/RDN name selection

    • New-ADUser -Name uses the defined priority order: Name > DisplayName > GivenName+Surname > IdentityKey fallback.
    • A warning/debug event is emitted when falling back to IdentityKey.
  • Path pass-through

    • If Attributes.Path is provided, it is used as New-ADUser -Path (no defaulting).
  • Tests

    • Unit tests cover all derivation branches and the “never override explicit values” rule.

Example Workflow Snippet

With = @{
  IdentityKey = 'foo.bar@contoso.com'   # treated as UPN
  Attributes  = @{
    Path       = 'OU=Users,DC=contoso,DC=com'
    GivenName  = 'Bar'
    Surname    = 'Foo'
    DisplayName = 'Bar Foo'
    # SamAccountName intentionally omitted -> should fail fast (B1) with clear guidance
  }
}
With = @{
  IdentityKey = 'foo.bar'               # samAccountName-like
  Attributes  = @{
    Path              = 'OU=Users,DC=contoso,DC=com'
    GivenName         = 'Bar'
    Surname           = 'Foo'
    DisplayName       = 'Bar Foo'
    # SamAccountName omitted -> derived from IdentityKey
    # New-ADUser -Name derived from DisplayName (or GivenName+Surname)
  }
}

Implementation Notes (non-binding)

  • Prefer a shared helper for IdentityKey classification to keep ResolveIdentity and CreateIdentity consistent.
  • Keep behavior provider-scoped; do not change the generic CreateIdentity step contract.

Metadata

Metadata

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions