Providers/FreeIPA/Private/Send-FreeIPAHttpRequest.ps1

function Send-FreeIPAHttpRequest {
    <#
    .SYNOPSIS
        Sends one HTTP request through a connection's client and returns status, headers and body

    .DESCRIPTION
        The only function that touches the network for the FreeIPA provider, which is what the
        tests mock. It sends a form or a JSON body to a path under the server, reads the
        response as UTF-8 bytes rather than trusting an inferred charset - the seed carries
        accented names on purpose - and returns the pieces the callers switch on: the status
        code, the headers FreeIPA answers with (a login failure is explained in
        X-IPA-Rejection-Reason, a password change in X-IPA-Pwchange-Result) and the body.

        Nothing is thrown for a non-success status. A 401 is a normal answer that
        Invoke-FreeIPARequest turns into a new login, so the decision belongs to the caller.

    .PARAMETER Connection
        The connection whose client to send through.

    .PARAMETER Path
        The path under the server, for example '/ipa/session/json'.

    .PARAMETER Form
        Form fields, sent URL-encoded.

    .PARAMETER Json
        A JSON document, sent as application/json.

    .PARAMETER Accept
        The Accept header. FreeIPA's login endpoints want text/plain; the API wants JSON.

    .OUTPUTS
        PSCustomObject with StatusCode, Headers and Body.

    .EXAMPLE
        PS> Send-FreeIPAHttpRequest -Connection $connection -Path '/ipa/session/json' -Json $payload -Accept 'application/json'

        DESCRIPTION: Sends a JSON-RPC call
        OUTPUT: The status, headers and raw body
        USE CASE: Invoke-FreeIPARequest

    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
    #>


    [CmdletBinding(DefaultParameterSetName = 'Json')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$Connection,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^/')]
        [string]$Path,

        [Parameter(Mandatory = $true, ParameterSetName = 'Form')]
        [hashtable]$Form,

        [Parameter(Mandatory = $true, ParameterSetName = 'Json')]
        [string]$Json,

        [Parameter()]
        [string]$Accept = 'application/json'
    )

    $client = $Connection.Client
    if (-not $client) { throw 'The connection holds no HTTP client. Reconnect with Connect-TestEnvironment.' }

    $uri = '{0}{1}' -f $Connection.BaseUrl.TrimEnd('/'), $Path
    $request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Post, $uri)
    try {
        if ($PSCmdlet.ParameterSetName -eq 'Form') {
            $fields = [System.Collections.Generic.Dictionary[string, string]]::new()
            foreach ($key in $Form.Keys) { $fields[[string]$key] = [string]$Form[$key] }
            $request.Content = [System.Net.Http.FormUrlEncodedContent]::new($fields)
        }
        else {
            $request.Content = [System.Net.Http.StringContent]::new($Json, [System.Text.Encoding]::UTF8, 'application/json')
        }
        $request.Headers.Accept.ParseAdd($Accept)

        Write-Verbose "FreeIPA POST $Path"
        $response = $client.SendAsync($request).GetAwaiter().GetResult()
        try {
            $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
            $body = ''
            if ($bytes -and $bytes.Length -gt 0) { $body = [System.Text.Encoding]::UTF8.GetString($bytes) }

            $headers = @{}
            foreach ($header in $response.Headers) { $headers[[string]$header.Key] = (@($header.Value) -join ', ') }

            return [PSCustomObject]@{
                StatusCode = [int]$response.StatusCode
                Headers    = $headers
                Body       = $body
            }
        }
        finally {
            $response.Dispose()
        }
    }
    finally {
        $request.Dispose()
    }
}