From 9817608815f2a3385b6ca7de0b66891d0148ef5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:16:03 +0000 Subject: [PATCH 1/9] Initial plan From 5440c35ad7f97773d9cd7e9034befc1b637df8df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:19:44 +0000 Subject: [PATCH 2/9] Add RevokeIdentitySessions capability and step implementation Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- .../Private/New-IdleEntraIDAdapter.ps1 | 25 ++++ .../New-IdleEntraIDIdentityProvider.ps1 | 44 +++++++ src/IdLE.Steps.Common/IdLE.Steps.Common.psm1 | 3 +- .../Public/Get-IdleStepMetadataCatalog.ps1 | 5 + .../Invoke-IdleStepRevokeIdentitySessions.ps1 | 115 ++++++++++++++++++ 5 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/IdLE.Steps.Common/Public/Invoke-IdleStepRevokeIdentitySessions.ps1 diff --git a/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 b/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 index 0dbb4fee..48d74211 100644 --- a/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 +++ b/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 @@ -429,5 +429,30 @@ function New-IdleEntraIDAdapter { } } -Force + $adapter | Add-Member -MemberType ScriptMethod -Name RevokeSignInSessions -Value { + param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $ObjectId, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $AccessToken + ) + + $uri = "$($this.BaseUri)/users/$ObjectId/revokeSignInSessions" + + try { + $response = $this.InvokeGraphRequest('POST', $uri, $AccessToken, $null) + # Graph returns { "@odata.context": "...", "value": true/false } + # The value indicates whether sessions were revoked + return $response + } + catch { + # If user not found or other errors, let them propagate + throw + } + } -Force + return $adapter } diff --git a/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 b/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 index 2b89d284..18aebc9b 100644 --- a/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 +++ b/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 @@ -304,6 +304,7 @@ function New-IdleEntraIDIdentityProvider { 'IdLE.Identity.Attribute.Ensure' 'IdLE.Identity.Disable' 'IdLE.Identity.Enable' + 'IdLE.Identity.RevokeSessions' 'IdLE.Entitlement.List' 'IdLE.Entitlement.Grant' 'IdLE.Entitlement.Revoke' @@ -699,6 +700,49 @@ function New-IdleEntraIDIdentityProvider { } } -Force + $provider | Add-Member -MemberType ScriptMethod -Name RevokeSessions -Value { + param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $IdentityKey, + + [Parameter()] + [AllowNull()] + [object] $AuthSession + ) + + $accessToken = $this.ExtractAccessToken($AuthSession) + $user = $this.ResolveIdentity($IdentityKey, $AuthSession) + + # Get id from user object + $userId = if ($user -is [System.Collections.IDictionary]) { + $user['id'] + } + else { + $user.id + } + + # Call the adapter to revoke sign-in sessions + $response = $this.Adapter.RevokeSignInSessions($userId, $accessToken) + + # Graph returns a response indicating whether sessions were revoked + # We consider this operation as always "changed" when successful, + # as there's no reliable way to know if sessions existed before revocation + $changed = $true + + # If response contains a value property, use it to determine if changes occurred + if ($null -ne $response -and ($response.PSObject.Properties.Name -contains 'value')) { + $changed = [bool]$response.value + } + + return [pscustomobject]@{ + PSTypeName = 'IdLE.ProviderResult' + Operation = 'RevokeSessions' + IdentityKey = $IdentityKey + Changed = $changed + } + } -Force + $provider | Add-Member -MemberType ScriptMethod -Name ListEntitlements -Value { param( [Parameter(Mandatory)] diff --git a/src/IdLE.Steps.Common/IdLE.Steps.Common.psm1 b/src/IdLE.Steps.Common/IdLE.Steps.Common.psm1 index d1d85993..8e5c5f9b 100644 --- a/src/IdLE.Steps.Common/IdLE.Steps.Common.psm1 +++ b/src/IdLE.Steps.Common/IdLE.Steps.Common.psm1 @@ -53,5 +53,6 @@ Export-ModuleMember -Function @( 'Invoke-IdleStepDisableIdentity', 'Invoke-IdleStepEnableIdentity', 'Invoke-IdleStepMoveIdentity', - 'Invoke-IdleStepDeleteIdentity' + 'Invoke-IdleStepDeleteIdentity', + 'Invoke-IdleStepRevokeIdentitySessions' ) diff --git a/src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1 b/src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1 index 42f2818a..cc5a95b7 100644 --- a/src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1 +++ b/src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1 @@ -63,5 +63,10 @@ function Get-IdleStepMetadataCatalog { RequiredCapabilities = @('IdLE.Entitlement.List', 'IdLE.Entitlement.Grant', 'IdLE.Entitlement.Revoke') } + # IdLE.Step.RevokeIdentitySessions - requires identity session revocation capability + $catalog['IdLE.Step.RevokeIdentitySessions'] = @{ + RequiredCapabilities = @('IdLE.Identity.RevokeSessions') + } + return $catalog } diff --git a/src/IdLE.Steps.Common/Public/Invoke-IdleStepRevokeIdentitySessions.ps1 b/src/IdLE.Steps.Common/Public/Invoke-IdleStepRevokeIdentitySessions.ps1 new file mode 100644 index 00000000..0f27ceea --- /dev/null +++ b/src/IdLE.Steps.Common/Public/Invoke-IdleStepRevokeIdentitySessions.ps1 @@ -0,0 +1,115 @@ +function Invoke-IdleStepRevokeIdentitySessions { + <# + .SYNOPSIS + Revokes all active sign-in sessions for an identity in the target system. + + .DESCRIPTION + This is a provider-agnostic step that revokes active sign-in sessions (refresh tokens) + for a given identity. The host must supply a provider instance via + Context.Providers[] that implements RevokeSessions(identityKey) + and returns an object with properties 'IdentityKey' and 'Changed'. + + This step is typically used in Leaver workflows after disabling an identity to ensure + that existing sessions are terminated immediately, rather than waiting for tokens to expire. + + The step does not modify the identity itself (e.g., does not disable the account). + Use IdLE.Step.DisableIdentity separately if account disabling is also required. + + Authentication: + - If With.AuthSessionName is present, the step acquires an auth session via + Context.AcquireAuthSession(Name, Options) and passes it to the provider method + if the provider supports an AuthSession parameter. + - With.AuthSessionOptions (optional, hashtable) is passed to the broker for + session selection (e.g., @{ Role = 'Tier0' }). + - ScriptBlocks in AuthSessionOptions are rejected (security boundary). + + .PARAMETER Context + Execution context created by IdLE.Core. + + .PARAMETER Step + Normalized step object from the plan. Must contain a 'With' hashtable with keys: + - IdentityKey (required): the identity identifier + - Provider (optional): provider alias, defaults to 'Identity' + - AuthSessionName (optional): name for auth session acquisition + - AuthSessionOptions (optional): routing options for auth session broker + + .OUTPUTS + PSCustomObject (PSTypeName: IdLE.StepResult) + + .EXAMPLE + # In a workflow definition (PSD1): + @{ + Name = 'Revoke Entra sessions' + Type = 'IdLE.Step.RevokeIdentitySessions' + With = @{ + Provider = 'Entra' + IdentityKey = 'max.power@contoso.com' + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + } + } + + .NOTES + Requires provider capability: IdLE.Identity.RevokeSessions + + For Entra ID provider, this calls Microsoft Graph API: + POST /users/{id}/revokeSignInSessions + + Required Graph permissions: User.RevokeSessions.All + + Note: Session revocation may not be instantaneous; a small propagation delay may occur + depending on the identity provider and token lifetime policies. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateNotNull()] + [object] $Context, + + [Parameter(Mandatory)] + [ValidateNotNull()] + [object] $Step + ) + + $with = $Step.With + if ($null -eq $with -or -not ($with -is [hashtable])) { + throw "RevokeIdentitySessions requires 'With' to be a hashtable." + } + + if (-not $with.ContainsKey('IdentityKey')) { + throw "RevokeIdentitySessions requires With.IdentityKey." + } + + $providerAlias = if ($with.ContainsKey('Provider')) { [string]$with.Provider } else { 'Identity' } + + if (-not ($Context.PSObject.Properties.Name -contains 'Providers')) { + throw "Context does not contain a Providers hashtable." + } + if ($null -eq $Context.Providers -or -not ($Context.Providers -is [hashtable])) { + throw "Context.Providers must be a hashtable." + } + if (-not $Context.Providers.ContainsKey($providerAlias)) { + throw "Provider '$providerAlias' was not supplied by the host." + } + + $result = Invoke-IdleProviderMethod ` + -Context $Context ` + -With $with ` + -ProviderAlias $providerAlias ` + -MethodName 'RevokeSessions' ` + -MethodArguments @([string]$with.IdentityKey) + + $changed = $false + if ($null -ne $result -and ($result.PSObject.Properties.Name -contains 'Changed')) { + $changed = [bool]$result.Changed + } + + return [pscustomobject]@{ + PSTypeName = 'IdLE.StepResult' + Name = [string]$Step.Name + Type = [string]$Step.Type + Status = 'Completed' + Changed = $changed + Error = $null + } +} From f15c4271b5efbc81ef58b4ca5e3d345cb49299fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:22:27 +0000 Subject: [PATCH 3/9] Add comprehensive unit tests for RevokeIdentitySessions feature Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- src/IdLE.Steps.Common/IdLE.Steps.Common.psd1 | 3 +- .../EntraIDIdentityProvider.Tests.ps1 | 145 ++++++++++++++ ...e-IdleStepRevokeIdentitySessions.Tests.ps1 | 179 ++++++++++++++++++ 3 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 tests/Steps/Invoke-IdleStepRevokeIdentitySessions.Tests.ps1 diff --git a/src/IdLE.Steps.Common/IdLE.Steps.Common.psd1 b/src/IdLE.Steps.Common/IdLE.Steps.Common.psd1 index b4e270f3..88389488 100644 --- a/src/IdLE.Steps.Common/IdLE.Steps.Common.psd1 +++ b/src/IdLE.Steps.Common/IdLE.Steps.Common.psd1 @@ -19,7 +19,8 @@ 'Invoke-IdleStepDisableIdentity', 'Invoke-IdleStepEnableIdentity', 'Invoke-IdleStepMoveIdentity', - 'Invoke-IdleStepDeleteIdentity' + 'Invoke-IdleStepDeleteIdentity', + 'Invoke-IdleStepRevokeIdentitySessions' ) PrivateData = @{ diff --git a/tests/Providers/EntraIDIdentityProvider.Tests.ps1 b/tests/Providers/EntraIDIdentityProvider.Tests.ps1 index 8296df97..f2896391 100644 --- a/tests/Providers/EntraIDIdentityProvider.Tests.ps1 +++ b/tests/Providers/EntraIDIdentityProvider.Tests.ps1 @@ -230,6 +230,7 @@ Describe 'EntraID identity provider - Capabilities' { $caps | Should -Contain 'IdLE.Identity.Attribute.Ensure' $caps | Should -Contain 'IdLE.Identity.Disable' $caps | Should -Contain 'IdLE.Identity.Enable' + $caps | Should -Contain 'IdLE.Identity.RevokeSessions' $caps | Should -Contain 'IdLE.Entitlement.List' $caps | Should -Contain 'IdLE.Entitlement.Grant' $caps | Should -Contain 'IdLE.Entitlement.Revoke' @@ -796,3 +797,147 @@ Describe 'EntraID identity provider - Entitlement operations' { @($afterRevoke | Where-Object { $_.Kind -eq 'Group' -and $_.Id -eq $entitlement.Id }).Count | Should -Be 0 } } + +Describe 'EntraID identity provider - RevokeSessions' { + BeforeAll { + # Create a fake adapter that tracks revocation calls + $fakeAdapter = [pscustomobject]@{ + PSTypeName = 'IdLE.EntraIDAdapter.Fake' + RevocationCallLog = @() + RevocationResponses = @{} + } + + $fakeAdapter | Add-Member -MemberType ScriptMethod -Name GetUserById -Value { + param($ObjectId, $AccessToken) + return @{ + id = $ObjectId + userPrincipalName = "$ObjectId@test.local" + accountEnabled = $true + } + } + + $fakeAdapter | Add-Member -MemberType ScriptMethod -Name GetUserByUpn -Value { + param($Upn, $AccessToken) + return @{ + id = 'test-user-id' + userPrincipalName = $Upn + accountEnabled = $true + } + } + + $fakeAdapter | Add-Member -MemberType ScriptMethod -Name GetUserByMail -Value { + param($Mail, $AccessToken) + return @{ + id = 'test-user-id' + mail = $Mail + userPrincipalName = "$Mail" + accountEnabled = $true + } + } + + $fakeAdapter | Add-Member -MemberType ScriptMethod -Name RevokeSignInSessions -Value { + param($ObjectId, $AccessToken) + $this.RevocationCallLog += @{ + ObjectId = $ObjectId + AccessToken = $AccessToken + Timestamp = [datetime]::UtcNow + } + # Return a response that simulates Graph API behavior + if ($this.RevocationResponses.ContainsKey($ObjectId)) { + return $this.RevocationResponses[$ObjectId] + } + return [pscustomobject]@{ + '@odata.context' = 'https://graph.microsoft.com/v1.0/$metadata#Edm.Boolean' + value = $true + } + } + + $script:RevokeAdapter = $fakeAdapter + $script:RevokeProvider = New-IdleEntraIDIdentityProvider -Adapter $script:RevokeAdapter + } + + It 'Advertises IdLE.Identity.RevokeSessions capability' { + $caps = $script:RevokeProvider.GetCapabilities() + $caps | Should -Contain 'IdLE.Identity.RevokeSessions' + } + + It 'Exposes RevokeSessions method' { + $script:RevokeProvider.PSObject.Methods.Name | Should -Contain 'RevokeSessions' + } + + It 'RevokeSessions calls adapter with correct user ID' { + $userId = [guid]::NewGuid().ToString() + $script:RevokeAdapter.RevocationCallLog = @() + + $result = $script:RevokeProvider.RevokeSessions($userId, 'fake-token') + + $script:RevokeAdapter.RevocationCallLog.Count | Should -Be 1 + $script:RevokeAdapter.RevocationCallLog[0].ObjectId | Should -Be $userId + } + + It 'RevokeSessions returns ProviderResult with correct shape' { + $userId = [guid]::NewGuid().ToString() + + $result = $script:RevokeProvider.RevokeSessions($userId, 'fake-token') + + $result | Should -Not -BeNullOrEmpty + $result.PSObject.TypeNames[0] | Should -Be 'IdLE.ProviderResult' + $result.Operation | Should -Be 'RevokeSessions' + $result.IdentityKey | Should -Be $userId + $result.PSObject.Properties.Name | Should -Contain 'Changed' + } + + It 'RevokeSessions reports Changed=true when Graph returns value=true' { + $userId = [guid]::NewGuid().ToString() + $script:RevokeAdapter.RevocationResponses[$userId] = [pscustomobject]@{ + value = $true + } + + $result = $script:RevokeProvider.RevokeSessions($userId, 'fake-token') + + $result.Changed | Should -Be $true + } + + It 'RevokeSessions reports Changed=false when Graph returns value=false' { + $userId = [guid]::NewGuid().ToString() + $script:RevokeAdapter.RevocationResponses[$userId] = [pscustomobject]@{ + value = $false + } + + $result = $script:RevokeProvider.RevokeSessions($userId, 'fake-token') + + $result.Changed | Should -Be $false + } + + It 'RevokeSessions resolves identity by UPN' { + $upn = 'test.user@contoso.com' + $script:RevokeAdapter.RevocationCallLog = @() + + $result = $script:RevokeProvider.RevokeSessions($upn, 'fake-token') + + $script:RevokeAdapter.RevocationCallLog.Count | Should -Be 1 + $script:RevokeAdapter.RevocationCallLog[0].ObjectId | Should -Be 'test-user-id' + } + + It 'RevokeSessions resolves identity by mail' { + $mail = 'test.user@contoso.com' + $script:RevokeAdapter.RevocationCallLog = @() + + $result = $script:RevokeProvider.RevokeSessions($mail, 'fake-token') + + $script:RevokeAdapter.RevocationCallLog.Count | Should -Be 1 + $script:RevokeAdapter.RevocationCallLog[0].ObjectId | Should -Be 'test-user-id' + } + + It 'RevokeSessions accepts AuthSession object' { + $userId = [guid]::NewGuid().ToString() + $authSession = [pscustomobject]@{ + AccessToken = 'session-token' + } + + $result = $script:RevokeProvider.RevokeSessions($userId, $authSession) + + $result | Should -Not -BeNullOrEmpty + $result.Operation | Should -Be 'RevokeSessions' + } +} diff --git a/tests/Steps/Invoke-IdleStepRevokeIdentitySessions.Tests.ps1 b/tests/Steps/Invoke-IdleStepRevokeIdentitySessions.Tests.ps1 new file mode 100644 index 00000000..76dce941 --- /dev/null +++ b/tests/Steps/Invoke-IdleStepRevokeIdentitySessions.Tests.ps1 @@ -0,0 +1,179 @@ +Set-StrictMode -Version Latest + +BeforeAll { + . (Join-Path (Split-Path -Path $PSScriptRoot -Parent) '_testHelpers.ps1') + Import-IdleTestModule +} + +Describe 'Invoke-IdleStepRevokeIdentitySessions (built-in step)' { + BeforeEach { + # Create a fake provider with RevokeSessions support + $script:FakeProvider = [pscustomobject]@{ + PSTypeName = 'IdLE.Provider.FakeWithRevoke' + CallLog = @() + } + + $script:FakeProvider | Add-Member -MemberType ScriptMethod -Name GetCapabilities -Value { + return @('IdLE.Identity.RevokeSessions') + } + + $script:FakeProvider | Add-Member -MemberType ScriptMethod -Name RevokeSessions -Value { + param( + [Parameter(Mandatory)] + [string] $IdentityKey, + + [Parameter()] + [object] $AuthSession + ) + + $this.CallLog += @{ + Method = 'RevokeSessions' + IdentityKey = $IdentityKey + AuthSession = $AuthSession + } + + return [pscustomobject]@{ + PSTypeName = 'IdLE.ProviderResult' + Operation = 'RevokeSessions' + IdentityKey = $IdentityKey + Changed = $true + } + } + + $script:Context = [pscustomobject]@{ + PSTypeName = 'IdLE.ExecutionContext' + Plan = $null + Providers = @{ Identity = $script:FakeProvider } + EventSink = [pscustomobject]@{ WriteEvent = { param($Type, $Message, $StepName, $Data) } } + } + + $script:Context | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value { + param($Name, $Options) + return [pscustomobject]@{ + SessionName = $Name + Options = $Options + Token = 'fake-auth-token' + } + } + + $script:StepTemplate = [pscustomobject]@{ + Name = 'Revoke sessions' + Type = 'IdLE.Step.RevokeIdentitySessions' + With = @{ + IdentityKey = 'user@contoso.com' + Provider = 'Identity' + } + } + } + + It 'calls provider RevokeSessions method with correct identity key' { + $step = $script:StepTemplate + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + + $result = & $handler -Context $script:Context -Step $step + + $result.Status | Should -Be 'Completed' + $result.Changed | Should -Be $true + $script:FakeProvider.CallLog.Count | Should -Be 1 + $script:FakeProvider.CallLog[0].IdentityKey | Should -Be 'user@contoso.com' + } + + It 'returns StepResult with correct shape' { + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + $result = & $handler -Context $script:Context -Step $script:StepTemplate + + $result | Should -Not -BeNullOrEmpty + $result.PSObject.TypeNames[0] | Should -Be 'IdLE.StepResult' + $result.Name | Should -Be 'Revoke sessions' + $result.Type | Should -Be 'IdLE.Step.RevokeIdentitySessions' + $result.Status | Should -Be 'Completed' + $result.PSObject.Properties.Name | Should -Contain 'Changed' + $result.PSObject.Properties.Name | Should -Contain 'Error' + $result.Error | Should -BeNullOrEmpty + } + + It 'acquires auth session when AuthSessionName is provided' { + $step = $script:StepTemplate + $step.With.AuthSessionName = 'MicrosoftGraph' + $step.With.AuthSessionOptions = @{ Role = 'Admin' } + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + $result = & $handler -Context $script:Context -Step $step + + $result.Status | Should -Be 'Completed' + $script:FakeProvider.CallLog.Count | Should -Be 1 + $script:FakeProvider.CallLog[0].AuthSession | Should -Not -BeNullOrEmpty + $script:FakeProvider.CallLog[0].AuthSession.SessionName | Should -Be 'MicrosoftGraph' + } + + It 'throws when With.IdentityKey is missing' { + $step = $script:StepTemplate + $step.With.Remove('IdentityKey') + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + { & $handler -Context $script:Context -Step $step } | Should -Throw '*requires With.IdentityKey*' + } + + It 'throws when provider is missing' { + $script:Context.Providers.Clear() + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + { & $handler -Context $script:Context -Step $script:StepTemplate } | Should -Throw '*Provider*was not supplied*' + } + + It 'throws when provider does not support RevokeSessions method' { + # Create a provider without RevokeSessions support + $unsupportedProvider = [pscustomobject]@{ + PSTypeName = 'IdLE.Provider.FakeWithoutRevoke' + } + $unsupportedProvider | Add-Member -MemberType ScriptMethod -Name GetCapabilities -Value { + return @('IdLE.Identity.Read') + } + + $script:Context.Providers['Identity'] = $unsupportedProvider + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + { & $handler -Context $script:Context -Step $script:StepTemplate } | Should -Throw -ErrorId * + } + + It 'respects Changed flag from provider result' { + # Modify provider to return Changed=false + $script:FakeProvider | Add-Member -MemberType ScriptMethod -Name RevokeSessions -Value { + param($IdentityKey, $AuthSession) + return [pscustomobject]@{ + PSTypeName = 'IdLE.ProviderResult' + Operation = 'RevokeSessions' + IdentityKey = $IdentityKey + Changed = $false + } + } -Force + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + $result = & $handler -Context $script:Context -Step $script:StepTemplate + + $result.Changed | Should -Be $false + } + + It 'uses default provider alias "Identity" when not specified' { + $step = $script:StepTemplate + $step.With.Remove('Provider') + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + $result = & $handler -Context $script:Context -Step $step + + $result.Status | Should -Be 'Completed' + $script:FakeProvider.CallLog.Count | Should -Be 1 + } + + It 'supports custom provider alias' { + $script:Context.Providers['CustomEntra'] = $script:FakeProvider + $step = $script:StepTemplate + $step.With.Provider = 'CustomEntra' + + $handler = 'IdLE.Steps.Common\Invoke-IdleStepRevokeIdentitySessions' + $result = & $handler -Context $script:Context -Step $step + + $result.Status | Should -Be 'Completed' + $script:FakeProvider.CallLog.Count | Should -Be 1 + } +} From c8cf42596a3ff542ca849a8ba8e4e12d01d3d758 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:23:45 +0000 Subject: [PATCH 4/9] Update documentation and examples for session revocation feature Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- docs/reference/providers/provider-entraID.md | 55 +++++++++++++++++++ .../templates/entraid-leaver-offboarding.psd1 | 11 +++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index 1f89ac30..8bc31ab4 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -21,6 +21,7 @@ sidebar_label: Entra ID - 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) @@ -49,6 +50,7 @@ sidebar_label: Entra ID - `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 @@ -325,6 +327,18 @@ Minimum required (same as delegated): **Note**: Application permissions require admin consent in the tenant. +### Additional Permissions for Session Revocation + +To use the `IdLE.Identity.RevokeSessions` capability and `IdLE.Step.RevokeIdentitySessions` step: + +**Delegated Permissions:** +- `User.RevokeSessions.All` (allows revoking sessions for any user) + +**Application Permissions:** +- `User.RevokeSessions.All` (allows revoking sessions for any user) + +**Note**: Session revocation is a security-sensitive operation. Ensure appropriate approval processes are in place before granting these permissions. + --- ## Identity Addressing @@ -426,6 +440,46 @@ All operations are idempotent: | 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 | Always returns `Changed=$true` when successful (no state to check) | + +### Session Revocation Behavior + +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 reports `Changed=$true` when successful, as there's no reliable way to determine the pre-revocation state +- **Idempotency**: Safe to call multiple times; subsequent calls succeed but have no additional effect +- **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 @@ -570,6 +624,7 @@ The provider works with these built-in IdLE steps: - `IdLE.Step.EnsureAttribute` - `IdLE.Step.DisableIdentity` - `IdLE.Step.EnableIdentity` +- `IdLE.Step.RevokeIdentitySessions` (revokes active sign-in sessions) - `IdLE.Step.DeleteIdentity` (when `AllowDelete = $true`) - `IdLE.Step.EnsureEntitlement` diff --git a/examples/workflows/templates/entraid-leaver-offboarding.psd1 b/examples/workflows/templates/entraid-leaver-offboarding.psd1 index dcfc9bf0..86a633d2 100644 --- a/examples/workflows/templates/entraid-leaver-offboarding.psd1 +++ b/examples/workflows/templates/entraid-leaver-offboarding.psd1 @@ -1,7 +1,7 @@ @{ Name = 'EntraID Leaver - Offboarding with Optional Delete' LifecycleEvent = 'Leaver' - Description = 'Disables user account and optionally deletes (requires AllowDelete provider flag).' + Description = 'Disables user account, revokes active sessions, and optionally deletes (requires AllowDelete provider flag).' Steps = @( @{ Name = 'RevokeAllGroupMemberships' @@ -44,6 +44,15 @@ IdentityKey = '{{Request.Input.UserObjectId}}' } } + @{ + Name = 'RevokeActiveSessions' + Type = 'IdLE.Step.RevokeIdentitySessions' + With = @{ + AuthSessionName = 'MicrosoftGraph' + AuthSessionOptions = @{ Role = 'Admin' } + IdentityKey = '{{Request.Input.UserObjectId}}' + } + } @{ Name = 'DeleteAccountAfterRetention' Type = 'IdLE.Step.DeleteIdentity' From 7a0c6d0d7971361d5756c6e0aa1f5cb42a4cd13c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 07:46:09 +0000 Subject: [PATCH 5/9] Reorganize permissions documentation by StepType instead of capabilities Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- docs/reference/providers/provider-entraID.md | 48 +++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index 8bc31ab4..c0cc8449 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -307,37 +307,41 @@ This provider has **no provider-specific option bag**. All configuration is done ## Required Microsoft Graph Permissions -### Delegated Permissions (User Context) +This section lists the Microsoft Graph API permissions required for each step type supported by this provider. -Minimum required: +### Permissions by Step Type -- `User.Read.All` (read user information) -- `User.ReadWrite.All` (create/update/delete users) -- `Group.Read.All` (list group memberships) -- `GroupMember.ReadWrite.All` (add/remove group members) +| Step Type | Delegated Permissions | Application Permissions | Notes | +|-----------|----------------------|------------------------|-------| +| `IdLE.Step.CreateIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Requires write permissions to create users | +| `IdLE.Step.DisableIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Modifies `accountEnabled` property | +| `IdLE.Step.EnableIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Modifies `accountEnabled` property | +| `IdLE.Step.EnsureAttribute` | `User.ReadWrite.All` | `User.ReadWrite.All` | Modifies user properties (displayName, department, etc.) | +| `IdLE.Step.DeleteIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Requires `AllowDelete = $true` on provider | +| `IdLE.Step.RevokeIdentitySessions` | `User.RevokeSessions.All` | `User.RevokeSessions.All` | Security-sensitive; invalidates all active sessions | +| `IdLE.Step.EnsureEntitlement` | `Group.Read.All`
`GroupMember.ReadWrite.All` | `Group.Read.All`
`GroupMember.ReadWrite.All` | Lists and modifies group memberships | -### Application Permissions (App-Only Context) +### Recommended Baseline Permissions -Minimum required (same as delegated): - -- `User.Read.All` -- `User.ReadWrite.All` -- `Group.Read.All` -- `GroupMember.ReadWrite.All` - -**Note**: Application permissions require admin consent in the tenant. - -### Additional Permissions for Session Revocation - -To use the `IdLE.Identity.RevokeSessions` capability and `IdLE.Step.RevokeIdentitySessions` step: +For most Joiner/Mover/Leaver workflows, grant these permissions: **Delegated Permissions:** -- `User.RevokeSessions.All` (allows revoking sessions for any user) +- `User.Read.All` - Read user information (identity resolution) +- `User.ReadWrite.All` - Create, update, disable, enable, and delete users +- `Group.Read.All` - List group memberships +- `GroupMember.ReadWrite.All` - Add/remove group members +- `User.RevokeSessions.All` - Revoke sign-in sessions (Leaver workflows) **Application Permissions:** -- `User.RevokeSessions.All` (allows revoking sessions for any user) +- Same as delegated permissions above + +**Note**: Application permissions require admin consent in the tenant. Grant only the permissions you need based on the workflow steps you will use. + +### Permission Scope Notes -**Note**: Session revocation is a security-sensitive operation. Ensure appropriate approval processes are in place before granting these permissions. +- **`User.RevokeSessions.All`**: Security-sensitive operation. Ensure appropriate approval processes are in place before granting. Only required if using `IdLE.Step.RevokeIdentitySessions`. +- **`User.ReadWrite.All`**: Broad permission covering most identity lifecycle operations. Consider organizational policies for least-privilege access. +- **Read permissions**: `User.Read.All` is included in `User.ReadWrite.All`, but explicitly listed for clarity. --- From 6da541f4be7252980558ca2461fc0ad580f98b76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 08:05:51 +0000 Subject: [PATCH 6/9] Simplify permissions table and remove redundant sections Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- docs/reference/providers/provider-entraID.md | 48 +++++++------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index c0cc8449..87b4858c 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -307,41 +307,25 @@ This provider has **no provider-specific option bag**. All configuration is done ## Required Microsoft Graph Permissions -This section lists the Microsoft Graph API permissions required for each step type supported by this provider. +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 | Delegated Permissions | Application Permissions | Notes | -|-----------|----------------------|------------------------|-------| -| `IdLE.Step.CreateIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Requires write permissions to create users | -| `IdLE.Step.DisableIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Modifies `accountEnabled` property | -| `IdLE.Step.EnableIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Modifies `accountEnabled` property | -| `IdLE.Step.EnsureAttribute` | `User.ReadWrite.All` | `User.ReadWrite.All` | Modifies user properties (displayName, department, etc.) | -| `IdLE.Step.DeleteIdentity` | `User.ReadWrite.All` | `User.ReadWrite.All` | Requires `AllowDelete = $true` on provider | -| `IdLE.Step.RevokeIdentitySessions` | `User.RevokeSessions.All` | `User.RevokeSessions.All` | Security-sensitive; invalidates all active sessions | -| `IdLE.Step.EnsureEntitlement` | `Group.Read.All`
`GroupMember.ReadWrite.All` | `Group.Read.All`
`GroupMember.ReadWrite.All` | Lists and modifies group memberships | - -### Recommended Baseline Permissions - -For most Joiner/Mover/Leaver workflows, grant these permissions: - -**Delegated Permissions:** -- `User.Read.All` - Read user information (identity resolution) -- `User.ReadWrite.All` - Create, update, disable, enable, and delete users -- `Group.Read.All` - List group memberships -- `GroupMember.ReadWrite.All` - Add/remove group members -- `User.RevokeSessions.All` - Revoke sign-in sessions (Leaver workflows) - -**Application Permissions:** -- Same as delegated permissions above - -**Note**: Application permissions require admin consent in the tenant. Grant only the permissions you need based on the workflow steps you will use. - -### Permission Scope Notes - -- **`User.RevokeSessions.All`**: Security-sensitive operation. Ensure appropriate approval processes are in place before granting. Only required if using `IdLE.Step.RevokeIdentitySessions`. -- **`User.ReadWrite.All`**: Broad permission covering most identity lifecycle operations. Consider organizational policies for least-privilege access. -- **Read permissions**: `User.Read.All` is included in `User.ReadWrite.All`, but explicitly listed for clarity. +| 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.EnsureAttribute` | `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 --- From 28744bb5611133c30a8404bfcd97118fb2106a83 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:04:03 +0100 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Private/New-IdleEntraIDAdapter.ps1 | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 b/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 index 48d74211..61b615ad 100644 --- a/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 +++ b/src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 @@ -441,17 +441,11 @@ function New-IdleEntraIDAdapter { ) $uri = "$($this.BaseUri)/users/$ObjectId/revokeSignInSessions" - - try { - $response = $this.InvokeGraphRequest('POST', $uri, $AccessToken, $null) - # Graph returns { "@odata.context": "...", "value": true/false } - # The value indicates whether sessions were revoked - return $response - } - catch { - # If user not found or other errors, let them propagate - throw - } + + $response = $this.InvokeGraphRequest('POST', $uri, $AccessToken, $null) + # Graph returns { "@odata.context": "...", "value": true/false } + # The value indicates whether sessions were revoked + return $response } -Force return $adapter From 4156c54e8d246af40254a5316123378ddee1dee2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:05:44 +0000 Subject: [PATCH 8/9] Update session revocation to pass through Graph API status and add step registry entry Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- docs/reference/providers/provider-entraID.md | 6 +++--- src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 | 7 +++++++ .../Public/New-IdleEntraIDIdentityProvider.ps1 | 8 ++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index 87b4858c..875b13fc 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -428,7 +428,7 @@ All operations are idempotent: | 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 | Always returns `Changed=$true` when successful (no state to check) | +| Revoke sessions | Returns `Changed` based on Graph API response (true if sessions existed, false if none to revoke) | ### Session Revocation Behavior @@ -438,8 +438,8 @@ The `RevokeSessions` operation invalidates all active sign-in sessions and refre - **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 reports `Changed=$true` when successful, as there's no reliable way to determine the pre-revocation state -- **Idempotency**: Safe to call multiple times; subsequent calls succeed but have no additional effect +- **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:** diff --git a/src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 b/src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 index 83bcc64d..2a4cc497 100644 --- a/src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 +++ b/src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 @@ -163,6 +163,13 @@ function Get-IdleStepRegistry { } } + if (-not $registry.ContainsKey('IdLE.Step.RevokeIdentitySessions')) { + $handler = Resolve-IdleStepHandlerName -CommandName 'Invoke-IdleStepRevokeIdentitySessions' -ModuleName 'IdLE.Steps.Common' + if (-not [string]::IsNullOrWhiteSpace($handler)) { + $registry['IdLE.Step.RevokeIdentitySessions'] = $handler + } + } + if (-not $registry.ContainsKey('IdLE.Step.TriggerDirectorySync')) { $handler = Resolve-IdleStepHandlerName -CommandName 'Invoke-IdleStepTriggerDirectorySync' -ModuleName 'IdLE.Steps.DirectorySync' if (-not [string]::IsNullOrWhiteSpace($handler)) { diff --git a/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 b/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 index 18aebc9b..d801b5fc 100644 --- a/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 +++ b/src/IdLE.Provider.EntraID/Public/New-IdleEntraIDIdentityProvider.ps1 @@ -725,12 +725,12 @@ function New-IdleEntraIDIdentityProvider { # Call the adapter to revoke sign-in sessions $response = $this.Adapter.RevokeSignInSessions($userId, $accessToken) - # Graph returns a response indicating whether sessions were revoked - # We consider this operation as always "changed" when successful, - # as there's no reliable way to know if sessions existed before revocation + # Graph returns a response with a 'value' property indicating whether sessions were revoked + # value=true means active sessions existed and were revoked + # value=false means there were no active sessions to revoke $changed = $true - # If response contains a value property, use it to determine if changes occurred + # Pass through the actual Graph API response for accurate Changed status if ($null -ne $response -and ($response.PSObject.Properties.Name -contains 'value')) { $changed = [bool]$response.value } From 9950b7a777579379c429f9d6a2ff9159ba381ea9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:06:14 +0000 Subject: [PATCH 9/9] Generate step reference documentation for RevokeIdentitySessions Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com> --- docs/reference/steps.md | 1 + .../steps/step-revoke-identity-sessions.md | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 docs/reference/steps/step-revoke-identity-sessions.md diff --git a/docs/reference/steps.md b/docs/reference/steps.md index a89fea83..f9e461c1 100644 --- a/docs/reference/steps.md +++ b/docs/reference/steps.md @@ -16,4 +16,5 @@ | [IdLE.Step.Mailbox.EnsureType](steps/step-mailbox-ensure-type.md) | ``IdLE.Steps.Mailbox`` | Ensures that a mailbox is of the desired type (User, Shared, Room, Equipment). | | [IdLE.Step.Mailbox.GetInfo](steps/step-mailbox-get-info.md) | ``IdLE.Steps.Mailbox`` | Retrieves mailbox details and returns a structured report. | | [IdLE.Step.MoveIdentity](steps/step-move-identity.md) | ``IdLE.Steps.Common`` | Moves an identity to a different container/OU in the target system. | +| [IdLE.Step.RevokeIdentitySessions](steps/step-revoke-identity-sessions.md) | ``IdLE.Steps.Common`` | Revokes all active sign-in sessions for an identity in the target system. | | [IdLE.Step.TriggerDirectorySync](steps/step-trigger-directory-sync.md) | ``IdLE.Steps.DirectorySync`` | Triggers a directory sync cycle and optionally waits for completion. | diff --git a/docs/reference/steps/step-revoke-identity-sessions.md b/docs/reference/steps/step-revoke-identity-sessions.md new file mode 100644 index 00000000..a0a6f861 --- /dev/null +++ b/docs/reference/steps/step-revoke-identity-sessions.md @@ -0,0 +1,64 @@ +# IdLE.Step.RevokeIdentitySessions + +> Generated file. Do not edit by hand. +> Source: tools/Generate-IdleStepReference.ps1 + +## Summary + +- **Step Type**: `IdLE.Step.RevokeIdentitySessions` +- **Module**: `IdLE.Steps.Common` +- **Implementation**: `Invoke-IdleStepRevokeIdentitySessions` +- **Idempotent**: `Unknown` + +## Synopsis + +Revokes all active sign-in sessions for an identity in the target system. + +## Description + +This is a provider-agnostic step that revokes active sign-in sessions (refresh tokens) +for a given identity. The host must supply a provider instance via +Context.Providers[<ProviderAlias>] that implements RevokeSessions(identityKey) +and returns an object with properties 'IdentityKey' and 'Changed'. + +This step is typically used in Leaver workflows after disabling an identity to ensure +that existing sessions are terminated immediately, rather than waiting for tokens to expire. + +The step does not modify the identity itself (e.g., does not disable the account). +Use IdLE.Step.DisableIdentity separately if account disabling is also required. + +Authentication: + +- If With.AuthSessionName is present, the step acquires an auth session via + Context.AcquireAuthSession(Name, Options) and passes it to the provider method + if the provider supports an AuthSession parameter. + +- With.AuthSessionOptions (optional, hashtable) is passed to the broker for + session selection (e.g., @\{ Role = 'Tier0' \}). + +- ScriptBlocks in AuthSessionOptions are rejected (security boundary). + +## Inputs (With.*) + +The following keys are required in the step's ``With`` configuration: + +| Key | Required | Description | +| --- | --- | --- | +| `IdentityKey` | Yes | Unique identifier for the identity | + +## Example + +```powershell +@{ + Name = 'IdLE.Step.RevokeIdentitySessions Example' + Type = 'IdLE.Step.RevokeIdentitySessions' + With = @{ + IdentityKey = 'user.name' + } +} +``` + +## See Also + +- [Capabilities Reference](../capabilities.md) - Overview of IdLE capabilities +- [Providers](../providers.md) - Available provider implementations