public/Invoke-OktaApi.ps1

Set-StrictMode -Version Latest

$script:limitThreshold = 10

function Get-OktaRateLimit {
    param ()
    return $script:rateLimit
}

function Invoke-OktaApi {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory)]
        [string] $RelativeUri,
        [ValidateSet('Get', 'Head', 'Post', 'Put', 'Delete', 'Trace', 'Options', 'Merge', 'Patch')]
        [string] $Method = "Get",
        [object] $Body,
        [switch] $Json,
        [string] $OktaApiToken,
        [string] $OktaBaseUri,
        [switch] $Next,
        [switch] $NotFoundOk,
        [switch] $NoRetryOnLimit,
        [switch] $NoWarn,
        [hashTable] $AdditionalHeaders
    )

    if ($Body -isnot [String]) {
        $Body = ConvertTo-Json $Body -Depth 10
    }

    $headers = @{
        Authorization = "SSWS $(Get-OktaApiToken $OktaApiToken)"
        Accept        = "application/json"
    }
    if ($AdditionalHeaders) {
        $headers += $AdditionalHeaders
    }
    $baseUri = Get-OktaBaseUri $OktaBaseUri

    $objectPath = ($RelativeUri -split "\?")[0]
    if ($Next) {
        if ($script:nextUrls[$objectPath]) {
            $RelativeUri = $script:nextUrls[$objectPath]
        } else {
            if (!$NoWarn) {
                Write-Warning "Nothing available for next '$objectPath'"
            }
            return $null
        }
    }

    $params = @{
        Uri = "$baseUri/api/v1/$RelativeUri"
        ContentType = "application/json"
        Headers = $headers
        Method = $Method
    }
    if ($PSVersionTable.PSVersion.Major -ge 7) {
        $params['SkipHttpErrorCheck'] = $true
    }
    Write-Verbose "$($params.method) $($params.Uri)"

    $result = $null
    $writeMethod = $Method -in "Post", "Put", "Patch", "Merge"
    if ($writeMethod -and $body) {
        Write-Verbose "Doing $method with body $body"
        $params["Body"] = $body
    }

    if (!$writeMethod -or $PSCmdlet.ShouldProcess($RelativeUri,"Invoke API")) {
        $prevPref = $progressPreference
        $progressPreference = "silentlyContinue"
        try {
            if (!$NoRetryOnLimit) {
                $limits = Get-OktaRateLimit
                if ($limits.RateLimitRemaining -and $limits.RateLimitRemaining -lt $script:limitThreshold) {
                    $sleepMs = ($limits.RateLimitResetLocal - (Get-Date)).TotalMilliseconds
                    if ($sleepMs -gt 0) {
                        Write-Warning "Sleeping for ${sleepMs}ms since ratelimitRemaining is $($limits.RateLimitRemaining)"
                        Start-Sleep -Milliseconds $sleepMs
                    }
                }
            }
            $response = Invoke-WebRequest @params
        } catch {
            $e = $_
            # PS 5 throws on since don't have skipHttpErrorCheck
            if (!($e | Get-Member -Name Exception) -or !($e.Exception | Get-Member -Name Response)) {
                Write-Warning "Got unexpected exception"
                throw $_
            }
            $response = $e.Exception.Response
            if ($PSVersionTable.PSVersion.Major -lt 6)
            {
                $result = $_.Exception.Response.GetResponseStream()
                $reader = New-Object System.IO.StreamReader($result)
                $reader.BaseStream.Position = 0
                $reader.DiscardBufferedData()
                $Response | Add-Member -NotePropertyName Content -NotePropertyValue $reader.ReadToEnd()
            }
        } finally {
            $progressPreference = $prevPref
        }
        Test-OktaResult -Result $response -Json:$Json -Method $Method -ObjectPath $objectPath -NotFoundOk:$NotFoundOk
    }
}

function Test-OktaNext
{
    param(
        [string] $ObjectName
    )

    return [bool]$script:nextUrls[$ObjectName]
}

Register-ArgumentCompleter -CommandName "Test-OktaNext" `
        -ParameterName "ObjectName" `
        -ScriptBlock {
            (Get-OktaNextUrl).keys
        }

function Get-OktaNextUrl
{
    return $script:nextUrls
}