Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 59 additions & 16 deletions docs/reference/providers/provider-entraID.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
title: Provider Reference - IdLE.Provider.EntraID
sidebar_label: Entra ID
Expand All @@ -21,6 +21,7 @@
- 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)
Expand Down Expand Up @@ -49,6 +50,7 @@
- `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
Expand Down Expand Up @@ -305,25 +307,25 @@

## 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. The same permissions apply for both delegated (user context) and application (app-only) permissions.

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 | 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`<br/>`GroupMember.ReadWrite.All` | Lists and modifies group memberships |

### Application Permissions (App-Only Context)

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.
**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

---

Expand Down Expand Up @@ -426,6 +428,46 @@
| 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) |

### 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 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

Expand Down Expand Up @@ -570,6 +612,7 @@
- `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`

Expand Down
1 change: 1 addition & 0 deletions docs/reference/steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
64 changes: 64 additions & 0 deletions docs/reference/steps/step-revoke-identity-sessions.md
Original file line number Diff line number Diff line change
@@ -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[&lt;ProviderAlias&gt;] 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
11 changes: 10 additions & 1 deletion examples/workflows/templates/entraid-leaver-offboarding.psd1
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'
Expand Down
7 changes: 7 additions & 0 deletions src/IdLE.Core/Private/Get-IdleStepRegistry.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
19 changes: 19 additions & 0 deletions src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -429,5 +429,24 @@ 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"

$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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 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

# 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
}
Comment thread
blindzero marked this conversation as resolved.

return [pscustomobject]@{
PSTypeName = 'IdLE.ProviderResult'
Operation = 'RevokeSessions'
IdentityKey = $IdentityKey
Changed = $changed
}
} -Force

$provider | Add-Member -MemberType ScriptMethod -Name ListEntitlements -Value {
param(
[Parameter(Mandatory)]
Expand Down
3 changes: 2 additions & 1 deletion src/IdLE.Steps.Common/IdLE.Steps.Common.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
'Invoke-IdleStepDisableIdentity',
'Invoke-IdleStepEnableIdentity',
'Invoke-IdleStepMoveIdentity',
'Invoke-IdleStepDeleteIdentity'
'Invoke-IdleStepDeleteIdentity',
'Invoke-IdleStepRevokeIdentitySessions'
)

PrivateData = @{
Expand Down
3 changes: 2 additions & 1 deletion src/IdLE.Steps.Common/IdLE.Steps.Common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ Export-ModuleMember -Function @(
'Invoke-IdleStepDisableIdentity',
'Invoke-IdleStepEnableIdentity',
'Invoke-IdleStepMoveIdentity',
'Invoke-IdleStepDeleteIdentity'
'Invoke-IdleStepDeleteIdentity',
'Invoke-IdleStepRevokeIdentitySessions'
)
5 changes: 5 additions & 0 deletions src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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'] = @{
Comment thread
blindzero marked this conversation as resolved.
RequiredCapabilities = @('IdLE.Identity.RevokeSessions')
}
Comment thread
blindzero marked this conversation as resolved.

return $catalog
}
Loading
Loading