Public/Invoke-IdleStepMailboxPermissionsEnsure.ps1
|
function Invoke-IdleStepMailboxPermissionsEnsure { <# .SYNOPSIS Ensures that mailbox delegate permissions match the desired state. .DESCRIPTION This is a provider-agnostic step. The host must supply a provider instance via Context.Providers[<ProviderAlias>]. The provider must implement an EnsureMailboxPermissions method with the signature (IdentityKey, Permissions, AuthSession) and return an object that contains a boolean property 'Changed'. The step is idempotent by design: it converges mailbox delegate permissions to the desired state by computing the delta between current and desired permissions and applying only the necessary changes. Supported rights (v1): - FullAccess - SendAs - SendOnBehalf Permissions array shape (data-only): Each entry must be a hashtable with: - AssignedUser: string (required) - UPN or SMTP address of the delegate - Right: 'FullAccess' | 'SendAs' | 'SendOnBehalf' (required) - Ensure: 'Present' | 'Absent' (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 With.AuthSessionName is absent, defaults to With.Provider value (e.g., 'ExchangeOnline'). - With.AuthSessionOptions (optional, hashtable) is passed to the broker for session selection (e.g., @{ Role = 'Admin' }). .PARAMETER Context Execution context created by IdLE.Core. .PARAMETER Step Normalized step object from the plan. Must contain a 'With' hashtable. .OUTPUTS PSCustomObject (PSTypeName: IdLE.StepResult) .EXAMPLE # In workflow definition (grant FullAccess and SendAs): @{ Name = 'Set Shared Mailbox Permissions' Type = 'IdLE.Step.Mailbox.EnsurePermissions' With = @{ Provider = 'ExchangeOnline' IdentityKey = 'shared@contoso.com' Permissions = @( @{ AssignedUser = 'user1@contoso.com'; Right = 'FullAccess'; Ensure = 'Present' } @{ AssignedUser = 'user2@contoso.com'; Right = 'SendAs'; Ensure = 'Present' } ) } } .EXAMPLE # In workflow definition (revoke access): @{ Name = 'Revoke Mailbox Access' Type = 'IdLE.Step.Mailbox.EnsurePermissions' With = @{ Provider = 'ExchangeOnline' IdentityKey = 'shared@contoso.com' Permissions = @( @{ AssignedUser = 'leaver@contoso.com'; Right = 'FullAccess'; Ensure = 'Absent' } @{ AssignedUser = 'leaver@contoso.com'; Right = 'SendOnBehalf'; Ensure = 'Absent' } ) } } .EXAMPLE # With dynamic identity from request: @{ Name = 'Grant Team Mailbox Access' Type = 'IdLE.Step.Mailbox.EnsurePermissions' With = @{ Provider = 'ExchangeOnline' IdentityKey = 'team@contoso.com' Permissions = @( @{ AssignedUser = @{ ValueFrom = 'Request.Intent.UserPrincipalName' }; Right = 'FullAccess'; Ensure = 'Present' } ) } } #> [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 "Mailbox.Permissions.Ensure requires 'With' to be a hashtable." } foreach ($key in @('IdentityKey', 'Permissions')) { if (-not $with.ContainsKey($key)) { throw "Mailbox.Permissions.Ensure requires With.$key." } } $permissions = $with.Permissions if ($null -eq $permissions) { throw "Mailbox.Permissions.Ensure requires With.Permissions to be an array." } # Accept single hashtable or array of hashtables if ($permissions -is [hashtable]) { $permissions = @($permissions) } $validRights = @('FullAccess', 'SendAs', 'SendOnBehalf') $validEnsure = @('Present', 'Absent') foreach ($entry in $permissions) { if ($null -eq $entry -or -not ($entry -is [hashtable])) { throw "Mailbox.Permissions.Ensure: each Permissions entry must be a hashtable." } foreach ($key in @('AssignedUser', 'Right', 'Ensure')) { if (-not $entry.ContainsKey($key)) { throw "Mailbox.Permissions.Ensure: each Permissions entry requires '$key'." } } if ($entry.Right -notin $validRights) { throw "Mailbox.Permissions.Ensure: Right must be one of: $($validRights -join ', '). Got: $($entry.Right)" } if ($entry.Ensure -notin $validEnsure) { throw "Mailbox.Permissions.Ensure: Ensure must be one of: $($validEnsure -join ', '). Got: $($entry.Ensure)" } } # Security: reject ScriptBlocks in Permissions (data-only constraint) Assert-IdleNoScriptBlock -InputObject $with.Permissions -Path 'With.Permissions' $providerAlias = if ($with.ContainsKey('Provider')) { [string]$with.Provider } else { 'ExchangeOnline' } 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." } # Create execution-local copy of With to avoid mutating the plan $effectiveWith = $with.Clone() # Apply AuthSessionName convention: default to Provider if not specified if (-not $effectiveWith.ContainsKey('AuthSessionName')) { $effectiveWith['AuthSessionName'] = $providerAlias } # Inject EventSink into the provider so it can emit diagnostics events $provider = $Context.Providers[$providerAlias] if ($provider.PSObject.Properties.Name -contains 'EventSink' -and $Context.PSObject.Properties.Name -contains 'EventSink') { $provider.EventSink = $Context.EventSink } $result = Invoke-IdleProviderMethod ` -Context $Context ` -With $effectiveWith ` -ProviderAlias $providerAlias ` -MethodName 'EnsureMailboxPermissions' ` -MethodArguments @([string]$effectiveWith.IdentityKey, $permissions) $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 } } |