private/Test-NexposeSearchCriteria.ps1

Function Test-NexposeSearchCriteria {
<#
    .SYNOPSIS
        Validates a given search criteria
 
    .DESCRIPTION
        Validates a given search criteria.
 
    .PARAMETER SearchCriteria
        Criteria presented as a hashtable
 
    .EXAMPLE
        Test-NexposeSearchCriteria -SearchCriteria @{filters = @(@{field='ip-address'; operator='is-like'; value='172.16.*'}, @{field='host-name'; operator='starts-with'; value='SVR'}); match='all'}
 
    .NOTES
        For additional information please see my GitHub wiki page
 
    .LINK
        https://github.com/My-Random-Thoughts/Rapid7Nexpose
#>


    [CmdletBinding()]
    [OutputType([string])]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Scope = 'Function')]
    Param (
        [hashtable]$SearchCriteria
    )

    Begin {
        If ($SearchCriteria.filters.Count -lt 1) {
            Throw 'No filters presented'
        }

        # Set up the values
        $types = @{
            'alternate-address-type'         = ('0','1')                # 0=ipv4, 1=ipv6
            'containers'                     = ('present','not-present')
            'container-status'               = ('created','running','paused','restarting','exited','dead','unknown')
            'criticality-tag'                = ('Very Low','Low','Medium','High','Very High')
            'cvss-access-complexity'         = ('L','M','H')            # L=Low, M=Medium, H=High
            'cvss-access-vector'             = ('L','A','N')            # L=Local, A=Adjacent, N=Network
            'cvss-availability-impact'       = ('N','P','C')            # N=None, P=Partial, C=Complete
            'cvss-integrity-impact'          = ('N','P','C')            # N=None, P=Partial, C=Complete
            'cvss-confidentiality-impact'    = ('N','P','C')            # N=None, P=Partial, C=Complete
            'cvss-authentication-required'   = ('N','S','M')            # N=None, S=Single, M=Multiple
            'cvss-v3-confidentiality-impact' = ('L','N','H')            # L=Local/Low, N=None, H=High
            'cvss-v3-integrity-impact'       = ('L','N','H')            # L=Local/Low, N=None, H=High
            'cvss-v3-availability-impact'    = ('L','N','H')            # L=Local/Low, N=None, H=High
            'cvss-v3-attack-vector'          = ('N','A','L','P')        # N=Network, A=Adjacent, L=Local, P=Physical
            'cvss-v3-attack-complexity'      = ('L','H')                # L=Low, H=High
            'cvss-v3-user-interaction'       = ('N','R')                # N=None, R=Required
            'cvss-v3-privileges-required'    = ('L','N','H')            # L=Local/Low, N=None, H=High
            'host-type'                      = ('0','1','2','3','4')    # 0=Unknown, 1=Guest, 2=Hypervisor, 3=Physical, 4=Mobile
            'ip-address-type'                = ('0','1')                # 0=ipv4, 1=ipv6
            'pci-compliance'                 = ('0','1')                # 0=fail, 1=pass
            'vulnerability-validated-status' = ('present','not-present')
            'vasset-power state'             = ('poweredOn','poweredOff','suspended')
            'vulnerability-exposures'        = ('malwarekit_exploits','exploit_database_exploits','metasploit_exploits',
                                                'type:"malware_type", name:"malwarekit"','type:"exploit_source_type", name:"107"','type:"exploit_source_type", name:"108"')
        }

        $fields = @{
            'alternate-address-type'         = ('in')
            'container-image'                = ('is','is-not','starts-with','ends-with','contains','does-not-contain','is-like','not-like')
            'container-status'               = ('is','is-not')
            'containers'                     = ('are')
            'criticality-tag'                = ('is','is-not','is-greater-than','is-less-than','is-applied','is-not-applied')
            'custom-tag'                     = ('is','is-not','starts-with','ends-with','contains','does-not-contain','is-applied','is-not-applied')
            'cve'                            = ('is','is-not','contains','does-not-contain')
            'cvss-access-complexity'         = ('is','is-not')
            'cvss-access-vector'             = ('is','is-not')
            'cvss-authentication-required'   = ('is','is-not')
            'cvss-availability-impact'       = ('is','is-not')
            'cvss-confidentiality-impact'    = ('is','is-not')
            'cvss-integrity-impact'          = ('is','is-not')
            'cvss-v3-attack-complexity'      = ('is','is-not')
            'cvss-v3-attack-vector'          = ('is','is-not')
            'cvss-v3-availability-impact'    = ('is','is-not')
            'cvss-v3-confidentiality-impact' = ('is','is-not')
            'cvss-v3-integrity-impact'       = ('is','is-not')
            'cvss-v3-privileges-required'    = ('is','is-not')
            'cvss-v3-user-interaction'       = ('is','is-not')
            'host-name'                      = ('is','is-not','starts-with','ends-with','contains','does-not-contain','is-empty','is-not-empty','is-like','not-like')
            'host-type'                      = ('in','not-in')
            'ip-address'                     = ('is','is-not','in-range','not-in-range','is-like','not-like')
            'ip-address-type'                = ('in','not-in')
            'last-scan-date'                 = ('is-on-or-before','is-on-or-after','is-between','is-earlier-than','is-within-the-last')
            'location-tag'                   = ('is','is-not','starts-with','ends-with','contains','does-not-contain','is-applied','is-not-applied')
            'mobile-device-last-sync-time'   = ('is-within-the-last','is-earlier-than')
            'open-ports'                     = ('is','is-not','in-range')
            'operating-system'               = ('contains','does-not-contain','is-empty','is-not-empty')
            'owner-tag'                      = ('is','is-not','starts-with','ends-with','contains','does-not-contain','is-applied','is-not-applied')
            'pci-compliance'                 = ('is')
            'risk-score'                     = ('is','is-not','in-range','is-greater-than','is-less-than')
            'service-name'                   = ('contains','does-not-contain')
            'site-id'                        = ('in','not-in')
            'software'                       = ('contains','does-not-contain')
            'vAsset-cluster'                 = ('is','is-not','contains','does-not-contain','starts-with')
            'vAsset-datacenter'              = ('is','is-not')
            'vAsset-host-name'               = ('is','is-not','contains','does-not-contain','starts-with')
            'vAsset-power-state'             = ('in','not-in')
            'vAsset-resource-pool-path'      = ('contains','does-not-contain')
            'vulnerability-assessed'         = ('is-on-or-before','is-on-or-after','is-between','is-earlier-than','is-within-the-last')
            'vulnerability-category'         = ('is','is-not','starts-with','ends-with','contains','does-not-contain')
            'vulnerability-cvss-score'       = ('is','is-not','in-range','is-greater-than','is-less-than')
            'vulnerability-cvss-v3-score'    = ('is','is-not')
            'vulnerability-exposures'        = ('includes','does-not-include')
            'vulnerability-title'            = ('contains','does-not-contain','is','is-not','starts-with','ends-with')
            'vulnerability-validated-status' = ('are')
        }

        $operators = @{
            'are'                = 'string'
            'contains'           = 'string'
            'does-not-contain'   = 'string'
            'does-not-include'   = 'array'
            'ends-with'          = 'string'
            'in'                 = 'array'
            'in-range'           = 'upper-lower'
            'includes'           = 'array'
            'is'                 = 'string'
            'is-string'          = 'none'
            'is-applied'         = 'none'
            'is-between'         = 'upper-lower'
            'is-earlier-than'    = 'numeric'
            'is-empty'           = 'none'
            'is-greater-than'    = 'numeric'
            'is-on-or-after'     = 'datetime'
            'is-on-or-before'    = 'datetime'
            'is-not'             = 'string'
            'is-not-applied'     = 'none'
            'is-not-empty'       = 'none'
            'is-within-the-last' = 'numeric'
            'is-less-than'       = 'numeric'
            'is-like'            = 'string'
            'not-contains'       = 'string'
            'not-in'             = 'array'
            'not-in-range'       = 'upper-lower'
            'not-like'           = 'string'
            'starts-with'        = 'string'
        }
    }

    Process {
        [int]$ReturnCounter = 0
        $ReturnValue = New-Object -TypeName 'boolean[]' $($SearchCriteria.filters).Count

        # Check there is a correct "match" value
        If (($SearchCriteria.match -ne 'all') -and ($SearchCriteria.match -ne 'any')) {
            Return "Invalid 'match' statement. Valid values are: all, any"
        }

        ForEach ($filter In $($SearchCriteria.filters)) {
            $ReturnValue[$ReturnCounter] = $false

            If ($fields.Keys -contains $($filter.field)) {
                If ($fields.$($filter.field) -contains $($filter.operator)) {
                    Switch ($($operators.$($filter.operator))) {
                        'array' {
                            If (([string]::IsNullOrEmpty($($filter.values)) -eq $false) -and ($($filter.values).Count -gt 0)) {
                                $ReturnValue[$ReturnCounter] = $true
                            }
                        }

                        'datetime' {
                            If (([string]::IsNullOrEmpty($($filter.value)) -eq $false) -and ($($filter.value) -as [datetime])) {
                                $ReturnValue[$ReturnCounter] = $true
                            }
                        }

                        'numeric' {
                            If (([string]::IsNullOrEmpty($($filter.value)) -eq $false) -and ($($filter.value) -match '^[0-9]+(?:.[0-9]+)?$') -and ($($filter.value).GetType().ToString() -ne 'system.string')) {
                                $ReturnValue[$ReturnCounter] = $true
                            }
                        }

                        'string' {
                            If (([string]::IsNullOrEmpty($($filter.value)) -eq $false) -and ($($filter.value).GetType().ToString() -is [string]) -and ($($filter.value).Count -eq 1)) {
                                $ReturnValue[$ReturnCounter] = $true
                            }
                        }

                        'upper-lower' {
                                [datetime]$dateValue = New-Object -TypeName 'DateTime'
                                [version] $verValue  = New-Object -TypeName 'Version'
                                If (([string]::IsNullOrEmpty($($filter.lower)) -eq $false) -and ([string]::IsNullOrEmpty($($filter.upper)) -eq $false)) {
                                    If (($($filter.lower) -match '^[0-9]+$') -and ($($filter.upper) -match '^[0-9]+$')) {
                                        If (($($filter.lower) -as [int]) -lt ($($filter.upper) -as [int])) {
                                            $ReturnValue[$ReturnCounter] = $true
                                        }
                                    }
                                    ElseIf (([datetime]::TryParse($($filter.lower), [ref]$dateValue)) -and ([datetime]::TryParse($($filter.upper), [ref]$dateValue))) {
                                        If ([datetime]::Compare($($filter.lower), $($filter.upper)) -lt 0) {
                                            $ReturnValue[$ReturnCounter] = $true
                                        }
                                    }
                                    ElseIf (([version]::TryParse($($filter.lower), [ref]$verValue)) -and ([version]::TryParse($($filter.upper), [ref]$verValue))) {
                                        If (($($filter.lower) -as [version]) -lt ($($filter.upper) -as [version])) {
                                            $ReturnValue[$ReturnCounter] = $true
                                        }
                                    }
                                    Else {
                                        $ReturnValue[$ReturnCounter] = $false
                                    }
                                }
                            }

                        'none' {
                            If ($null -eq $($filter.value)) {
                                $ReturnValue[$ReturnCounter] = $true
                            }
                        }

                        default {
                            $ReturnValue[$ReturnCounter] = $false
                        }
                    }

                    # Does the TYPES list contain this FIELD
                    If (($ReturnValue[$ReturnCounter] -eq $true) -and ($operators.$($filter.operator) -ne 'none')) {
                        If ($types.Keys -contains $($filter.field)) {
                            $ReturnValue[$ReturnCounter] = $false
                            If ([string]::IsNullOrEmpty($($filter.value)) -eq $false) {
                                If ($types.$($filter.field) -contains $($filter.value)) { $ReturnValue[$ReturnCounter] = $true }
                            }
                            ElseIf ([string]::IsNullOrEmpty($($filter.values)) -eq $false) {
                                ForEach ($values in $($filter.values)) {
                                    If ($types.$($filter.field) -contains    $values) { $ReturnValue[$ReturnCounter] = $true  }
                                    If ($types.$($filter.field) -notcontains $values) { $ReturnValue[$ReturnCounter] = $false }
                                }
                            }
                            Else {
                                Write-Error 'We should never see this'
                            }
                        }
                    }
                }
                Else {
                    Return "Invalid filter operator for this field. Valid entries are: '$($fields.$($filter.field) -join ', ')"
                }
            }
            Else {
                Return "Invalid filter field. Valid entries are: $($fields.Keys -join ', ')"
            }

            If ($($ReturnValue[$ReturnCounter]) -eq $false) {
                Return "Invalid filter value or input type. Valid entries are: ^$($types.$($filter.field) -join '^')^^ You entered: $($filter.value) $($filter.values)."
            }
            $ReturnCounter++
        }

        Return 'Success'
    }

    End {
    }
}