From 774e07a27f6e123556463145b185b741f6dc618f Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:53:21 +0100 Subject: [PATCH 01/28] docs: new provider docu template --- .../providers/_provider-name_template.md | 213 ++++++++++-------- 1 file changed, 116 insertions(+), 97 deletions(-) diff --git a/docs/reference/providers/_provider-name_template.md b/docs/reference/providers/_provider-name_template.md index 9b119a30..b6620357 100644 --- a/docs/reference/providers/_provider-name_template.md +++ b/docs/reference/providers/_provider-name_template.md @@ -1,95 +1,137 @@ # Provider Reference Template -> **Purpose:** This page is a **reference** for a specific provider implementation. -> Keep it factual and contract-oriented. Put conceptual explanations elsewhere and link to them. +> **Audience:** Admins and workflow authors (not developers). +> **Goal:** Help users get the provider running, wire authentication, understand what steps it supports, and copy working examples. +> +> Keep the page **practical** and **scan-friendly**: +> - Prefer short tables over long prose. +> - Avoid implementation details (interfaces, contracts, test paths, CI notes). +> - Do not document "from source" installation here. --- ## Summary -- **Provider name:** `` -- **Module:** `` (e.g. `IdLE.Provider.*`) -- **Provider kind:** `` -- **Targets:** `` -- **Status:** `` -- **Since:** `` (optional) -- **Compatibility:** PowerShell 7+ (IdLE requirement) +| Item | Value | +| --- | --- | +| **Provider name** | `` | +| **Module** | `` (e.g. `IdLE.Provider.*`) | +| **Provider role** | `` | +| **Targets** | `` | +| **Status** | `` | +| **Since** | `` (optional) | +| **PowerShell** | PowerShell 7+ | --- -## What this provider does +## When to use this provider -- **Primary responsibilities:** - - `` - - `` -- **Out of scope / non-goals:** - - `` - - `` +### Use cases + +- `` +- `` + +### Out of scope + +- `` +- `` --- -## Contracts and capabilities +## Getting started -### Contracts implemented +### Requirements -List the IdLE provider contracts this provider implements and what they mean at a glance. +> List only what an admin must prepare **before** installing. -| Contract | Used by steps for | Notes | -| --- | --- | --- | -| `` | `` | `` | -| `` | `` | `` | +- **Dependencies:** `` +- **Permissions / roles:** `` +- **Network / endpoints:** `` (if applicable) -> Keep the contract list stable and link to the canonical contract reference. +### Install (PowerShell Gallery) -### Capability advertisement (`GetCapabilities()`) +```powershell +Install-Module -Scope CurrentUser +``` + +> Optional: add `-RequiredVersion` or `Update-Module` notes if needed. + +### Import & basic check -- **Implements `GetCapabilities()`**: `` -- **Capabilities returned (stable identifiers):** - - `` - - `` - - `` +```powershell +Import-Module + +# Create provider instance (minimal) +$provider = +``` + +If import or creation fails, see **Troubleshooting**. + +--- + +## Quickstart (minimal runnable) + +> Provide the smallest, realistic end-to-end example (copy/paste). + +```powershell +# 1) Provider instance +$provider = + +# 2) Provider map (alias used in workflows) +$providers = @{ + = $provider +} + +# 3) Plan + execute (example shape) +$plan = New-IdlePlan -WorkflowPath -Request -Providers $providers +$result = Invoke-IdlePlan -Plan $plan -Providers $providers +``` --- -## Authentication and session acquisition +## Authentication -> Providers must not prompt for auth. Use the host-provided broker contract. +> Providers must not prompt for auth. They acquire sessions via the host's AuthSessionBroker. -- **Auth session name(s) requested via `Context.AcquireAuthSession(...)`:** - - `` +- **Auth session type(s):** `` +- **Auth session name(s):** `` (if multiple, list when/why) - **Session options (data-only):** - - ``: `` — `` (optional default: `<...>`) + - ``: `` — `` (default: `<...>`) :::warning +**Security** +- Do not pass secrets in provider options or workflow files. +- Ensure credentials/tokens are not written to logs or events. +::: -**Security notes** +--- -- Do not pass secrets in provider options. -- Ensure token/credential objects are not emitted in events. +## Supported step types -::: +> Admins think in **Step Types**. List what works with this provider. + +| Step Type | Typical use | Notes | +| --- | --- | --- | +| `` | `` | `` | +| `` | `<...>` | `<...>` | --- ## Configuration -### Provider constructor / factory +### Provider creation -How to create an instance. - -- **Public constructor cmdlet(s):** +- **Factory cmdlet(s):** - `` — `` -**Parameters (high signal only)** +**High-signal parameters (only)** - `-Name ` — `<...>` - `-Options ` — `<...>` -> Do not copy full comment-based help here. Link to the cmdlet reference. - -### Provider bag / alias usage +> Link to cmdlet reference instead of copying full help. -How to pass the provider instance to IdLE as part of the host's provider map. +### Provider alias usage ```powershell $providers = @{ @@ -97,12 +139,10 @@ $providers = @{ } ``` -- **Recommended alias pattern:** `` -- **Default alias expected by built-in steps (if any):** `` (if applicable) +- **Recommended alias:** `` +- **Default alias expected by built-in steps (if any):** `` (optional) ---- - -## Provider-specific options reference +### Options reference > Document only **data-only** keys. Keep this list short and unambiguous. @@ -115,44 +155,18 @@ $providers = @{ ## Operational behavior -### Idempotency and consistency - -- **Idempotent operations:** `` -- **Consistency model:** `` -- **Concurrency notes:** `` - -### Error mapping and retry behavior - -- **Common error categories:** `` -- **Retry strategy:** `` - ---- - -## Observability - -- **Events emitted by provider (if any):** - - `` — `` — `` -- **Sensitive data redaction:** `` +- **Idempotency:** `` — `` +- **Consistency model:** `` +- **Throttling / rate limits:** `` +- **Retry behavior:** `` --- ## Examples -### Minimal host usage - -```powershell -# 1) Create provider instance -$provider = - -# 2) Build provider map -$providers = @{ = $provider } - -# 3) Plan + execute -$plan = New-IdlePlan -WorkflowPath -Request -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` +> Keep only 1–2 short examples inline. Link to the repository's `examples/` for more. -### Example workflow snippet +### Example workflow (template) ```powershell @{ @@ -161,7 +175,7 @@ $result = Invoke-IdlePlan -Plan $plan -Providers $providers Name = '' Type = '' With = @{ - Provider = '' + Provider = '' # ... } } @@ -169,23 +183,28 @@ $result = Invoke-IdlePlan -Plan $plan -Providers $providers } ``` ---- +### More examples -## Limitations and known issues +- `` +- `` -- `` -- `` +> Documentation author note: if your site uses MDX, you can embed `.psd1` examples directly from `/examples` to avoid duplication. --- -## Testing +## Troubleshooting -- **Unit tests:** `` -- **Contract tests:** `` -- **Known CI constraints:** `` +> Keep this practical and symptom-driven. ---- +### Common problems + +- **Import fails:** `` → `` +- **Auth session not found:** `` → `` +- **Permission denied:** `` → `` +- **Step fails due to provider mismatch:** `` → `` -## Changelog (optional) +### What to collect for support -- `` — `` +- IdLE version, provider module version +- Redacted error message / event id +- Target system region/tenant (if relevant), without secrets From c92cfc9f7dd90c4151c55b9056c9be9b0a35888c Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:17:49 +0100 Subject: [PATCH 02/28] examples: rewrote ad tempalte examples --- .../templates/ad-joiner-complete.psd1 | 107 --------------- examples/workflows/templates/ad-joiner.psd1 | 122 ++++++++++++++++++ .../templates/ad-leaver-offboarding.psd1 | 49 ------- examples/workflows/templates/ad-leaver.psd1 | 75 +++++++++++ .../templates/ad-mover-department-change.psd1 | 57 -------- 5 files changed, 197 insertions(+), 213 deletions(-) delete mode 100644 examples/workflows/templates/ad-joiner-complete.psd1 create mode 100644 examples/workflows/templates/ad-joiner.psd1 delete mode 100644 examples/workflows/templates/ad-leaver-offboarding.psd1 create mode 100644 examples/workflows/templates/ad-leaver.psd1 delete mode 100644 examples/workflows/templates/ad-mover-department-change.psd1 diff --git a/examples/workflows/templates/ad-joiner-complete.psd1 b/examples/workflows/templates/ad-joiner-complete.psd1 deleted file mode 100644 index b75135a0..00000000 --- a/examples/workflows/templates/ad-joiner-complete.psd1 +++ /dev/null @@ -1,107 +0,0 @@ -@{ - Name = 'Joiner - AD Complete Workflow' - LifecycleEvent = 'Joiner' - Steps = @( - @{ - Name = 'Create AD user account' - Type = 'IdLE.Step.CreateIdentity' - With = @{ - IdentityKey = 'newuser' - Attributes = @{ - SamAccountName = 'newuser' - UserPrincipalName = 'newuser@contoso.local' - GivenName = 'New' - Surname = 'User' - 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 - - # 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' - extensionAttribute1 = 'EMPL-2024-001' - company = 'Contoso Ltd' - } - } - # Provider alias - references the key in the provider hashtable. - # The host chooses this name when creating the provider hashtable. - # 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' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - IdentityKey = 'newuser@contoso.local' - Attributes = @{ - Department = 'IT' - Title = 'Software Engineer' - } - Provider = 'Identity' - } - }, - @{ - Name = 'Grant base access group' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - IdentityKey = 'newuser@contoso.local' - Entitlement = @{ - Kind = 'Group' - Id = 'CN=All-Employees,OU=Groups,DC=contoso,DC=local' - DisplayName = 'All Employees' - } - State = 'Present' - Provider = 'Identity' - } - }, - @{ - Name = 'Grant IT department group' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - IdentityKey = 'newuser@contoso.local' - Entitlement = @{ - Kind = 'Group' - Id = 'CN=IT-Department,OU=Groups,DC=contoso,DC=local' - DisplayName = 'IT Department' - } - State = 'Present' - Provider = 'Identity' - } - }, - @{ - Name = 'Move to active users OU' - Type = 'IdLE.Step.MoveIdentity' - With = @{ - IdentityKey = 'newuser@contoso.local' - TargetContainer = 'OU=Active,OU=Users,DC=contoso,DC=local' - Provider = 'Identity' - } - } - ) -} diff --git a/examples/workflows/templates/ad-joiner.psd1 b/examples/workflows/templates/ad-joiner.psd1 new file mode 100644 index 00000000..42beb878 --- /dev/null +++ b/examples/workflows/templates/ad-joiner.psd1 @@ -0,0 +1,122 @@ +@{ + Metadata = @{ + Name = 'AD - Joiner (complete)' + Description = 'Creates/updates an AD identity and applies baseline attributes and memberships. Includes optional mover patterns.' + Version = '1.0' + Tags = @('AD', 'Joiner', 'JML', 'Template') + } + + Workflow = @{ + Name = 'ad-joiner-complete' + Description = 'AD joiner workflow template (safe defaults).' + + # The workflow author decides the provider alias. Example: "Directory" + With = @{ + Provider = 'Directory' + } + + Steps = @( + # --- Identity creation / baseline --- + @{ + StepType = 'IdLE.Step.Identity.Create' + Name = 'Create identity (if missing)' + With = @{ + # Required by the provider: which auth session to use + AuthSessionName = '{{Request.Auth.Directory}}' + + # Provider-specific: identify the target identity + # The exact key names depend on provider contracts; keep it consistent with your provider docs. + Identity = @{ + SamAccountName = '{{Request.Input.SamAccountName}}' + UserPrincipalName = '{{Request.Input.UserPrincipalName}}' + } + + # Optional: initial attributes that are commonly required + Attributes = @{ + GivenName = '{{Request.Input.GivenName}}' + Surname = '{{Request.Input.Surname}}' + DisplayName = '{{Request.Input.DisplayName}}' + } + } + } + + @{ + StepType = 'IdLE.Step.Identity.EnsureAttributes' + Name = 'Ensure core attributes' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ + SamAccountName = '{{Request.Input.SamAccountName}}' + } + Attributes = @{ + Mail = '{{Request.Input.Mail}}' + Department = '{{Request.Input.Department}}' + Title = '{{Request.Input.Title}}' + Company = '{{Request.Input.Company}}' + Office = '{{Request.Input.Office}}' + Manager = '{{Request.Input.ManagerSamAccountName}}' + TelephoneNumber = '{{Request.Input.Phone}}' + } + } + } + + @{ + StepType = 'IdLE.Step.Identity.EnsureEntitlements' + Name = 'Ensure baseline group memberships' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ + SamAccountName = '{{Request.Input.SamAccountName}}' + } + + # Use explicit, predictable lists. Prefer allow-lists for baseline access. + Entitlements = @( + '{{Request.Input.BaselineGroups.0}}' + '{{Request.Input.BaselineGroups.1}}' + ) + } + } + + # --- Optional: Mover patterns (disabled by default) --- + # Use one of these approaches: + # A) Guard execution via a flag (preferred) + # B) Keep steps commented out and enable when needed + + @{ + StepType = 'IdLE.Step.Identity.EnsureAttributes' + Name = 'Mover: update org attributes (optional)' + With = @{ + # Guard by convention: only run when request indicates mover + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Attributes = @{ + Department = '{{Request.Input.NewDepartment}}' + Title = '{{Request.Input.NewTitle}}' + Office = '{{Request.Input.NewOffice}}' + Manager = '{{Request.Input.NewManagerSamAccountName}}' + Description = 'Moved on {{Request.Execution.Timestamp}}' + } + } + } + + @{ + StepType = 'IdLE.Step.Identity.EnsureEntitlements' + Name = 'Mover: adjust group memberships (optional)' + With = @{ + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + + # Optional: baseline + department-specific groups. + Entitlements = @( + '{{Request.Input.BaselineGroups.0}}' + '{{Request.Input.BaselineGroups.1}}' + '{{Request.Input.DepartmentGroups.0}}' + '{{Request.Input.DepartmentGroups.1}}' + ) + } + } + ) + } +} \ No newline at end of file diff --git a/examples/workflows/templates/ad-leaver-offboarding.psd1 b/examples/workflows/templates/ad-leaver-offboarding.psd1 deleted file mode 100644 index f8f1f489..00000000 --- a/examples/workflows/templates/ad-leaver-offboarding.psd1 +++ /dev/null @@ -1,49 +0,0 @@ -@{ - Name = 'Leaver - AD Offboarding Workflow' - LifecycleEvent = 'Leaver' - Steps = @( - @{ - Name = 'Disable user account' - Type = 'IdLE.Step.DisableIdentity' - With = @{ - IdentityKey = 'leavinguser@contoso.local' - # Provider alias references the provider hashtable key set by the host. - # The alias name is flexible and chosen when injecting providers. - Provider = 'Identity' - } - }, - @{ - Name = 'Update Description with termination date' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - IdentityKey = 'leavinguser@contoso.local' - Attributes = @{ - Description = 'Terminated 2026-01-18' - } - Provider = 'Identity' - } - }, - @{ - Name = 'Move to Leavers OU' - Type = 'IdLE.Step.MoveIdentity' - With = @{ - IdentityKey = 'leavinguser@contoso.local' - TargetContainer = 'OU=Leavers,OU=Disabled,DC=contoso,DC=local' - Provider = 'Identity' - } - }, - @{ - Name = 'Delete user account (opt-in required)' - Type = 'IdLE.Step.DeleteIdentity' - With = @{ - IdentityKey = 'leavinguser@contoso.local' - Provider = 'Identity' - } - Condition = @{ - Exists = @{ - Path = 'Input.AllowDelete' - } - } - } - ) -} diff --git a/examples/workflows/templates/ad-leaver.psd1 b/examples/workflows/templates/ad-leaver.psd1 new file mode 100644 index 00000000..dab32c1c --- /dev/null +++ b/examples/workflows/templates/ad-leaver.psd1 @@ -0,0 +1,75 @@ +@{ + Metadata = @{ + Name = 'AD - Leaver (offboarding)' + Description = 'Disables an AD identity and applies offboarding changes. Includes notes for mover-to-leaver transitions.' + Version = '1.0' + Tags = @('AD', 'Leaver', 'JML', 'Template') + } + + Workflow = @{ + Name = 'ad-leaver-offboarding' + Description = 'AD leaver workflow template (safe defaults).' + + With = @{ + Provider = 'Directory' + } + + Steps = @( + @{ + StepType = 'IdLE.Step.Identity.Disable' + Name = 'Disable identity' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Reason = '{{Request.Input.LeaverReason}}' + } + } + + @{ + StepType = 'IdLE.Step.Identity.EnsureAttributes' + Name = 'Stamp offboarding attributes' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Attributes = @{ + Description = 'Leaver on {{Request.Execution.Timestamp}} - {{Request.Input.LeaverReason}}' + } + } + } + + # Optional, use with caution: + # Removing groups can break business processes unexpectedly. + # Prefer an explicit allow-list or a "remove only managed groups" approach. + @{ + StepType = 'IdLE.Step.Identity.RemoveEntitlements' + Name = 'Remove managed group memberships (optional)' + With = @{ + Condition = '{{Request.Input.RemoveGroups}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + + # Only remove what you explicitly manage via IdLE. + Entitlements = @( + '{{Request.Input.ManagedGroupsToRemove.0}}' + '{{Request.Input.ManagedGroupsToRemove.1}}' + ) + } + } + + # --- Mover-to-leaver transition notes (operational) --- + # Common approach: + # - Day 0: Disable + stamp description (safe, minimal risk) + # - Day N: Remove managed groups + move to Disabled OU (explicit opt-in) + @{ + StepType = 'IdLE.Step.Identity.MoveContainer' + Name = 'Move to Disabled OU (optional)' + With = @{ + Condition = '{{Request.Input.MoveToDisabledOu}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + TargetPath = '{{Request.Input.DisabledOuPath}}' + } + } + ) + } +} \ No newline at end of file diff --git a/examples/workflows/templates/ad-mover-department-change.psd1 b/examples/workflows/templates/ad-mover-department-change.psd1 deleted file mode 100644 index 749af356..00000000 --- a/examples/workflows/templates/ad-mover-department-change.psd1 +++ /dev/null @@ -1,57 +0,0 @@ -@{ - Name = 'Mover - AD Department Change Workflow' - LifecycleEvent = 'Mover' - Steps = @( - @{ - Name = 'Update Department and Title' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - IdentityKey = 'existinguser@contoso.local' - Attributes = @{ - Department = 'Sales' - Title = 'Sales Manager' - } - # Provider alias - can be customized when host creates the provider hashtable. - # Examples: 'Identity', 'SourceAD', 'TargetAD', 'SystemX', etc. - Provider = 'Identity' - } - }, - @{ - Name = 'Revoke old IT department group' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - IdentityKey = 'existinguser@contoso.local' - Entitlement = @{ - Kind = 'Group' - Id = 'CN=IT-Department,OU=Groups,DC=contoso,DC=local' - DisplayName = 'IT Department' - } - State = 'Absent' - Provider = 'Identity' - } - }, - @{ - Name = 'Grant Sales department group' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - IdentityKey = 'existinguser@contoso.local' - Entitlement = @{ - Kind = 'Group' - Id = 'CN=Sales-Department,OU=Groups,DC=contoso,DC=local' - DisplayName = 'Sales Department' - } - State = 'Present' - Provider = 'Identity' - } - }, - @{ - Name = 'Move to Sales OU' - Type = 'IdLE.Step.MoveIdentity' - With = @{ - IdentityKey = 'existinguser@contoso.local' - TargetContainer = 'OU=Sales,OU=Users,DC=contoso,DC=local' - Provider = 'Identity' - } - } - ) -} From 909d42c7a77b7d816614f7341d9de90b082ca93e Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:52:37 +0100 Subject: [PATCH 03/28] update docusaurus npm --- website/package-lock.json | 8 ++++---- website/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index d1d58d06..c4a7a343 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,7 +11,7 @@ "@docusaurus/core": "3.9.2", "@docusaurus/plugin-client-redirects": "^3.9.2", "@docusaurus/preset-classic": "3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", + "@docusaurus/theme-mermaid": "^3.7.0", "@easyops-cn/docusaurus-search-local": "^0.52.2", "@mdx-js/react": "^3.0.0", "@saucelabs/theme-github-codeblock": "^0.3.0", @@ -16961,9 +16961,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/website/package.json b/website/package.json index b5d46f4b..45b025ee 100644 --- a/website/package.json +++ b/website/package.json @@ -18,7 +18,7 @@ "@docusaurus/core": "3.9.2", "@docusaurus/plugin-client-redirects": "^3.9.2", "@docusaurus/preset-classic": "3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", + "@docusaurus/theme-mermaid": "^3.7.0", "@easyops-cn/docusaurus-search-local": "^0.52.2", "@mdx-js/react": "^3.0.0", "@saucelabs/theme-github-codeblock": "^0.3.0", From 566bd047bbce5ddee7c14c5b84ffcceac4e6c222 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:52:52 +0100 Subject: [PATCH 04/28] include examples psd1 in docusaurus --- website/docusaurus.config.js | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 18487ec0..68b5e9b6 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -11,6 +11,26 @@ const darkCodeTheme = themes.dracula; const repoOwner = 'blindzero'; const repoName = 'IdentityLifecycleEngine'; + +// Allow importing .ps1/.psd1 files as raw source strings in MDX (for embedding examples) +function idleRawPowerShellFiles() { + return { + name: 'idle-raw-powershell-files', + configureWebpack() { + return { + module: { + rules: [ + { + test: /\.(psd1|ps1)$/i, + type: 'asset/source', + }, + ], + }, + }; + }, + }; +} + // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) /** @type {import('@docusaurus/types').Config} */ @@ -34,7 +54,7 @@ const config = { projectName: repoName, // Usually your repo name. onBrokenLinks: 'warn', - + // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you // may want to replace "en" with "zh-Hans". @@ -65,9 +85,9 @@ const config = { 'extend/providers.md', 'extend/steps.md'], }, - + blog: false, // Disable blog plugin - maybe enable later - + theme: { customCss: require.resolve('./src/css/custom.css'), }, @@ -149,8 +169,9 @@ const config = { }, }, }), - + plugins: [ + idleRawPowerShellFiles, // Local search (no Algolia account needed) [ require.resolve('@easyops-cn/docusaurus-search-local'), @@ -182,7 +203,7 @@ const config = { require.resolve('@saucelabs/theme-github-codeblock'), // Optional Mermaid support (if installed) - require.resolve('@docusaurus/theme-mermaid'), + //require.resolve('@docusaurus/theme-mermaid'), ], // If you enable Mermaid theme above, also enable markdown mermaid: From a338d43c6ff6808c74ade2a33c0020de0e526758 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:53:01 +0100 Subject: [PATCH 05/28] docs: updated ad provider docu --- docs/reference/providers/provider-ad.md | 1016 ++--------------------- 1 file changed, 81 insertions(+), 935 deletions(-) diff --git a/docs/reference/providers/provider-ad.md b/docs/reference/providers/provider-ad.md index d4d879bf..32dba30b 100644 --- a/docs/reference/providers/provider-ad.md +++ b/docs/reference/providers/provider-ad.md @@ -3,995 +3,141 @@ title: Provider Reference - IdLE.Provider.AD (Active Directory) sidebar_label: Active Directory --- +import CodeBlock from '@theme/CodeBlock'; + +import AdJoiner from '@site/../examples/workflows/templates/ad-joiner.psd1'; +import AdLeaver from '@site/../examples/workflows/templates/ad-leaver.psd1'; + ## Summary -- **Provider name:** `AD` (Active Directory) - **Module:** `IdLE.Provider.AD` -- **Provider kind:** `Identity | Entitlement` -- **Targets:** Windows Active Directory (on-premises domains) +- **Provider kind:** `Identity` + `Entitlement (Groups)` +- **Targets:** On-premises Windows Active Directory domains - **Status:** Built-in -- **Since:** 0.9.0 -- **Compatibility:** PowerShell 7+ (IdLE requirement), Windows-only (requires RSAT/ActiveDirectory PowerShell module) - ---- - -## What this provider does +- **Runs on:** Windows only (requires RSAT / `ActiveDirectory` PowerShell module) +- **Default safety:** destructive operations are **opt-in** (e.g. delete) -- **Primary responsibilities:** - - Create, read, update, disable, enable, and delete (opt-in) user accounts in Active Directory - - Set and update user attributes (department, title, office location, etc.) - - Move users between organizational units (OUs) - - Manage group memberships (grant/revoke entitlements) -- **Out of scope / non-goals:** - - Establishing AD connectivity or authentication (handled by host-provided credentials or integrated auth) - - Managing group policy objects (GPOs) - - Managing other AD object types (computers, contacts, etc.) +## When to use this provider ---- - -## Contracts and capabilities +Use this provider when your workflow needs to manage **on-premises AD user accounts**, such as: -### Contracts implemented +- Joiner: create/update AD users and set baseline attributes +- Mover: update org attributes and adjust managed group memberships +- Leaver: disable accounts and apply offboarding changes -| Contract | Used by steps for | Notes | -| --- | --- | --- | -| Identity provider (implicit) | Identity read/write operations | Supports comprehensive identity lifecycle operations including OU moves | -| Entitlement provider (implicit) | Grant/revoke/list entitlements | Only supports `Kind='Group'` (AD platform limitation) | +Non-goals: -> Keep the contract list stable and link to the canonical contract reference. +- Configuring connectivity/authentication itself (handled via your runtime context and the AuthSessionBroker) +- Managing non-user object types (computers, GPOs, etc.) -### Capability advertisement (`GetCapabilities()`) +## Getting started -- **Implements `GetCapabilities()`**: Yes -- **Capabilities returned (stable identifiers):** - - `IdLE.Identity.Read` - Query identity information - - `IdLE.Identity.List` - List identities (provider API only, no built-in step) - - `IdLE.Identity.Create` - Create new user accounts - - `IdLE.Identity.Delete` - Delete user accounts (opt-in via `-AllowDelete`) - - `IdLE.Identity.Disable` - Disable user accounts - - `IdLE.Identity.Enable` - Enable user accounts - - `IdLE.Identity.Move` - Move users between OUs - - `IdLE.Identity.Attribute.Ensure` - Set/update user attributes - - `IdLE.Entitlement.List` - List group memberships - - `IdLE.Entitlement.Grant` - Add users to groups - - `IdLE.Entitlement.Revoke` - Remove users from groups +### Requirements -**Note:** AD only supports `Kind='Group'` for entitlements. This is a platform limitation - Active Directory only provides security groups and distribution groups, not arbitrary entitlement types (roles, licenses, etc.). - ---- - -## Authentication and session acquisition - -> Providers must not prompt for auth. Use the host-provided broker contract. - -- **Auth session name(s) requested via `Context.AcquireAuthSession(...)`:** - - `ActiveDirectory` -- **Session options (data-only):** - - Any hashtable; commonly `@{ Role = 'Tier0' }` or `@{ Role = 'Admin' }` or `@{ Domain = 'SourceForest' }` -- **Auth session formats supported:** - - `$null` (integrated authentication / run-as context) - - `PSCredential` (used for AD cmdlets `-Credential` parameter) - -:::warning - -**Security notes** - -- Do not pass secrets in workflow files or provider options. -- Ensure credential objects (or their secure strings) are not emitted in logs/events. - -::: - ---- +- Windows host with RSAT / `ActiveDirectory` module available +- Permissions sufficient for the operations you plan to run (create/modify users, move OUs, manage group membership) -## Prerequisites +### Install (PowerShell Gallery) -### Windows and RSAT - -The provider requires Windows with the Active Directory PowerShell module (RSAT). - -**Install RSAT on Windows Server:** -```powershell -Install-WindowsFeature -Name RSAT-AD-PowerShell -``` - -**Install RSAT on Windows 10/11:** ```powershell -Get-WindowsCapability -Online -Name "Rsat.ActiveDirectory*" | Add-WindowsCapability -Online +Install-Module IdLE.Provider.AD -Scope CurrentUser ``` -### Active Directory Permissions - -The account running IdLE (or provided via `-Credential`) must have appropriate AD permissions: - -| Operation | Required Permission | -| --------- | ------------------- | -| Read identity | Read access to user objects | -| Create identity | Create user objects in target OU | -| Delete identity | Delete user objects | -| Disable/Enable | Modify user account flags | -| Set attributes | Write access to specific attributes | -| Move identity | Move objects between OUs | -| Grant/Revoke group membership | Modify group membership | - -Follow the principle of least privilege - grant only the permissions required for your workflows. - ---- - -## Installation and Import - -The AD provider is a **standalone provider module** that must be imported separately: +### Import ```powershell Import-Module IdLE.Provider.AD ``` -**Note:** The AD provider requires `IdLE.Core` to be available. When using IdLE in development mode (from the repository), import the main `IdLE` module first, which automatically loads the required dependencies and extends `PSModulePath` to make provider modules discoverable by name. When using published packages from PowerShell Gallery, module dependencies are resolved automatically. - -This makes `New-IdleADIdentityProvider` available in your session. - ---- - -## Configuration - -### Provider constructor / factory - -How to create an instance. -- **Auth session name(s) used by built-in steps:** `ActiveDirectory` -- **Auth session formats supported:** - - `null` (integrated authentication / run-as) - - `PSCredential` (used for AD cmdlets `-Credential`) -- **Session options (data-only):** Any hashtable; commonly `@{ Role = 'Tier0' }` / `@{ Role = 'Admin' }` -- **Required `AuthSessionType`:** `Credential` - -The AD provider uses credential-based authentication where the module capabilities exist without requiring explicit session management. When creating the `AuthSessionBroker`, specify `AuthSessionType = 'Credential'` to indicate this authentication pattern. - -- **Public constructor cmdlet(s):** - - `New-IdleADIdentityProvider` — Creates an Active Directory identity provider instance - -**Parameters (high signal only)** - -- `-AllowDelete` (switch) — Opt-in to enable the `IdLE.Identity.Delete` capability (disabled by default for safety) - -> Do not copy full comment-based help here. Link to the cmdlet reference. - -### Provider bag / alias usage - -How to pass the provider instance to IdLE as part of the host's provider map. - -```powershell -$providers = @{ - Identity = New-IdleADIdentityProvider -} -``` - -- **Recommended alias pattern:** `Identity` (single provider) or `SourceAD` / `TargetAD` (multi-provider scenarios) -- **Default alias expected by built-in steps (if any):** `Identity` (if applicable) - ---- - -## Provider-specific options reference - -> Document only **data-only** keys. Keep this list short and unambiguous. - -This provider has **no provider-specific option bag**. All configuration is done through the constructor parameters and authentication is managed via the `AuthSessionBroker`. - ---- - -## Auth examples (Authentication patterns) - -**A) Integrated authentication (no broker)** - -```powershell -# Run the host under an account that already has the required AD permissions. -$providers = @{ - Identity = New-IdleADIdentityProvider -} -``` - -**B) Role-based routing with `New-IdleAuthSession` (typical Tier0/Admin)** - -```powershell -$tier0Credential = Get-Credential -Message 'Enter Tier0 AD admin credentials' -$adminCredential = Get-Credential -Message 'Enter AD admin credentials' - -# Create broker with Credential session type -$broker = New-IdleAuthSession -SessionMap @{ - @{ Role = 'Tier0' } = $tier0Credential - @{ Role = 'Admin' } = $adminCredential -} -DefaultAuthSession $adminCredential -AuthSessionType 'Credential' - -$providers = @{ - Identity = New-IdleADIdentityProvider - AuthSessionBroker = $broker -} - -# In the workflow step: -# With.AuthSessionName = 'ActiveDirectory' -# With.AuthSessionOptions = @{ Role = 'Tier0' } -``` - -**C) Multi-forest / multi-domain routing** - -```powershell -$sourceCred = Get-Credential -Message 'Enter credentials for source forest' -$targetCred = Get-Credential -Message 'Enter credentials for target forest' - -# Create broker with Credential session type -$broker = New-IdleAuthSession -SessionMap @{ - @{ Domain = 'SourceForest' } = $sourceCred - @{ Domain = 'TargetForest' } = $targetCred -} -AuthSessionType 'Credential' - -# Steps use With.AuthSessionOptions = @{ Domain = 'SourceForest' } etc. -``` - ---- - -## Operational behavior - -### Idempotency and consistency - -- **Idempotent operations:** Yes (all operations) -- **Consistency model:** Strong (Active Directory platform consistency) -- **Concurrency notes:** Operations are safe for retries. AD handles concurrent operations natively. - -All operations are idempotent and safe for retries: - -| Operation | Idempotent Behavior | -| --------- | ------------------- | -| Create | If identity exists, returns `Changed=$false` (no error) | -| Delete | If identity already gone, returns `Changed=$false` (no error) | -| Move | If already in target OU, returns `Changed=$false` | -| Enable/Disable | If already in desired state, returns `Changed=$false` | -| Grant membership | If already a member, returns `Changed=$false` | -| Revoke membership | If not a member, returns `Changed=$false` | - -This design ensures workflows can be re-run safely without causing duplicate operations or errors. - -### Error mapping and retry behavior - -- **Common error categories:** `NotFound`, `AlreadyExists`, `PermissionDenied`, `ObjectNotFound` -- **Retry strategy:** none (delegated to host) - ---- - -## Attribute contracts - -The AD provider enforces strict validation of attributes to ensure fail-fast behavior and prevent silent failures. - -### CreateIdentity - Supported attributes - -The following attributes are supported when creating identities via `CreateIdentity`: - -#### Identity attributes -- `SamAccountName` (string) - User logon name (pre-Windows 2000) -- `UserPrincipalName` (string) - User principal name (email-style logon) -- `Path` (string) - DistinguishedName of the OU where the user should be created - -#### Name attributes -- `Name` (string) - Full name (CN/RDN) -- `GivenName` (string) - First name -- `Surname` (string) - Last name -- `DisplayName` (string) - Display name - -#### Organizational attributes -- `Description` (string) - User description -- `Department` (string) - Department -- `Title` (string) - Job title - -#### Contact attributes -- `EmailAddress` (string) - Email address - -#### Relationship attributes -- `Manager` (string) - Manager DN, GUID, UPN, or sAMAccountName (auto-resolved to DN) - -#### 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: +## Quickstart -```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 -``` +Minimal provider creation (safe defaults): -**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`) - -#### Extension container -- `OtherAttributes` (hashtable) - Custom LDAP attributes not covered by named parameters - - Must be a hashtable - - Keys are interpreted as LDAP attribute names and are validated by the underlying AD cmdlets at runtime - - Values must use types supported by the AD cmdlets for `-OtherAttributes` (for example: string, string[], byte[]) - -**Example:** -```powershell -$attrs = @{ - GivenName = 'John' - Surname = 'Doe' - DisplayName = 'John Doe' - Department = 'IT' - Title = 'Engineer' - EmailAddress = 'john.doe@example.com' - OtherAttributes = @{ - extensionAttribute1 = 'CustomValue' - employeeType = 'Contractor' - } -} -``` - -### EnsureAttribute - Supported attributes - -The following attributes are supported when updating identities via `EnsureAttribute`: - -#### Name attributes -- `GivenName` (string) - First name -- `Surname` (string) - Last name -- `DisplayName` (string) - Display name - -#### Organizational attributes -- `Description` (string) - User description -- `Department` (string) - Department -- `Title` (string) - Job title - -#### Contact attributes -- `EmailAddress` (string) - Email address - -#### Identity attributes -- `UserPrincipalName` (string) - User principal name - -#### Relationship attributes -- `Manager` (string) - Manager DN, GUID, UPN, or sAMAccountName (auto-resolved to DN) - -:::info -**Note:** Custom LDAP attributes (via `OtherAttributes`) are not supported in `EnsureAttribute`. They can only be set during identity creation via `CreateIdentity`. - -Password, Path, Name, and Enabled attributes are also CreateIdentity-only and cannot be modified via `EnsureAttribute`. -::: - -### Validation behavior - -**Strict mode (default):** -- Unsupported attribute keys cause an immediate error -- Error messages list the unsupported attributes and provide guidance -- No silent attribute dropping - -**Example error:** -``` -AD Provider: Unsupported attributes in CreateIdentity operation. -Unsupported attributes: InvalidAttr1, InvalidAttr2 - -Supported attributes for CreateIdentity: - - Identity: SamAccountName, UserPrincipalName, Path - - Name: Name, GivenName, Surname, DisplayName - - Organization: Description, Department, Title - - Contact: EmailAddress - - Relationship: Manager - - Password: AccountPassword, AccountPasswordAsPlainText - - State: Enabled - - Extension: OtherAttributes (hashtable of LDAP attributes) - -To set custom LDAP attributes, use the 'OtherAttributes' container. -``` - ---- - -## Observability - -- **Events emitted by provider:** - - `Provider.AD.CreateIdentity.AttributesRequested` - Emitted after identity creation with requested attributes - - `Provider.AD.EnsureAttribute.AttributeChanged` - Emitted when an attribute is modified -- **Event data includes:** - - Requested attributes (for CreateIdentity) - - Old and new values (for attribute changes in EnsureAttribute) -- **Sensitive data redaction:** Credential objects and secure strings are not included in operation results or events - ---- - -## Usage - -### Basic Usage (Integrated Auth) - -```powershell -# Create provider using integrated authentication (run-as) $provider = New-IdleADIdentityProvider - -# Use in workflows -$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{ - Identity = $provider -} ``` -### AuthSessionBroker-based Authentication +Typical workflow usage: -Use an AuthSessionBroker to manage authentication centrally and enable multi-role scenarios. +- Set the provider alias in your workflow (`With.Provider = 'Directory'` is a common convention) +- Reference your auth session via `With.AuthSessionName` in steps (recommended for multi-role scenarios) -**Simple approach with New-IdleAuthSession:** +## Authentication -```powershell -# Assuming you have credentials available (e.g., from a secure vault or credential manager) -$tier0Credential = Get-Credential -Message "Enter Tier0 admin credentials" -$adminCredential = Get-Credential -Message "Enter regular admin credentials" +- By default, the AD provider uses the **run-as** identity (integrated authentication). +- For explicit runtime credential selection, use the **AuthSessionBroker** and pass an `AuthSession` via step configuration: + - `With.AuthSessionName` + - `With.AuthSessionOptions` (optional) -# Create provider -$provider = New-IdleADIdentityProvider +> Keep credentials/secrets **out of** workflow files. Use the broker/host to resolve them at runtime. -# Create broker with role-based credential mapping and Credential session type -$broker = New-IdleAuthSession -SessionMap @{ - @{ Role = 'Tier0' } = $tier0Credential - @{ Role = 'Admin' } = $adminCredential -} -DefaultAuthSession $adminCredential -AuthSessionType 'Credential' - -# Use provider with broker -$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{ - Identity = $provider - AuthSessionBroker = $broker -} -``` +## Supported Step Types -**Custom broker for advanced scenarios:** +The AD provider supports the common identity lifecycle and entitlement operations used by these step types: -For advanced scenarios (vault integration, MFA, dynamic credential retrieval), implement a custom broker: +| Step type | Typical use | Notes | +| --- | --- | --- | +| `IdLE.Step.Identity.Create` | Create user (if missing) | Identity can be addressed by GUID, UPN, or sAMAccountName | +| `IdLE.Step.Identity.EnsureAttributes` | Set/update AD user attributes | Use placeholders from your request input | +| `IdLE.Step.Identity.Disable` | Disable user account | Typical leaver action | +| `IdLE.Step.Identity.Enable` | Enable user account | Rare (rehire) | +| `IdLE.Step.Identity.MoveContainer` | Move user to another OU | Useful for leaver or org changes | +| `IdLE.Step.Identity.EnsureEntitlements` | Ensure group memberships | AD entitlements are **groups** | +| `IdLE.Step.Identity.RemoveEntitlements` | Remove managed groups | Prefer explicit allow-lists / managed lists | +| `IdLE.Step.Identity.Delete` | Delete user | **Opt-in** via `-AllowDelete` (see Configuration) | -```powershell -$broker = [pscustomobject]@{} -$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { - param($Name, $Options) - # Custom logic: retrieve from vault, prompt for MFA, etc. - if ($Options.Role -eq 'Tier0') { - return Get-SecretFromVault -Name 'AD-Tier0' - } - return Get-SecretFromVault -Name 'AD-Admin' -} -``` +## Configuration -In workflow definitions, steps specify which auth context to use via `AuthSessionOptions`: +### Provider factory ```powershell -@{ - Type = 'IdLE.Step.EnsureAttributes' - Name = 'SetDepartment' - With = @{ - IdentityKey = 'user@domain.com' - Attributes = @{ - Department = 'IT' - } - AuthSessionName = 'ActiveDirectory' - AuthSessionOptions = @{ Role = 'Admin' } # Broker returns Admin credential - } -} -``` - -**Key points:** -- The `Role` key (or any other key) is **defined by you** - it's not a built-in keyword -- Your broker implementation decides how to interpret `AuthSessionOptions` -- The broker can use any logic you want: hashtable lookups, vault APIs, interactive prompts, etc. -- `AuthSessionOptions` must be data-only (no ScriptBlocks) for security - -### With Delete Capability (Opt-in) - -By default, the Delete capability is **not** advertised for safety. Enable it explicitly: +# Safe defaults +$provider = New-IdleADIdentityProvider -```powershell +# Opt-in: allow identity deletion (advertises IdLE.Identity.Delete) $provider = New-IdleADIdentityProvider -AllowDelete ``` -### Multi-Provider Scenarios - -For scenarios with multiple AD forests or domains, use provider aliases with the AuthSessionBroker: - -```powershell -# Assuming you have credentials for each domain -$sourceCred = Get-Credential -Message "Enter Source AD admin credentials" -$targetCred = Get-Credential -Message "Enter Target AD admin credentials" - -# Create providers for different AD environments -$sourceAD = New-IdleADIdentityProvider -$targetAD = New-IdleADIdentityProvider -AllowDelete - -# Use New-IdleAuthSession for domain-based credential routing -$broker = New-IdleAuthSession -SessionMap @{ - @{ Domain = 'Source' } = $sourceCred - @{ Domain = 'Target' } = $targetCred -} -AuthSessionType 'Credential' - -$plan = New-IdlePlan -WorkflowPath './migration.psd1' -Request $request -Providers @{ - SourceAD = $sourceAD - TargetAD = $targetAD - AuthSessionBroker = $broker -} -``` - -Workflow steps specify which domain to authenticate against: - -```powershell -@{ - Type = 'IdLE.Step.GetIdentity' - Name = 'ReadSource' - With = @{ - IdentityKey = 'user@source.com' - Provider = 'SourceAD' - AuthSessionName = 'ActiveDirectory' - AuthSessionOptions = @{ Domain = 'Source' } - } -} - -@{ - Type = 'IdLE.Step.CreateIdentity' - Name = 'CreateTarget' - With = @{ - IdentityKey = 'user@target.com' - Attributes = @\{ ... \} - Provider = 'TargetAD' - AuthSessionName = 'ActiveDirectory' - AuthSessionOptions = @{ Domain = 'Target' } - } -} -``` - ---- - -## Identity Resolution - -The provider supports multiple identifier formats and resolves them deterministically: - -1. **GUID** (ObjectGuid): Pattern matches `[System.Guid]::TryParse()` - most deterministic -2. **UPN** (UserPrincipalName): Contains `@` symbol -3. **sAMAccountName**: Fallback for simple usernames - -**Resolution order:** -```powershell -# GUID format → resolve by ObjectGuid -'a1b2c3d4-e5f6-7890-abcd-ef1234567890' - -# Contains @ → resolve by UPN -'john.doe@contoso.local' - -# Otherwise → resolve by sAMAccountName -'jdoe' -``` - -**Canonical output:** The provider returns the input IdentityKey as-is in operation results to maintain workflow consistency. - -**Error handling:** On ambiguous or missing identities, the provider throws deterministic errors (no best-effort guessing). - ---- - -## CreateIdentity Derivation Behavior - -When creating identities with `CreateIdentity()` (or `IdLE.Step.CreateIdentity`), the AD provider implements smart defaults to reduce boilerplate and improve workflow usability: - -### 1. SamAccountName Derivation - -**If `Attributes.SamAccountName` is missing or empty:** - -- When `IdentityKey` is **SamAccountName-like** (no `@`, not a GUID): - - **Derives** `SamAccountName = IdentityKey` - - Example: `IdentityKey='jdoe'` → `SamAccountName='jdoe'` - -- When `IdentityKey` is a **UPN** (contains `@`): - - **Fails fast** with a clear error requiring explicit `SamAccountName` - - Rationale: Automatic truncation/sanitization introduces org-specific policy decisions and collision risks - -- When `IdentityKey` is a **GUID**: - - **Fails fast** with a clear error requiring explicit `SamAccountName` - - Rationale: GUIDs are not valid SamAccountNames - -**Explicit values are never overridden:** If `Attributes.SamAccountName` is provided, it is always used as-is. - -### 2. UserPrincipalName Auto-Set - -**If `IdentityKey` is a UPN and `Attributes.UserPrincipalName` is missing or empty:** - -- **Auto-sets** `UserPrincipalName = IdentityKey` -- Example: `IdentityKey='john.doe@contoso.com'` → `UserPrincipalName='john.doe@contoso.com'` - -**Explicit values are never overridden:** If `Attributes.UserPrincipalName` is provided, it is always used as-is. - -### 3. CN/RDN Name Derivation - -The AD object's Common Name (CN/RDN, used in the DistinguishedName) is derived using this priority order: - -1. **`Attributes.Name`** (explicit CN/RDN) -2. **`Attributes.DisplayName`** -3. **`GivenName + Surname`** (if both are present) -4. **`IdentityKey`** (fallback) - -**Example:** -```powershell -# IdentityKey='jdoe', GivenName='John', Surname='Doe', DisplayName='John Doe' -# → CN/RDN = 'John Doe' (from DisplayName) - -# IdentityKey='jdoe', GivenName='John', Surname='Doe' -# → CN/RDN = 'John Doe' (from GivenName+Surname) - -# IdentityKey='jdoe' -# → CN/RDN = 'jdoe' (fallback to IdentityKey) -``` - -**Verbose logging:** All derivations emit `Write-Verbose` messages for observability. - -### 4. Path Pass-Through - -**`Attributes.Path`** is always passed through to `New-ADUser -Path` when provided. No derivation or defaulting occurs at the provider level. - ---- - -## Entitlement Model - -Active Directory entitlements use: - -- **Kind:** Always `'Group'` (AD limitation - only supports security and distribution groups) -- **Id (canonical key):** DistinguishedName (DN) - -**Input flexibility:** The provider MAY accept SID or sAMAccountName as input but MUST normalize to DN internally. - -**Example:** -```powershell -@{ - Kind = 'Group' - Id = 'CN=IT-Team,OU=Groups,DC=contoso,DC=local' -} -``` - ---- - -## Built-in Steps - -The following built-in steps in `IdLE.Steps.Common` work with the AD provider: +### Options reference -- **IdLE.Step.CreateIdentity** - Create new user accounts -- **IdLE.Step.DisableIdentity** - Disable user accounts -- **IdLE.Step.EnableIdentity** - Enable user accounts -- **IdLE.Step.MoveIdentity** - Move users between OUs -- **IdLE.Step.DeleteIdentity** - Delete user accounts (requires provider initialization with `-AllowDelete` switch) -- **IdLE.Step.EnsureAttributes** - Set/update user attributes -- **IdLE.Step.EnsureEntitlement** - Manage group memberships +| Option | Type | Default | Meaning | +| --- | --- | --- | --- | +| `AllowDelete` | `bool` | `false` | Enables identity deletion capability (opt-in for safety) | +| `PasswordGenerationFallbackMinLength` | `int` | `24` | Fallback minimum length if domain policy cannot be read | +| `PasswordGenerationRequireUpper` | `bool` | `true` | Require uppercase in generated passwords (fallback) | +| `PasswordGenerationRequireLower` | `bool` | `true` | Require lowercase in generated passwords (fallback) | +| `PasswordGenerationRequireDigit` | `bool` | `true` | Require digit in generated passwords (fallback) | +| `PasswordGenerationRequireSpecial` | `bool` | `true` | Require special char in generated passwords (fallback) | -Step metadata (including required capabilities) is provided by step pack modules (`IdLE.Steps.Common`) and used for plan-time validation. +## Operational behavior ---- +- **Identity addressing:** GUID (ObjectGuid), UPN, or sAMAccountName (fallback) +- **Safety defaults:** deletion is disabled unless you pass `-AllowDelete` +- **Entitlements:** groups only (`Kind='Group'`) ## Examples -### Minimal host usage - -```powershell -# 1) Create provider instance -$provider = New-IdleADIdentityProvider - -# 2) Build provider map -$providers = @{ Identity = $provider } - -# 3) Plan + execute -$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` - -### Example workflow snippet - -```powershell -@{ - Steps = @( - @{ - Name = 'CreateUser' - Type = 'IdLE.Step.CreateIdentity' - With = @{ - Provider = 'Identity' - IdentityKey = 'jdoe' - Attributes = @{ - GivenName = 'John' - Surname = 'Doe' - UserPrincipalName = 'jdoe@contoso.local' - # SamAccountName is automatically derived from IdentityKey ('jdoe') - # CN/RDN Name will be derived from GivenName+Surname ('John Doe') - } - AuthSessionName = 'ActiveDirectory' - AuthSessionOptions = @{ Role = 'Admin' } - } - } - ) -} -``` - -### Example with UPN IdentityKey - -```powershell -@{ - Steps = @( - @{ - Name = 'CreateUserWithUPN' - Type = 'IdLE.Step.CreateIdentity' - With = @{ - Provider = 'Identity' - IdentityKey = 'john.doe@contoso.com' - Attributes = @{ - SamAccountName = 'jdoe' # Required when IdentityKey is UPN - GivenName = 'John' - Surname = 'Doe' - DisplayName = 'John Doe' - # UserPrincipalName is automatically set from IdentityKey - # CN/RDN Name will be 'John Doe' (from DisplayName) - } - AuthSessionName = 'ActiveDirectory' - } - } - ) -} -``` - -### 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 EnsureAttributes (UPN):** - -```powershell -@{ - Name = 'SetManager' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - Provider = 'Identity' - IdentityKey = 'jdoe' - Attributes = @{ - Manager = 'jsmith@contoso.local' # UPN - will be resolved to DN - } - AuthSessionName = 'ActiveDirectory' - } -} -``` - -**Clearing Manager:** - -To clear the Manager attribute, set the value to `$null`: +These are the canonical, **doc-embed friendly** templates for AD. +Mover scenarios are intentionally folded into Joiner/Leaver (as optional patterns) to keep the template set small. -```powershell -@{ - Name = 'ClearManager' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - Provider = 'Identity' - IdentityKey = 'jdoe' - Attributes = @{ - Manager = $null - } - 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: - -- **examples/workflows/ad-joiner-complete.psd1** - Full joiner workflow (Create + Attributes + Groups + OU move) -- **examples/workflows/ad-mover-department-change.psd1** - Mover workflow (Update attributes + Group delta + OU move) -- **examples/workflows/ad-leaver-offboarding.psd1** - Leaver workflow (Disable + OU move + conditional Delete) - ---- - -## Limitations and known issues + + {AdJoiner} + -- **Platform:** Windows-only (requires RSAT/ActiveDirectory PowerShell module) -- **Entitlement types:** Only supports `Kind='Group'` (AD platform limitation - no roles, licenses, etc.) -- **Concurrency:** While operations are thread-safe, concurrent modifications to the same object should be managed by the host -- **Delete capability:** Disabled by default; must opt-in with `-AllowDelete` for safety - ---- - -## Testing - -- **Unit tests:** `tests/Providers/ADIdentityProvider.Tests.ps1` -- **Contract tests:** Provider contract tests validate implementation compliance -- **Known CI constraints:** Tests use mock adapter layer; no live AD dependency in CI - ---- + + {AdLeaver} + ## Troubleshooting -### ActiveDirectory Module Not Found - -**Error:** `The specified module 'ActiveDirectory' was not loaded...` - -**Solution:** Install RSAT as described in Prerequisites. - -### Insufficient Permissions - -**Error:** `Insufficient access rights to perform the operation` - -**Solution:** Verify the account has required AD permissions. Use a dedicated service account with least-privilege access. - -### Identity Not Found - -**Error:** `Identity with <identifier> not found` - -**Solution:** -- Verify the identifier format (GUID/UPN/sAMAccountName) -- Check the user exists in AD -- Ensure the account has read access to the user object - -### Delete Capability Missing - -**Error:** Plan validation fails with `Required capability 'IdLE.Identity.Delete' not available` - -**Solution:** Create the provider with `-AllowDelete` parameter: -```powershell -$provider = New-IdleADIdentityProvider -AllowDelete -``` - ---- - -## Architecture Notes - -### Testability - -The AD provider uses an internal adapter layer (`New-IdleADAdapter`) that isolates AD cmdlet dependencies. This design: - -- Enables unit testing without real AD (unit tests inject fake adapters) -- Keeps provider logic testable and deterministic -- Separates provider contract from AD implementation details - -### Security +- **Import fails / ActiveDirectory module missing** + Install RSAT / the `ActiveDirectory` module on the machine where you run IdLE. -- **No interactive prompts:** The provider never prompts for credentials (violates headless principle) -- **Opt-in Delete:** Delete capability requires explicit `-AllowDelete` for safety -- **Credential handling:** Credentials are passed to AD cmdlets securely via `-Credential` parameter +- **Access denied / insufficient rights** + Ensure the account used (run-as or broker-provided credential) has the required rights for the operation (create user, set attributes, group membership, move OU). -### Capability-Driven Design +- **Delete step doesn’t work** + Deletion is **opt-in**. Create the provider with `-AllowDelete` and ensure your workflow uses that provider instance. -The provider implements `GetCapabilities()` and announces all supported capabilities. The engine validates capabilities at plan-time before execution, enabling fail-fast behavior. +- **Group membership changes are risky** + Prefer removing only explicit “managed groups” (allow-list) to avoid breaking access unexpectedly. From 229a838fef1dff916565cd3afd90ec64db635539 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:58:28 +0100 Subject: [PATCH 06/28] docs: consolidated entra id examples --- .../templates/entraid-joiner-complete.psd1 | 86 ---------- .../workflows/templates/entraid-joiner.psd1 | 147 ++++++++++++++++++ ...r-offboarding.psd1 => entraid-leaver.psd1} | 55 +++++-- .../entraid-mover-department-change.psd1 | 106 ------------- 4 files changed, 185 insertions(+), 209 deletions(-) delete mode 100644 examples/workflows/templates/entraid-joiner-complete.psd1 create mode 100644 examples/workflows/templates/entraid-joiner.psd1 rename examples/workflows/templates/{entraid-leaver-offboarding.psd1 => entraid-leaver.psd1} (67%) delete mode 100644 examples/workflows/templates/entraid-mover-department-change.psd1 diff --git a/examples/workflows/templates/entraid-joiner-complete.psd1 b/examples/workflows/templates/entraid-joiner-complete.psd1 deleted file mode 100644 index 48b7847e..00000000 --- a/examples/workflows/templates/entraid-joiner-complete.psd1 +++ /dev/null @@ -1,86 +0,0 @@ -@{ - Name = 'EntraID Joiner - Complete Onboarding' - LifecycleEvent = 'Joiner' - Description = 'Creates a new Entra ID user account with attributes and group memberships.' - Steps = @( - @{ - Name = 'CreateEntraIDUser' - Type = 'IdLE.Step.CreateIdentity' - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - Attributes = @{ - UserPrincipalName = '{{Request.Input.UserPrincipalName}}' - DisplayName = '{{Request.Input.DisplayName}}' - GivenName = '{{Request.Input.GivenName}}' - Surname = '{{Request.Input.Surname}}' - Mail = '{{Request.Input.Mail}}' - Department = '{{Request.Input.Department}}' - JobTitle = '{{Request.Input.JobTitle}}' - OfficeLocation = '{{Request.Input.OfficeLocation}}' - CompanyName = 'Contoso Ltd' - PasswordProfile = @{ - forceChangePasswordNextSignIn = $true - password = '{{Request.Input.TemporaryPassword}}' - } - } - } - } - @{ - Name = 'AddToBaseGroups' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserPrincipalName}}' - Desired = @( - @{ - Kind = 'Group' - Id = 'all-employees-group-id' - DisplayName = 'All Employees' - } - @{ - Kind = 'Group' - Id = '{{Request.Input.DepartmentGroupId}}' - DisplayName = '{{Request.Input.DepartmentName}}' - } - ) - } - } - @{ - Name = 'SetManagerAttribute' - Type = 'IdLE.Step.EnsureAttributes' - Condition = @{ - All = @( - @{ - Exists = 'Request.Input.ManagerId' - } - ) - } - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserPrincipalName}}' - Attributes = @{ - Manager = '{{Request.Input.ManagerId}}' - } - } - } - @{ - Name = 'EnableAccount' - Type = 'IdLE.Step.EnableIdentity' - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserPrincipalName}}' - } - } - @{ - Name = 'EmitCompletionEvent' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'EntraID user {{Request.Input.UserPrincipalName}} created and configured successfully.' - } - } - ) -} diff --git a/examples/workflows/templates/entraid-joiner.psd1 b/examples/workflows/templates/entraid-joiner.psd1 new file mode 100644 index 00000000..fa3334b4 --- /dev/null +++ b/examples/workflows/templates/entraid-joiner.psd1 @@ -0,0 +1,147 @@ +@{ + Name = 'EntraID Joiner - Complete Onboarding' + LifecycleEvent = 'Joiner' + Description = 'Creates or updates an Entra ID user with baseline attributes and group memberships. Includes optional mover patterns.' + + Steps = @( + @{ + Name = 'CreateEntraIDUser' + Type = 'IdLE.Step.CreateIdentity' + With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + + Attributes = @{ + UserPrincipalName = '{{Request.Input.UserPrincipalName}}' + DisplayName = '{{Request.Input.DisplayName}}' + GivenName = '{{Request.Input.GivenName}}' + Surname = '{{Request.Input.Surname}}' + Mail = '{{Request.Input.Mail}}' + + # Optional org attributes (safe when empty) + Department = '{{Request.Input.Department}}' + JobTitle = '{{Request.Input.JobTitle}}' + OfficeLocation = '{{Request.Input.OfficeLocation}}' + CompanyName = '{{Request.Input.CompanyName}}' + + # Password profile is typically relevant for "new user" scenarios. + # Your host can generate and provide a temporary password in Request.Input. + PasswordProfile = @{ + forceChangePasswordNextSignIn = $true + password = '{{Request.Input.TemporaryPassword}}' + } + } + } + } + + @{ + Name = 'AddToBaselineGroups' + Type = 'IdLE.Step.EnsureEntitlement' + With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + + # Using UPN keeps it human-friendly in templates. + IdentityKey = '{{Request.Input.UserPrincipalName}}' + + # Baseline groups should be explicit and driven by request input (no hardcoding). + Desired = @( + @{ + Kind = 'Group' + Id = '{{Request.Input.AllEmployeesGroupId}}' + DisplayName = '{{Request.Input.AllEmployeesGroupName}}' + } + @{ + Kind = 'Group' + Id = '{{Request.Input.DepartmentGroupId}}' + DisplayName = '{{Request.Input.DepartmentGroupName}}' + } + ) + } + } + + @{ + Name = 'EnableAccount' + Type = 'IdLE.Step.EnableIdentity' + With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserPrincipalName}}' + } + } + + # ---------------------------- + # Mover patterns (optional) + # Enable by setting: Request.Input.IsMover = $true + # ---------------------------- + + @{ + Name = 'Mover_UpdateOrgAttributes' + Type = 'IdLE.Step.EnsureAttributes' + Condition = @{ + All = @( + @{ + Equals = @{ + Path = 'Request.Input.IsMover' + Value = $true + } + } + ) + } + With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserPrincipalName}}' + + Attributes = @{ + Department = '{{Request.Input.NewDepartment}}' + JobTitle = '{{Request.Input.NewJobTitle}}' + OfficeLocation = '{{Request.Input.NewOfficeLocation}}' + Manager = '{{Request.Input.NewManagerObjectId}}' + } + } + } + + @{ + Name = 'Mover_AdjustManagedGroups' + Type = 'IdLE.Step.EnsureEntitlement' + Condition = @{ + All = @( + @{ + Equals = @{ + Path = 'Request.Input.IsMover' + Value = $true + } + } + ) + } + With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserPrincipalName}}' + + # Optional: add department/project groups as part of a move. + Desired = @( + @{ + Kind = 'Group' + Id = '{{Request.Input.DepartmentGroupId}}' + DisplayName = '{{Request.Input.DepartmentGroupName}}' + } + @{ + Kind = 'Group' + Id = '{{Request.Input.ProjectGroupId}}' + DisplayName = '{{Request.Input.ProjectGroupName}}' + } + ) + } + } + + @{ + Name = 'EmitCompletionEvent' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'EntraID user {{Request.Input.UserPrincipalName}} created/updated successfully.' + } + } + ) +} \ No newline at end of file diff --git a/examples/workflows/templates/entraid-leaver-offboarding.psd1 b/examples/workflows/templates/entraid-leaver.psd1 similarity index 67% rename from examples/workflows/templates/entraid-leaver-offboarding.psd1 rename to examples/workflows/templates/entraid-leaver.psd1 index a9aa4e38..9ea2a1ee 100644 --- a/examples/workflows/templates/entraid-leaver-offboarding.psd1 +++ b/examples/workflows/templates/entraid-leaver.psd1 @@ -1,52 +1,72 @@ @{ - Name = 'EntraID Leaver - Offboarding with Optional Delete' + Name = 'EntraID Leaver - Offboarding (Optional Cleanup)' LifecycleEvent = 'Leaver' - Description = 'Disables user account, revokes active sessions, and optionally deletes (requires AllowDelete provider flag).' + Description = 'Disables the user, revokes active sessions, and performs optional cleanup (group revoke and delete).' + Steps = @( @{ - Name = 'RevokeAllGroupMemberships' - Type = 'IdLE.Step.EnsureEntitlement' + Name = 'DisableAccount' + Type = 'IdLE.Step.DisableIdentity' With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } + + # Prefer ObjectId for leaver (stable), but you may also use UPN if your provider supports it. IdentityKey = '{{Request.Input.UserObjectId}}' - Desired = @() } } + @{ - Name = 'ClearManagerAndUpdateDisplayName' - Type = 'IdLE.Step.EnsureAttributes' + Name = 'RevokeActiveSessions' + Type = 'IdLE.Step.RevokeIdentitySessions' With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } IdentityKey = '{{Request.Input.UserObjectId}}' - Attributes = @{ - Manager = $null - DisplayName = '{{Request.Input.DisplayName}} (LEAVER)' - } } } + @{ - Name = 'DisableAccount' - Type = 'IdLE.Step.DisableIdentity' + Name = 'StampOffboardingMarker' + Type = 'IdLE.Step.EnsureAttributes' With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } IdentityKey = '{{Request.Input.UserObjectId}}' + Attributes = @{ + DisplayName = '{{Request.Input.DisplayName}} (LEAVER)' + Manager = $null + } } } + + # Optional & potentially disruptive: + # Setting Desired = @() will remove *all* group memberships the provider manages. @{ - Name = 'RevokeActiveSessions' - Type = 'IdLE.Step.RevokeIdentitySessions' + Name = 'RevokeAllGroupMemberships_Optional' + Type = 'IdLE.Step.EnsureEntitlement' + Condition = @{ + All = @( + @{ + Equals = @{ + Path = 'Request.Input.RevokeAllGroupMemberships' + Value = $true + } + } + ) + } With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } IdentityKey = '{{Request.Input.UserObjectId}}' + Desired = @() } } + + # Optional delete (requires provider to be created with -AllowDelete) @{ - Name = 'DeleteAccountAfterRetention' - Type = 'IdLE.Step.DeleteIdentity' + Name = 'DeleteAccount_Optional' + Type = 'IdLE.Step.DeleteIdentity' Condition = @{ All = @( @{ @@ -63,6 +83,7 @@ IdentityKey = '{{Request.Input.UserObjectId}}' } } + @{ Name = 'EmitCompletionEvent' Type = 'IdLE.Step.EmitEvent' diff --git a/examples/workflows/templates/entraid-mover-department-change.psd1 b/examples/workflows/templates/entraid-mover-department-change.psd1 deleted file mode 100644 index 1acde4a3..00000000 --- a/examples/workflows/templates/entraid-mover-department-change.psd1 +++ /dev/null @@ -1,106 +0,0 @@ -@{ - Name = 'EntraID Mover - Department Change' - LifecycleEvent = 'Mover' - Description = 'Updates user attributes and group memberships when user moves to new department.' - Steps = @( - @{ - Name = 'UpdateDepartmentAttributes' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Attributes = @{ - Department = '{{Request.Input.NewDepartment}}' - } - } - } - @{ - Name = 'UpdateJobTitle' - Type = 'IdLE.Step.EnsureAttributes' - Condition = @{ - All = @( - @{ - Exists = 'Request.Input.NewJobTitle' - } - ) - } - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Attributes = @{ - JobTitle = '{{Request.Input.NewJobTitle}}' - } - } - } - @{ - Name = 'UpdateOfficeLocation' - Type = 'IdLE.Step.EnsureAttributes' - Condition = @{ - All = @( - @{ - Exists = 'Request.Input.NewOfficeLocation' - } - ) - } - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Attributes = @{ - OfficeLocation = '{{Request.Input.NewOfficeLocation}}' - } - } - } - @{ - Name = 'UpdateGroupMemberships' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Desired = @( - @{ - Kind = 'Group' - Id = '{{Request.Input.NewDepartmentGroupId}}' - DisplayName = '{{Request.Input.NewDepartment}}' - } - ) - Remove = @( - @{ - Kind = 'Group' - Id = '{{Request.Input.OldDepartmentGroupId}}' - DisplayName = '{{Request.Input.OldDepartment}}' - } - ) - } - } - @{ - Name = 'UpdateManager' - Type = 'IdLE.Step.EnsureAttributes' - Condition = @{ - All = @( - @{ - Exists = 'Request.Input.NewManagerId' - } - ) - } - With = @{ - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Attributes = @{ - Manager = '{{Request.Input.NewManagerId}}' - } - } - } - @{ - Name = 'EmitCompletionEvent' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'EntraID user {{Request.Input.UserObjectId}} moved to new department successfully.' - } - } - ) -} From 9d9e2c4ae3ef11ce1af5ef4e9593e0f95924ab1c Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:59:07 +0100 Subject: [PATCH 07/28] docs: updated EntraId Provider docu --- docs/reference/providers/provider-entraID.md | 730 ++----------------- 1 file changed, 66 insertions(+), 664 deletions(-) diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index 8b203629..44110cd6 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -1,726 +1,128 @@ --- -title: Provider Reference - IdLE.Provider.EntraID +title: Provider Reference - Microsoft Entra ID (IdLE.Provider.EntraID) sidebar_label: Entra ID --- -## Summary - -- **Provider name:** `EntraID` (Microsoft Entra ID) -- **Module:** `IdLE.Provider.EntraID` -- **Provider kind:** `Identity | Entitlement` -- **Targets:** Microsoft Entra ID (formerly Azure Active Directory) via Microsoft Graph API (v1.0) -- **Status:** First-party (bundled) -- **Since:** 0.9.0 -- **Compatibility:** PowerShell 7+ (IdLE requirement) - ---- - -## What this provider does +import CodeBlock from '@theme/CodeBlock'; -- **Primary responsibilities:** - - Create, read, update, disable, enable, and delete (opt-in) user accounts in Microsoft Entra ID - - Set and update user attributes (givenName, surname, department, jobTitle, etc.) - - List group memberships and manage group entitlements (grant/revoke) - - Revoke active sign-in sessions (refresh tokens) for user accounts - - Resolve identities by objectId (GUID), UserPrincipalName (UPN), or mail address -- **Out of scope / non-goals:** - - Establishing authentication or obtaining Graph access tokens (handled by host-provided broker) - - Managing M365 groups, distribution lists, or Teams - - License assignment or MFA/Conditional Access management - - Custom attributes or schema extensions (not supported in MVP) - ---- +import EntraJoiner from '@site/../examples/workflows/templates/entraid-joiner-complete.psd1'; +import EntraLeaver from '@site/../examples/workflows/templates/entraid-leaver-offboarding.psd1'; -## Contracts and capabilities - -### Contracts implemented - -| Contract | Used by steps for | Notes | -| --- | --- | --- | -| Identity provider (implicit) | Identity read/write/delete operations | Full identity lifecycle support via Microsoft Graph API | -| Entitlement provider (implicit) | Grant/revoke/list group memberships | Only Entra ID groups; not M365 groups or distribution lists | - -### Capability advertisement (`GetCapabilities()`) - -- **Implements `GetCapabilities()`**: Yes -- **Capabilities returned (stable identifiers):** - - `IdLE.Identity.Read` - Read identity information - - `IdLE.Identity.List` - List identities (filter support varies) - - `IdLE.Identity.Create` - Create new identities - - `IdLE.Identity.Attribute.Ensure` - Set/update identity attributes - - `IdLE.Identity.Disable` - Disable user accounts - - `IdLE.Identity.Enable` - Enable user accounts - - `IdLE.Identity.RevokeSessions` - Revoke active sign-in sessions - - `IdLE.Entitlement.List` - List group memberships - - `IdLE.Entitlement.Grant` - Add group membership - - `IdLE.Entitlement.Revoke` - Remove group membership - - `IdLE.Identity.Delete` - **Opt-in only** (see Safety section) - ---- +## Summary -## Authentication +- **Module:** `IdLE.Provider.EntraID` +- **What it’s for:** Entra ID user lifecycle + group entitlements (Microsoft Graph API) +- **Targets:** Microsoft Entra ID (formerly Azure AD) via Microsoft Graph (v1.0) -### Host-Owned Authentication (Required Pattern) +## When to use -The EntraID provider follows IdLE's **host-owned authentication** pattern. The provider does NOT perform authentication internally. Instead, authentication is managed by the host application via the `AuthSessionBroker`. +Use this provider when your workflow needs to manage **Entra ID user accounts**, for example: -### What the Host Must Provide +- **Joiner:** create or update a user, set baseline attributes, assign baseline groups +- **Mover:** update org attributes and managed groups (covered as *optional patterns* inside the Joiner template) +- **Leaver:** disable account, revoke sessions, optional cleanup (groups, delete) -The host must: +Non-goals: -1. Obtain a valid Microsoft Graph access token (delegated or app-only) -2. Create an `AuthSessionBroker` that returns the token when requested -3. Pass the broker to IdLE via `Providers.AuthSessionBroker` +- Obtaining tokens or storing secrets (handled by your runtime + AuthSessionBroker pattern) +- Exchange Online mailbox configuration (use the Exchange Online provider/steps) -### Supported Auth Session Formats +## Getting started -The provider accepts authentication sessions in these formats: +### Requirements -- **String**: Direct access token (`"eyJ0eXAiOiJKV1Qi..."`) -- **Object with AccessToken property**: `@{ AccessToken = "token" }` -- **Object with GetAccessToken() method**: Custom object with method returning token string -- **PSCredential**: Token in password field (legacy compatibility) +- Your runtime must be able to supply a **Microsoft Graph auth session** (token/session object) to IdLE +- Graph permissions must allow the actions you intend to run (users + groups) -### Example: Delegated Authentication +### Install (PowerShell Gallery) ```powershell -# Host obtains token (example using Azure PowerShell) -Connect-AzAccount -$token = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com").Token - -# Create broker with OAuth session type -$broker = New-IdleAuthSession -DefaultAuthSession $token -AuthSessionType 'OAuth' - -# Create provider -$provider = New-IdleEntraIDIdentityProvider - -# Use in plan -$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{ - Identity = $provider - AuthSessionBroker = $broker -} +Install-Module IdLE.Provider.EntraID -Scope CurrentUser ``` -### Example: App-Only Authentication (Service Principal) +### Import ```powershell -# Host obtains app-only token (example using MSAL or Azure PowerShell) -$clientId = "your-app-id" -$clientSecret = "your-secret" -$tenantId = "your-tenant-id" - -# Obtain token (pseudo-code - use your preferred auth library) -$token = Get-GraphAppOnlyToken -ClientId $clientId -ClientSecret $clientSecret -TenantId $tenantId - -# Create broker with OAuth session type -$broker = New-IdleAuthSession -DefaultAuthSession $token -AuthSessionType 'OAuth' - -# Rest is identical to delegated flow +Import-Module IdLE.Provider.EntraID ``` -### Example: Device Code Flow (MFA-enabled environments) +## Quickstart -For environments requiring MFA, use Device Code Flow with an app registration and MSAL.PS. - -**Prerequisites:** -- Install MSAL.PS module: `Install-Module MSAL.PS -Scope CurrentUser` -- App registration with delegated permissions (e.g., `User.ReadWrite.All`, `Group.ReadWrite.All`) -- App must allow public client flows (Authentication > Advanced settings > Allow public client flows: Yes) +Create provider (safe defaults): ```powershell -# Import MSAL.PS -Import-Module MSAL.PS - -# Obtain token via Device Code Flow -$clientId = "your-app-id" # Application (client) ID from app registration -$tenantId = "your-tenant-id" - -$token = Get-MsalToken ` - -ClientId $clientId ` - -TenantId $tenantId ` - -Scopes "https://graph.microsoft.com/.default" ` - -DeviceCode - -# Create broker with OAuth session type -$broker = New-IdleAuthSession -DefaultAuthSession $token.AccessToken -AuthSessionType 'OAuth' - -# Create provider $provider = New-IdleEntraIDIdentityProvider - -# Use in plan -$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{ - Identity = $provider - AuthSessionBroker = $broker -} ``` -The Device Code Flow will display a code and URL for the user to authenticate in a browser, supporting MFA and conditional access policies. - -### Example: Multi-Role Scenario +Typical alias pattern: ```powershell -$tier0Token = Get-GraphToken -Role 'Tier0' -$adminToken = Get-GraphToken -Role 'Admin' - -# Create broker with role-based routing -$broker = New-IdleAuthSession -SessionMap @{ - @{ Role = 'Tier0' } = $tier0Token - @{ Role = 'Admin' } = $adminToken -} -DefaultAuthSession $adminToken -AuthSessionType 'OAuth' - -# Workflow steps specify: With.AuthSessionOptions = @{ Role = 'Tier0' } -``` - -### Auth Session Type - -**Required `AuthSessionType`:** `OAuth` - -The EntraID provider uses OAuth-based authentication via Microsoft Graph API tokens. When creating the `AuthSessionBroker`, specify `AuthSessionType = 'OAuth'` to indicate token-based authentication is expected. - - - -> Providers must not prompt for auth. Use the host-provided broker contract. - -- **Auth session name(s) used by built-in steps:** `MicrosoftGraph` -- **Auth session formats supported:** - - `string` Bearer access token - - object with `AccessToken` property - - object with `GetAccessToken()` method - - `PSCredential` (token stored in password field; username is ignored) -- **Session options (data-only):** Any hashtable; common keys: `Role`, `Tenant`, `Environment` - -:::warning - -**Security notes** - -- Do not pass secrets in workflow files or provider options. -- If you use access tokens, ensure your host does not log them (events, transcripts, verbose output). - -::: - -### Auth examples - -**A) Delegated auth (interactive) – host obtains token, provider consumes token** - -```powershell -# Host responsibility: -# Example with Microsoft Graph PowerShell (interactive sign-in) -Connect-MgGraph -Scopes 'User.ReadWrite.All','Group.ReadWrite.All' | Out-Null -$ctx = Get-MgContext - -# Provide a token supplier object so tokens can refresh -$tokenSupplier = [pscustomobject]@{ Context = $ctx } -$tokenSupplier | Add-Member -MemberType ScriptMethod -Name GetAccessToken -Value { - # NOTE: Replace this with your real token retrieval logic. - # In many hosts you would acquire tokens via MSAL / managed identity. - throw 'Implement token acquisition in the host.' -} - -$broker = [pscustomobject]@{} -$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { - param($Name, $Options) - return $tokenSupplier -} - $providers = @{ - Identity = New-IdleEntraIDIdentityProvider - AuthSessionBroker = $broker + Identity = $provider } - -# Steps use: -# With.AuthSessionName = 'MicrosoftGraph' ``` -**B) App-only auth – host supplies a fixed token string (simple demo / lab)** +In a workflow template, reference your auth session via steps (example): ```powershell -$accessToken = Get-MyGraphAppOnlyToken # host-managed (MSAL / managed identity / etc.) - -$broker = [pscustomobject]@{} -$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { - param($Name, $Options) - return $accessToken -} - -$providers = @{ - Identity = New-IdleEntraIDIdentityProvider - AuthSessionBroker = $broker +With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } } ``` -**C) Multi-tenant routing** +> Keep tokens/secrets **out of workflow files**. Resolve them in the host/runtime and provide them via the broker. -```powershell -$tokenProd = Get-GraphToken -Tenant 'contoso.onmicrosoft.com' -$tokenLab = Get-GraphToken -Tenant 'contoso-lab.onmicrosoft.com' - -$broker = [pscustomobject]@{} -$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { - param($Name, $Options) - if ($Options.Tenant -eq 'Prod') { return $tokenProd } - if ($Options.Tenant -eq 'Lab') { return $tokenLab } - throw "Unknown tenant option: $($Options.Tenant)" -} +## Authentication -# Steps use With.AuthSessionOptions = @{ Tenant = 'Prod' } etc. -``` +This provider expects Graph authentication to be supplied at runtime (AuthSessionBroker pattern). Common session shapes used by hosts include: ---- +- raw access token string (Bearer token) +- object with an `AccessToken` property +- object that can produce a token (e.g., `GetAccessToken()`) + +Recommended wiring in examples: +- `AuthSessionName = 'MicrosoftGraph'` +- `AuthSessionOptions = @{ Role = 'Admin' }` for routing (optional) +- Use a more privileged role only for privileged actions (e.g. delete) ## Configuration ### Provider constructor / factory -How to create an instance. - -- **Public constructor cmdlet(s):** - - `New-IdleEntraIDIdentityProvider` — Creates an Entra ID identity provider instance - -**Parameters (high signal only)** - -- `-AllowDelete` (switch) — Opt-in to enable the `IdLE.Identity.Delete` capability (disabled by default for safety) - -> Do not copy full comment-based help here. Link to the cmdlet reference. - -### Provider bag / alias usage - -How to pass the provider instance to IdLE as part of the host's provider map. - -```powershell -$providers = @{ - Identity = New-IdleEntraIDIdentityProvider -} -``` - -- **Recommended alias pattern:** `Identity` (single provider) or `TargetEntra` (multi-provider scenarios) -- **Default alias expected by built-in steps (if any):** `Identity` (if applicable) - ---- - -## Provider-specific options reference - -> Document only **data-only** keys. Keep this list short and unambiguous. - -This provider has **no provider-specific option bag**. All configuration is done through the constructor parameters and authentication is managed via the `AuthSessionBroker`. - ---- - -## Required Microsoft Graph Permissions - -This section lists the Microsoft Graph API permissions required for each step type supported by this provider. The same permissions apply for both delegated (user context) and application (app-only) permissions. - -### Permissions by Step Type - -| Step Type | Required Permissions | Notes | -|-----------|---------------------|-------| -| `IdLE.Step.CreateIdentity` | `User.ReadWrite.All` | Requires write permissions to create users | -| `IdLE.Step.DisableIdentity` | `User.ReadWrite.All` | Modifies `accountEnabled` property | -| `IdLE.Step.EnableIdentity` | `User.ReadWrite.All` | Modifies `accountEnabled` property | -| `IdLE.Step.EnsureAttributes` | `User.ReadWrite.All` | Modifies user properties (displayName, department, etc.) | -| `IdLE.Step.DeleteIdentity` | `User.ReadWrite.All` | Requires `AllowDelete = $true` on provider | -| `IdLE.Step.RevokeIdentitySessions` | `User.RevokeSessions.All` | Security-sensitive; invalidates all active sessions | -| `IdLE.Step.EnsureEntitlement` | `Group.Read.All`
`GroupMember.ReadWrite.All` | Lists and modifies group memberships | - -**Notes:** -- Application permissions require admin consent in the tenant -- `User.Read.All` is included in `User.ReadWrite.All` for identity resolution -- Grant only the permissions you need based on the workflow steps you will use -- `User.RevokeSessions.All` is security-sensitive; ensure appropriate approval processes are in place before granting - ---- - -## Identity Addressing - -### Lookup Modes - -The provider supports multiple ways to reference an identity: - -| Format | Example | Notes | -|--------|---------|-------| -| objectId (GUID) | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` | Most deterministic | -| UserPrincipalName (UPN) | `"user@contoso.com"` | Contains `@` | -| mail | `"user.name@contoso.com"` | Fallback if UPN lookup fails | - -### Canonical Identity Key - -**All provider methods return the user objectId (GUID) as the canonical IdentityKey.** - -This ensures deterministic identity references across workflows and is the recommended format for workflow definitions. - -### Resolution Rules - -1. If the input is a valid GUID format, look up by objectId -2. If the input contains `@`, try UPN lookup, then mail lookup -3. Otherwise, throw an error - -## Entitlement Model (Groups) - -### Entitlement Object Format - -```powershell -@{ - Kind = 'Group' - Id = '' # Canonical - DisplayName = 'Group Display Name' # Optional - Mail = 'group@contoso.com' # Optional -} -``` - -### Group Resolution - -The provider accepts group references in two formats: - -1. **objectId (GUID)** - Direct lookup (most reliable) -2. **displayName** - Lookup by name (must be unique) - -#### Ambiguity Handling - -If multiple groups share the same displayName, the provider throws an error. Use objectId for deterministic lookup. - -### Idempotency - -All group operations are idempotent: - -- **Grant**: Returns `Changed = $false` if already a member -- **Revoke**: Returns `Changed = $false` if not a member - -## Safety: Delete Capability +- `New-IdleEntraIDIdentityProvider` -### Default Behavior (Delete Disabled) +**High-signal parameters** +- `-AllowDelete` — opt-in to enable the `IdLE.Identity.Delete` capability (disabled by default for safety) -By default, the `IdLE.Identity.Delete` capability is **NOT advertised** and delete operations will fail. +### Provider-specific options reference -```powershell -$provider = New-IdleEntraIDIdentityProvider -# Delete is NOT available -``` - -### Opt-In for Delete - -To enable delete capability, use the `-AllowDelete` switch: - -```powershell -$provider = New-IdleEntraIDIdentityProvider -AllowDelete -# Delete is now available -``` - -### Workflow Requirements - -Workflows that require delete must explicitly declare the capability requirement in their metadata (not yet implemented in IdLE core, but provider is ready). - ---- - -## Operational behavior - -### Idempotency and consistency - -- **Idempotent operations:** Yes (all operations) -- **Consistency model:** Eventually consistent (Microsoft Graph API) -- **Concurrency notes:** Microsoft Graph enforces rate limits; provider marks throttling errors as transient +This provider has **no provider-specific option bag**. Configuration is done through constructor parameters; authentication is handled by your runtime via the broker. -All operations are idempotent: +## Required Microsoft Graph permissions -| Operation | Idempotent Behavior | -| --------- | ------------------- | -| Create | If identity exists, returns `Changed=$false` (no error) | -| Delete | If identity already gone, returns `Changed=$false` (no error) | -| Enable/Disable | If already in desired state, returns `Changed=$false` | -| Grant membership | If already a member, returns `Changed=$false` | -| Revoke membership | If not a member, returns `Changed=$false` | -| Set attribute | If already at desired value, returns `Changed=$false` | -| Revoke sessions | Returns `Changed` based on Graph API response (true if sessions existed, false if none to revoke) | +At minimum, you typically need: +- **Users:** read/write (create/update/disable/delete if enabled) +- **Groups:** read/write memberships (if you use entitlement steps) -### Session Revocation Behavior +Exact permission names depend on your auth model (delegated vs application) and what operations you enable. -The `RevokeSessions` operation invalidates all active sign-in sessions and refresh tokens for a user account. This is typically used in Leaver workflows after disabling an account to ensure immediate sign-out. - -**Important characteristics:** - -- **Immediate effect**: Sign-in sessions are invalidated, forcing re-authentication on the next request -- **Propagation delay**: Due to token caching and Conditional Access Evaluation (CAE), there may be a short delay (typically a few minutes) before all sessions are terminated -- **Changed flag**: The operation passes through the Graph API response: `Changed=$true` if active sessions were revoked, `Changed=$false` if there were no active sessions to revoke -- **Idempotency**: Safe to call multiple times; if no active sessions exist, returns `Changed=$false` -- **No account state change**: This operation does NOT disable the account; use `DisableIdentity` separately if account disabling is also required - -**Workflow pattern for Leaver scenarios:** - -```powershell -Steps = @( - @{ - Name = 'DisableAccount' - Type = 'IdLE.Step.DisableIdentity' - With = @{ - Provider = 'Identity' - AuthSessionName = 'MicrosoftGraph' - IdentityKey = '{{Request.Input.UserObjectId}}' - } - } - @{ - Name = 'RevokeActiveSessions' - Type = 'IdLE.Step.RevokeIdentitySessions' - With = @{ - Provider = 'Identity' - AuthSessionName = 'MicrosoftGraph' - IdentityKey = '{{Request.Input.UserObjectId}}' - } - } -) -``` - -**Note**: The `DisableIdentity` step does NOT automatically revoke sessions. Session revocation must be explicitly requested via the `RevokeIdentitySessions` step. - -### Error mapping and retry behavior - -- **Common error categories:** `NotFound`, `AlreadyExists`, `PermissionDenied`, `Throttled` (HTTP 429) -- **Retry strategy:** None (provider marks transient errors; retry is delegated to host) - ---- - -## Observability - -- **Events emitted by provider (if any):** - - Steps emit events via the execution context; provider operations are traced through step events -- **Sensitive data redaction:** Access tokens and credential objects are not included in operation results or events - ---- +## Examples (canonical templates) -## Transient Error Handling +These are the **two** canonical Entra ID templates, intended to be embedded directly in documentation. +Mover scenarios are integrated as **optional patterns** in the Joiner template. -The provider classifies errors as transient or permanent for retry policy support. + + {EntraJoiner} + -### Transient Errors (Retryable) - -These errors set `Exception.Data['Idle.IsTransient'] = $true`: - -- HTTP 429 (Rate limiting) -- HTTP 5xx (Server errors) -- HTTP 408 (Request timeout) -- Network timeouts - -### Retry Metadata - -Transient errors include metadata in the exception message: - -- HTTP status code -- Microsoft Graph request ID (if available) -- `Retry-After` header (if present) - -**Note**: The provider does NOT perform retries automatically. Retry policy is a host concern. - -## Supported Attributes - -### Identity Attributes - -These attributes can be set via `CreateIdentity` and `EnsureAttributes`: - -| Attribute | Graph Property | Notes | -|-----------|---------------|-------| -| `GivenName` | `givenName` | First name | -| `Surname` | `surname` | Last name | -| `DisplayName` | `displayName` | Display name (required for create) | -| `UserPrincipalName` | `userPrincipalName` | UPN (required for create) | -| `Mail` | `mail` | Email address | -| `Department` | `department` | Department | -| `JobTitle` | `jobTitle` | Job title | -| `OfficeLocation` | `officeLocation` | Office location | -| `CompanyName` | `companyName` | Company name | -| `MailNickname` | `mailNickname` | Mail alias (auto-generated if not provided) | -| `PasswordProfile` | `passwordProfile` | Password policy for new users (create only) | -| `Enabled` | `accountEnabled` | Account enabled state | - -### Password Policy (Create Only) - -#### 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 = @{ - UserPrincipalName = 'newuser@contoso.com' - DisplayName = 'New User' - PasswordProfile = @{ - forceChangePasswordNextSignIn = $true - password = 'Temp@Pass123!' - } -} -``` - -When `PasswordProfile` is explicitly provided, no password generation occurs and no password information is returned in the result. - -## Paging - -The provider automatically handles Microsoft Graph paging for `ListUsers` and `ListUserGroups` operations using the `@odata.nextLink` continuation token. - -No additional configuration required. - ---- - -## Examples - -### Minimal host usage - -```powershell -# 1) Create provider instance -$provider = New-IdleEntraIDIdentityProvider - -# 2) Obtain Graph token (host responsibility) -$token = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com").Token - -# 3) Create broker -$broker = New-IdleAuthSession -AuthSessionType OAuth -SessionMap @{ MicrosoftGraph = $token } -DefaultAuthSession $token - -# 4) Build provider map -$providers = @{ - Identity = $provider - AuthSessionBroker = $broker -} - -# 5) Plan + execute -$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` - -### Example workflow snippet - -```powershell -@{ - Steps = @( - @{ - Name = 'CreateUser' - Type = 'IdLE.Step.CreateIdentity' - With = @{ - Provider = 'Identity' - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - Attributes = @{ - UserPrincipalName = 'newuser@contoso.com' - DisplayName = 'New User' - GivenName = 'New' - Surname = 'User' - } - } - } - ) -} -``` - ---- - -## Built-in Steps Compatibility - -The provider works with these built-in IdLE steps: - -- `IdLE.Step.CreateIdentity` -- `IdLE.Step.EnsureAttributes` -- `IdLE.Step.DisableIdentity` -- `IdLE.Step.EnableIdentity` -- `IdLE.Step.RevokeIdentitySessions` (revokes active sign-in sessions) -- `IdLE.Step.DeleteIdentity` (when `AllowDelete = $true`) -- `IdLE.Step.EnsureEntitlement` - ---- - -## Limitations and known issues - -- **Supported API version**: v1.0 (beta endpoints not used) -- **Group types**: Only Entra ID groups (not M365 groups or distribution lists) -- **Licensing**: The provider does NOT manage license assignments -- **MFA/Conditional Access**: Not managed by provider -- **Custom attributes/extensions**: Not supported in MVP - ---- - -## Testing - -- **Unit tests:** `tests/Providers/EntraIDIdentityProvider.Tests.ps1` -- **Contract tests:** Provider contract tests validate implementation compliance -- **Known CI constraints:** Tests use mock HTTP layer; no live Microsoft Graph calls in CI - ---- + + {EntraLeaver} + ## Troubleshooting -### "AuthSession is required" - -Ensure you're passing an `AuthSessionBroker` to `New-IdlePlan` and that steps are using `With.AuthSessionName`. - -### "Multiple groups found with displayName" - -Use the group objectId instead of displayName for deterministic lookup. - -### "429 Too Many Requests" - -Microsoft Graph enforces rate limits. The provider marks these as transient errors. Implement retry logic in your host or reduce request frequency. - -### "Insufficient permissions" - -Verify the access token has the required Graph API permissions (see Required Permissions section). +- **401/403 from Microsoft Graph**: token missing/expired or insufficient Graph permissions for the requested operation. +- **Auth session not found**: check `AuthSessionName` matches your runtime/broker configuration. +- **Delete doesn’t work**: deletion is opt-in. Create the provider with `-AllowDelete` and only use delete with a privileged auth role. +- **Group cleanup is disruptive**: only enable revoke/remove operations when you fully understand the impact (prefer managed allow-lists). From be441db1c6dc387892f73537ab2c5dc24173a3c4 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:02:59 +0100 Subject: [PATCH 08/28] docs: updated entraID --- docs/reference/providers/provider-entraID.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index 44110cd6..a4c3e244 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -5,8 +5,8 @@ sidebar_label: Entra ID import CodeBlock from '@theme/CodeBlock'; -import EntraJoiner from '@site/../examples/workflows/templates/entraid-joiner-complete.psd1'; -import EntraLeaver from '@site/../examples/workflows/templates/entraid-leaver-offboarding.psd1'; +import EntraJoiner from '@site/../examples/workflows/templates/entraid-joiner.psd1'; +import EntraLeaver from '@site/../examples/workflows/templates/entraid-leaver.psd1'; ## Summary @@ -112,11 +112,11 @@ Exact permission names depend on your auth model (delegated vs application) and These are the **two** canonical Entra ID templates, intended to be embedded directly in documentation. Mover scenarios are integrated as **optional patterns** in the Joiner template. - + {EntraJoiner} - + {EntraLeaver} From 39760c9bf017b317a468b74b1bc742a51b74afa8 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:58:54 +0100 Subject: [PATCH 09/28] docs: update entraconnect sync examples und docs --- .../provider-directorysync-entraconnect.md | 248 +++++------------- .../ad-joiner-entraconnect-entraid.psd1 | 63 +++++ ...rectorysync-entraconnect-trigger-sync.psd1 | 37 +++ .../templates/joiner-with-entraid-sync.psd1 | 50 ---- 4 files changed, 161 insertions(+), 237 deletions(-) create mode 100644 examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 create mode 100644 examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 delete mode 100644 examples/workflows/templates/joiner-with-entraid-sync.psd1 diff --git a/docs/reference/providers/provider-directorysync-entraconnect.md b/docs/reference/providers/provider-directorysync-entraconnect.md index 44e4e24f..b6deeae4 100644 --- a/docs/reference/providers/provider-directorysync-entraconnect.md +++ b/docs/reference/providers/provider-directorysync-entraconnect.md @@ -1,238 +1,112 @@ --- -title: Provider Reference - IdLE.Provider.DirectorySync.EntraConnect -sidebar_label: DirectorySync.EntraConnect +title: Provider Reference - Entra Connect Directory Sync (IdLE.Provider.DirectorySync.EntraConnect) +sidebar_label: Directory Sync (Entra Connect) --- -## Summary - -- **Provider name:** EntraConnect DirectorySync -- **Module:** `IdLE.Provider.DirectorySync.EntraConnect` -- **Provider kind:** Other -- **Targets:** Entra ID Connect (ADSync) sync scheduler on a Windows server (remote execution) -- **Status:** First-party (bundled) -- **Since:** 0.9.0 -- **Compatibility:** PowerShell 7+ (IdLE requirement) - ---- - -## What this provider does - -- **Primary responsibilities:** - - Trigger an Entra Connect sync cycle (`Delta` or `Initial`). - - Query sync cycle state (whether a cycle is in progress). -- **Out of scope / non-goals:** - - Establishing remote connectivity, authentication, or elevation (handled by the host/broker). - - Installing or configuring Entra Connect / ADSync. - ---- +import CodeBlock from '@theme/CodeBlock'; -## Contracts and capabilities +import EntraConnectTriggerSync from '@site/../examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1'; -### Contracts implemented - -| Contract | Used by steps for | Notes | -| --- | --- | --- | -| Directory sync provider (implicit) | Trigger and monitor directory sync cycles | Exposes `StartSyncCycle(PolicyType, AuthSession)` and `GetSyncCycleState(AuthSession)` as script methods. | - -### Capability advertisement (`GetCapabilities()`) - -- **Implements `GetCapabilities()`**: Yes -- **Capabilities returned (stable identifiers):** - - `IdLE.DirectorySync.Trigger` - - `IdLE.DirectorySync.Status` - ---- - -## Authentication and session acquisition - -> Providers must not prompt for auth. Use the host-provided broker contract. +## Summary -This provider expects a host-provided **AuthSession** object that implements: +- **Module:** `IdLE.Provider.DirectorySync.EntraConnect` +- **What it’s for:** Triggering and monitoring **Entra Connect (ADSync)** sync cycles on an on-prem server +- **Execution model:** Remote execution via a host-provided AuthSession (elevated context) -- `InvokeCommand(CommandName, Parameters)` +## When to use -The provider does not call `Context.AcquireAuthSession(...)` directly; IdLE steps acquire an auth session -and pass it to provider methods. +Use this provider when your workflow needs to: -- **Auth session name(s) used by built-in steps:** - - `DirectorySync` (see `IdLE.Step.TriggerDirectorySync`) -- **Session options (data-only):** - - Forwarded to the host broker for session selection (provider does not interpret option keys). -- **Required `AuthSessionType`:** `PSRemoting` +- Trigger an Entra Connect sync cycle (`Delta` or `Initial`) +- Optionally wait/poll until the cycle is no longer in progress -The EntraConnect provider uses PowerShell remoting to execute commands on a remote Entra Connect server. When creating the `AuthSessionBroker`, specify `AuthSessionType = 'PSRemoting'` to indicate remote execution context is expected. +Typical use cases: -:::warning +- Joiner: after creating an AD identity, trigger delta sync so the object appears in Entra ID sooner +- Operational: run an initial sync after configuration changes (explicit, controlled) -**Security notes** +Non-goals: -- Do not pass secrets in provider options. -- Ensure token/credential objects are not emitted in events. +- Handling remote connectivity, authentication, or elevation itself (host/runtime responsibility) +- Replacing your monitoring/operations tooling (this is workflow orchestration) -::: +## Getting started -### Auth examples +### Requirements -**A) Simple WinRM/PowerShell Remoting wrapper (typical)** +- An Entra Connect (Azure AD Connect) server with ADSync installed (ADSync cmdlets available) +- A host/runtime that can provide an **elevated remote execution handle** to IdLE via AuthSessionBroker +- Rights to run `Start-ADSyncSyncCycle` and `Get-ADSyncScheduler` in that remote context -The provider expects an auth session object with an `InvokeCommand(CommandName, Parameters)` method. -Your host can wrap `Invoke-Command` like this: +### Install (PowerShell Gallery) ```powershell -$syncCred = Get-Credential -Message 'Enter credentials for Entra Connect server' - -$authSession = [pscustomobject]@{ - ComputerName = 'entra-connect-01.contoso.local' - Credential = $syncCred -} -$authSession | Add-Member -MemberType ScriptMethod -Name InvokeCommand -Value { - param([string] $CommandName, [hashtable] $Parameters) - - Invoke-Command -ComputerName $this.ComputerName -Credential $this.Credential -ScriptBlock { - param($cmd, $params) - & $cmd @params - } -ArgumentList $CommandName, $Parameters -} - -$broker = [pscustomobject]@{} -$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { - param($Name, $Options) - return $authSession -} - -$providers = @{ - DirectorySync = New-IdleEntraConnectDirectorySyncProvider - AuthSessionBroker = $broker -} - -# Steps use With.AuthSessionName = 'DirectorySync' +Install-Module IdLE.Provider.DirectorySync.EntraConnect -Scope CurrentUser ``` -**B) Role-based routing (Tier0 vs. Admin)** +### Import ```powershell -$tier0 = New-EntraConnectAuthSession -ComputerName 'entra-connect-01' -Credential (Get-Credential) -$admin = New-EntraConnectAuthSession -ComputerName 'entra-connect-01' -Credential (Get-Credential) - -$broker = [pscustomobject]@{} -$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { - param($Name, $Options) - if ($Options.Role -eq 'Tier0') { return $tier0 } - return $admin -} +Import-Module IdLE.Provider.DirectorySync.EntraConnect ``` -> Note: if `Start-ADSyncSyncCycle` requires elevation on your server, handle that in the host -> (scheduled task, JEA endpoint, endpoint configuration), not inside the provider. - ---- - -## Configuration +## Quickstart -### Provider constructor / factory +Create provider: -- **Public constructor cmdlet(s):** - - `New-IdleEntraConnectDirectorySyncProvider` — Creates a provider instance. - -**Parameters (high signal only)** - -- No mandatory parameters; provider has no configuration options - -> Do not copy full comment-based help here. Link to the cmdlet reference. +```powershell +$provider = New-IdleEntraConnectDirectorySyncProvider +``` -### Provider bag / alias usage +Register it (example convention): ```powershell $providers = @{ - DirectorySync = New-IdleEntraConnectDirectorySyncProvider + DirectorySync = $provider } ``` -- **Recommended alias pattern:** `DirectorySync` -- **Default alias expected by built-in steps (if any):** `DirectorySync` (used by `IdLE.Step.TriggerDirectorySync`) - ---- +## Authentication (important) -## Provider-specific options reference +This provider requires an AuthSession that supports remote execution and **must be elevated**. -This provider has **no provider-specific option bag**. Runtime behavior depends on the host-provided AuthSession. +The AuthSession object must provide a method: ---- - -## Operational behavior - -### Idempotency and consistency - -- **Idempotent operations:** Partial (triggering a sync cycle is an action; the step may optionally wait for completion) -- **Consistency model:** Depends on the directory synchronization runtime -- **Concurrency notes:** - - Triggering a new cycle may fail if a cycle is already in progress. - -### Error mapping and retry behavior - -- **Common error categories:** `PermissionDenied/ElevationRequired`, `Throttled/Busy`, `RemoteExecutionFailed` -- **Retry strategy:** None in the provider; any retries/backoff should be handled by the host or by the calling step. - ---- - -## Observability - -- **Events emitted by provider (if any):** None -- **Sensitive data redaction:** Enforced by IdLE output-boundary redaction; hosts should ensure the AuthSession does not leak secrets. +- `InvokeCommand(CommandName, Parameters)` ---- +Your host/runtime should provide this session via the AuthSessionBroker and you reference it in the step via: -## Examples +- `AuthSessionName = 'EntraConnect'` +- `AuthSessionOptions = @{ Role = 'EntraConnectAdmin' }` (optional routing key) -### Minimal host usage +> No interactive prompts are made. If the remote context is not elevated, triggering a sync cycle will fail with a privilege/elevation error. -```powershell -# 1) Create provider instance -$provider = New-IdleEntraConnectDirectorySyncProvider +## Supported operations -# 2) Build provider map -$providers = @{ - DirectorySync = $provider - AuthSessionBroker = $broker # host-provided -} +This provider advertises these capabilities: -# 3) Plan + execute -$plan = New-IdlePlan -WorkflowPath .\workflow.psd1 -Request $request -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` +- `IdLE.DirectorySync.Trigger` +- `IdLE.DirectorySync.Status` -### Example workflow snippet +Those are typically used by step types like: -```powershell -@{ - Steps = @( - @{ - Name = 'Trigger directory sync' - Type = 'IdLE.Step.TriggerDirectorySync' - With = @{ - Provider = 'DirectorySync' - AuthSessionName = 'DirectorySync' - PolicyType = 'Delta' - Wait = $true - TimeoutSeconds = 600 - PollIntervalSeconds = 10 - } - } - ) -} -``` +- `IdLE.Step.TriggerDirectorySync` (trigger + optional wait/poll) ---- +## Configuration -## Limitations and known issues +This provider has no admin-facing option bag. Configuration is done through: +- step inputs (`PolicyType`, `Wait`, `TimeoutSeconds`, `PollIntervalSeconds`) +- host configuration (remote connection and elevation) -- Requires an elevated remote execution context on the Entra Connect server. -- The remote target must have the ADSync cmdlets available (`Start-ADSyncSyncCycle`, `Get-ADSyncScheduler`). +## Examples (canonical template) ---- + + {EntraConnectTriggerSync} + -## Testing +## Troubleshooting -- **Unit tests:** `tests/Providers/EntraConnectDirectorySyncProvider.Tests.ps1` -- **Contract tests:** Provider contract tests validate implementation compliance -- **Known CI constraints:** Tests use mock remote execution layer; no live Entra Connect server dependency in CI +- **“Missing privileges or elevation”**: your AuthSession must run commands in an elevated context on the Entra Connect server. +- **“AuthSession must implement InvokeCommand”**: your host must provide an AuthSession object with an `InvokeCommand()` method. +- **Get-ADSyncScheduler not found**: ensure ADSync cmdlets are available in the remote session (module installed/accessible). +- **Timeout waiting for completion**: increase `TimeoutSeconds` or check the scheduler state on the server. diff --git a/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 b/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 new file mode 100644 index 00000000..71008955 --- /dev/null +++ b/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 @@ -0,0 +1,63 @@ +@{ + Name = 'Scenario - Joiner with Entra Connect Sync (AD + Entra ID)' + LifecycleEvent = 'Joiner' + Description = 'Creates an AD account, triggers Entra Connect delta sync, then assigns an Entra ID group. Demonstrates multi-provider orchestration.' + + Steps = @( + @{ + Name = 'Create AD account' + Type = 'IdLE.Step.CreateIdentity' + With = @{ + Provider = 'Directory' + AuthSessionName = '{{Request.Input.Auth.Directory}}' + + IdentityKey = '{{Request.Input.SamAccountName}}' + + Attributes = @{ + GivenName = '{{Request.Input.GivenName}}' + Surname = '{{Request.Input.Surname}}' + Department = '{{Request.Input.Department}}' + } + } + } + + @{ + Name = 'Trigger Entra Connect Delta Sync' + Type = 'IdLE.Step.TriggerDirectorySync' + With = @{ + Provider = 'DirectorySync' + AuthSessionName = 'EntraConnect' + AuthSessionOptions = @{ + Role = 'EntraConnectAdmin' + } + + PolicyType = 'Delta' + Wait = $true + TimeoutSeconds = 300 + PollIntervalSeconds = 10 + } + } + + @{ + Name = 'Assign Entra ID group membership' + Type = 'IdLE.Step.EnsureEntitlement' + With = @{ + Provider = 'Identity' + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ + Role = 'Admin' + } + + IdentityKey = '{{Request.Input.UserPrincipalName}}' + + Desired = @( + @{ + Kind = 'Group' + Id = '{{Request.Input.AllEmployeesGroupId}}' + DisplayName = '{{Request.Input.AllEmployeesGroupName}}' + } + ) + } + } + ) +} diff --git a/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 b/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 new file mode 100644 index 00000000..18678f81 --- /dev/null +++ b/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 @@ -0,0 +1,37 @@ +@{ + Name = 'DirectorySync - Trigger Entra Connect Sync Cycle' + LifecycleEvent = 'Operational' + Description = 'Triggers an Entra Connect (ADSync) sync cycle on the Entra Connect server and optionally waits for completion.' + + Steps = @( + @{ + Name = 'TriggerEntraConnectSync' + Type = 'IdLE.Step.TriggerDirectorySync' + With = @{ + Provider = 'DirectorySync' + + # Auth session is provided by the host (remote execution handle). + AuthSessionName = 'EntraConnect' + AuthSessionOptions = @{ + Role = 'EntraConnectAdmin' + } + + # Delta or Initial + PolicyType = 'Delta' + + # Optional wait/polling behavior (step-specific) + Wait = $true + TimeoutSeconds = 300 + PollIntervalSeconds = 10 + } + } + + @{ + Name = 'EmitCompletionEvent' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'Entra Connect sync cycle ({{Request.Input.PolicyType}}) triggered successfully.' + } + } + ) +} \ No newline at end of file diff --git a/examples/workflows/templates/joiner-with-entraid-sync.psd1 b/examples/workflows/templates/joiner-with-entraid-sync.psd1 deleted file mode 100644 index b54c0337..00000000 --- a/examples/workflows/templates/joiner-with-entraid-sync.psd1 +++ /dev/null @@ -1,50 +0,0 @@ -@{ - Name = 'Joiner - Trigger Entra Connect Sync' - LifecycleEvent = 'Joiner' - Steps = @( - @{ - Name = 'Create AD account' - Type = 'IdLE.Step.CreateIdentity' - With = @{ - IdentityKey = '{{ Request.Username }}' - Attributes = @{ - GivenName = '{{ Request.GivenName }}' - Surname = '{{ Request.Surname }}' - Department = '{{ Request.Department }}' - } - AuthSessionName = 'SourceAD' - Provider = 'Identity' - } - } - @{ - Name = 'Trigger Entra Connect Delta Sync' - Type = 'IdLE.Step.TriggerDirectorySync' - With = @{ - AuthSessionName = 'EntraConnect' - AuthSessionOptions = @{ - Role = 'EntraConnectAdmin' - } - PolicyType = 'Delta' - Wait = $true - TimeoutSeconds = 300 - PollIntervalSeconds = 10 - Provider = 'DirectorySync' - } - } - @{ - Name = 'Assign Entra ID group membership' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - IdentityKey = '{{ Request.Username }}' - Entitlement = @{ - Kind = 'Group' - Id = 'EntraID-Users-Group' - DisplayName = 'Entra ID Users' - } - State = 'Present' - AuthSessionName = 'EntraID' - Provider = 'Cloud' - } - } - ) -} From a2ee8ff3106b11a9e147253a66ad1579ae6f898b Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:00:03 +0100 Subject: [PATCH 10/28] docs rename entraid-exo-leaver --- .../{complete-leaver-entraid-exo.psd1 => entraid-exo-leaver.psd1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/workflows/templates/{complete-leaver-entraid-exo.psd1 => entraid-exo-leaver.psd1} (100%) diff --git a/examples/workflows/templates/complete-leaver-entraid-exo.psd1 b/examples/workflows/templates/entraid-exo-leaver.psd1 similarity index 100% rename from examples/workflows/templates/complete-leaver-entraid-exo.psd1 rename to examples/workflows/templates/entraid-exo-leaver.psd1 From 947d4a5157163e632e81e27f5d49deb72dd8e1f8 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:08:17 +0100 Subject: [PATCH 11/28] docs: update mock provider docs and example --- docs/reference/providers/provider-mock.md | 193 ++++-------------- .../mock/mock-identity-and-entitlements.psd1 | 88 ++++++++ 2 files changed, 129 insertions(+), 152 deletions(-) create mode 100644 examples/workflows/mock/mock-identity-and-entitlements.psd1 diff --git a/docs/reference/providers/provider-mock.md b/docs/reference/providers/provider-mock.md index 6aaf989f..1d6d49bf 100644 --- a/docs/reference/providers/provider-mock.md +++ b/docs/reference/providers/provider-mock.md @@ -1,190 +1,79 @@ --- -title: Provider Reference - IdLE.Provider.Mock +title: Provider Reference - Mock (IdLE.Provider.Mock) sidebar_label: Mock --- -## Summary - -- **Provider name:** MockIdentity -- **Module:** `IdLE.Provider.Mock` -- **Provider kind:** Identity + Entitlement -- **Targets:** In-memory store (tests/demos) -- **Status:** First-party (bundled) -- **Since:** 0.9.0 -- **Compatibility:** PowerShell 7+ (IdLE requirement) - ---- - -## What this provider does +import CodeBlock from '@theme/CodeBlock'; -- **Primary responsibilities:** - - Provide deterministic, in-memory identity operations for tests and examples. - - Converge identity attributes. - - List, grant and revoke entitlements (in-memory). - - Avoid any external dependencies and avoid global state. -- **Out of scope / non-goals:** - - Any live system integration. - - Authentication and session handling. - ---- +import MockIdentityAndEntitlements from '@site/../examples/workflows/mock/mock-identity-and-entitlements.psd1'; -## Contracts and capabilities - -### Contracts implemented - -| Contract | Used by steps for | Notes | -| --- | --- | --- | -| Identity provider (implicit) | Read identities and ensure attributes | Creates missing identities on demand to keep demos frictionless. | -| Entitlement provider (implicit) | List/grant/revoke entitlements | Entitlements are normalized to `{ Kind; Id; DisplayName? }` and compared case-insensitively by `Id`. | - -### Capability advertisement (`GetCapabilities()`) - -- **Implements `GetCapabilities()`**: Yes -- **Capabilities returned (stable identifiers):** - - `IdLE.Identity.Read` - - `IdLE.Identity.Attribute.Ensure` - - `IdLE.Identity.Disable` - - `IdLE.Entitlement.List` - - `IdLE.Entitlement.Grant` - - `IdLE.Entitlement.Revoke` - ---- +## Summary -## Authentication and session acquisition +- **Module:** `IdLE.Provider.Mock` +- **What it’s for:** Running workflows **without touching real systems** (dry runs, demos, pipeline tests) +- **Provider kind:** Identity + Entitlement (in-memory) -This provider does not require authentication. +## When to use -- **AuthSessionType usage:** Not applicable +Use the Mock provider when you want to: -The Mock provider does not acquire or require auth sessions. You do not need to configure an `AuthSessionBroker` when using this provider. If a broker is supplied for broader test scaffolding, this provider will ignore any acquired auth session. +- validate **workflow logic**, conditions, and error handling +- validate **template placeholders** (e.g. `{{Request.Input...}}`) without external dependencies +- build demos or CI checks that should never modify production systems -:::warning +Non-goals: -**Security notes** +- not a replacement for integration testing against real providers +- not meant for performance testing or concurrency simulation -- Even in tests, do not embed real secrets into workflow files or fixtures. +## Getting started -::: +### Requirements -### Auth examples +None beyond IdLE itself. The Mock provider stores everything in-memory during the workflow run. -This provider does not require authentication. +### Install (PowerShell Gallery) ```powershell -$providers = @{ - Identity = New-IdleMockIdentityProvider -} +Install-Module IdLE.Provider.Mock -Scope CurrentUser ``` ---- - -## Configuration - -### Provider constructor / factory - -- **Public constructor cmdlet(s):** - - `New-IdleMockIdentityProvider` — creates an isolated in-memory provider instance. +### Import -**Parameters (high signal only)** +```powershell +Import-Module IdLE.Provider.Mock +``` -- `-InitialStore ` — optional initial content, shallow-copied into the provider store. +## Quickstart -### Provider bag / alias usage +Create the provider and register it under a workflow alias (example): ```powershell -$provider = New-IdleMockIdentityProvider - $providers = @{ - Identity = $provider + Identity = New-IdleMockIdentityProvider } ``` -- **Recommended alias pattern:** `Identity` -- **Default alias expected by built-in steps (if any):** `Identity` +## Authentication ---- +No authentication is required. The Mock provider ignores `AuthSessionName`. -## Provider-specific options reference +## Supported operations -This provider has no additional data-only option keys beyond its constructor parameters. +- Identity: create/update attributes (in-memory) +- Entitlements: ensure/remove group memberships (in-memory) ---- - -## Operational behavior - -### Idempotency and consistency - -- **Idempotent operations:** Partial - - `EnsureAttributes` step is idempotent (returns `Changed = $false` when already converged). - - The step calls the provider's `EnsureAttributes` method if available (batch operation). - - Otherwise, it falls back to calling `EnsureAttribute` for each attribute individually. - - `DisableIdentity` is idempotent. - - Entitlement grant/revoke are idempotent by Kind+Id. - - `GetIdentity` creates missing identities on demand (test convenience). -- **Consistency model:** Strong (in-memory) -- **Concurrency notes:** Not designed for concurrent mutation across threads/runspaces. - -### Error mapping and retry behavior - -- **Common error categories:** input validation errors (e.g., missing entitlement id) -- **Retry strategy:** none (deterministic, in-memory) - ---- - -## Observability - -- **Events emitted by provider (if any):** none -- **Sensitive data redaction:** not applicable (no auth material handled) - ---- - -## Examples - -### Minimal host usage - -```powershell -# 1) Create provider instance -$provider = New-IdleMockIdentityProvider - -# 2) Build provider map -$providers = @{ Identity = $provider } - -# 3) Plan + execute -$plan = New-IdlePlan -WorkflowPath -Request -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` - -### Example workflow snippet - -```powershell -@{ - Steps = @( - @{ - Name = 'Ensure department' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - Provider = 'Identity' - IdentityKey = 'user1' - Attributes = @{ - Department = 'IT' - } - } - } - ) -} -``` - ---- +## Configuration -## Limitations and known issues +This provider has no admin-facing options. -- Designed for tests and examples only. -- `GetIdentity` auto-creates missing identities, which may hide "NotFound" scenarios unless tests seed the store explicitly. +## Example (canonical) ---- + + {MockIdentityAndEntitlements} + -## Testing +## Troubleshooting -- **Unit tests:** `tests/Providers/MockIdentityProvider.Tests.ps1` -- **Contract tests:** Provider contract tests validate implementation compliance -- **Known CI constraints:** None (in-memory provider designed for testing) +- **Values don’t persist across runs**: the Mock provider is in-memory per execution by design. +- **You need to test real permissions or connectivity**: switch to the real provider (AD/Entra/EXO/DirectorySync) and run in a test environment. diff --git a/examples/workflows/mock/mock-identity-and-entitlements.psd1 b/examples/workflows/mock/mock-identity-and-entitlements.psd1 new file mode 100644 index 00000000..83bc4a00 --- /dev/null +++ b/examples/workflows/mock/mock-identity-and-entitlements.psd1 @@ -0,0 +1,88 @@ +@{ + Name = 'Mock Provider - Identity + Entitlements (Demo)' + LifecycleEvent = 'Joiner' + Description = 'Demonstrates using the Mock provider to set attributes and group entitlements without touching real systems.' + + Steps = @( + @{ + Name = 'Ensure user attributes (mock)' + Type = 'IdLE.Step.EnsureAttributes' + With = @{ + Provider = 'Identity' + IdentityKey = '{{Request.Input.IdentityKey}}' + Attributes = @{ + GivenName = '{{Request.Input.GivenName}}' + Surname = '{{Request.Input.Surname}}' + Department = '{{Request.Input.Department}}' + Title = '{{Request.Input.Title}}' + } + } + } + + @{ + Name = 'Ensure group memberships (mock)' + Type = 'IdLE.Step.EnsureEntitlement' + With = @{ + Provider = 'Identity' + IdentityKey = '{{Request.Input.IdentityKey}}' + + # In the mock provider, entitlements are just stored in-memory. + # Use this to validate your workflow logic and template placeholders. + Desired = @( + @{ + Kind = 'Group' + Id = '{{Request.Input.GroupId}}' + DisplayName = '{{Request.Input.GroupName}}' + } + ) + } + } + + @{ + Name = 'Emit completion' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'Mock demo completed for {{Request.Input.IdentityKey}}.' + } + } + ) +} + +``` + +### File: examples/workflows/mock/mock-onfailure.psd1 + +```powershell +@{ + Name = 'Mock Provider - OnFailure handling (Demo)' + LifecycleEvent = 'Joiner' + Description = 'Demonstrates OnFailureSteps for cleanup/notification when primary steps fail (using Mock provider).' + + Steps = @( + @{ + Name = 'Emit start' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'Starting workflow with OnFailure handling.' + } + } + + @{ + Name = 'Primary action (will fail intentionally)' + Type = 'IdLE.Step.Fail' + With = @{ + Message = 'Intentional failure to demonstrate OnFailureSteps.' + } + + OnFailureSteps = @( + @{ + Name = 'Emit failure notification' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'Primary action failed for {{Request.Input.IdentityKey}}.' + } + } + ) + } + ) +} From 9defc849dd1d1377baf7719e4572d453938695f1 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:16:56 +0100 Subject: [PATCH 12/28] docs: updated EXO provider docs and examples --- .../providers/provider-exchangeonline.md | 435 ++---------------- ...ilbox-offboarding.psd1 => exo-leaver.psd1} | 26 +- 2 files changed, 54 insertions(+), 407 deletions(-) rename examples/workflows/templates/{exo-leaver-mailbox-offboarding.psd1 => exo-leaver.psd1} (61%) diff --git a/docs/reference/providers/provider-exchangeonline.md b/docs/reference/providers/provider-exchangeonline.md index 9a3a0c0d..1d6d49bf 100644 --- a/docs/reference/providers/provider-exchangeonline.md +++ b/docs/reference/providers/provider-exchangeonline.md @@ -1,436 +1,79 @@ --- -title: Provider Reference - IdLE.Provider.ExchangeOnline -sidebar_label: ExchangeOnline +title: Provider Reference - Mock (IdLE.Provider.Mock) +sidebar_label: Mock --- -## Purpose +import CodeBlock from '@theme/CodeBlock'; -This provider manages Exchange Online mailbox configuration and Out of Office settings as part of IdLE workflows. +import MockIdentityAndEntitlements from '@site/../examples/workflows/mock/mock-identity-and-entitlements.psd1'; ---- ## Summary -- **Provider name:** ExchangeOnline -- **Module:** `IdLE.Provider.ExchangeOnline` -- **Provider kind:** Messaging -- **Targets:** Exchange Online (ExchangeOnlineManagement cmdlets) -- **Status:** First-party (bundled) -- **Since:** 0.9.0 -- **Compatibility:** PowerShell 7+ (IdLE requirement) - ---- - -## What this provider does - -- **Primary responsibilities:** - - Read mailbox information (type, primary SMTP, UPN, GUID). - - Converge mailbox type (User/Shared/Room/Equipment). - - Converge Out of Office configuration. -- **Out of scope / non-goals:** - - Establishing an Exchange Online session (handled by the host/broker). - - Managing identity objects (use an identity provider such as AD or EntraID). - ---- - -## Contracts and capabilities - -### Contracts implemented - -| Contract | Used by steps for | Notes | -| --- | --- | --- | -| Mailbox provider (implicit) | Read mailbox info, ensure mailbox type, ensure Out of Office | Methods are exposed as script methods on the provider object. | - -### Capability advertisement (`GetCapabilities()`) - -- **Implements `GetCapabilities()`**: Yes -- **Capabilities returned (stable identifiers):** - - `IdLE.Mailbox.Info.Read` - - `IdLE.Mailbox.Type.Ensure` - - `IdLE.Mailbox.OutOfOffice.Ensure` - ---- - -## Authentication and session acquisition +- **Module:** `IdLE.Provider.Mock` +- **What it’s for:** Running workflows **without touching real systems** (dry runs, demos, pipeline tests) +- **Provider kind:** Identity + Entitlement (in-memory) -> Providers must not prompt for auth. Use the host-provided broker contract. +## When to use -- **Auth session name(s) requested via `Context.AcquireAuthSession(...)`:** - - Typically the step passes `With.AuthSessionName` (if present). For built-in mailbox steps, if `With.AuthSessionName` is absent, it defaults to the provider alias (commonly `ExchangeOnline`). -- **Session options (data-only):** - - The provider does not interpret options; they are used by the host/broker to select credentials/route to a tenant/session. -- **Required `AuthSessionType`:** `OAuth` +Use the Mock provider when you want to: -The ExchangeOnline provider uses OAuth-based authentication via Exchange Online PowerShell. When creating the `AuthSessionBroker`, specify `AuthSessionType = 'OAuth'` to indicate token-based authentication is expected. +- validate **workflow logic**, conditions, and error handling +- validate **template placeholders** (e.g. `{{Request.Input...}}`) without external dependencies +- build demos or CI checks that should never modify production systems -:::warning +Non-goals: -**Security notes** +- not a replacement for integration testing against real providers +- not meant for performance testing or concurrency simulation -- Do not pass secrets in workflow/provider options. -- Ensure token/credential objects are not emitted in events. +## Getting started -::: +### Requirements -### Auth examples +None beyond IdLE itself. The Mock provider stores everything in-memory during the workflow run. -**A) Delegated auth (interactive) – connect once in the host** +### Install (PowerShell Gallery) ```powershell -# Host responsibility: -Connect-ExchangeOnline -UserPrincipalName 'admin@contoso.com' - -$providers = @{ - ExchangeOnline = New-IdleExchangeOnlineProvider -} +Install-Module IdLE.Provider.Mock -Scope CurrentUser ``` -**B) App-only (certificate) – connect once in the host** +### Import ```powershell -# Host responsibility: -Connect-ExchangeOnline ` - -AppId '00000000-0000-0000-0000-000000000000' ` - -Organization 'contoso.onmicrosoft.com' ` - -CertificateThumbprint 'THUMBPRINT' - -$providers = @{ - ExchangeOnline = New-IdleExchangeOnlineProvider -} +Import-Module IdLE.Provider.Mock ``` -**C) Multi-connection routing (advanced)** - -If you need **multiple** Exchange Online sessions (e.g., multiple tenants), implement a custom -`AuthSessionBroker` that returns an **AuthSession** object understood by your host (for example, -an object that selects the right connection context before invoking cmdlets). The provider itself -does not create or own sessions. - ---- - -## Configuration - -### Provider constructor / factory - -- **Public constructor cmdlet(s):** - - `New-IdleExchangeOnlineProvider` — creates an Exchange Online mailbox provider. - -**Parameters (high signal only)** +## Quickstart -- `-Adapter ` — dependency injection hook for tests (optional). - -> Do not copy full comment-based help here. Link to the cmdlet reference. - -### Provider bag / alias usage +Create the provider and register it under a workflow alias (example): ```powershell $providers = @{ - ExchangeOnline = (New-IdleExchangeOnlineProvider) -} -``` - -- **Recommended alias pattern:** `ExchangeOnline` (or role-based, e.g. `Messaging`) -- **Default alias expected by built-in steps (if any):** `ExchangeOnline` (Mailbox steps default to this when `With.Provider` is not provided) - ---- - -## Provider-specific options reference - -This provider has no dedicated data-only `-Options` surface. Session selection is done via: - -- `With.AuthSessionName` -- `With.AuthSessionOptions` (data-only hashtable, validated by the engine/steps) - ---- - -## Operational behavior - -### Idempotency and consistency - -- **Idempotent operations:** Yes (for `Ensure*` methods; no-op when already in desired state) -- **Consistency model:** Depends on Exchange Online / service latency -- **Concurrency notes:** Exchange Online can throttle; retries are delegated to the host/workflow design. - -### Error mapping and retry behavior - -- **Common error categories:** NotFound, PermissionDenied, Throttled -- **Retry strategy:** None in the provider (delegate retries/backoff to the host if needed) - ---- - -## Observability - -- **Events emitted by provider (if any):** None (steps emit events via the execution context). -- **Sensitive data redaction:** IdLE redacts secrets at output boundaries; providers should avoid returning secret material. - ---- - -## Examples - -### Minimal host usage - -```powershell -# 1) Create provider instance -$provider = New-IdleExchangeOnlineProvider - -# 2) Build provider map -$providers = @{ ExchangeOnline = $provider } - -# 3) Plan + execute -$plan = New-IdlePlan -WorkflowPath -Request -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` - -### Example workflow snippet - -```powershell -@{ - Steps = @( - @{ - Name = 'Ensure mailbox type' - Type = 'IdLE.Step.Mailbox.EnsureType' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = 'user@contoso.com' - MailboxType = 'Shared' - # AuthSessionName is optional; defaults to the provider alias if omitted - # AuthSessionOptions = @{ ... } - } - } - ) + Identity = New-IdleMockIdentityProvider } ``` -### OOF with template variables and dynamic manager attributes - -This example shows how to use template variables (`{{...}}`) in Out of Office messages -with dynamic user attributes (e.g., manager information). Templates are resolved during -plan building against the request object. - -**Important:** Manager lookup is performed **host-side**, not inside the step. This -maintains the security boundary: steps do not perform directory lookups. - -**Host enrichment (example using AD):** - -```powershell -# 1. Retrieve user and manager details from AD -$user = Get-ADUser -Identity 'max.power' -Properties Manager -$mgr = $null - -if ($user.Manager) { - $mgr = Get-ADUser -Identity $user.Manager -Properties DisplayName, Mail -} - -# Provide fallback contact if no manager is found -if (-not $mgr) { - $mgr = [PSCustomObject]@{ - DisplayName = 'IT Support' - Mail = 'support@contoso.com' - } -} - -# 2. Build request with manager data in DesiredState -$req = New-IdleRequest ` - -LifecycleEvent 'Leaver' ` - -Actor $env:USERNAME ` - -Input @{ UserPrincipalName = 'max.power@contoso.com' } ` - -DesiredState @{ - Manager = @{ - DisplayName = $mgr.DisplayName - Mail = $mgr.Mail - } - } +## Authentication -# 3. Plan and execute -$plan = New-IdlePlan -WorkflowPath './leaver-workflow.psd1' -Request $req -Providers $providers -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -``` - -**Workflow step using templates:** - -```powershell -@{ - Name = 'Set Exchange OOF' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } - Config = @{ - Mode = 'Enabled' - InternalMessage = 'This mailbox is no longer monitored. Please contact {{Request.DesiredState.Manager.DisplayName}} ({{Request.DesiredState.Manager.Mail}}).' - ExternalMessage = 'This mailbox is no longer monitored. Please contact {{Request.DesiredState.Manager.Mail}}.' - ExternalAudience = 'All' - } - } -} -``` - -**Alternative (using Entra ID / Microsoft Graph):** - -```powershell -# Host enrichment using Microsoft Graph -Connect-MgGraph -Scopes 'User.Read.All' - -$user = Get-MgUser -UserId 'max.power@contoso.com' -Property 'Manager' -$mgr = if ($user.Manager.Id) { - Get-MgUser -UserId $user.Manager.Id -Property 'DisplayName', 'Mail' -} else { $null } - -# Provide fallback contact if no manager is found -if (-not $mgr) { - $mgr = [PSCustomObject]@{ - DisplayName = 'IT Support' - Mail = 'support@contoso.com' - } -} - -$req = New-IdleRequest ` - -LifecycleEvent 'Leaver' ` - -Actor $env:USERNAME ` - -Input @{ UserPrincipalName = 'max.power@contoso.com' } ` - -DesiredState @{ - Manager = @{ - DisplayName = $mgr.DisplayName - Mail = $mgr.Mail - } - } -``` +No authentication is required. The Mock provider ignores `AuthSessionName`. -### HTML formatted Out of Office messages +## Supported operations -Exchange Online supports formatted automatic reply messages with HTML markup (bold, links, lists, line breaks). -IdLE provides stable idempotency for HTML messages by normalizing server-side canonicalization. +- Identity: create/update attributes (in-memory) +- Entitlements: ensure/remove group memberships (in-memory) -**Example with HTML formatted messages:** - -```powershell -@{ - Name = 'Set formatted OOF for Leaver' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = 'user@contoso.com' - Config = @{ - Mode = 'Enabled' - MessageFormat = 'Html' - InternalMessage = @' -

