Public/oauth2.ps1

function Request-FalconToken {
<#
.SYNOPSIS
Request an OAuth2 access token
.DESCRIPTION
If successful, your credentials ('ClientId', 'ClientSecret', 'MemberCid' and 'Cloud' or 'Hostname') and token are
cached for re-use.
 
If an active OAuth2 access token is due to expire in less than 60 seconds, a new token will automatically be
requested using your cached credentials.
 
The 'Collector' parameter allows for the submission of a [System.Collections.Hashtable] object containing the
parameters included with a 'Register-FalconEventCollector' command ('Path', 'Token' and 'Enabled') in order to
log an initial OAuth2 access token request.
.PARAMETER ClientId
OAuth2 client identifier
.PARAMETER ClientSecret
OAuth2 client secret
.PARAMETER Cloud
CrowdStrike cloud [default: 'us-1']
.PARAMETER Hostname
CrowdStrike API hostname
.PARAMETER MemberCid
Member CID, used when authenticating within a multi-CID environment ('Falcon Flight Control')
.PARAMETER Collector
A hashtable containing 'Path', 'Token' and 'Enabled' properties for 'Register-FalconEventCollector'
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Request-FalconToken
#>

    [CmdletBinding(DefaultParameterSetName='Hostname',SupportsShouldProcess)]
    param(
        [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=1)]
        [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=1)]
        [Alias('client_id')]
        [ValidatePattern('^[a-fA-F0-9]{32}$')]
        [string]$ClientId,
        [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=2)]
        [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=2)]
        [Alias('client_secret')]
        [ValidatePattern('^\w{40}$')]
        [string]$ClientSecret,
        [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=3)]
        [ValidateSet('eu-1','us-gov-1','us-1','us-2',IgnoreCase=$false)]
        [string]$Cloud,
        [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=3)]
        [ValidateSet('https://api.crowdstrike.com','https://api.us-2.crowdstrike.com',
            'https://api.laggar.gcw.crowdstrike.com','https://api.eu-1.crowdstrike.com',IgnoreCase=$false)]
        [string]$Hostname,
        [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=4)]
        [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=4)]
        [Alias('cid','member_cid')]
        [ValidatePattern('^[a-fA-F0-9]{32}(-\w{2})?$')]
        [string]$MemberCid,
        [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=5)]
        [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=5)]
        [ValidateScript({
            @($_.Keys).foreach{
                if ($_ -notmatch '^(Enable|Token|Uri)$') { throw "Unexpected key in 'Collector' object. ['$_']" }
            }
            foreach ($Key in @('Token','Uri')) {
                if ($_.Keys -notcontains $Key) { throw "'Collector' requires '$Key'." } else { $true }
            }
        })]
        [System.Collections.Hashtable]$Collector
    )
    begin {
        if ($PSBoundParameters.MemberCid -match '^[a-fA-F0-9]{32}-\w{2}$'){
            $PSBoundParameters.MemberCid = $PSBoundParameters.MemberCid.Split('-')[0]
        }
        function Get-ApiCredential ($Inputs) {
            $Output = @{}
            @('ClientId','ClientSecret','Hostname','MemberCid').foreach{
                # Use input before existing ApiClient value
                $Value = if ($Inputs.$_) { $Inputs.$_ } elseif ($null -ne $Script:Falcon.$_) { $Script:Falcon.$_ }
                if (!$Value -and $_ -match '^(ClientId|ClientSecret)$') {
                    # Prompt for ClientId/ClientSecret and validate input
                    $Value = Read-Host $_
                    $BaseError = 'Cannot validate argument on parameter "{0}". The argument "{1}" does not ' +
                        'match the "{2}" pattern. Supply an argument that matches "{2}" and try the command again.'
                    $ValidPattern = if ($_ -eq 'ClientId') { '^[a-fA-F0-9]{32}$' } else { '^\w{40}$' }
                    if ($Value -notmatch $ValidPattern) {
                        $InvalidValue = $BaseError -f $_,$Value,$ValidPattern
                        throw $InvalidValue
                    }
                } elseif (!$Value -and $_ -eq 'Hostname') {
                    # Default to 'us-1' cloud
                    $Value = 'https://api.crowdstrike.com'
                }
                if ($Value) { $Output.Add($_,$Value) }
            }
            return $Output
        }
    }
    process {
        if ($PSBoundParameters.Cloud) {
            # Convert 'Cloud' to 'Hostname'
            $Value = switch ($PSBoundParameters.Cloud) {
                'eu-1'     { 'https://api.eu-1.crowdstrike.com' }
                'us-gov-1' { 'https://api.laggar.gcw.crowdstrike.com' }
                'us-1'     { 'https://api.crowdstrike.com' }
                'us-2'     { 'https://api.us-2.crowdstrike.com' }
            }
            $PSBoundParameters['Hostname'] = $Value
            [void]$PSBoundParameters.Remove('Cloud')
        }
        if (!$Script:Falcon) {
            try {
                # Initiate ApiClient, set SslProtocol and UserAgent
                $Script:Falcon = Get-ApiCredential $PSBoundParameters
                $Script:Falcon.Add('Api',[ApiClient]::New())
                if ($Script:Falcon.Api) {
                    try {
                        # Set TLS 1.2 for [System.Net.Http.HttpClientHandler]
                        $Script:Falcon.Api.Handler.SslProtocols = 'Tls12'
                        Write-Verbose "[Request-FalconToken] Set TLS 1.2 via [System.Net.Http.HttpClientHandler]"
                    } catch {
                        if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') {
                            # Set TLS 1.2 for PowerShell session
                            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                            Write-Verbose "[Request-FalconToken] Set TLS 1.2 via [Net.ServicePointManager]"
                        }
                    }
                    $Script:Falcon.Api.Handler.AutomaticDecompression = [System.Net.DecompressionMethods]::Gzip,
                        [System.Net.DecompressionMethods]::Deflate
                    $Script:Falcon.Api.Client.DefaultRequestHeaders.UserAgent.ParseAdd(
                        "$((Show-FalconModule).UserAgent)")
                } else {
                    Write-Error "Unable to initialize [ApiClient] object."
                }
            } catch {
                throw $_
            }
        } else {
            (Get-ApiCredential $PSBoundParameters).GetEnumerator().foreach{
                # Update existing ApiClient with new input
                if ($Script:Falcon.($_.Key) -ne $_.Value) { $Script:Falcon.($_.Key) = $_.Value }
            }
        }
        if ($PSBoundParameters.Collector) {
            $Collector = $PSBoundParameters.Collector
            Register-FalconEventCollector @Collector
        }
        if ($Script:Falcon.ClientId -and $Script:Falcon.ClientSecret) {
            $Param = @{
                Path = "$($Script:Falcon.Hostname)/oauth2/token"
                Method = 'post'
                Headers = @{
                    Accept = 'application/json'
                    ContentType = 'application/x-www-form-urlencoded'
                }
                Body = "client_id=$($Script:Falcon.ClientId)&client_secret=$($Script:Falcon.ClientSecret)"
            }
            if ($Script:Falcon.MemberCid) { $Param.Body += "&member_cid=$($Script:Falcon.MemberCid)" }
            $Request = $Script:Falcon.Api.Invoke($Param)
            if ($Request.Result) {
                $Region = $Request.Result.Headers.GetEnumerator().Where({ $_.Key -eq 'X-Cs-Region' }).Value
                $Redirect = switch ($Region) {
                    # Update ApiClient hostname if redirected
                    'us-1'     { 'https://api.crowdstrike.com' }
                    'us-2'     { 'https://api.us-2.crowdstrike.com' }
                    'us-gov-1' { 'https://api.laggar.gcw.crowdstrike.com' }
                    'eu-1'     { 'https://api.eu-1.crowdstrike.com' }
                }
                if ($Redirect -and $Script:Falcon.Hostname -ne $Redirect) {
                    Write-Verbose "[Request-FalconToken] Redirected to '$Region'"
                    $Script:Falcon.Hostname = $Redirect
                }
                $Result = Write-Result $Request
                if ($Result.access_token) {
                    # Cache access token in ApiClient
                    [string]$Token = $Result.token_type,$Result.access_token -join ' '
                    if (!$Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization) {
                        $Script:Falcon.Api.Client.DefaultRequestHeaders.Add('Authorization',$Token)
                    } else {
                        $Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization = $Token
                    }
                    $Script:Falcon.Expiration = (Get-Date).AddSeconds($Result.expires_in)
                    Write-Verbose "[Request-FalconToken] Authorized until: $($Script:Falcon.Expiration)"
                } elseif (@(308,429) -contains $Request.Result.StatusCode.GetHashCode()) {
                    # Retry token request when rate limited or unable to automatically follow redirection
                    & $MyInvocation.MyCommand.Name
                }
            } else {
                @('ClientId','ClientSecret','MemberCid').foreach{ [void]$Script:Falcon.Remove("$_") }
                [void]$Script:Falcon.Api.Client.DefaultRequestHeaders.Remove('Authorization')
                throw 'Authorization token request failed.'
            }
        } else {
            throw 'Missing required credentials.'
        }
    }
}
function Revoke-FalconToken {
<#
.SYNOPSIS
Revoke your active OAuth2 access token
.DESCRIPTION
Revokes your active OAuth2 access token and clears cached credential information ('ClientId', 'ClientSecret',
'MemberCid', 'Cloud'/'Hostname') from the module.
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Revoke-FalconToken
#>

    [CmdletBinding(DefaultParameterSetName='/oauth2/revoke:post',SupportsShouldProcess)]
    param()
    process {
        if ($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization.Parameter -and
        $Script:Falcon.ClientId -and $Script:Falcon.ClientSecret) {
            # Revoke OAuth2 access token
            $Param = @{
                Path = "$($Script:Falcon.Hostname)/oauth2/revoke"
                Method = 'post'
                Headers = @{
                    Accept = 'application/json'
                    ContentType = 'application/x-www-form-urlencoded'
                    Authorization = "basic $([System.Convert]::ToBase64String(
                        [System.Text.Encoding]::ASCII.GetBytes("$($Script:Falcon.ClientId):$(
                        $Script:Falcon.ClientSecret)")))"

                }
                Body = "token=$($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization.Parameter)"
            }
            $Request = $Script:Falcon.Api.Invoke($Param)
            Write-Result $Request
            [void]$Script:Falcon.Api.Client.DefaultRequestHeaders.Remove('Authorization')
        }
        @('ClientId','ClientSecret','MemberCid').foreach{
            if ($Script:Falcon.$_) { [void]$Script:Falcon.Remove($_) }
        }
    }
}
function Test-FalconToken {
<#
.SYNOPSIS
Display OAuth2 access token status
.DESCRIPTION
Displays a [PSCustomObject] containing token status ('Token') along with cached 'Hostname', 'ClientId' and
'MemberCid' values.
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Test-FalconToken
#>

    [CmdletBinding()]
    param()
    process {
        if ($Script:Falcon) {
            [PSCustomObject]@{
                Token = if ($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization -and
                    ($Script:Falcon.Expiration -gt (Get-Date).AddSeconds(60))) { $true } else { $false }
                Hostname = $Script:Falcon.Hostname
                ClientId = $Script:Falcon.ClientId
                MemberCid = $Script:Falcon.MemberCid
            }
        } else {
            Write-Error "No authorization token available. Try 'Request-FalconToken'."
        }
    }
}