IdentityCommand.AccessRequest.psm1
|
function ConvertTo-ARFilterString { <# .SYNOPSIS Assembles an Access Requests filter expression. .DESCRIPTION Formats filter clauses into the expression syntax accepted by the Access Requests service, which requires every expression to be complete within parentheses. Each clause is parenthesised, and clauses are combined pairwise from the left, each combination parenthesised in turn: 1 clause (a eq 1) 2 clauses ((a eq 1) and (b eq 2)) 3 clauses (((a eq 1) and (b eq 2)) and (c eq 3)) Values are formatted by ConvertTo-FilterClause and quoted with single quotes, as the service documentation renders them. The expression is not url encoded - Add-QueryString encodes the query string as a whole. .PARAMETER Filter The clauses to assemble, each a hashtable with a Field key, an Operator key, and - for operators which take one - a Value key. Operators taking no value (is_null, is_not_null, is_true, is_false) are given without a Value. .PARAMETER LogicalOperator The operator to combine clauses with. Defaults to 'and'. .EXAMPLE ConvertTo-ARFilterString -Filter @{ Field = 'requestState'; Operator = 'eq'; Value = 'finished' } Outputs: (requestState eq finished) .EXAMPLE ConvertTo-ARFilterString -Filter @( @{ Field = 'requestState'; Operator = 'eq'; Value = 'finished' } @{ Field = 'priority'; Operator = 'gt'; Value = 5 } @{ Field = 'createdBy'; Operator = 'eq'; Value = 'John.Doe@cyberark.com' } ) Outputs: (((requestState eq finished) and (priority gt 5)) and (createdBy eq 'John.Doe@cyberark.com')) .EXAMPLE ConvertTo-ARFilterString -Filter @{ Field = 'finalizationReason'; Operator = 'is_null' } Outputs: (finalizationReason is_null) .OUTPUTS String #> [CmdletBinding()] [OutputType([string])] param( [parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [hashtable[]]$Filter, [parameter(Mandatory = $false)] [ValidateSet('and', 'or')] [string]$LogicalOperator = 'and' ) begin { #Operators which compare against no value $ValuelessOperator = @('is_null', 'is_not_null', 'is_true', 'is_false') $ValidOperator = $ValuelessOperator + @( 'eq', 'neq', 'contains', 'not_contains', 'sw', 'ew', 'gt', 'lt', 'ge', 'le', 'in' ) $Clauses = [System.Collections.Generic.List[string]]::new() #The service renders anything but a bare word quoted - an email address, a date, a timestamp $RequiresQuoting = { param($Item) ($Item -is [string]) -and ($Item -notmatch '^[A-Za-z0-9_]+$') } } process { foreach ($Clause in $Filter) { if (-not $Clause.ContainsKey('Field')) { throw 'Each filter clause requires a Field' } if (-not $Clause.ContainsKey('Operator')) { throw "Filter clause for field '$($Clause['Field'])' requires an Operator" } $Operator = $Clause['Operator'] if ($Operator -notin $ValidOperator) { throw "'$Operator' is not a filter operator supported by the Access Requests service. Valid operators are: $($ValidOperator -join ', ')" } if ($Operator -in $ValuelessOperator) { $Clauses.Add($(ConvertTo-FilterClause -Field $Clause['Field'] -Operator $Operator)) } elseif ($Operator -eq 'in') { #TODO: the service documents an 'in' operator but not how it delimits the list. #Verify against a tenant before relying on this rendering. $Values = @($Clause['Value']) | ForEach-Object { ConvertTo-FilterClause -Value $_ -ValueOnly -QuoteCharacter "'" -QuoteValue:(& $RequiresQuoting $_) } $Clauses.Add("$($Clause['Field']) in ($($Values -join ', '))") } else { $Value = $Clause['Value'] $Clauses.Add($(ConvertTo-FilterClause -Field $Clause['Field'] -Operator $Operator -Value $Value -QuoteCharacter "'" -QuoteValue:(& $RequiresQuoting $Value))) } } } end { if ($Clauses.Count -eq 0) { return } $Expression = "($($Clauses[0]))" for ($i = 1; $i -lt $Clauses.Count; $i++) { $Expression = "($Expression $LogicalOperator ($($Clauses[$i])))" } $Expression } } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Approve-ARRequest { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$requestId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 4096)] [String]$finalizationReason ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/workflows/requests/$requestId/finalize" $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove requestId $boundParameters['result'] = 'APPROVED' $body = $boundParameters | ConvertTo-Json -Depth 8 if ($PSCmdlet.ShouldProcess($requestId, 'Approve Access Request')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method POST -Body $body } }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Connect-ARTenant { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Subdomain')] param( #subdomain [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Subdomain')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SubdomainCredential')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SubdomainSAML')] [ValidateNotNullOrEmpty()] [Alias('subdomain')] [String]$tenant_subdomain, #tenant_url [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URL')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URLCredential')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URLSAML')] [ValidateNotNullOrEmpty()] [Alias('ars_url','uar_url')] [String]$tenant_url, #Credential used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it [parameter(Mandatory = $true, ParameterSetName = 'SubdomainCredential')] [parameter(Mandatory = $true, ParameterSetName = 'URLCredential')] [ValidateNotNullOrEmpty()] [PSCredential]$Credential, #Authenticate as a service user via New-IDPlatformToken (OAuth client_credentials) instead of the interactive New-IDSession [parameter(ParameterSetName = 'SubdomainCredential')] [parameter(ParameterSetName = 'URLCredential')] [Switch]$PlatformToken, #SAML assertion used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it [parameter(Mandatory = $true, ParameterSetName = 'SubdomainSAML')] [parameter(Mandatory = $true, ParameterSetName = 'URLSAML')] [ValidateNotNullOrEmpty()] [String]$SAMLResponse ) begin { $IDSession = Get-IDSession $HaveSession = $null -ne $IDSession.tenant_url $AuthRequested = $PSBoundParameters.ContainsKey('Credential') -or $PSBoundParameters.ContainsKey('SAMLResponse') if ($HaveSession -and $AuthRequested) { Write-Verbose 'Authentication parameters were supplied; authenticating and replacing the existing IdentityCommand session' } }#begin process { $UsingSubdomain = $PSCmdlet.ParameterSetName -like 'Subdomain*' #Resolve service URLs from platform discovery when a subdomain was supplied, or when #authentication is required and the CyberArk Identity URL must be discovered. $ServiceUrl = $null if ($UsingSubdomain) { $ServiceUrl = Resolve-ServiceUrl -Service ars -Subdomain $tenant_subdomain $tenant_url = $ServiceUrl.ServiceUrl } else { #Ensure URL is in expected format - remove trailing slash if provided in Url $tenant_url = $tenant_url -replace '/$', '' if ($AuthRequested) { $ServiceUrl = Resolve-ServiceUrl -Service ars -Url $tenant_url } } if ($AuthRequested -or (-not $HaveSession)) { if (-not $AuthRequested) { throw 'Authenticate with New-IDSession or New-IDPlatformToken, or supply -Credential, and try again' } $IdentityUrl = $ServiceUrl.IdentityUrl if ($PSCmdlet.ShouldProcess($IdentityUrl, 'Authenticate to CyberArk Identity')) { if ($PSCmdlet.ParameterSetName -like '*SAML') { $null = New-IDSession -tenant_url $IdentityUrl -SAMLResponse $SAMLResponse } elseif ($PlatformToken) { $null = New-IDPlatformToken -tenant_url $IdentityUrl -Credential $Credential } else { $null = New-IDSession -tenant_url $IdentityUrl -Credential $Credential } $IDSession = Get-IDSession } } #Make the CyberArk Identity Session available in the IdentityCommand.AccessRequest scope foreach ($key in $IDSession.keys) { if ($null -ne $IDSession[$key]) { $ISPSSSession[$key] = $IDSession[$key] } } #Set the Access Requests URL in the session data $ISPSSSession.tenant_url = $tenant_url }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Deny-ARRequest { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$requestId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 4096)] [String]$finalizationReason ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/workflows/requests/$requestId/finalize" $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove requestId $boundParameters['result'] = 'REJECTED' $body = $boundParameters | ConvertTo-Json -Depth 8 if ($PSCmdlet.ShouldProcess($requestId, 'Reject Access Request')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method POST -Body $body } }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Get-ARModuleData { [CmdletBinding()] param() begin { }#begin process { #Calculate the time elapsed since the start of the session and include in return data if ($null -ne $ISPSSSession.StartTime) { $ISPSSSession.ElapsedTime = '{0:HH:mm:ss}' -f ([datetime]$($(Get-Date) - $($ISPSSSession.StartTime)).Ticks) } else { $ISPSSSession.ElapsedTime = $null } #Deep Copy the $ISPSSSession session object and return as IdCmd Session type. Get-SessionClone -InputObject $ISPSSSession | Add-CustomType -Type IdCmd.Session }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Get-ARRequest { [CmdletBinding(DefaultParameterSetName = 'byQuery')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byId' )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$requestId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [ValidateLength(1, 10240)] [String]$filter, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('STARTING', 'RUNNING', 'PENDING', 'FINISHED', 'EXPIRED')] [String]$requestState, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('APPROVED', 'REJECTED', 'CANCELED', 'FAILED', 'UNKNOWN')] [String]$requestResult, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateNotNullOrEmpty()] [String]$createdBy, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateNotNullOrEmpty()] [String]$updatedBy, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [datetime]$createdAfter, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [datetime]$createdBefore, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateLength(1, 10240)] [String]$freeText, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateRange(0, 1000)] [int]$limit, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateRange(0, 10000)] [int]$offset, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('CREATOR', 'APPROVER')] [String]$requestRole, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateLength(1, 10240)] [String]$sort ) begin { }#begin process { if ($PSCmdlet.ParameterSetName -eq 'byId') { $URI = "$($ISPSSSession.tenant_url)/api/workflows/requests/$requestId" #Send Request Invoke-IDRestMethod -Uri $URI -Method GET } else { $URI = "$($ISPSSSession.tenant_url)/api/workflows/requests" $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove requestState, requestResult, createdBy, updatedBy, createdAfter, createdBefore if ($PSCmdlet.ParameterSetName -eq 'byFilterCriteria') { $FilterClause = @() foreach ($Field in 'requestState', 'requestResult', 'createdBy', 'updatedBy') { if ($PSBoundParameters.ContainsKey($Field)) { $FilterClause += @{ Field = $Field; Operator = 'eq'; Value = $PSBoundParameters[$Field] } } } #The service renders a filtered timestamp as 'yyyy-MM-dd HH:mm:ss' if ($PSBoundParameters.ContainsKey('createdAfter')) { $FilterClause += @{ Field = 'createdAt'; Operator = 'ge'; Value = $createdAfter.ToString('yyyy-MM-dd HH:mm:ss') } } if ($PSBoundParameters.ContainsKey('createdBefore')) { $FilterClause += @{ Field = 'createdAt'; Operator = 'le'; Value = $createdBefore.ToString('yyyy-MM-dd HH:mm:ss') } } if ($FilterClause.Count -gt 0) { $boundParameters['filter'] = ConvertTo-ARFilterString -Filter $FilterClause } } $URI = Add-QueryString -URI $URI -Parameter $boundParameters #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { Get-PagedResult -InitialResult $result -URI $URI -Style Offset -ResultProperty 'items' -TotalResponseKey 'totalCount' } } }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Get-ARRequestForm { [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('CLOUD_CONSOLE')] [String]$targetCategory, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('ON_DEMAND', 'DUAL_CONTROL')] [String]$requestType ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/workflows/request-forms" $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter) #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { $result.requestForms } }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function New-ARRequest { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('CLOUD_CONSOLE')] [String]$targetCategory, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('ON_DEMAND', 'DUAL_CONTROL')] [String]$requestType, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [hashtable]$requestDetails ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/workflows/requests" $body = $PSBoundParameters | Get-Parameter | ConvertTo-Json -Depth 8 if ($PSCmdlet.ShouldProcess($targetCategory, 'Create Access Request')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method POST -Body $body } }#process end { }#end } # .ExternalHelp IdentityCommand.AccessRequest-help.xml function Stop-ARRequest { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$requestId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 4096)] [String]$cancelReason ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/workflows/requests/$requestId/cancel" $body = $PSBoundParameters | Get-Parameter -ParametersToRemove requestId | ConvertTo-Json -Depth 8 if ($PSCmdlet.ShouldProcess($requestId, 'Cancel Access Request')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method POST -Body $body } }#process end { }#end } #Copy IdentityCommand's private helpers into this module: this module's functions call them, and #the argument completer registrations below do so at import time. #Each copy is created from the function definition, so it runs in this module's scope and uses this #module's $ISPSSSession, whether IdentityCommand loaded from source or from its combined psm1. #Resolve a single IdentityCommand module: with more than one version loaded, Get-Module returns #an array. $Module = Get-Module -Name IdentityCommand | Sort-Object Version -Descending | Select-Object -First 1 if ($null -eq $Module) { throw 'The IdentityCommand module is not loaded. Import IdentityCommand and try again.' } & $Module { Get-ChildItem -Path Function: } | Where-Object { $_.ModuleName -eq $Module.Name -and -not $Module.ExportedFunctions.ContainsKey($_.Name) } | ForEach-Object { . ([scriptblock]::Create("function $($_.Name) {$($_.Definition)}")) } #region Registration #Requests are identified by an opaque uuid, so label the completion with the target category and #state rather than leaving the caller to recognise the id. Register-ArgumentCompleter -ParameterName 'requestId' -ScriptBlock ( Get-ArgumentCompleter -RetrievalCommand 'Get-ARRequest' -ValueProperty 'requestId' -LabelProperty 'requestState' ) -CommandName @( 'Approve-ARRequest' 'Deny-ARRequest' 'Get-ARRequest' 'Stop-ARRequest' ) #endregion Registration # Script scope session object for session data $ISPSSSession = [ordered]@{ tenant_url = $null User = $null TenantId = $null SessionId = $null WebSession = $null StartTime = $null ElapsedTime = $null LastCommand = $null LastCommandTime = $null LastCommandResults = $null LastError = $null LastErrorTime = $null } | Add-CustomType -Type IdCmd.Session New-Variable -Name ISPSSSession -Value $ISPSSSession -Scope Script -Force |