This mailbox is no longer monitored.

-

For urgent matters, please contact:

- -'@ - ExternalMessage = @' -

This mailbox is no longer monitored.

-

Please contact our Service Desk at servicedesk@contoso.com.

-'@ - ExternalAudience = 'All' - } - } -} -``` - -**Idempotency behavior:** - -- When `MessageFormat = 'Html'`, the provider normalizes messages for comparison to handle Exchange server-side HTML canonicalization. -- Common normalization operations include: - - Line ending normalization (CRLF ↔ LF) - - Removal of Exchange-added HTML wrappers (``, ``, ``) - - Whitespace normalization -- This ensures workflows report `Changed = $false` on subsequent runs when the effective message content has not changed. - -**Combining HTML with template variables:** - -```powershell -@{ - Name = 'Set formatted OOF with dynamic manager' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } - Config = @{ - Mode = 'Enabled' - MessageFormat = 'Html' - InternalMessage = @' -

This mailbox is no longer monitored.

-

For urgent matters, please contact {{Request.DesiredState.Manager.DisplayName}}.

-'@ - ExternalMessage = @' -

This mailbox is no longer monitored.

-

Please contact our Service Desk at servicedesk@contoso.com.

-'@ - ExternalAudience = 'All' - } - } -} -``` - -**Loading messages from external files (host-side approach):** - -For long or complex HTML messages, you can load content from external files in your host script before creating the plan: - -```powershell -# Host script - load templates before planning -$internalMessageTemplate = Get-Content -Path './templates/oof-internal.html' -Raw -Encoding UTF8 -$externalMessageTemplate = Get-Content -Path './templates/oof-external.html' -Raw -Encoding UTF8 - -# Build request with template content -$req = New-IdleRequest ` - -LifecycleEvent 'Leaver' ` - -Actor $env:USERNAME ` - -Input @{ UserPrincipalName = 'user@contoso.com' } ` - -DesiredState @{ - InternalOOFMessage = $internalMessageTemplate - ExternalOOFMessage = $externalMessageTemplate - Manager = @{ - DisplayName = 'Jane Manager' - Mail = 'jmanager@contoso.com' - } - } - -# Workflow definition references the loaded content -@{ - Name = 'Set OOF with templates' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } - Config = @{ - Mode = 'Enabled' - MessageFormat = 'Html' - InternalMessage = '{{Request.DesiredState.InternalOOFMessage}}' - ExternalMessage = '{{Request.DesiredState.ExternalOOFMessage}}' - ExternalAudience = 'All' - } - } -} -``` - -**Template file example** (`./templates/oof-internal.html`): - -```html -

