SecretsManager.ps1
#requires -Version 5.1 $Keeper_KSMAppCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $result = @() [KeeperSecurity.Vault.VaultOnline]$private:vault = getVault if (-not $vault) { return $null } $toComplete = $wordToComplete if ($toComplete.Length -ge 1) { if ($toComplete[0] -eq '''') { $toComplete = $toComplete.Substring(1, $toComplete.Length - 1) $toComplete = $toComplete -replace '''''', '''' } if ($toComplete[0] -eq '"') { $toComplete = $toComplete.Substring(1, $toComplete.Length - 1) $toComplete = $toComplete -replace '""', '"' $toComplete = $toComplete -replace '`"', '"' } } $toComplete += '*' foreach ($app in $vault.KeeperApplications) { if ($app.Title -like $toComplete) { $name = $app.Title if ($name -match ' ') { $name = $name -replace '''', '''''' $name = '''' + $name + '''' } $result += $name } } if ($result.Count -gt 0) { return $result } else { return $null } } function Get-KeeperSecretManagerApp { <# .Synopsis Get Keeper Secret Manager Applications .Parameter Uid Record UID .Parameter Filter Return matching applications only .Parameter Detail Application details #> [CmdletBinding()] Param ( [string] $Uid, [string] $Filter, [Switch] $Detail ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault if ($Uid) { [KeeperSecurity.Vault.ApplicationRecord] $application = $null if ($vault.TryGetKeeperRecord($Uid, [ref]$application)) { if (-not $application.Type -eq 'app') { throw "No application found with UID '$Uid'." } if ($Detail.IsPresent) { return $vault.GetSecretManagerApplication($application.Uid, $false).GetAwaiter().GetResult() } else { return $application } } else { throw "No application found with UID '$Uid'." } } else { $applications = $vault.KeeperRecords | Where-Object { $_.Type -eq 'app' } $results = @() foreach ($application in $applications) { if ($Filter) { $match = @($application.Uid, $application.Title) | Select-String $Filter | Select-Object -First 1 if (-not $match) { continue } } if ($Detail.IsPresent) { $results += $vault.GetSecretManagerApplication($application.Uid, $false).GetAwaiter().GetResult() } else { $results += $application } } return $results } } New-Alias -Name ksm -Value Get-KeeperSecretManagerApp function Add-KeeperSecretManagerApp { <# .Synopsis Creates Keeper Secret Manager Application .Parameter Name Secret Manager Application #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)][string]$AppName ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault $vault.CreateSecretManagerApplication($AppName).GetAwaiter().GetResult() } New-Alias -Name ksm-create -Value Add-KeeperSecretManagerApp function Remove-KeeperSecretManagerApp { <# .SYNOPSIS Deletes a Keeper Secret Manager Application .DESCRIPTION This cmdlet deletes a Keeper Secrets Manager application by UID. .PARAMETER Uid The UID of the application to delete. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] Param ( [Parameter(Position = 0, Mandatory = $true)] [string] $Uid ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault if ($PSCmdlet.ShouldProcess("Secrets Manager App UID: $Uid", "Delete")) { $vault.DeleteSecretManagerApplication($Uid).GetAwaiter().GetResult() Write-Host "Secrets Manager Application with UID '$Uid' has been deleted." -ForegroundColor Green } } New-Alias -Name ksm-delete -Value Remove-KeeperSecretManagerApp function Grant-KeeperSecretManagerFolderAccess { <# .Synopsis Adds shared folder to KSM Application .Parameter App KSM Application UID or Title .Parameter Secret Shared Folder UID or Name .Parameter CanEdit Enable write access to shared secrets #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)][string]$App, [Parameter(Mandatory = $true)][string]$Secret, [Parameter()][switch]$CanEdit ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault $apps = Get-KeeperSecretManagerApp -Filter $App if (-not $apps) { Write-Error -Message "Cannot find Secret Manager Application: $App" -ErrorAction Stop } [KeeperSecurity.Vault.ApplicationRecord]$application = $apps[0] [string]$uid = $null $sfs = Get-KeeperSharedFolder -Filter $Secret if ($sfs) { $uid = $sfs[0].Uid } else { $recs = Get-KeeperRecord -Filter $Secret if ($recs) { $uid = $recs[0].Uid } } if (-not $uid) { Write-Error -Message "Cannot find Shared Folder: $Secret" -ErrorAction Stop } $vault.ShareToSecretManagerApplication($application.Uid, $uid, $CanEdit.IsPresent).GetAwaiter().GetResult() } Register-ArgumentCompleter -CommandName Grant-KeeperSecretManagerFolderAccess -ParameterName Secret -ScriptBlock $Keeper_SharedFolderCompleter Register-ArgumentCompleter -CommandName Grant-KeeperSecretManagerFolderAccess -ParameterName App -ScriptBlock $Keeper_KSMAppCompleter New-Alias -Name ksm-share -Value Grant-KeeperSecretManagerFolderAccess function Revoke-KeeperSecretManagerFolderAccess { <# .Synopsis Removes Shared Folder from KSM Application .Parameter App Secret Manager Application .Parameter Secret Shared Folder UID or Name #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)][string]$App, [Parameter(Mandatory = $true)][string]$Secret ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault $apps = Get-KeeperSecretManagerApp -Filter $App if (-not $apps) { Write-Error -Message "Cannot find Secret Manager Application: $App" -ErrorAction Stop } [KeeperSecurity.Vault.ApplicationRecord]$application = $apps[0] [string]$uid = $null $sfs = Get-KeeperSharedFolder -Filter $Secret if ($sfs) { $uid = $sfs[0].Uid } else { $recs = Get-KeeperRecord -Filter $Secret if ($recs) { $uid = $recs[0].Uid } } if (-not $uid) { Write-Error -Message "Cannot find Shared Folder: $Secret" -ErrorAction Stop } $vault.UnshareFromSecretManagerApplication($application.Uid, $uid).GetAwaiter().GetResult() } Register-ArgumentCompleter -CommandName Revoke-KeeperSecretManagerFolderAccess -ParameterName Secret -ScriptBlock $Keeper_SharedFolderCompleter Register-ArgumentCompleter -CommandName Revoke-KeeperSecretManagerFolderAccess -ParameterName App -ScriptBlock $Keeper_KSMAppCompleter New-Alias -Name ksm-unshare -Value Revoke-KeeperSecretManagerFolderAccess function Add-KeeperSecretManagerClient { <# .Synopsis Adds client/device to KSM Application .Parameter App KSM Application UID or Title .Parameter Name Client or Device Name .Parameter UnlockIP Enable write access to shared secrets #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)][string]$App, [Parameter()][string]$Name, [Parameter()][switch]$UnlockIP, [Parameter()][switch]$B64 ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault $apps = Get-KeeperSecretManagerApp -Filter $App if (-not $apps) { Write-Error -Message "Cannot find Secret Manager Application: $App" -ErrorAction Stop } [KeeperSecurity.Vault.ApplicationRecord]$application = $apps[0] $rs = $vault.AddSecretManagerClient($application.Uid, $UnlockIP.IsPresent, $null, $null, $name).GetAwaiter().GetResult() if ($rs) { if ($B64.IsPresent) { $configuration = $vault.GetConfiguration($rs.Item2).GetAwaiter().GetResult() if ($configuration) { $configData = [KeeperSecurity.Utils.JsonUtils]::DumpJson($configuration, $true) [System.Convert]::ToBase64String($configData) } } else { $rs.Item2 } } } Register-ArgumentCompleter -CommandName Add-KeeperSecretManagerClient -ParameterName App -ScriptBlock $Keeper_KSMAppCompleter New-Alias -Name ksm-addclient -Value Add-KeeperSecretManagerClient function Remove-KeeperSecretManagerClient { <# .Synopsis Removes client/device from KSM Application .Parameter App KSM Application UID or Title .Parameter Name Client Id or Device Name #> [CmdletBinding(SupportsShouldProcess = $true)] Param ( [Parameter(Mandatory = $true)][string]$App, [Parameter(Mandatory = $true)][string]$Name ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault $apps = Get-KeeperSecretManagerApp -Filter $App -Detail if (-not $apps) { Write-Error -Message "Cannot find Secret Manager Application: $App" -ErrorAction Stop } [KeeperSecurity.Vault.ApplicationRecord]$application = $apps[0] $device = $application.Devices | Where-Object { $_.Name -ceq $Name -or $_.ShortDeviceId -ceq $Name } if (-not $device) { Write-Error -Message "Cannot find Device: $Name" -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($application.Title, "Removing KSM Device '$($device.Name)'")) { $vault.DeleteSecretManagerClient($application.Uid, $device.DeviceId).GetAwaiter().GetResult() | Out-Null Write-Information -MessageData "Device $($device.Name) has been deleted from KSM application `"$($application.Title)`"." } } Register-ArgumentCompleter -CommandName Remove-KeeperSecretManagerClient -ParameterName App -ScriptBlock $Keeper_KSMAppCompleter New-Alias -Name ksm-rmclient -Value Remove-KeeperSecretManagerClient function Test-KeeperUserNeedsUpdate { <# .SYNOPSIS Determines whether any of the user's record or folder shares need to be updated. .DESCRIPTION Iterates through a list of share UIDs and checks if any need their permissions updated for the given user. .PARAMETER Vault The Keeper VaultOnline context. .PARAMETER User The username/email of the user. .PARAMETER IsAdmin True if full permissions (edit/share/manage) are expected. .PARAMETER ShareUids A list of UIDs representing shared records or folders. .PARAMETER ApplicationUid The UID of the Secrets Manager Application record. .OUTPUTS Boolean — True if any share requires an update for the user. #> param ( [Parameter(Mandatory)] $vault, [Parameter(Mandatory)] [string]$User, [Parameter(Mandatory)] [bool]$IsAdmin, [Parameter(Mandatory)] [System.Collections.Generic.List[string]]$ShareUids, [Parameter(Mandatory)] [string]$ApplicationUid ) foreach ($uid in $ShareUids) { $needsUpdate = Test-KeeperShareNeedsUpdate -Vault $vault -User $User -ShareUid $uid -Elevated $IsAdmin -ApplicationUid $ApplicationUid if ($needsUpdate) { return $true } } return $false } function Test-KeeperShareNeedsUpdate { <# .SYNOPSIS Determines whether a share (record or folder) needs its permissions updated for a user. .DESCRIPTION Compares current permissions of a user on a shared record or shared folder against expected `elevated` (admin) status. .PARAMETER Vault The VaultOnline object. .PARAMETER User Username/email of the user being evaluated. .PARAMETER ShareUid UID of the shared record or shared folder. .PARAMETER Elevated $true for admin-level (edit/share or manage), $false otherwise. .PARAMETER ApplicationUid UID of the Secrets Manager application record. .OUTPUTS [bool] — $true if an update is required, $false if current permissions match expected. #> param ( [Parameter(Mandatory)] $Vault, [Parameter(Mandatory)] [string] $User, [Parameter(Mandatory)] [string] $ShareUid, [Parameter(Mandatory)] [bool] $Elevated, [Parameter(Mandatory)] [string] $ApplicationUid ) $appInfo = $vault.GetSecretManagerApplication($ApplicationUid, $true).GetAwaiter().GetResult() if (-not $appInfo) { return $false } $isRecordShare = $false foreach ($share in $appInfo.Shares) { $shareId = $share.SecretUid $recordTypeEnum = [KeeperSecurity.Vault.SecretManagerSecretType]::Record if ($shareId -eq $ShareUid -and $share.SecretType -eq $recordTypeEnum) { $isRecordShare = $true break } } if ($isRecordShare) { $recordUids = [System.Collections.Generic.List[string]]::new() $recordUids.Add($ShareUid) $recordShares = $Vault.GetSharesForRecords($recordUids).GetAwaiter().GetResult() $shareInfo = $recordShares | Where-Object { $_.RecordUid -eq $ShareUid } if (-not $shareInfo) { return $false } $userPerms = $shareInfo.UserPermissions | Where-Object { $_.Username -eq $User } if (-not $userPerms) { return $true } return ($userPerms.CanEdit -ne $Elevated -or $userPerms.CanShare -ne $Elevated) } else { $sharedFolder = $null if (-not $Vault.TryGetSharedFolder($ShareUid, [ref]$sharedFolder)) { return $false } $folderPerms = $sharedFolder.UsersPermissions | Where-Object { $_.Uid -eq $User } if (-not $folderPerms) { return $true } return ($folderPerms.ManageUsers -ne $Elevated -or $folderPerms.ManageRecords -ne $Elevated) } } function Invoke-KeeperSharedFolderShareAction { <# .SYNOPSIS Adds or removes users from shared folders based on permission needs. .DESCRIPTION For each shared folder and user, checks if permissions are out of sync. If so: - Removes user from the folder if action is "remove" - Adds/updates user with appropriate permissions if action is "add" .PARAMETER Vault The VaultOnline context. .PARAMETER ApplicationUid UID of the Secrets Manager Application. .PARAMETER SharedFolders A list of shared folder UIDs. .PARAMETER Group The group name ("admins" or other) to determine permission level. .PARAMETER Users List of user emails or UIDs. .PARAMETER ShareFolderAction Either "remove" or "add" to define how permissions should be applied. .EXAMPLE Invoke-KeeperSharedFolderShareAction -Vault $vault -ApplicationUid "abc123" -SharedFolders @("UID1") -Group "admins" -Users @("user@example.com") -ShareFolderAction "add" #> param ( [Parameter(Mandatory)] $vault, [Parameter(Mandatory)] [string]$ApplicationUid, [Parameter(Mandatory)] [string[]]$SharedFolders, [Parameter(Mandatory)] [string]$Group, [Parameter(Mandatory)] [string[]]$Users, [Parameter(Mandatory)] [ValidateSet("grant", "remove")] [string]$ShareFolderAction ) foreach ($folder in $SharedFolders) { foreach ($user in $Users) { $isAdmin = ($Group -eq "admins") $needsUpdate = Test-KeeperShareNeedsUpdate -Vault $vault -User $user -ShareUid $folder -Elevated $isAdmin -ApplicationUid $ApplicationUid if ($needsUpdate) { if ($ShareFolderAction -eq "remove") { Write-Debug "Removing user '$user' from shared folder '$folder'..." $vault.RemoveUserFromSharedFolder($folder, $user, [KeeperSecurity.Vault.UserType]::User).GetAwaiter().GetResult() | Out-Null } else { Write-Debug "Adding user '$user' to shared folder '$folder' with permissions (admin: $isAdmin)..." $options = New-Object KeeperSecurity.Vault.SharedFolderUserOptions $options.ManageUsers = $isAdmin $options.ManageRecords = $isAdmin $vault.PutUserToSharedFolder($folder, $user, [KeeperSecurity.Vault.UserType]::User, $options).GetAwaiter().GetResult() | Out-Null } } } } } function Invoke-KeeperHandleRecordShares { <# .SYNOPSIS Shares or revokes Keeper records for specified users based on group access level. .DESCRIPTION Iterates over records and users. If the user's permissions are out of sync: - Revokes the share if 'revoke' action is specified. - Otherwise, shares the record with edit/share permissions if the group is 'admins'. .PARAMETER Vault VaultOnline session object. .PARAMETER ApplicationUid UID of the application record (used to resolve appInfo for permission checks). .PARAMETER RecordShares List of records with RecordUid properties (e.g., RecordSharePermissions objects). .PARAMETER Group Group string ("admins" or anything else), used to determine full or limited permission. .PARAMETER Users List of user emails to apply actions to. .PARAMETER ShareRecordAction "revoke" to remove access, anything else to add/update the share. .EXAMPLE Invoke-KeeperHandleRecordShares -Vault $vault -ApplicationUid "abc123" -RecordShares $records -Group "admins" -Users @("user@example.com") -ShareRecordAction "add" #> param ( [Parameter(Mandatory)] $vault, [Parameter(Mandatory)] [string]$ApplicationUid, [Parameter(Mandatory)] [System.Collections.IEnumerable]$RecordShares, [Parameter(Mandatory)] [string]$Group, [Parameter(Mandatory)] [string[]]$Users, [Parameter(Mandatory)] [ValidateSet("revoke", "share")] [string]$ShareRecordAction ) foreach ($record in $RecordShares) { $recordUid = $record.RecordUid foreach ($user in $Users) { $isAdmin = ($Group -eq "admins") $needsUpdate = Test-KeeperShareNeedsUpdate -Vault $vault -User $user -ShareUid $recordUid -Elevated $isAdmin -ApplicationUid $ApplicationUid if ($needsUpdate) { if ($ShareRecordAction -eq "revoke") { Write-Debug "Revoking user '$user' from record '$recordUid'..." $vault.RevokeShareFromUser($recordUid, $user).GetAwaiter().GetResult() | Out-Null } else { Write-Debug "Sharing record '$recordUid' with user '$user' (Edit: $isAdmin, Share: $isAdmin)..." $options = New-Object KeeperSecurity.Vault.SharedFolderRecordOptions $options.CanEdit = $isAdmin $options.CanShare = $isAdmin $vault.ShareRecordWithUser($recordUid, $user, $options).GetAwaiter().GetResult() | Out-Null } } } } } function Update-KeeperShareUserPermissions { <# .SYNOPSIS Updates app-related record and shared folder permissions for a given user. .DESCRIPTION Based on existing share state, grants/revokes appropriate permissions across shared folders and records. Uses app record metadata to determine what to share and with whom. .PARAMETER Vault The VaultOnline object. .PARAMETER ApplicationUid The UID of the Secrets Manager Application record. .PARAMETER UserUid The username or email of the user whose permissions need update. .PARAMETER Removed Optional string flag to indicate user should be unshared. If null, grants are applied instead. .EXAMPLE Update-KeeperShareUserPermissions -Vault $vault -ApplicationUid "abc123" -UserUid "bob@example.com" Update-KeeperShareUserPermissions -Vault $vault -ApplicationUid "abc123" -UserUid "bob@example.com" -Removed "true" #> param ( [Parameter(Mandatory)] $vault, [Parameter(Mandatory)] [string]$ApplicationUid, [Parameter(Mandatory)] [string]$UserUid, [string]$Removed ) $ApplicationUidsForShare = [System.Collections.Generic.List[string]]::new() $ApplicationUidsForShare.Add($ApplicationUid) $appShares = $vault.GetSharesForRecords($ApplicationUidsForShare).GetAwaiter().GetResult() $userPermissions = ($appShares | Where-Object { $_.RecordUid -eq $ApplicationUid }).UserPermissions $appInfo = $vault.GetSecretManagerApplication($ApplicationUid, $true).GetAwaiter().GetResult() $recordTypeEnum = [KeeperSecurity.Vault.SecretManagerSecretType]::Record $folderTypeEnum = [KeeperSecurity.Vault.SecretManagerSecretType]::Folder $shareUids = $appInfo.Shares | ForEach-Object { $_.SecretUid } $sharesRecords = $appInfo.Shares | Where-Object { $_.SecretType -eq $recordTypeEnum } | ForEach-Object { $_.SecretUid } $sharedFolders = $appInfo.Shares | Where-Object { $_.SecretType -eq $folderTypeEnum } | ForEach-Object { $_.SecretUid } $RecordUidsForShare = [System.Collections.Generic.List[string]]::new() if($null -ne $sharesRecords){ $sharesRecords | ForEach-Object { $RecordUidsForShare.Add($_) } } $recordShares = $vault.GetSharesForRecords($RecordUidsForShare).GetAwaiter().GetResult() $admins = $userPermissions | Where-Object { $_.CanEdit -and $_.Username -ne $vault.Auth.Username } | Select-Object -ExpandProperty Username $viewers = $userPermissions | Where-Object { -not $_.CanEdit } | Select-Object -ExpandProperty Username $removedUsers = if ($Removed) { @($UserUid) } else { @() } $appUsersMap = @{ "admins" = $admins "viewers" = $viewers "removed" = $removedUsers } foreach ($group in $appUsersMap.Keys) { $usersList = $appUsersMap[$group] if ($usersList.Count -eq 0) { continue } $userResults = @() foreach ($user in $usersList) { $needsUpdate = Test-KeeperUserNeedsUpdate -Vault $vault -User $user -IsAdmin:($group -eq "admins") -ShareUids $shareUids -ApplicationUid $ApplicationUid if ($needsUpdate) { $userResults += $user } } $shareFolderAction = if ($Removed) { "remove" } else { "grant" } $shareRecordAction = if ($Removed) { "revoke" } else { "share" } Invoke-KeeperSharedFolderShareAction -Vault $vault -ApplicationUid $ApplicationUid -SharedFolders $sharedFolders -Group $group -Users $userResults -ShareFolderAction $shareFolderAction Invoke-KeeperHandleRecordShares -Vault $vault -ApplicationUid $ApplicationUid -RecordShares $recordShares -Group $group -Users $userResults -ShareRecordAction $shareRecordAction } } function Test-KeeperUserIsSharable { <# .SYNOPSIS Verifies if a given user UID is eligible for record sharing. .DESCRIPTION Calls `GetUsersForShare()` and checks if the specified user is in `GroupUsers` or `SharesWith`. Throws if no shareable users are available at all. .PARAMETER vault The VaultOnline object. .PARAMETER UserUid The UID or email of the user to validate for sharing. .OUTPUTS Boolean — $true if the user can be shared with, otherwise $false. .EXAMPLE Test-KeeperUserIsSharable -Vault $vault -UserUid "alice@example.com" #> param ( [Parameter(Mandatory)] $vault, [Parameter(Mandatory)] [string]$UserUid ) $users = $vault.GetUsersForShare().GetAwaiter().GetResult() if (-not $users -or ($users.SharesFrom.Count -eq 0 -and $users.GroupUsers.Count -eq 0 -and $users.SharesWith.Count -eq 0)) { Write-Error "No users found for sharing." throw "No users found for sharing. [ShareSecretsManagerApplicationWithUser: userUid=$UserUid]" } if ($users.GroupUsers -contains $UserUid -or $users.SharesWith -contains $UserUid) { return $true } else { Write-Host "User '$UserUid' is not found in the list of users eligible for sharing." return $false } } function Invoke-KeeperHandleAppSharePermissions { <# .SYNOPSIS Shares or revokes the Keeper Secrets Manager application record with a user. .DESCRIPTION If sharing, checks whether the user is eligible for sharing. If not, sends an invite. If unsharing, revokes the user's access to the application record. .PARAMETER Vault The VaultOnline object. .PARAMETER AppInfo The AppInfo object representing the application and its shares. .PARAMETER UserUid The UID (email) of the user. .PARAMETER IsAdmin If true, grants CanEdit and CanShare permissions. .PARAMETER Unshare If true, revokes the share instead of granting it. .EXAMPLE Invoke-KeeperHandleAppSharePermissions -Vault $vault -AppInfo $appInfo -UserUid "alice@example.com" -IsAdmin $true -Unshare:$false #> param ( [Parameter(Mandatory)] $Vault, [Parameter(Mandatory)] $AppInfo, [Parameter(Mandatory)] [string]$UserUid, [Parameter(Mandatory)] [bool]$IsAdmin, [Parameter(Mandatory)] [bool]$Unshare ) $recordUid = $AppInfo.Uid if (-not $Unshare) { $isShareable = Test-KeeperUserIsSharable -Vault $Vault -UserUid $UserUid if (-not $isShareable) { $Vault.SendShareInvitationRequest($UserUid).GetAwaiter().GetResult() | Out-Null Write-Host "Share invitation request has been sent to user '$UserUid'. Please wait for the user to accept before sharing the application." return } $recordPermissions = New-Object KeeperSecurity.Vault.SharedFolderRecordOptions $recordPermissions.CanEdit = $IsAdmin $recordPermissions.CanShare = $IsAdmin Write-Debug "Sharing application record '$recordUid' with user '$UserUid' (Edit: $IsAdmin, Share: $IsAdmin)..." $Vault.ShareRecordWithUser($recordUid, $UserUid, $recordPermissions).GetAwaiter().GetResult() | Out-Null } else { Write-Debug "Revoking user '$UserUid' from application record '$recordUid'..." $Vault.RevokeShareFromUser($recordUid, $UserUid).GetAwaiter().GetResult() | Out-Null } } function Invoke-KeeperAppShareWithUser { <# .SYNOPSIS Shares or unshares a Secrets Manager Application with a user. .DESCRIPTION Handles full flow of granting/revoking access to an application record, its shared folders, and records by calling permission update helpers and syncing the vault state. .PARAMETER ApplicationId UID of the Application record. .PARAMETER UserUid Username or email of the target user. .PARAMETER Unshare Switch to unshare instead of share. .PARAMETER IsAdmin Whether to give full edit/share/manage access when sharing. .EXAMPLE Invoke-KeeperAppShareWithUser -Vault $vault -ApplicationId "abc123" -UserUid "alice@example.com" -IsAdmin $true .EXAMPLE Invoke-KeeperAppShareWithUser -Vault $vault -ApplicationId "abc123" -UserUid "bob@example.com" -Unshare #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$ApplicationId, [Parameter(Mandatory)] [string]$UserUid, [Parameter()] [switch]$Unshare, [Parameter()] [switch]$IsAdmin ) [KeeperSecurity.Vault.VaultOnline]$private:vault = getVault $record = $null if (-not $vault) { return $null } if (-not $vault.TryGetKeeperRecord($ApplicationId, [ref]$record)) { throw "Application record not found for UID '$ApplicationId'" } if (-not ($record -is [KeeperSecurity.Vault.ApplicationRecord])) { throw "Record with UID '$ApplicationId' is not an application record." } $application = [KeeperSecurity.Vault.ApplicationRecord]$record $appInfo = $vault.GetSecretManagerApplication($ApplicationId, $true).GetAwaiter().GetResult() if (-not $appInfo) { throw "AppInfo not found for application UID '$($application.Uid)'" } Invoke-KeeperHandleAppSharePermissions -Vault $vault -AppInfo $appInfo -UserUid $UserUid -IsAdmin $IsAdmin.IsPresent -Unshare $Unshare.IsPresent $vault.SyncDown().GetAwaiter().GetResult() | Out-Null $removedUser = if ($Unshare.IsPresent) { $UserUid } else { $null } Update-KeeperShareUserPermissions -Vault $vault -ApplicationUid $ApplicationId -UserUid $UserUid -Removed $removedUser # Do a Full Sync $vault.Storage.Clear() $vault.Storage.VaultSettings.Load() $vault.ScheduleSyncDown([TimeSpan]::FromMilliseconds(0)) | Out-Null } function Grant-KeeperAppAccess { <# .SYNOPSIS Grants a user access to a Secrets Manager Application. .DESCRIPTION Shares the application record, associated shared folders, and records with the specified user. Supports admin or viewer level access. .PARAMETER ApplicationId UID of the application record. .PARAMETER UserUid Email or UID of the user to grant access to. .PARAMETER IsAdmin If specified, grants edit/share/manage permissions (admin access). .EXAMPLE Grant-KeeperAppAccess -ApplicationId "abc123" -UserUid "alice@example.com" -IsAdmin #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$ApplicationId, [Parameter(Mandatory)] [string]$UserUid, [Parameter()] [switch]$IsAdmin ) try { Write-Host "Granting Secrets Manager application access to '$UserUid'..." -ForegroundColor Cyan Invoke-KeeperAppShareWithUser -ApplicationId $ApplicationId -UserUid $UserUid -IsAdmin:$IsAdmin Write-Host "Successfully granted access to application '$ApplicationId' for user '$UserUid'." -ForegroundColor Green } catch { Write-Host "Failed to grant access to '$UserUid'. Error: $_" -ForegroundColor Red } } function Revoke-KeeperAppAccess { <# .SYNOPSIS Revokes a user's access to a Secrets Manager Application. .DESCRIPTION Unshares the application record, shared folders, and any related records from the specified user and updates permissions accordingly. .PARAMETER ApplicationId UID of the application record. .PARAMETER UserUid Email or UID of the user to revoke access from. .EXAMPLE Revoke-KeeperAppAccess -ApplicationId "abc123" -UserUid "bob@example.com" #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$ApplicationId, [Parameter(Mandatory)] [string]$UserUid ) try { Write-Host "Revoking Secrets Manager application access from '$UserUid'..." -ForegroundColor Cyan Invoke-KeeperAppShareWithUser -ApplicationId $ApplicationId -UserUid $UserUid -Unshare Write-Host "Successfully revoked access to application '$ApplicationId' from user '$UserUid'." -ForegroundColor Green } catch { Write-Host "Failed to revoke access from '$UserUid'. Error: $_" -ForegroundColor Red } } # SIG # Begin signature block # MIIngQYJKoZIhvcNAQcCoIIncjCCJ24CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAlYMV067liZ2W0 # +Dy4ui2SqD2AL0xDKQHMpq8CtMXFL6CCIQQwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqG # SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy # RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg # Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXH # JQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMf # UBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w # 1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRk # tFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYb # qMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUm # cJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP6 # 5x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzK # QtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo # 80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjB # Jgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXche # MBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB # /wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU # 7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG # CCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j # c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDig # NqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v # dEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI # hvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd # 4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiC # qBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl # /Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeC # RK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYT # gAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/ # a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37 # xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmL # NriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0 # YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJ # RyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIG # sDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw # HhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0 # ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjAN # BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zr # PYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHM # gQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8Irg # nQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyC # EUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0 # p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQa # khCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0 # XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960I # HnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2 # FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBH # X8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q2 # 7IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD # VR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1k # TN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcD # AzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj # ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t # L0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmww # HAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIB # ADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6j # fCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmI # moqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtf # JqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrx # oj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3 # LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx # 4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9 # Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+I # Cw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug # 0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5 # Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQ # C65mvFq6f5WHxvnpBOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0 # ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAw # MDAwMFoXDTM1MTEyNTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp # Z2lDZXJ0MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjE # iDtqmeOlwf0KMCBDEr4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOc # Re8+CEJp+3R2O8oo76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/ # GLoUb35SfWHh43rOH3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0Cha # V76Nhnj37DEYTX9ReNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8U # uKGn9966fR5X6kgXj3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHw # SJ+QQRZ1fisD8UTVDSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4 # EfvFrpVNnes4c16Jidj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzI # Xp4P0wXkgNs+CO/CacBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3Jyidx # W48jwBqIJqImd93NRxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizch # NULpUEoA6Vva7b1XCB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJ # cv6dQ4aEKOX5AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/ # BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEE # AjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8w # HQYDVR0OBBYEFJ9XLAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuG # SWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQw # OTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKG # TGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJT # QTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIB # AD2tHh92mVvjOIQSR9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq # 3igpwrPvBmZdrlWBb0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcH # zBMutB6HzeledbDCzFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTV # OoJ4eTq7gj9UFAL1UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4H # v5swO+aAXxWUm3WpByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgt # d7/fvWTlCs30VAGEsshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaid # RJXrI+UzB6vAlk/8a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhd # mm4bhYsVA6G2WgNFYagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dH # PoWrUhftNpFC5H7QEY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDi # CLg4D+TPVgKx2EgEdeoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7z # cEO1xwcdcqJsyz/JceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHSTCCBTGgAwIBAgIQ # BaOjGrg1T58olh09AgdhuDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0 # ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTI0 # MTIzMTAwMDAwMFoXDTI1MTIzMDIzNTk1OVowgdExEzARBgsrBgEEAYI3PAIBAxMC # VVMxGTAXBgsrBgEEAYI3PAIBAhMIRGVsYXdhcmUxHTAbBgNVBA8MFFByaXZhdGUg # T3JnYW5pemF0aW9uMRAwDgYDVQQFEwczNDA3OTg1MQswCQYDVQQGEwJVUzERMA8G # A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xHTAbBgNVBAoTFEtlZXBl # ciBTZWN1cml0eSBJbmMuMR0wGwYDVQQDExRLZWVwZXIgU2VjdXJpdHkgSW5jLjCC # AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAM7/rBevApUP+XJjlSxdyASA # AnLFQ1r4NFXPo/S0RaTv1OCahApEeSN6oy+0OwbLNlwaQeooOanMcZhh64/+fF8S # zCMHDc/Pv8aBsd1B2XIw/VT+Nawfj0NxAX1zpKPp/tPqavm6smRDMOAeOo7qLxzI # u68bS2EnqvST1367tMpxhggrVl3GYKPhdCPeNDRskwheCSxI2czR8oe7mguo2nVa # ZR5VEq4xYkMZwTuT7RN8ER4r5crOSbJFyabp79SgYP7NyKmDcYZ6XJ26AfZsEDZr # e4VhzaqO0rl8i5HBmVmDKwU0PaIoAUdyeultIaS5oe0FjcTjGtrkBl+B7TCtvN1J # RE9Tmy3spnqLyvlRhrVJdDKCGovQKKJk87BAjIoiNSmEXs0H0PbB1ZYOA6m4ce7/ # BOmUafliYWBqrWHmHixqi/ha5ZKxKlYxGlikD4p1WlMmDEBhg3RPodW1Z5eGq92Z # exMGOWsfOQp3YhTDdMOA7tjWP2XzAaebGxCeOENEpQIDAQABo4ICAjCCAf4wHwYD # VR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFOcovsKg6xAz # zjzRmmWQRpa7p47MMD0GA1UdIAQ2MDQwMgYFZ4EMAQMwKTAnBggrBgEFBQcCARYb # aHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIHgDATBgNV # HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js # My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw # OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy # MUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUFBzABhhhodHRwOi8v # b2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6Ly9jYWNlcnRzLmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggIBALIq # AoEjkKZluMiOffwU+V+wiKkmDblKIZymyszEZot+niB6g7tRXrWkQo6gn8OG2qG6 # IO8L+o0VvwW0+V08p6gVqb0jeR9kCm7kDZk2RmzevhZDrRbZj0Q7Kb3pIeD9KEuc # RfEF0UGqgp0q7jerFXPzKtQk5kJpP65sSRV7bghIMWtq5sdHn/iGUMj+8Fd9AExq # 4kR+dyTw/6p1ZFiY7pIv4YAjjDrjkyUMSogt6ej9YGwTC8yVXJsjarLq2F+svwn8 # NlU+T03U/ZjXc/ZxDc5g3iqrl5Gm9QCaLhG2aLIrGRXN59Pcokp7JFNa6nkkWSSg # h4w01tz+xRSyiqKWAXNs2lHTD2F9ceGlz9Uw/RvPhPcl6bILqJcR6RUkzZtrKHNK # j85PBm/Kmurx0co5xRxXsXsF3tmp2r+Tt11veA9je+pyzuqE/kRQPn5hF8fIRuea # h7JVMaaHBTMbRaDcVFioGmCGHUx270yhLapA0eYXpZJv0n62QIMoX9NPcW2EcwhL # WGAV1IW+TIo/xcprAXBtXCO/mhscgInbMzesdg0uWsboiy4HfeTEzCe9ld54biUK # TJQu4wqbzkN5SGewOKTd/+c4k5w6yzuUWsk3YZpjWqsgpTlA3zU591uvMFsq0FYd # A3Py8YsVabLwTxz9d7kpBAHTPRYwDcsKNLGMPc+6MYIF0zCCBc8CAQEwfTBpMQsw # CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERp # Z2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIw # MjEgQ0ExAhAFo6MauDVPnyiWHT0CB2G4MA0GCWCGSAFlAwQCAQUAoIGEMBgGCisG # AQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEID9n # aY2/CqblulAcECruw3mVbH7ZqYlKOy3HW70R2MLpMA0GCSqGSIb3DQEBAQUABIIB # gLzMWmrj/ijDVKRhqKhlWaUR0AAFAUDLjKrr9e1DKWhx8GU6XrJRhCMFoCZECyrp # 7bmDTI0iezbIVRwMGC4yIwHwe/Cm3o+wX8I8DbpSHMt+6ydSHXX6ofMD+k6oYRhw # OiUkhkiVWRFycHeKn4DLc1VljoJO34ikOxedg+WJq8AbJNWE2ggJi2pDOK7pjrYf # uvHQGvcxZx0REaNrorEhdmZooVr1QCMQwdHpv6+HA/mLrxOBe9X6C7L8jkmw7Eo3 # PXd+qXK34mUTGWP3VZ7H1WKfFFGhjuHoKmiLKr7wQYMbcErum759q55pMqJtPW24 # rHBO5QzaixgriWbba6AZ9XlSvdQ2+2Tj1jqTAT1oVZCxqNO6TwzM024RToysFsOc # /+zl16bztFEMZM/PVFMkIwCzv9lmjxIJpex3wZWtUcpHo0A/2wnoG9hSfbS6ejRZ # AgMbKt/2OfllfwbjAqNrWqQV8kcs9Mey0imnzGPSy6G97K+rmXJAKaNkBdJOf9Ej # 8aGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+V # h8b56QTjMwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN # AQcBMBwGCSqGSIb3DQEJBTEPFw0yNTA2MzAxNzI2MTlaMC8GCSqGSIb3DQEJBDEi # BCBroRcHt67Liqyd9gvKThK4TvE96OIQj/9e9nttakvH4TANBgkqhkiG9w0BAQEF # AASCAgAqpTqKtNe7EiLwfJLw6beNmEevziebcTzla+cfIldvDrpLLhgcdtDe1vR+ # Lq0Ncf7G7PZWrKTG3LjIFjTZyLNXUv1qB5mDVQJmYRqg7JXjBt0eRBVCJqwoGxQK # /M87QmEmydcXBPZ6nAOaDTRyYx3Cg4CLoBQqG+NMOU2PL5FLVOyPoCoF4ihYDJdY # QHdGL2LFoc6rvHj8g8SeVn6QSLcmGN/mRYwF0aSp8h/48P3vuft8W4orjP89n/fs # 5Isk5u1i9WYWj9PhKbm7CrZQE54mwnR6snz5e1TZIlzHu4+tiWTR6AqCwmhQpr6n # cc0a1GOApvlXCgkizEKxPDOwaLG58cz/5IqWXOtAQ2JSlCsCnNpn02K6syUK61Gl # XsQUCUq9mMfNJKrqhXq3GNfS6SDPtYoETkr3B+m0cLBkjci3RcOaYoYeFMzQyQyS # 5qBMHSI6fnw49gqFAem/qdLFiF/tEg3lzTmOxxPTPeKiSrLrCUvRteHxGaYmBXRI # Vsat1Rb0IIJJzxC3y/daKQuRGL7AXBLHehr4bNkuQAw8pH4X2x2BI/45kNm+p/eC # Azay54Jv1c0Xk7Toa2hqOBQrLmfhd9zGVl9ItoERQh28q3D3EZZnddy1ZNfIINEU # +Td+sL9TVCURmykNjtaBJPepyeu9relt0r60u0WAkfDIBWVuZA== # SIG # End signature block |