This mailbox is no longer monitored.

-

For urgent matters, please contact:

- -``` - -This approach keeps workflow definitions clean, allows template reuse, and maintains the data-only principle by loading files at the host level before planning. - ---- - -## Limitations and known issues +## Configuration -- Requires the `ExchangeOnlineManagement` PowerShell module at runtime. -- The host must establish or broker a usable Exchange Online session; the provider does not connect interactively. +This provider has no admin-facing options. ---- +## Example (canonical) -## Testing + + {MockIdentityAndEntitlements} + -- **Unit tests:** `tests/Providers/ExchangeOnlineProvider.Tests.ps1` -- **Contract tests:** Provider contract tests validate implementation compliance -- **Known CI constraints:** Tests use mock cmdlet layer; no live Exchange Online calls in CI +## Troubleshooting +- **Values don’t persist across runs**: the Mock provider is in-memory per execution by design. +- **You need to test real permissions or connectivity**: switch to the real provider (AD/Entra/EXO/DirectorySync) and run in a test environment. diff --git a/examples/workflows/templates/exo-leaver-mailbox-offboarding.psd1 b/examples/workflows/templates/exo-leaver.psd1 similarity index 61% rename from examples/workflows/templates/exo-leaver-mailbox-offboarding.psd1 rename to examples/workflows/templates/exo-leaver.psd1 index b778df36..0dc55402 100644 --- a/examples/workflows/templates/exo-leaver-mailbox-offboarding.psd1 +++ b/examples/workflows/templates/exo-leaver.psd1 @@ -1,14 +1,15 @@ @{ Name = 'ExchangeOnline Leaver - Mailbox Offboarding' LifecycleEvent = 'Leaver' - Description = 'Converts mailbox to shared, enables Out of Office with HTML-formatted dynamic manager contact info, and optionally delegates access for offboarding users.' + Description = 'Converts mailbox to shared and enables Out of Office with dynamic manager/service-desk contact information.' + Steps = @( @{ Name = 'GetMailboxInfo' Type = 'IdLE.Step.Mailbox.GetInfo' With = @{ Provider = 'ExchangeOnline' - IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } + IdentityKey = '{{Request.Input.UserPrincipalName}}' } } @{ @@ -16,7 +17,7 @@ Type = 'IdLE.Step.Mailbox.EnsureType' With = @{ Provider = 'ExchangeOnline' - IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } + IdentityKey = '{{Request.Input.UserPrincipalName}}' MailboxType = 'Shared' } } @@ -25,22 +26,25 @@ Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' With = @{ Provider = 'ExchangeOnline' - IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } + IdentityKey = '{{Request.Input.UserPrincipalName}}' Config = @{ - Mode = 'Enabled' - MessageFormat = 'Html' - InternalMessage = @' + Mode = 'Enabled' + MessageFormat = 'Html' + + InternalMessage = @'

This mailbox is no longer monitored.

For urgent matters, please contact:

'@ - ExternalMessage = @' + + ExternalMessage = @'

This mailbox is no longer monitored.

-

Please contact our Service Desk at servicedesk@contoso.com.

+

Please contact our Service Desk at {{Request.Input.ServiceDesk.Mail}}.

'@ + ExternalAudience = 'All' } } @@ -49,7 +53,7 @@ Name = 'EmitCompletionEvent' Type = 'IdLE.Step.EmitEvent' With = @{ - Message = 'Mailbox offboarding completed.' + Message = 'Mailbox offboarding completed for {{Request.Input.UserPrincipalName}}.' } } ) From d1d462f4bbf053765aebdbaf785c1fd47cab6789 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:32:28 +0100 Subject: [PATCH 13/28] docs: updated exo examples and docs --- .../providers/provider-exchangeonline.md | 85 ++++++++---- examples/workflows/templates/exo-joiner.psd1 | 123 ++++++++++++++++++ 2 files changed, 181 insertions(+), 27 deletions(-) create mode 100644 examples/workflows/templates/exo-joiner.psd1 diff --git a/docs/reference/providers/provider-exchangeonline.md b/docs/reference/providers/provider-exchangeonline.md index 1d6d49bf..d4f237c0 100644 --- a/docs/reference/providers/provider-exchangeonline.md +++ b/docs/reference/providers/provider-exchangeonline.md @@ -1,79 +1,110 @@ --- -title: Provider Reference - Mock (IdLE.Provider.Mock) -sidebar_label: Mock +title: Provider Reference - Exchange Online (IdLE.Provider.ExchangeOnline) +sidebar_label: Exchange Online --- import CodeBlock from '@theme/CodeBlock'; -import MockIdentityAndEntitlements from '@site/../examples/workflows/mock/mock-identity-and-entitlements.psd1'; +import ExoJoinerMailboxBaseline from '@site/../examples/workflows/templates/exo-joiner.psd1'; +import ExoLeaverMailboxOffboarding from '@site/../examples/workflows/templates/exo-leaver.psd1'; ## Summary -- **Module:** `IdLE.Provider.Mock` -- **What it’s for:** Running workflows **without touching real systems** (dry runs, demos, pipeline tests) -- **Provider kind:** Identity + Entitlement (in-memory) +- **Module:** `IdLE.Provider.ExchangeOnline` +- **What it’s for:** Exchange Online mailbox configuration (type conversion, Out of Office, mailbox info) +- **Targets:** Exchange Online via `ExchangeOnlineManagement` cmdlets +- **Identity keys:** UPN (recommended), SMTP address, mailbox identifiers (provider-specific) ## When to use -Use the Mock provider when you want to: +Use this provider when your workflows need to manage **mailbox settings** in Exchange Online, for example: -- validate **workflow logic**, conditions, and error handling -- validate **template placeholders** (e.g. `{{Request.Input...}}`) without external dependencies -- build demos or CI checks that should never modify production systems +- read mailbox info (type, primary SMTP, identifiers) +- apply a safe baseline at onboarding (verify mailbox + ensure expected type) +- convert mailbox type (e.g. user → shared for leavers) +- set Out of Office messages (internal/external) and audience Non-goals: -- not a replacement for integration testing against real providers -- not meant for performance testing or concurrency simulation +- establishing the Exchange Online connection (host/runtime responsibility) +- managing identity objects (use AD / Entra ID providers for accounts) ## Getting started ### Requirements -None beyond IdLE itself. The Mock provider stores everything in-memory during the workflow run. +- `ExchangeOnlineManagement` module available on the execution host +- A host/runtime that establishes an Exchange Online session (delegated or app-only) +- Permissions for the mailbox operations you intend to run (conversion, OOO, etc.) ### Install (PowerShell Gallery) ```powershell -Install-Module IdLE.Provider.Mock -Scope CurrentUser +Install-Module IdLE.Provider.ExchangeOnline -Scope CurrentUser ``` ### Import ```powershell -Import-Module IdLE.Provider.Mock +Import-Module IdLE.Provider.ExchangeOnline ``` ## Quickstart -Create the provider and register it under a workflow alias (example): +Create provider and register it (example convention): ```powershell $providers = @{ - Identity = New-IdleMockIdentityProvider + ExchangeOnline = New-IdleExchangeOnlineProvider } ``` ## Authentication -No authentication is required. The Mock provider ignores `AuthSessionName`. +This provider does **not** authenticate by itself. -## Supported operations +Your host/runtime must establish the Exchange Online session and (optionally) route it via the AuthSessionBroker. +Mailbox steps typically reference that session via: -- Identity: create/update attributes (in-memory) -- Entitlements: ensure/remove group memberships (in-memory) +- `AuthSessionName = 'ExchangeOnline'` +- `AuthSessionOptions = @{ Role = 'Admin' }` (optional routing key) + +> Keep credentials/secrets **out of workflow files**. Resolve them in the host/runtime and provide them via the broker. + +## Supported Step Types + +Common step types using this provider include: + +- `IdLE.Step.Mailbox.GetInfo` +- `IdLE.Step.Mailbox.EnsureType` +- `IdLE.Step.Mailbox.EnsureOutOfOffice` ## Configuration -This provider has no admin-facing options. +No admin-facing provider options. + +## Examples (canonical templates) -## Example (canonical) +To keep provider documentation focused and consistent, this page embeds only the **canonical** Exchange Online templates: - - {MockIdentityAndEntitlements} + + {ExoJoinerMailboxBaseline} + + + + {ExoLeaverMailboxOffboarding} ## Troubleshooting -- **Values don’t persist across runs**: the Mock provider is in-memory per execution by design. -- **You need to test real permissions or connectivity**: switch to the real provider (AD/Entra/EXO/DirectorySync) and run in a test environment. +- **Module not found**: install `ExchangeOnlineManagement` on the execution host. +- **Not connected**: ensure the host establishes an Exchange Online session before IdLE runs. +- **Access denied**: the session identity must have permission to change mailbox settings. +- **OOO formatting issues**: use `MessageFormat = 'Html'` and validate HTML in a test mailbox first. + +## Scenarios (link-only) + +Cross-provider orchestration examples are valuable, but should not be embedded in a single provider reference page. +Keep them as **link-only** and collect them on a central Examples/Scenarios page: + +- `examples/workflows/templates/entraid-exo-leaver.psd1` diff --git a/examples/workflows/templates/exo-joiner.psd1 b/examples/workflows/templates/exo-joiner.psd1 new file mode 100644 index 00000000..f42092e5 --- /dev/null +++ b/examples/workflows/templates/exo-joiner.psd1 @@ -0,0 +1,123 @@ +@{ + Name = 'ExchangeOnline Joiner - Mailbox Baseline' + LifecycleEvent = 'Joiner' + Description = 'Verifies mailbox existence/type and applies a minimal safe baseline (idempotent).' + + Steps = @( + @{ + Name = 'GetMailboxInfo' + Type = 'IdLE.Step.Mailbox.GetInfo' + With = @{ + Provider = 'ExchangeOnline' + IdentityKey = '{{Request.Input.UserPrincipalName}}' + } + } + @{ + Name = 'EnsureUserMailboxType' + Type = 'IdLE.Step.Mailbox.EnsureType' + With = @{ + Provider = 'ExchangeOnline' + IdentityKey = '{{Request.Input.UserPrincipalName}}' + MailboxType = 'User' + } + } + @{ + Name = 'EmitCompletionEvent' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'Mailbox baseline verified/applied for {{Request.Input.UserPrincipalName}}.' + } + } + ) +} + +``` + +### 2) Keep `entraid-exo-leaver.psd1` but update it to the standard placeholder style + +This file is a **cross-provider scenario** (Entra ID + EXO). It should remain **link-only** (not embedded in a single provider page), but it should be consistent: + +- Replace all `IdentityKey = @{ ValueFrom = 'Request.Input.X' }` with `IdentityKey = '{{Request.Input.X}}'` + +Updated full content: + +```powershell +@{ + Name = 'Complete Leaver - EntraID + ExchangeOnline Offboarding' + LifecycleEvent = 'Leaver' + Description = 'Complete offboarding workflow: disables EntraID account, converts mailbox to shared, and enables Out of Office.' + Steps = @( + @{ + Name = 'GetMailboxInfo' + Type = 'IdLE.Step.Mailbox.GetInfo' + With = @{ + Provider = 'ExchangeOnline' + IdentityKey = '{{Request.Input.UserPrincipalName}}' + } + } + @{ + Name = 'ConvertToSharedMailbox' + Type = 'IdLE.Step.Mailbox.EnsureType' + With = @{ + Provider = 'ExchangeOnline' + IdentityKey = '{{Request.Input.UserPrincipalName}}' + MailboxType = 'Shared' + } + } + @{ + Name = 'EnableOutOfOffice' + Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' + With = @{ + Provider = 'ExchangeOnline' + IdentityKey = '{{Request.Input.UserPrincipalName}}' + Config = @{ + Mode = 'Enabled' + InternalMessage = 'This person is no longer with the organization. For assistance, please contact their manager or the main office.' + ExternalMessage = 'This person is no longer with the organization. Please contact the main office for assistance.' + ExternalAudience = 'All' + } + } + } + @{ + Name = 'RevokeAllGroupMemberships' + Type = 'IdLE.Step.EnsureEntitlement' + With = @{ + Provider = 'Identity' + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserObjectId}}' + Desired = @() + } + } + @{ + Name = 'ClearManager' + Type = 'IdLE.Step.EnsureAttributes' + With = @{ + Provider = 'Identity' + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserObjectId}}' + Attributes = @{ + Manager = $null + } + } + } + @{ + Name = 'DisableEntraIDAccount' + Type = 'IdLE.Step.DisableIdentity' + With = @{ + Provider = 'Identity' + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserObjectId}}' + } + } + @{ + Name = 'EmitCompletionEvent' + Type = 'IdLE.Step.EmitEvent' + With = @{ + Message = 'Complete offboarding finished: Mailbox converted to Shared, OOF enabled, EntraID account disabled.' + } + } + ) +} From b796580b663f306cdf5e99998b7837e021ced0d2 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:09:59 +0100 Subject: [PATCH 14/28] docs: fix md trailing --- .../mock/mock-identity-and-entitlements.psd1 | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/examples/workflows/mock/mock-identity-and-entitlements.psd1 b/examples/workflows/mock/mock-identity-and-entitlements.psd1 index 83bc4a00..52aa8004 100644 --- a/examples/workflows/mock/mock-identity-and-entitlements.psd1 +++ b/examples/workflows/mock/mock-identity-and-entitlements.psd1 @@ -1,58 +1,3 @@ -@{ - Name = 'Mock Provider - Identity + Entitlements (Demo)' - LifecycleEvent = 'Joiner' - Description = 'Demonstrates using the Mock provider to set attributes and group entitlements without touching real systems.' - - Steps = @( - @{ - Name = 'Ensure user attributes (mock)' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - Provider = 'Identity' - IdentityKey = '{{Request.Input.IdentityKey}}' - Attributes = @{ - GivenName = '{{Request.Input.GivenName}}' - Surname = '{{Request.Input.Surname}}' - Department = '{{Request.Input.Department}}' - Title = '{{Request.Input.Title}}' - } - } - } - - @{ - Name = 'Ensure group memberships (mock)' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - Provider = 'Identity' - IdentityKey = '{{Request.Input.IdentityKey}}' - - # In the mock provider, entitlements are just stored in-memory. - # Use this to validate your workflow logic and template placeholders. - Desired = @( - @{ - Kind = 'Group' - Id = '{{Request.Input.GroupId}}' - DisplayName = '{{Request.Input.GroupName}}' - } - ) - } - } - - @{ - Name = 'Emit completion' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Mock demo completed for {{Request.Input.IdentityKey}}.' - } - } - ) -} - -``` - -### File: examples/workflows/mock/mock-onfailure.psd1 - -```powershell @{ Name = 'Mock Provider - OnFailure handling (Demo)' LifecycleEvent = 'Joiner' From e9559299807eb8c30e0483343fdebabef40c0437 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:11:07 +0100 Subject: [PATCH 15/28] website: remove mermaid as not used --- website/package-lock.json | 1 - website/package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index c4a7a343..1c109a67 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,7 +11,6 @@ "@docusaurus/core": "3.9.2", "@docusaurus/plugin-client-redirects": "^3.9.2", "@docusaurus/preset-classic": "3.9.2", - "@docusaurus/theme-mermaid": "^3.7.0", "@easyops-cn/docusaurus-search-local": "^0.52.2", "@mdx-js/react": "^3.0.0", "@saucelabs/theme-github-codeblock": "^0.3.0", diff --git a/website/package.json b/website/package.json index 45b025ee..bd880d28 100644 --- a/website/package.json +++ b/website/package.json @@ -18,7 +18,6 @@ "@docusaurus/core": "3.9.2", "@docusaurus/plugin-client-redirects": "^3.9.2", "@docusaurus/preset-classic": "3.9.2", - "@docusaurus/theme-mermaid": "^3.7.0", "@easyops-cn/docusaurus-search-local": "^0.52.2", "@mdx-js/react": "^3.0.0", "@saucelabs/theme-github-codeblock": "^0.3.0", From f2ea1f3b9f54d92132e465343192e471c8db4e62 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:14:04 +0100 Subject: [PATCH 16/28] Update docs/reference/providers/provider-ad.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/reference/providers/provider-ad.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/reference/providers/provider-ad.md b/docs/reference/providers/provider-ad.md index 32dba30b..9343165c 100644 --- a/docs/reference/providers/provider-ad.md +++ b/docs/reference/providers/provider-ad.md @@ -77,14 +77,14 @@ The AD provider supports the common identity lifecycle and entitlement operation | Step type | Typical use | Notes | | --- | --- | --- | -| `IdLE.Step.Identity.Create` | Create user (if missing) | Identity can be addressed by GUID, UPN, or sAMAccountName | -| `IdLE.Step.Identity.EnsureAttributes` | Set/update AD user attributes | Use placeholders from your request input | -| `IdLE.Step.Identity.Disable` | Disable user account | Typical leaver action | -| `IdLE.Step.Identity.Enable` | Enable user account | Rare (rehire) | -| `IdLE.Step.Identity.MoveContainer` | Move user to another OU | Useful for leaver or org changes | -| `IdLE.Step.Identity.EnsureEntitlements` | Ensure group memberships | AD entitlements are **groups** | -| `IdLE.Step.Identity.RemoveEntitlements` | Remove managed groups | Prefer explicit allow-lists / managed lists | -| `IdLE.Step.Identity.Delete` | Delete user | **Opt-in** via `-AllowDelete` (see Configuration) | +| `IdLE.Step.CreateIdentity` | Create user (if missing) | Identity can be addressed by GUID, UPN, or sAMAccountName | +| `IdLE.Step.EnsureAttributes` | Set/update AD user attributes | Use placeholders from your request input | +| `IdLE.Step.DisableIdentity` | Disable user account | Typical leaver action | +| `IdLE.Step.EnableIdentity` | Enable user account | Rare (rehire) | +| `IdLE.Step.MoveIdentity` | Move user to another OU | Useful for leaver or org changes | +| `IdLE.Step.EnsureEntitlement` | Ensure group memberships | AD entitlements are **groups** | +| `IdLE.Step.RemoveEntitlement` | Remove managed groups | Prefer explicit allow-lists / managed lists | +| `IdLE.Step.DeleteIdentity` | Delete user | **Opt-in** via `-AllowDelete` (see Configuration) | ## Configuration From a3c8a259bcfc138323c96abb079c484fcc304437 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:15:42 +0100 Subject: [PATCH 17/28] examples: fix md trailing --- examples/workflows/templates/exo-joiner.psd1 | 44 -------------------- 1 file changed, 44 deletions(-) diff --git a/examples/workflows/templates/exo-joiner.psd1 b/examples/workflows/templates/exo-joiner.psd1 index f42092e5..e0504a56 100644 --- a/examples/workflows/templates/exo-joiner.psd1 +++ b/examples/workflows/templates/exo-joiner.psd1 @@ -1,47 +1,3 @@ -@{ - Name = 'ExchangeOnline Joiner - Mailbox Baseline' - LifecycleEvent = 'Joiner' - Description = 'Verifies mailbox existence/type and applies a minimal safe baseline (idempotent).' - - Steps = @( - @{ - Name = 'GetMailboxInfo' - Type = 'IdLE.Step.Mailbox.GetInfo' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = '{{Request.Input.UserPrincipalName}}' - } - } - @{ - Name = 'EnsureUserMailboxType' - Type = 'IdLE.Step.Mailbox.EnsureType' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = '{{Request.Input.UserPrincipalName}}' - MailboxType = 'User' - } - } - @{ - Name = 'EmitCompletionEvent' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Mailbox baseline verified/applied for {{Request.Input.UserPrincipalName}}.' - } - } - ) -} - -``` - -### 2) Keep `entraid-exo-leaver.psd1` but update it to the standard placeholder style - -This file is a **cross-provider scenario** (Entra ID + EXO). It should remain **link-only** (not embedded in a single provider page), but it should be consistent: - -- Replace all `IdentityKey = @{ ValueFrom = 'Request.Input.X' }` with `IdentityKey = '{{Request.Input.X}}'` - -Updated full content: - -```powershell @{ Name = 'Complete Leaver - EntraID + ExchangeOnline Offboarding' LifecycleEvent = 'Leaver' From 456dfddeedc4edce974f2e365331cd51f751f854 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:22:07 +0100 Subject: [PATCH 18/28] examples: fix wrong steptypes and workflow definition syntax --- examples/workflows/templates/ad-joiner.psd1 | 191 ++++++++---------- examples/workflows/templates/ad-leaver.psd1 | 113 +++++------ ...rectorysync-entraconnect-trigger-sync.psd1 | 2 +- 3 files changed, 140 insertions(+), 166 deletions(-) diff --git a/examples/workflows/templates/ad-joiner.psd1 b/examples/workflows/templates/ad-joiner.psd1 index 42beb878..c8845f40 100644 --- a/examples/workflows/templates/ad-joiner.psd1 +++ b/examples/workflows/templates/ad-joiner.psd1 @@ -1,122 +1,109 @@ @{ - Metadata = @{ - Name = 'AD - Joiner (complete)' - Description = 'Creates/updates an AD identity and applies baseline attributes and memberships. Includes optional mover patterns.' - Version = '1.0' - Tags = @('AD', 'Joiner', 'JML', 'Template') - } + Name = 'Complete Joiner - EntraID + ExchangeOnline Offboarding' + LifecycleEvent = 'Joiner' + Description = 'AD joiner workflow template (safe defaults).' - Workflow = @{ - Name = 'ad-joiner-complete' - Description = 'AD joiner workflow template (safe defaults).' + Steps = @( + # --- Identity creation / baseline --- + @{ + StepType = 'IdLE.Step.CreateIdentity' + Name = 'Create identity (if missing)' + With = @{ + # Required by the provider: which auth session to use + AuthSessionName = '{{Request.Auth.Directory}}' - # The workflow author decides the provider alias. Example: "Directory" - With = @{ - Provider = 'Directory' - } - - Steps = @( - # --- Identity creation / baseline --- - @{ - StepType = 'IdLE.Step.Identity.Create' - Name = 'Create identity (if missing)' - With = @{ - # Required by the provider: which auth session to use - AuthSessionName = '{{Request.Auth.Directory}}' - - # Provider-specific: identify the target identity - # The exact key names depend on provider contracts; keep it consistent with your provider docs. - Identity = @{ - SamAccountName = '{{Request.Input.SamAccountName}}' - UserPrincipalName = '{{Request.Input.UserPrincipalName}}' - } + # Provider-specific: identify the target identity + # The exact key names depend on provider contracts; keep it consistent with your provider docs. + Identity = @{ + SamAccountName = '{{Request.Input.SamAccountName}}' + UserPrincipalName = '{{Request.Input.UserPrincipalName}}' + } - # Optional: initial attributes that are commonly required - Attributes = @{ - GivenName = '{{Request.Input.GivenName}}' - Surname = '{{Request.Input.Surname}}' - DisplayName = '{{Request.Input.DisplayName}}' - } + # Optional: initial attributes that are commonly required + Attributes = @{ + GivenName = '{{Request.Input.GivenName}}' + Surname = '{{Request.Input.Surname}}' + DisplayName = '{{Request.Input.DisplayName}}' } } + } - @{ - StepType = 'IdLE.Step.Identity.EnsureAttributes' - Name = 'Ensure core attributes' - With = @{ - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ - SamAccountName = '{{Request.Input.SamAccountName}}' - } - Attributes = @{ - Mail = '{{Request.Input.Mail}}' - Department = '{{Request.Input.Department}}' - Title = '{{Request.Input.Title}}' - Company = '{{Request.Input.Company}}' - Office = '{{Request.Input.Office}}' - Manager = '{{Request.Input.ManagerSamAccountName}}' - TelephoneNumber = '{{Request.Input.Phone}}' - } + @{ + StepType = 'IdLE.Step.EnsureAttributes' + Name = 'Ensure core attributes' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ + SamAccountName = '{{Request.Input.SamAccountName}}' + } + Attributes = @{ + Mail = '{{Request.Input.Mail}}' + Department = '{{Request.Input.Department}}' + Title = '{{Request.Input.Title}}' + Company = '{{Request.Input.Company}}' + Office = '{{Request.Input.Office}}' + Manager = '{{Request.Input.ManagerSamAccountName}}' + TelephoneNumber = '{{Request.Input.Phone}}' } } + } - @{ - StepType = 'IdLE.Step.Identity.EnsureEntitlements' - Name = 'Ensure baseline group memberships' - With = @{ - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ - SamAccountName = '{{Request.Input.SamAccountName}}' - } - - # Use explicit, predictable lists. Prefer allow-lists for baseline access. - Entitlements = @( - '{{Request.Input.BaselineGroups.0}}' - '{{Request.Input.BaselineGroups.1}}' - ) + @{ + StepType = 'IdLE.Step.EnsureEntitlements' + Name = 'Ensure baseline group memberships' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ + SamAccountName = '{{Request.Input.SamAccountName}}' } + + # Use explicit, predictable lists. Prefer allow-lists for baseline access. + Entitlements = @( + '{{Request.Input.BaselineGroups.0}}' + '{{Request.Input.BaselineGroups.1}}' + ) } + } - # --- Optional: Mover patterns (disabled by default) --- - # Use one of these approaches: - # A) Guard execution via a flag (preferred) - # B) Keep steps commented out and enable when needed + # --- Optional: Mover patterns (disabled by default) --- + # Use one of these approaches: + # A) Guard execution via a flag (preferred) + # B) Keep steps commented out and enable when needed - @{ - StepType = 'IdLE.Step.Identity.EnsureAttributes' - Name = 'Mover: update org attributes (optional)' - With = @{ - # Guard by convention: only run when request indicates mover - Condition = '{{Request.Input.IsMover}}' - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } - Attributes = @{ - Department = '{{Request.Input.NewDepartment}}' - Title = '{{Request.Input.NewTitle}}' - Office = '{{Request.Input.NewOffice}}' - Manager = '{{Request.Input.NewManagerSamAccountName}}' - Description = 'Moved on {{Request.Execution.Timestamp}}' - } + @{ + StepType = 'IdLE.Step.EnsureAttributes' + Name = 'Mover: update org attributes (optional)' + With = @{ + # Guard by convention: only run when request indicates mover + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Attributes = @{ + Department = '{{Request.Input.NewDepartment}}' + Title = '{{Request.Input.NewTitle}}' + Office = '{{Request.Input.NewOffice}}' + Manager = '{{Request.Input.NewManagerSamAccountName}}' + Description = 'Moved on {{Request.Execution.Timestamp}}' } } + } - @{ - StepType = 'IdLE.Step.Identity.EnsureEntitlements' - Name = 'Mover: adjust group memberships (optional)' - With = @{ - Condition = '{{Request.Input.IsMover}}' - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + @{ + StepType = 'IdLE.Step.EnsureEntitlement' + Name = 'Mover: adjust group memberships (optional)' + With = @{ + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } - # Optional: baseline + department-specific groups. - Entitlements = @( - '{{Request.Input.BaselineGroups.0}}' - '{{Request.Input.BaselineGroups.1}}' - '{{Request.Input.DepartmentGroups.0}}' - '{{Request.Input.DepartmentGroups.1}}' - ) - } + # Optional: baseline + department-specific groups. + Entitlements = @( + '{{Request.Input.BaselineGroups.0}}' + '{{Request.Input.BaselineGroups.1}}' + '{{Request.Input.DepartmentGroups.0}}' + '{{Request.Input.DepartmentGroups.1}}' + ) } - ) - } + } + ) } \ No newline at end of file diff --git a/examples/workflows/templates/ad-leaver.psd1 b/examples/workflows/templates/ad-leaver.psd1 index dab32c1c..1071964f 100644 --- a/examples/workflows/templates/ad-leaver.psd1 +++ b/examples/workflows/templates/ad-leaver.psd1 @@ -1,75 +1,62 @@ @{ - Metadata = @{ - Name = 'AD - Leaver (offboarding)' - Description = 'Disables an AD identity and applies offboarding changes. Includes notes for mover-to-leaver transitions.' - Version = '1.0' - Tags = @('AD', 'Leaver', 'JML', 'Template') - } + Name = 'AD - Leaver (offboarding)' + Description = 'Disables an AD identity and applies offboarding changes. Includes notes for mover-to-leaver transitions.' - Workflow = @{ - Name = 'ad-leaver-offboarding' - Description = 'AD leaver workflow template (safe defaults).' - - With = @{ - Provider = 'Directory' - } - - Steps = @( - @{ - StepType = 'IdLE.Step.Identity.Disable' - Name = 'Disable identity' - With = @{ - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } - Reason = '{{Request.Input.LeaverReason}}' - } + Steps = @( + @{ + StepType = 'IdLE.Step.DisableIdentity' + Name = 'Disable identity' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Reason = '{{Request.Input.LeaverReason}}' } + } - @{ - StepType = 'IdLE.Step.Identity.EnsureAttributes' - Name = 'Stamp offboarding attributes' - With = @{ - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } - Attributes = @{ - Description = 'Leaver on {{Request.Execution.Timestamp}} - {{Request.Input.LeaverReason}}' - } + @{ + StepType = 'IdLE.Step.EnsureAttributes' + Name = 'Stamp offboarding attributes' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Attributes = @{ + Description = 'Leaver on {{Request.Execution.Timestamp}} - {{Request.Input.LeaverReason}}' } } + } - # Optional, use with caution: - # Removing groups can break business processes unexpectedly. - # Prefer an explicit allow-list or a "remove only managed groups" approach. - @{ - StepType = 'IdLE.Step.Identity.RemoveEntitlements' - Name = 'Remove managed group memberships (optional)' - With = @{ - Condition = '{{Request.Input.RemoveGroups}}' - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + # Optional, use with caution: + # Removing groups can break business processes unexpectedly. + # Prefer an explicit allow-list or a "remove only managed groups" approach. + @{ + StepType = 'IdLE.Step.EnsureEntitlement' + Name = 'Remove managed group memberships (optional)' + With = @{ + Condition = '{{Request.Input.RemoveGroups}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } - # Only remove what you explicitly manage via IdLE. - Entitlements = @( - '{{Request.Input.ManagedGroupsToRemove.0}}' - '{{Request.Input.ManagedGroupsToRemove.1}}' - ) - } + # Only remove what you explicitly manage via IdLE. + Entitlements = @( + '{{Request.Input.ManagedGroupsToRemove.0}}' + '{{Request.Input.ManagedGroupsToRemove.1}}' + ) } + } - # --- Mover-to-leaver transition notes (operational) --- - # Common approach: - # - Day 0: Disable + stamp description (safe, minimal risk) - # - Day N: Remove managed groups + move to Disabled OU (explicit opt-in) - @{ - StepType = 'IdLE.Step.Identity.MoveContainer' - Name = 'Move to Disabled OU (optional)' - With = @{ - Condition = '{{Request.Input.MoveToDisabledOu}}' - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } - TargetPath = '{{Request.Input.DisabledOuPath}}' - } + # --- Mover-to-leaver transition notes (operational) --- + # Common approach: + # - Day 0: Disable + stamp description (safe, minimal risk) + # - Day N: Remove managed groups + move to Disabled OU (explicit opt-in) + @{ + StepType = 'IdLE.Step.MoveIdentity' + Name = 'Move to Disabled OU (optional)' + With = @{ + Condition = '{{Request.Input.MoveToDisabledOu}}' + AuthSessionName = '{{Request.Auth.Directory}}' + Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + TargetPath = '{{Request.Input.DisabledOuPath}}' } - ) - } + } + ) } \ No newline at end of file diff --git a/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 b/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 index 18678f81..5f3af1cb 100644 --- a/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 +++ b/examples/workflows/templates/directorysync-entraconnect-trigger-sync.psd1 @@ -17,7 +17,7 @@ } # Delta or Initial - PolicyType = 'Delta' + PolicyType = '{{Request.Input.PolicyType}}' # Optional wait/polling behavior (step-specific) Wait = $true From 82f49489fd8c135f58d296cf48c69a8091a56e7d Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:29:35 +0100 Subject: [PATCH 19/28] tests: change to new example names --- tests/Core/CapabilityDeprecation.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/CapabilityDeprecation.Tests.ps1 b/tests/Core/CapabilityDeprecation.Tests.ps1 index cdbbe6a3..5b8aba9c 100644 --- a/tests/Core/CapabilityDeprecation.Tests.ps1 +++ b/tests/Core/CapabilityDeprecation.Tests.ps1 @@ -27,7 +27,7 @@ Describe 'Capability Deprecation and Migration' { } # Use a real workflow file that uses mailbox steps - $wfPath = Join-Path $PSScriptRoot '..' '..' 'examples' 'workflows' 'templates' 'exo-leaver-mailbox-offboarding.psd1' + $wfPath = Join-Path $PSScriptRoot '..' '..' 'examples' 'workflows' 'templates' 'exo-leaver.psd1' # Verify the workflow file exists $wfPath | Should -Exist @@ -70,7 +70,7 @@ Describe 'Capability Deprecation and Migration' { } # Use a real workflow file - $wfPath = Join-Path $PSScriptRoot '..' '..' 'examples' 'workflows' 'templates' 'exo-leaver-mailbox-offboarding.psd1' + $wfPath = Join-Path $PSScriptRoot '..' '..' 'examples' 'workflows' 'templates' 'exo-leaver.psd1' $req = New-IdleTestRequest -LifecycleEvent 'Leaver' -DesiredState @{ Manager = @{ From df25c349799041c27c90ee6d5e4e9e492d0b07f4 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:32:28 +0100 Subject: [PATCH 20/28] examples: fix wrong placeholder strings --- examples/workflows/joiner-with-retry-profiles.psd1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/workflows/joiner-with-retry-profiles.psd1 b/examples/workflows/joiner-with-retry-profiles.psd1 index 25ed8469..f7e02471 100644 --- a/examples/workflows/joiner-with-retry-profiles.psd1 +++ b/examples/workflows/joiner-with-retry-profiles.psd1 @@ -16,7 +16,7 @@ # In a real deployment, this would be a system-specific "resolve from HR" step. # Here we emit an event as a simple example, using the default retry profile. With = @{ - Message = 'Resolve identity for HR record {{Request.Data.HrEmployeeId}}' + Message = 'Resolve identity for HR record {{Request.IdentityKeys.HrEmployeeId}}' } } @@ -60,7 +60,7 @@ RetryProfile = 'GraphAPI' With = @{ Attributes = @{ - manager = '{{Request.Data.ManagerId}}' + manager = '{{Request.DesiredState.ManagerId}}' } } } @@ -74,7 +74,7 @@ RetryProfile = 'Notifications' # Notification systems may have their own rate limits With = @{ - Message = 'Joiner workflow failed for user {{Request.Data.UserPrincipalName}}' + Message = 'Joiner workflow failed for user {{Request.DesiredState.UserPrincipalName}}' } } ) From 241085e9c5eefdc4dffe5ac82ec87620b33d14d6 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:33:39 +0100 Subject: [PATCH 21/28] example: remove unnecessary example --- .../mock/mock-identity-and-entitlements.psd1 | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 examples/workflows/mock/mock-identity-and-entitlements.psd1 diff --git a/examples/workflows/mock/mock-identity-and-entitlements.psd1 b/examples/workflows/mock/mock-identity-and-entitlements.psd1 deleted file mode 100644 index 52aa8004..00000000 --- a/examples/workflows/mock/mock-identity-and-entitlements.psd1 +++ /dev/null @@ -1,33 +0,0 @@ -@{ - Name = 'Mock Provider - OnFailure handling (Demo)' - LifecycleEvent = 'Joiner' - Description = 'Demonstrates OnFailureSteps for cleanup/notification when primary steps fail (using Mock provider).' - - Steps = @( - @{ - Name = 'Emit start' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Starting workflow with OnFailure handling.' - } - } - - @{ - Name = 'Primary action (will fail intentionally)' - Type = 'IdLE.Step.Fail' - With = @{ - Message = 'Intentional failure to demonstrate OnFailureSteps.' - } - - OnFailureSteps = @( - @{ - Name = 'Emit failure notification' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Primary action failed for {{Request.Input.IdentityKey}}.' - } - } - ) - } - ) -} From e43c79eb7b4ea19f8cb3946b47834445a751de0b Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:44:36 +0100 Subject: [PATCH 22/28] docs: fix step example syntax --- examples/workflows/templates/ad-joiner.psd1 | 75 ++++++++++++++------- examples/workflows/templates/ad-leaver.psd1 | 47 ++++++++----- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/examples/workflows/templates/ad-joiner.psd1 b/examples/workflows/templates/ad-joiner.psd1 index c8845f40..50898ba3 100644 --- a/examples/workflows/templates/ad-joiner.psd1 +++ b/examples/workflows/templates/ad-joiner.psd1 @@ -6,7 +6,7 @@ Steps = @( # --- Identity creation / baseline --- @{ - StepType = 'IdLE.Step.CreateIdentity' + Type = 'IdLE.Step.CreateIdentity' Name = 'Create identity (if missing)' With = @{ # Required by the provider: which auth session to use @@ -14,10 +14,7 @@ # Provider-specific: identify the target identity # The exact key names depend on provider contracts; keep it consistent with your provider docs. - Identity = @{ - SamAccountName = '{{Request.Input.SamAccountName}}' - UserPrincipalName = '{{Request.Input.UserPrincipalName}}' - } + IdentityKey = '{{Request.Input.SamAccountName}}' # Optional: initial attributes that are commonly required Attributes = @{ @@ -29,13 +26,12 @@ } @{ - StepType = 'IdLE.Step.EnsureAttributes' + Type = 'IdLE.Step.EnsureAttributes' Name = 'Ensure core attributes' With = @{ AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ - SamAccountName = '{{Request.Input.SamAccountName}}' - } + IdentityKey = '{{Request.Input.SamAccountName}}' + Attributes = @{ Mail = '{{Request.Input.Mail}}' Department = '{{Request.Input.Department}}' @@ -49,13 +45,11 @@ } @{ - StepType = 'IdLE.Step.EnsureEntitlements' + Type = 'IdLE.Step.EnsureEntitlements' Name = 'Ensure baseline group memberships' With = @{ AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ - SamAccountName = '{{Request.Input.SamAccountName}}' - } + IdentityKey = '{{Request.Input.SamAccountName}}' # Use explicit, predictable lists. Prefer allow-lists for baseline access. Entitlements = @( @@ -71,13 +65,13 @@ # B) Keep steps commented out and enable when needed @{ - StepType = 'IdLE.Step.EnsureAttributes' + Type = 'IdLE.Step.EnsureAttributes' Name = 'Mover: update org attributes (optional)' With = @{ # Guard by convention: only run when request indicates mover Condition = '{{Request.Input.IsMover}}' AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + IdentityKey = '{{Request.Input.SamAccountName}}' Attributes = @{ Department = '{{Request.Input.NewDepartment}}' Title = '{{Request.Input.NewTitle}}' @@ -89,20 +83,55 @@ } @{ - StepType = 'IdLE.Step.EnsureEntitlement' + Type = 'IdLE.Step.EnsureEntitlement' Name = 'Mover: adjust group memberships (optional)' With = @{ Condition = '{{Request.Input.IsMover}}' AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + IdentityKey = '{{Request.Input.SamAccountName}}' # Optional: baseline + department-specific groups. - Entitlements = @( - '{{Request.Input.BaselineGroups.0}}' - '{{Request.Input.BaselineGroups.1}}' - '{{Request.Input.DepartmentGroups.0}}' - '{{Request.Input.DepartmentGroups.1}}' - ) + Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.BaselineGroups.0}}' } + State = 'Present' + } + } + @{ + Type = 'IdLE.Step.EnsureEntitlement' + Name = 'Mover: adjust group memberships (optional)' + With = @{ + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + IdentityKey = '{{Request.Input.SamAccountName}}' + + # Optional: baseline + department-specific groups. + Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.BaselineGroups.1}}' } + State = 'Present' + } + } +@{ + Type = 'IdLE.Step.EnsureEntitlement' + Name = 'Mover: adjust group memberships (optional)' + With = @{ + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + IdentityKey = '{{Request.Input.SamAccountName}}' + + # Optional: baseline + department-specific groups. + Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.DepartmentGroups.0}}' } + State = 'Present' + } + } + @{ + Type = 'IdLE.Step.EnsureEntitlement' + Name = 'Mover: adjust group memberships (optional)' + With = @{ + Condition = '{{Request.Input.IsMover}}' + AuthSessionName = '{{Request.Auth.Directory}}' + IdentityKey = '{{Request.Input.SamAccountName}}' + + # Optional: baseline + department-specific groups. + Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.DepartmentGroups.1}}' } + State = 'Present' } } ) diff --git a/examples/workflows/templates/ad-leaver.psd1 b/examples/workflows/templates/ad-leaver.psd1 index 1071964f..911a4c70 100644 --- a/examples/workflows/templates/ad-leaver.psd1 +++ b/examples/workflows/templates/ad-leaver.psd1 @@ -4,23 +4,23 @@ Steps = @( @{ - StepType = 'IdLE.Step.DisableIdentity' + Type = 'IdLE.Step.DisableIdentity' Name = 'Disable identity' With = @{ - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + AuthSessionName = 'Directory' + Identity = '{{Request.Input.SamAccountName}}' Reason = '{{Request.Input.LeaverReason}}' } } @{ - StepType = 'IdLE.Step.EnsureAttributes' + Type = 'IdLE.Step.EnsureAttributes' Name = 'Stamp offboarding attributes' With = @{ - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + AuthSessionName = 'Directory' + Identity = '{{Request.Input.SamAccountName}}' Attributes = @{ - Description = 'Leaver on {{Request.Execution.Timestamp}} - {{Request.Input.LeaverReason}}' + Description = 'Leaver (CorrelationId: {{Request.CorrelationId}}) - {{Request.Input.LeaverReason}}' } } } @@ -29,18 +29,29 @@ # Removing groups can break business processes unexpectedly. # Prefer an explicit allow-list or a "remove only managed groups" approach. @{ - StepType = 'IdLE.Step.EnsureEntitlement' + Type = 'IdLE.Step.EnsureEntitlement' Name = 'Remove managed group memberships (optional)' With = @{ - Condition = '{{Request.Input.RemoveGroups}}' - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + Condition = @{ Equals = @{ Path = 'Request.Input.RemoveGroups'; Value = $true } } + AuthSessionName = 'Directory' + Identity = '{{Request.Input.SamAccountName}}' # Only remove what you explicitly manage via IdLE. - Entitlements = @( - '{{Request.Input.ManagedGroupsToRemove.0}}' - '{{Request.Input.ManagedGroupsToRemove.1}}' - ) + Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.ManagedGroupsToRemove.0}}' } + State = 'Absent' + } + } + @{ + Type = 'IdLE.Step.EnsureEntitlement' + Name = 'Remove managed group memberships (optional)' + With = @{ + Condition = @{ Equals = @{ Path = 'Request.Input.RemoveGroups'; Value = $true } } + AuthSessionName = 'Directory' + Identity = '{{Request.Input.SamAccountName}}' + + # Only remove what you explicitly manage via IdLE. + Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.ManagedGroupsToRemove.1}}' } + State = 'Absent' } } @@ -49,12 +60,12 @@ # - Day 0: Disable + stamp description (safe, minimal risk) # - Day N: Remove managed groups + move to Disabled OU (explicit opt-in) @{ - StepType = 'IdLE.Step.MoveIdentity' + Type = 'IdLE.Step.MoveIdentity' Name = 'Move to Disabled OU (optional)' With = @{ Condition = '{{Request.Input.MoveToDisabledOu}}' - AuthSessionName = '{{Request.Auth.Directory}}' - Identity = @{ SamAccountName = '{{Request.Input.SamAccountName}}' } + AuthSessionName = 'Directory' + Identity = '{{Request.Input.SamAccountName}}' TargetPath = '{{Request.Input.DisabledOuPath}}' } } From 7f7a22f503930b9f7dacf10b219f7caed4a4fc48 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:07:49 +0100 Subject: [PATCH 23/28] examples: fix syntax errors + copy paste errors --- .../workflows/joiner-with-retry-profiles.psd1 | 132 ----------------- .../mock/joiner-with-retry-profiles.psd1 | 133 ++++++++++++++++++ .../ad-joiner-entraconnect-entraid.psd1 | 13 +- examples/workflows/templates/ad-joiner.psd1 | 34 +++-- examples/workflows/templates/ad-leaver.psd1 | 25 ++-- .../templates/entraid-exo-leaver.psd1 | 4 +- .../workflows/templates/entraid-joiner.psd1 | 4 + .../workflows/templates/entraid-leaver.psd1 | 18 ++- examples/workflows/templates/exo-joiner.psd1 | 14 -- examples/workflows/templates/exo-leaver.psd1 | 4 +- 10 files changed, 198 insertions(+), 183 deletions(-) delete mode 100644 examples/workflows/joiner-with-retry-profiles.psd1 create mode 100644 examples/workflows/mock/joiner-with-retry-profiles.psd1 diff --git a/examples/workflows/joiner-with-retry-profiles.psd1 b/examples/workflows/joiner-with-retry-profiles.psd1 deleted file mode 100644 index f7e02471..00000000 --- a/examples/workflows/joiner-with-retry-profiles.psd1 +++ /dev/null @@ -1,132 +0,0 @@ -@{ - # Example workflow demonstrating configurable retry behavior - # - # This workflow shows how to use RetryProfile to configure - # different retry behavior for steps targeting different systems. - - Name = 'Joiner - With Retry Profiles' - LifecycleEvent = 'Joiner' - Description = 'Example workflow with custom retry profiles for different target systems' - - Steps = @( - @{ - Name = 'Resolve identity from HR system' - Type = 'IdLE.Step.EmitEvent' - Description = 'Lookup user in HR database' - # In a real deployment, this would be a system-specific "resolve from HR" step. - # Here we emit an event as a simple example, using the default retry profile. - With = @{ - Message = 'Resolve identity for HR record {{Request.IdentityKeys.HrEmployeeId}}' - } - } - - @{ - Name = 'Create Entra ID account' - Type = 'IdLE.Step.CreateIdentity' - Description = 'Create user in Entra ID (Microsoft Graph API)' - RetryProfile = 'GraphAPI' - # Microsoft Graph has specific throttling limits - use a profile - # optimized for Graph API retry behavior - } - - @{ - Name = 'Create mailbox' - Type = 'IdLE.Step.EnsureEntitlement' - Description = 'Provision Exchange Online mailbox' - RetryProfile = 'ExchangeOnline' - With = @{ - Kind = 'Mailbox' - MailboxType = 'UserMailbox' - } - # Exchange Online has different throttling characteristics - # than Graph - use a dedicated profile - } - - @{ - Name = 'Add to security group' - Type = 'IdLE.Step.EnsureEntitlement' - Description = 'Add user to Entra ID security group' - RetryProfile = 'GraphAPI' - With = @{ - Kind = 'Group' - Value = 'All_Users' - } - } - - @{ - Name = 'Set manager attribute' - Type = 'IdLE.Step.EnsureAttributes' - Description = 'Set manager reference in Entra ID' - RetryProfile = 'GraphAPI' - With = @{ - Attributes = @{ - manager = '{{Request.DesiredState.ManagerId}}' - } - } - } - ) - - OnFailureSteps = @( - @{ - Name = 'Emit failure notification' - Type = 'IdLE.Step.EmitEvent' - Description = 'Notify on workflow failure' - RetryProfile = 'Notifications' - # Notification systems may have their own rate limits - With = @{ - Message = 'Joiner workflow failed for user {{Request.DesiredState.UserPrincipalName}}' - } - } - ) -} - -<# -Example ExecutionOptions configuration: - -$executionOptions = @{ - RetryProfiles = @{ - Default = @{ - MaxAttempts = 3 - InitialDelayMilliseconds = 200 - BackoffFactor = 2.0 - MaxDelayMilliseconds = 5000 - JitterRatio = 0.2 - } - GraphAPI = @{ - # Microsoft Graph throttling can be aggressive - # Use more retries with longer delays - MaxAttempts = 5 - InitialDelayMilliseconds = 1000 - BackoffFactor = 2.0 - MaxDelayMilliseconds = 16000 - JitterRatio = 0.3 - } - ExchangeOnline = @{ - # Exchange Online often requires patience - MaxAttempts = 6 - InitialDelayMilliseconds = 500 - BackoffFactor = 2.5 - MaxDelayMilliseconds = 30000 - JitterRatio = 0.25 - } - Notifications = @{ - # Notifications should retry but not delay the workflow too much - MaxAttempts = 3 - InitialDelayMilliseconds = 100 - BackoffFactor = 1.5 - MaxDelayMilliseconds = 1000 - JitterRatio = 0.1 - } - } - DefaultRetryProfile = 'Default' -} - -# Invoke the plan with retry configuration -$result = Invoke-IdlePlan -Plan $plan -Providers $providers -ExecutionOptions $executionOptions - -# Each step will use its configured retry profile: -# - Steps without RetryProfile use 'Default' (from DefaultRetryProfile) -# - Steps with RetryProfile='GraphAPI' use the GraphAPI profile -# - Steps with RetryProfile='ExchangeOnline' use the ExchangeOnline profile -# - Steps with RetryProfile='Notifications' use the Notifications profile -#> diff --git a/examples/workflows/mock/joiner-with-retry-profiles.psd1 b/examples/workflows/mock/joiner-with-retry-profiles.psd1 new file mode 100644 index 00000000..e15e6f28 --- /dev/null +++ b/examples/workflows/mock/joiner-with-retry-profiles.psd1 @@ -0,0 +1,133 @@ +@{ + # Mock example workflow demonstrating configurable retry behavior + # + # This workflow is designed to run with the Mock/File providers + # and therefore uses only generic, provider-agnostic steps. + + Name = 'Joiner - With Retry Profiles (Mock)' + LifecycleEvent = 'Joiner' + Description = 'Mock workflow showing RetryProfile usage across steps with different retry characteristics.' + + Steps = @( + @{ + Name = 'Emit start' + Type = 'IdLE.Step.EmitEvent' + Description = 'Start marker event (Default retry profile)' + With = @{ + Message = 'Joiner workflow started (mock retry profiles demo).' + } + } + + @{ + Name = 'Ensure baseline attributes' + Type = 'IdLE.Step.EnsureAttributes' + Description = 'Simulate a system with stricter throttling (GraphAPI profile)' + RetryProfile = 'GraphAPI' + With = @{ + Provider = 'Identity' + IdentityKey = 'user1' + Attributes = @{ + Department = 'IT' + Title = 'Engineer' + } + } + } + + @{ + Name = 'Ensure mailbox entitlement' + Type = 'IdLE.Step.EnsureEntitlement' + Description = 'Simulate a slower system (ExchangeOnline profile)' + RetryProfile = 'ExchangeOnline' + With = @{ + Provider = 'Identity' + IdentityKey = 'user1' + Entitlement = @{ + Kind = 'Mailbox' + Id = 'UserMailbox' + DisplayName = 'User Mailbox' + } + State = 'Present' + } + } + + @{ + Name = 'Ensure group membership' + Type = 'IdLE.Step.EnsureEntitlement' + Description = 'Simulate Graph-like throttling again (GraphAPI profile)' + RetryProfile = 'GraphAPI' + With = @{ + Provider = 'Identity' + IdentityKey = 'user1' + Entitlement = @{ + Kind = 'Group' + Id = 'demo-group' + DisplayName = 'Demo Group' + } + State = 'Present' + } + } + + @{ + Name = 'Emit done' + Type = 'IdLE.Step.EmitEvent' + Description = 'Completion marker event (Default retry profile)' + With = @{ + Message = 'Joiner workflow completed (mock retry profiles demo).' + } + } + ) + + OnFailureSteps = @( + @{ + Name = 'Emit failure notification' + Type = 'IdLE.Step.EmitEvent' + Description = 'Notify on workflow failure (Notifications profile)' + RetryProfile = 'Notifications' + With = @{ + Message = 'ALERT: Joiner workflow failed for user1 (mock retry profiles demo).' + } + } + ) +} + +<# +Example ExecutionOptions configuration: + +$executionOptions = @{ + RetryProfiles = @{ + Default = @{ + MaxAttempts = 3 + InitialDelayMilliseconds = 200 + BackoffFactor = 2.0 + MaxDelayMilliseconds = 5000 + JitterRatio = 0.2 + } + GraphAPI = @{ + MaxAttempts = 5 + InitialDelayMilliseconds = 1000 + BackoffFactor = 2.0 + MaxDelayMilliseconds = 16000 + JitterRatio = 0.3 + } + ExchangeOnline = @{ + MaxAttempts = 6 + InitialDelayMilliseconds = 500 + BackoffFactor = 2.5 + MaxDelayMilliseconds = 30000 + JitterRatio = 0.25 + } + Notifications = @{ + MaxAttempts = 3 + InitialDelayMilliseconds = 100 + BackoffFactor = 1.5 + MaxDelayMilliseconds = 1000 + JitterRatio = 0.1 + } + } + DefaultRetryProfile = 'Default' +} + +# Each step uses: +# - Default profile if RetryProfile is not set +# - Named profile if RetryProfile is set on the step +#> diff --git a/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 b/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 index 71008955..c19cd4d0 100644 --- a/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 +++ b/examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1 @@ -50,13 +50,12 @@ IdentityKey = '{{Request.Input.UserPrincipalName}}' - Desired = @( - @{ - Kind = 'Group' - Id = '{{Request.Input.AllEmployeesGroupId}}' - DisplayName = '{{Request.Input.AllEmployeesGroupName}}' - } - ) + Entitlement = @{ + Kind = 'Group' + Id = '{{Request.Input.AllEmployeesGroupId}}' + DisplayName = '{{Request.Input.AllEmployeesGroupName}}' + } + State = 'Present' } } ) diff --git a/examples/workflows/templates/ad-joiner.psd1 b/examples/workflows/templates/ad-joiner.psd1 index 50898ba3..312c2ca8 100644 --- a/examples/workflows/templates/ad-joiner.psd1 +++ b/examples/workflows/templates/ad-joiner.psd1 @@ -45,17 +45,31 @@ } @{ - Type = 'IdLE.Step.EnsureEntitlements' - Name = 'Ensure baseline group memberships' - With = @{ + Type = 'IdLE.Step.EnsureEntitlement' + Name = 'Ensure baseline group membership (1)' + With = @{ AuthSessionName = '{{Request.Auth.Directory}}' - IdentityKey = '{{Request.Input.SamAccountName}}' - - # Use explicit, predictable lists. Prefer allow-lists for baseline access. - Entitlements = @( - '{{Request.Input.BaselineGroups.0}}' - '{{Request.Input.BaselineGroups.1}}' - ) + IdentityKey = '{{Request.Input.SamAccountName}}' + Entitlement = @{ + Kind = 'Group'; + Id = '{{Request.Input.BaselineGroups.0}}'; + DisplayName = '{{Request.Input.BaselineGroups.0}}' + } + State = 'Present' + } + }, + @{ + Type = 'IdLE.Step.EnsureEntitlement' + Name = 'Ensure baseline group membership (2)' + With = @{ + AuthSessionName = '{{Request.Auth.Directory}}' + IdentityKey = '{{Request.Input.SamAccountName}}' + Entitlement = @{ + Kind = 'Group'; + Id = '{{Request.Input.BaselineGroups.1}}'; + DisplayName = '{{Request.Input.BaselineGroups.1}}' + } + State = 'Present' } } diff --git a/examples/workflows/templates/ad-leaver.psd1 b/examples/workflows/templates/ad-leaver.psd1 index 911a4c70..19d6a8e0 100644 --- a/examples/workflows/templates/ad-leaver.psd1 +++ b/examples/workflows/templates/ad-leaver.psd1 @@ -1,5 +1,6 @@ @{ Name = 'AD - Leaver (offboarding)' + LifecycleEvent = 'Leaver' Description = 'Disables an AD identity and applies offboarding changes. Includes notes for mover-to-leaver transitions.' Steps = @( @@ -8,7 +9,7 @@ Name = 'Disable identity' With = @{ AuthSessionName = 'Directory' - Identity = '{{Request.Input.SamAccountName}}' + IdentityKey = '{{Request.Input.SamAccountName}}' Reason = '{{Request.Input.LeaverReason}}' } } @@ -18,7 +19,7 @@ Name = 'Stamp offboarding attributes' With = @{ AuthSessionName = 'Directory' - Identity = '{{Request.Input.SamAccountName}}' + IdentityKey = '{{Request.Input.SamAccountName}}' Attributes = @{ Description = 'Leaver (CorrelationId: {{Request.CorrelationId}}) - {{Request.Input.LeaverReason}}' } @@ -34,10 +35,13 @@ With = @{ Condition = @{ Equals = @{ Path = 'Request.Input.RemoveGroups'; Value = $true } } AuthSessionName = 'Directory' - Identity = '{{Request.Input.SamAccountName}}' + IdentityKey = '{{Request.Input.SamAccountName}}' # Only remove what you explicitly manage via IdLE. - Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.ManagedGroupsToRemove.0}}' } + Entitlement = @{ + Kind = 'Group'; + Id = '{{Request.Input.ManagedGroupsToRemove.0}}' + } State = 'Absent' } } @@ -47,10 +51,13 @@ With = @{ Condition = @{ Equals = @{ Path = 'Request.Input.RemoveGroups'; Value = $true } } AuthSessionName = 'Directory' - Identity = '{{Request.Input.SamAccountName}}' + IdentityKey = '{{Request.Input.SamAccountName}}' # Only remove what you explicitly manage via IdLE. - Entitlement = @{ Kind = 'Group'; Id = '{{Request.Input.ManagedGroupsToRemove.1}}' } + Entitlement = @{ + Kind = 'Group'; + Id = '{{Request.Input.ManagedGroupsToRemove.1}}' + } State = 'Absent' } } @@ -63,10 +70,10 @@ Type = 'IdLE.Step.MoveIdentity' Name = 'Move to Disabled OU (optional)' With = @{ - Condition = '{{Request.Input.MoveToDisabledOu}}' + Condition = @{ Equals = @{ Path = 'Request.Input.MoveToDisabledOu'; Value = $true } } AuthSessionName = 'Directory' - Identity = '{{Request.Input.SamAccountName}}' - TargetPath = '{{Request.Input.DisabledOuPath}}' + IdentityKey = '{{Request.Input.SamAccountName}}' + TargetContainer = '{{Request.Input.DisabledOuPath}}' } } ) diff --git a/examples/workflows/templates/entraid-exo-leaver.psd1 b/examples/workflows/templates/entraid-exo-leaver.psd1 index 23cee81e..9674084e 100644 --- a/examples/workflows/templates/entraid-exo-leaver.psd1 +++ b/examples/workflows/templates/entraid-exo-leaver.psd1 @@ -13,7 +13,7 @@ } @{ Name = 'ConvertToSharedMailbox' - Type = 'IdLE.Step.Mailbox.EnsureType' + Type = 'IdLE.Step.Mailbox.TypeEnsure' With = @{ Provider = 'ExchangeOnline' IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } @@ -22,7 +22,7 @@ } @{ Name = 'EnableOutOfOffice' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' + Type = 'IdLE.Step.Mailbox.OutOfOfficeEnsure' With = @{ Provider = 'ExchangeOnline' IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } diff --git a/examples/workflows/templates/entraid-joiner.psd1 b/examples/workflows/templates/entraid-joiner.psd1 index fa3334b4..f0a54174 100644 --- a/examples/workflows/templates/entraid-joiner.psd1 +++ b/examples/workflows/templates/entraid-joiner.psd1 @@ -10,6 +10,9 @@ With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } + + # Using UPN keeps it human-friendly in templates. + IdentityKey = '{{Request.Input.UserPrincipalName}}' Attributes = @{ UserPrincipalName = '{{Request.Input.UserPrincipalName}}' @@ -78,6 +81,7 @@ @{ Name = 'Mover_UpdateOrgAttributes' Type = 'IdLE.Step.EnsureAttributes' + Condition = @{ All = @( @{ diff --git a/examples/workflows/templates/entraid-leaver.psd1 b/examples/workflows/templates/entraid-leaver.psd1 index 9ea2a1ee..926929bd 100644 --- a/examples/workflows/templates/entraid-leaver.psd1 +++ b/examples/workflows/templates/entraid-leaver.psd1 @@ -12,7 +12,7 @@ AuthSessionOptions = @{ Role = 'Admin' } # Prefer ObjectId for leaver (stable), but you may also use UPN if your provider supports it. - IdentityKey = '{{Request.Input.UserObjectId}}' + IdentityKey = '{{Request.Input.UserPrincipalName}}' } } @@ -22,7 +22,7 @@ With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' + IdentityKey = '{{Request.Input.UserPrincipalName}}' } } @@ -32,7 +32,7 @@ With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' + IdentityKey = '{{Request.Input.UserPrincipalName}}' Attributes = @{ DisplayName = '{{Request.Input.DisplayName}} (LEAVER)' Manager = $null @@ -58,8 +58,12 @@ With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Desired = @() + IdentityKey = '{{Request.Input.UserPrincipalName}}' + Entitlement = @{ + Kind = 'Group'; + Id = '*' + } + State = 'Absent' } } @@ -80,7 +84,7 @@ With = @{ AuthSessionName = 'MicrosoftGraph' AuthSessionOptions = @{ Role = 'Tier0' } - IdentityKey = '{{Request.Input.UserObjectId}}' + IdentityKey = '{{Request.Input.UserPrincipalName}}' } } @@ -88,7 +92,7 @@ Name = 'EmitCompletionEvent' Type = 'IdLE.Step.EmitEvent' With = @{ - Message = 'EntraID user {{Request.Input.UserObjectId}} offboarding completed.' + Message = 'EntraID user {{Request.Input.UserPrincipalName}} offboarding completed.' } } ) diff --git a/examples/workflows/templates/exo-joiner.psd1 b/examples/workflows/templates/exo-joiner.psd1 index e0504a56..07ecc5e1 100644 --- a/examples/workflows/templates/exo-joiner.psd1 +++ b/examples/workflows/templates/exo-joiner.psd1 @@ -20,20 +20,6 @@ MailboxType = 'Shared' } } - @{ - Name = 'EnableOutOfOffice' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' - With = @{ - Provider = 'ExchangeOnline' - IdentityKey = '{{Request.Input.UserPrincipalName}}' - Config = @{ - Mode = 'Enabled' - InternalMessage = 'This person is no longer with the organization. For assistance, please contact their manager or the main office.' - ExternalMessage = 'This person is no longer with the organization. Please contact the main office for assistance.' - ExternalAudience = 'All' - } - } - } @{ Name = 'RevokeAllGroupMemberships' Type = 'IdLE.Step.EnsureEntitlement' diff --git a/examples/workflows/templates/exo-leaver.psd1 b/examples/workflows/templates/exo-leaver.psd1 index 0dc55402..056d23a5 100644 --- a/examples/workflows/templates/exo-leaver.psd1 +++ b/examples/workflows/templates/exo-leaver.psd1 @@ -14,7 +14,7 @@ } @{ Name = 'ConvertToSharedMailbox' - Type = 'IdLE.Step.Mailbox.EnsureType' + Type = 'IdLE.Step.Mailbox.TypeEnsure' With = @{ Provider = 'ExchangeOnline' IdentityKey = '{{Request.Input.UserPrincipalName}}' @@ -23,7 +23,7 @@ } @{ Name = 'EnableOutOfOfficeWithManagerContact' - Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' + Type = 'IdLE.Step.Mailbox.OutOfOfficeEnsure' With = @{ Provider = 'ExchangeOnline' IdentityKey = '{{Request.Input.UserPrincipalName}}' From 87d8868fb22b5b6e49261d225bfd3419a3ae380f Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:10:41 +0100 Subject: [PATCH 24/28] examples: real exo joiner --- examples/workflows/templates/exo-joiner.psd1 | 77 ++++++++------------ 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/examples/workflows/templates/exo-joiner.psd1 b/examples/workflows/templates/exo-joiner.psd1 index 07ecc5e1..80006e73 100644 --- a/examples/workflows/templates/exo-joiner.psd1 +++ b/examples/workflows/templates/exo-joiner.psd1 @@ -1,64 +1,51 @@ @{ - Name = 'Complete Leaver - EntraID + ExchangeOnline Offboarding' - LifecycleEvent = 'Leaver' - Description = 'Complete offboarding workflow: disables EntraID account, converts mailbox to shared, and enables Out of Office.' - Steps = @( + Name = 'Complete Joiner - ExchangeOnline Mailbox Provisioning' + LifecycleEvent = 'Joiner' + Description = 'Joiner workflow for Exchange Online: ensures mailbox type is User and Out of Office is disabled.' + + Steps = @( @{ - Name = 'GetMailboxInfo' - Type = 'IdLE.Step.Mailbox.GetInfo' - With = @{ + Name = 'GetMailboxInfo' + Type = 'IdLE.Step.Mailbox.GetInfo' + Description = 'Reads mailbox details (useful for auditing and troubleshooting).' + With = @{ Provider = 'ExchangeOnline' IdentityKey = '{{Request.Input.UserPrincipalName}}' } } + @{ - Name = 'ConvertToSharedMailbox' - Type = 'IdLE.Step.Mailbox.EnsureType' - With = @{ + Name = 'EnsureUserMailboxType' + Type = 'IdLE.Step.Mailbox.EnsureType' + Description = 'Ensures the mailbox is a regular user mailbox.' + With = @{ Provider = 'ExchangeOnline' IdentityKey = '{{Request.Input.UserPrincipalName}}' - MailboxType = 'Shared' + # Allowed values: User | Shared | Room | Equipment + MailboxType = 'User' } } + @{ - Name = 'RevokeAllGroupMemberships' - Type = 'IdLE.Step.EnsureEntitlement' - With = @{ - Provider = 'Identity' - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Desired = @() - } - } - @{ - Name = 'ClearManager' - Type = 'IdLE.Step.EnsureAttributes' - With = @{ - Provider = 'Identity' - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - Attributes = @{ - Manager = $null + Name = 'DisableOutOfOffice' + Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' + Description = 'Ensures Out of Office is disabled for a new joiner mailbox.' + With = @{ + Provider = 'ExchangeOnline' + IdentityKey = '{{Request.Input.UserPrincipalName}}' + Config = @{ + # Allowed values: Disabled | Enabled | Scheduled + Mode = 'Disabled' } } } + @{ - Name = 'DisableEntraIDAccount' - Type = 'IdLE.Step.DisableIdentity' - With = @{ - Provider = 'Identity' - AuthSessionName = 'MicrosoftGraph' - AuthSessionOptions = @{ Role = 'Admin' } - IdentityKey = '{{Request.Input.UserObjectId}}' - } - } - @{ - Name = 'EmitCompletionEvent' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Complete offboarding finished: Mailbox converted to Shared, OOF enabled, EntraID account disabled.' + Name = 'EmitCompletionEvent' + Type = 'IdLE.Step.EmitEvent' + Description = 'Completion marker.' + With = @{ + Message = 'EXO joiner completed: mailbox type ensured (User) and Out of Office disabled.' } } ) From 9550ad4333abe49aef3df5253e480edf9e6a6e4a Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:13:13 +0100 Subject: [PATCH 25/28] docs: change braces on import for mdx error --- docs/reference/providers/provider-ad.md | 8 +- .../provider-directorysync-entraconnect.md | 4 +- docs/reference/providers/provider-entraID.md | 8 +- .../providers/provider-exchangeonline.md | 8 +- docs/reference/providers/provider-mock.md | 8 - website/package-lock.json | 1469 ++--------------- 6 files changed, 107 insertions(+), 1398 deletions(-) diff --git a/docs/reference/providers/provider-ad.md b/docs/reference/providers/provider-ad.md index 9343165c..9147d105 100644 --- a/docs/reference/providers/provider-ad.md +++ b/docs/reference/providers/provider-ad.md @@ -120,13 +120,9 @@ $provider = New-IdleADIdentityProvider -AllowDelete These are the canonical, **doc-embed friendly** templates for AD. Mover scenarios are intentionally folded into Joiner/Leaver (as optional patterns) to keep the template set small. - - {AdJoiner} - +{AdJoiner} - - {AdLeaver} - +{AdLeaver} ## Troubleshooting diff --git a/docs/reference/providers/provider-directorysync-entraconnect.md b/docs/reference/providers/provider-directorysync-entraconnect.md index b6deeae4..e3ed42d3 100644 --- a/docs/reference/providers/provider-directorysync-entraconnect.md +++ b/docs/reference/providers/provider-directorysync-entraconnect.md @@ -100,9 +100,7 @@ This provider has no admin-facing option bag. Configuration is done through: ## Examples (canonical template) - - {EntraConnectTriggerSync} - +{EntraConnectTriggerSync} ## Troubleshooting diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index a4c3e244..1667fef5 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -112,13 +112,9 @@ Exact permission names depend on your auth model (delegated vs application) and These are the **two** canonical Entra ID templates, intended to be embedded directly in documentation. Mover scenarios are integrated as **optional patterns** in the Joiner template. - - {EntraJoiner} - +{EntraJoiner} - - {EntraLeaver} - +{EntraLeaver} ## Troubleshooting diff --git a/docs/reference/providers/provider-exchangeonline.md b/docs/reference/providers/provider-exchangeonline.md index d4f237c0..fa2b9b83 100644 --- a/docs/reference/providers/provider-exchangeonline.md +++ b/docs/reference/providers/provider-exchangeonline.md @@ -87,13 +87,9 @@ No admin-facing provider options. To keep provider documentation focused and consistent, this page embeds only the **canonical** Exchange Online templates: - - {ExoJoinerMailboxBaseline} - +{ExoJoinerMailboxBaseline} - - {ExoLeaverMailboxOffboarding} - +{ExoLeaverMailboxOffboarding} ## Troubleshooting diff --git a/docs/reference/providers/provider-mock.md b/docs/reference/providers/provider-mock.md index 1d6d49bf..576e861c 100644 --- a/docs/reference/providers/provider-mock.md +++ b/docs/reference/providers/provider-mock.md @@ -5,8 +5,6 @@ sidebar_label: Mock import CodeBlock from '@theme/CodeBlock'; -import MockIdentityAndEntitlements from '@site/../examples/workflows/mock/mock-identity-and-entitlements.psd1'; - ## Summary - **Module:** `IdLE.Provider.Mock` @@ -67,12 +65,6 @@ No authentication is required. The Mock provider ignores `AuthSessionName`. This provider has no admin-facing options. -## Example (canonical) - - - {MockIdentityAndEntitlements} - - ## Troubleshooting - **Values don’t persist across runs**: the Mock provider is in-memory per execution by design. diff --git a/website/package-lock.json b/website/package-lock.json index 1c109a67..ed3496a5 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -229,19 +229,6 @@ "node": ">= 14.0.0" } }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -1943,63 +1930,6 @@ "node": ">=6.9.0" } }, - "node_modules/@braintree/sanitize-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", - "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", - "license": "Apache-2.0" - }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -3970,34 +3900,6 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/theme-mermaid": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz", - "integrity": "sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "mermaid": ">=11.6.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@mermaid-js/layout-elk": "^0.1.9", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@mermaid-js/layout-elk": { - "optional": true - } - } - }, "node_modules/@docusaurus/theme-search-algolia": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz", @@ -4301,23 +4203,6 @@ "@hapi/hoek": "^9.0.0" } }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -4872,15 +4757,6 @@ "react": ">=16" } }, - "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", - "license": "MIT", - "dependencies": { - "langium": "3.3.1" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -5771,259 +5647,6 @@ "@types/node": "*" } }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -6092,12 +5715,6 @@ "@types/send": "*" } }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, "node_modules/@types/gtag.js": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", @@ -6335,13 +5952,6 @@ "@types/node": "*" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -7479,38 +7089,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, - "node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7808,12 +7386,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -8014,15 +7586,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" - } - }, "node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -8379,662 +7942,134 @@ "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-advanced": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", - "license": "MIT", - "dependencies": { - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.0", - "cssnano-preset-default": "^6.1.2", - "postcss-discard-unused": "^6.0.5", - "postcss-merge-idents": "^6.0.3", - "postcss-reduce-idents": "^6.0.3", - "postcss-zindex": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^1.0.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "license": "MIT", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/d3-dsv/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", "dependencies": { - "d3-array": "2 - 3" + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", "dependencies": { - "d3-time": "1 - 3" + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "d3-selection": "2 - 3" + "postcss": "^8.4.31" } }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" + "css-tree": "~2.2.0" }, "engines": { - "node": ">=12" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/debounce": { @@ -9198,15 +8233,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9358,15 +8384,6 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -10625,12 +9642,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -11318,15 +10329,6 @@ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -11822,31 +10824,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/katex": { - "version": "0.16.28", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", - "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -11856,11 +10833,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -11888,22 +10860,6 @@ "node": ">=6" } }, - "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/latest-version": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", @@ -11929,12 +10885,6 @@ "shell-quote": "^1.8.3" } }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -12010,12 +10960,6 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12126,18 +11070,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -12623,47 +11555,6 @@ "node": ">= 8" } }, - "node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", - "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", - "khroma": "^2.1.0", - "lodash-es": "^4.17.21", - "marked": "^16.2.1", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, - "node_modules/mermaid/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -14571,18 +13462,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -15040,12 +13919,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "license": "MIT" - }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -15185,12 +14058,6 @@ "tslib": "^2.0.3" } }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", - "license": "MIT" - }, "node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -15260,12 +14127,6 @@ "node": ">=8" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15299,17 +14160,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, "node_modules/pkijs": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", @@ -15327,22 +14177,6 @@ "node": ">=16.0.0" } }, - "node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/points-on-path": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", - "license": "MIT", - "dependencies": { - "path-data-parser": "0.1.0", - "points-on-curve": "0.2.0" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -17790,24 +16624,6 @@ "node": ">=0.10.0" } }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, - "node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, "node_modules/rtlcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", @@ -17861,12 +16677,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -18692,12 +17502,6 @@ "postcss": "^8.4.31" } }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18896,15 +17700,6 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/tinypool": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", @@ -18980,15 +17775,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -19069,12 +17855,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, "node_modules/undici": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/undici/-/undici-7.19.1.tgz", @@ -19592,55 +18372,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "license": "MIT" - }, "node_modules/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", From b143d94b1f169dafd235b3a933d7c7c63e26f618 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:15:33 +0100 Subject: [PATCH 26/28] ci: reduce MDX risk to warning no fail --- .github/workflows/pages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index f37bc873..9f004724 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -39,6 +39,7 @@ jobs: -DocsPath ./docs -WebsitePath ./website -FailOnOrphans:$false + -FailOnMdxRisks:$false - name: Upload docs audit artifact if: always() From b675cdf6eb2f26c80447878e12a618e5bc2c0ec8 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:20:12 +0100 Subject: [PATCH 27/28] website: fix docs-consistency checker to mdx risks and stricter pipeline run --- .github/workflows/pages.yml | 2 -- tools/Test-DocsConsistency.ps1 | 28 +++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9f004724..13b8672a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -38,8 +38,6 @@ jobs: pwsh -NoProfile -File ./tools/Test-DocsConsistency.ps1 -DocsPath ./docs -WebsitePath ./website - -FailOnOrphans:$false - -FailOnMdxRisks:$false - name: Upload docs audit artifact if: always() diff --git a/tools/Test-DocsConsistency.ps1 b/tools/Test-DocsConsistency.ps1 index 8996d564..4102d34e 100644 --- a/tools/Test-DocsConsistency.ps1 +++ b/tools/Test-DocsConsistency.ps1 @@ -314,7 +314,10 @@ function Find-MdxRisks { param( [Parameter(Mandatory)] [AllowNull()] - [object] $Lines + [object] $Lines, + + [Parameter()] + [string[]] $IgnoredMdxBlockTags = @('CodeBlock') ) # Normalize $Lines to a string[] for safe processing @@ -331,8 +334,17 @@ function Find-MdxRisks { } $inFence = $false + $inIgnoredMdxBlock = $false $risks = New-Object System.Collections.Generic.List[object] + $openTagPattern = $null + $closeTagPattern = $null + if ($IgnoredMdxBlockTags.Count -gt 0) { + $tagAlternation = ($IgnoredMdxBlockTags | ForEach-Object { [regex]::Escape($_) }) -join '|' + $openTagPattern = "<(?:$tagAlternation)\b[^>]*>" + $closeTagPattern = "" + } + for ($i = 0; $i -lt $normalizedLines.Count; $i++) { $line = $normalizedLines[$i] @@ -342,6 +354,20 @@ function Find-MdxRisks { } if ($inFence) { continue } + if ($openTagPattern -and ($line -match $openTagPattern)) { + if ($line -match $closeTagPattern) { + continue + } + $inIgnoredMdxBlock = $true + continue + } + if ($inIgnoredMdxBlock) { + if ($closeTagPattern -and ($line -match $closeTagPattern)) { + $inIgnoredMdxBlock = $false + } + continue + } + # Ignore any MDX-like patterns that are inside inline code. # Simple heuristic: split by backticks and only scan "outside" segments (even indices). $segments = $line -split '`' From 793334a804425d3b866c1a6c62b85ffaf7715911 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:26:00 +0100 Subject: [PATCH 28/28] tests: fix examples and tests after whole docs+examples rewrite --- examples/workflows/templates/ad-joiner.psd1 | 10 +++++----- examples/workflows/templates/ad-leaver.psd1 | 4 ++-- examples/workflows/templates/entraid-exo-leaver.psd1 | 4 ++-- examples/workflows/templates/exo-leaver.psd1 | 4 ++-- tests/Core/CapabilityDeprecation.Tests.ps1 | 10 ++++++++++ 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/workflows/templates/ad-joiner.psd1 b/examples/workflows/templates/ad-joiner.psd1 index 312c2ca8..74f4dd66 100644 --- a/examples/workflows/templates/ad-joiner.psd1 +++ b/examples/workflows/templates/ad-joiner.psd1 @@ -98,7 +98,7 @@ @{ Type = 'IdLE.Step.EnsureEntitlement' - Name = 'Mover: adjust group memberships (optional)' + Name = 'Mover: adjust group memberships (optional, baseline 1)' With = @{ Condition = '{{Request.Input.IsMover}}' AuthSessionName = '{{Request.Auth.Directory}}' @@ -111,7 +111,7 @@ } @{ Type = 'IdLE.Step.EnsureEntitlement' - Name = 'Mover: adjust group memberships (optional)' + Name = 'Mover: adjust group memberships (optional, baseline 2)' With = @{ Condition = '{{Request.Input.IsMover}}' AuthSessionName = '{{Request.Auth.Directory}}' @@ -122,9 +122,9 @@ State = 'Present' } } -@{ + @{ Type = 'IdLE.Step.EnsureEntitlement' - Name = 'Mover: adjust group memberships (optional)' + Name = 'Mover: adjust group memberships (optional, department 1)' With = @{ Condition = '{{Request.Input.IsMover}}' AuthSessionName = '{{Request.Auth.Directory}}' @@ -137,7 +137,7 @@ } @{ Type = 'IdLE.Step.EnsureEntitlement' - Name = 'Mover: adjust group memberships (optional)' + Name = 'Mover: adjust group memberships (optional, department 2)' With = @{ Condition = '{{Request.Input.IsMover}}' AuthSessionName = '{{Request.Auth.Directory}}' diff --git a/examples/workflows/templates/ad-leaver.psd1 b/examples/workflows/templates/ad-leaver.psd1 index 19d6a8e0..7bf4e6da 100644 --- a/examples/workflows/templates/ad-leaver.psd1 +++ b/examples/workflows/templates/ad-leaver.psd1 @@ -31,7 +31,7 @@ # Prefer an explicit allow-list or a "remove only managed groups" approach. @{ Type = 'IdLE.Step.EnsureEntitlement' - Name = 'Remove managed group memberships (optional)' + Name = 'Remove managed group memberships (optional, item 1)' With = @{ Condition = @{ Equals = @{ Path = 'Request.Input.RemoveGroups'; Value = $true } } AuthSessionName = 'Directory' @@ -47,7 +47,7 @@ } @{ Type = 'IdLE.Step.EnsureEntitlement' - Name = 'Remove managed group memberships (optional)' + Name = 'Remove managed group memberships (optional, item 2)' With = @{ Condition = @{ Equals = @{ Path = 'Request.Input.RemoveGroups'; Value = $true } } AuthSessionName = 'Directory' diff --git a/examples/workflows/templates/entraid-exo-leaver.psd1 b/examples/workflows/templates/entraid-exo-leaver.psd1 index 9674084e..23cee81e 100644 --- a/examples/workflows/templates/entraid-exo-leaver.psd1 +++ b/examples/workflows/templates/entraid-exo-leaver.psd1 @@ -13,7 +13,7 @@ } @{ Name = 'ConvertToSharedMailbox' - Type = 'IdLE.Step.Mailbox.TypeEnsure' + Type = 'IdLE.Step.Mailbox.EnsureType' With = @{ Provider = 'ExchangeOnline' IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } @@ -22,7 +22,7 @@ } @{ Name = 'EnableOutOfOffice' - Type = 'IdLE.Step.Mailbox.OutOfOfficeEnsure' + Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' With = @{ Provider = 'ExchangeOnline' IdentityKey = @{ ValueFrom = 'Request.Input.UserPrincipalName' } diff --git a/examples/workflows/templates/exo-leaver.psd1 b/examples/workflows/templates/exo-leaver.psd1 index 056d23a5..0dc55402 100644 --- a/examples/workflows/templates/exo-leaver.psd1 +++ b/examples/workflows/templates/exo-leaver.psd1 @@ -14,7 +14,7 @@ } @{ Name = 'ConvertToSharedMailbox' - Type = 'IdLE.Step.Mailbox.TypeEnsure' + Type = 'IdLE.Step.Mailbox.EnsureType' With = @{ Provider = 'ExchangeOnline' IdentityKey = '{{Request.Input.UserPrincipalName}}' @@ -23,7 +23,7 @@ } @{ Name = 'EnableOutOfOfficeWithManagerContact' - Type = 'IdLE.Step.Mailbox.OutOfOfficeEnsure' + Type = 'IdLE.Step.Mailbox.EnsureOutOfOffice' With = @{ Provider = 'ExchangeOnline' IdentityKey = '{{Request.Input.UserPrincipalName}}' diff --git a/tests/Core/CapabilityDeprecation.Tests.ps1 b/tests/Core/CapabilityDeprecation.Tests.ps1 index 5b8aba9c..ad99a6b8 100644 --- a/tests/Core/CapabilityDeprecation.Tests.ps1 +++ b/tests/Core/CapabilityDeprecation.Tests.ps1 @@ -33,10 +33,15 @@ Describe 'Capability Deprecation and Migration' { $wfPath | Should -Exist $req = New-IdleTestRequest -LifecycleEvent 'Leaver' -DesiredState @{ + UserPrincipalName = 'leaver@contoso.com' Manager = @{ DisplayName = 'IT Support' Mail = 'support@contoso.com' } + ServiceDesk = @{ + DisplayName = 'Service Desk' + Mail = 'servicedesk@contoso.com' + } } $providers = @{ MockProvider = $mockProvider } @@ -73,10 +78,15 @@ Describe 'Capability Deprecation and Migration' { $wfPath = Join-Path $PSScriptRoot '..' '..' 'examples' 'workflows' 'templates' 'exo-leaver.psd1' $req = New-IdleTestRequest -LifecycleEvent 'Leaver' -DesiredState @{ + UserPrincipalName = 'leaver@contoso.com' Manager = @{ DisplayName = 'IT Support' Mail = 'support@contoso.com' } + ServiceDesk = @{ + DisplayName = 'Service Desk' + Mail = 'servicedesk@contoso.com' + } } $providers = @{ MockProvider = $mockProvider }