Public/New-IDSession.ps1

# .ExternalHelp IdentityCommand-help.xml
Function New-IDSession {

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Actual legitimate use of Write-Host')]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        #tenant_url
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$tenant_url,

        #User Creds
        [Parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Credential'
        )]
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential,

        #SAML Assertion
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'SAML'
        )]
        [String]$SAMLResponse

    )

    Begin {

        #Remove WebSession which may exist in module scope
        $ISPSSSession.WebSession = $null

        $LogonRequest = @{ }
        $LogonRequest['Method'] = 'POST'
        $LogonRequest['SessionVariable'] = 'IDSession'

    }

    Process {

        #Ensure URL is in expected format
        #Remove trailing space if provided in Url
        $tenant_url = $tenant_url -replace '/$', ''

        #Set Module Scope variables
        $ISPSSSession.tenant_url = $tenant_url
        Set-Variable -Name Version -Value '1.0' -Scope Script

        $LogonRequest['Headers'] = @{'accept' = '*/*' }

        #Must be passed in order to get the parameters needed for OOB IdP auth
        $LogonRequest['Headers'].Add('OobIdPAuth', $true)

        switch ($PSCmdlet.ParameterSetName) {

            'Credential' {
                #*Start Authentication
                $IDSession = $LogonRequest | Start-Authentication -Credential $Credential
                break
            }
            'SAML' {
                #*Send SAML Assertion
                $IDSession = $LogonRequest | Start-SamlAuthentication -SAMLResponse $SAMLResponse
                break
            }
        }

        #Set request properties for Next authentication stage.
        $LogonRequest.Remove('SessionVariable')
        $LogonRequest['Headers'].Add('X-IDAP-NATIVE-CLIENT', $true)

        #Set Module Scope variables
        $ISPSSSession.TenantId = $IDSession.TenantId
        $ISPSSSession.SessionId = $IDSession.SessionId
        #? does SessionId need to be available in script scope?

        switch ($PSCmdlet.ParameterSetName) {

            'Credential' {

                #IdpRedirectShortUrl is only included in the response if the OobIdPAuth header is set to true
                if ($IDSession.IdpRedirectShortUrl) {
                    Write-Host @"
You are being redirected to your browser in order to authenticate to your external identity provider.
If your browser does not open, click on the below URL to navigate to your identity provider.

$($IDSession.IdpRedirectShortUrl)
"@

                    #Launches the user's default browser and navigates it to the external identity provider
                    Start-Process $IDSession.IdpRedirectShortUrl

                    if ($IDSession.IdpOobAuthPinRequired) {

                        #A PIN code is displayed in the browser after IdP login and must be entered here
                        #to complete authentication via AdvanceAuthentication.
                        $Pin = Read-Host -Prompt 'Enter the PIN code displayed after you logged in to your identity provider' -AsSecureString

                        $OobPinAuthRequest = @{ }
                        $OobPinAuthRequest['Method'] = 'POST'
                        #Use the session tenant_url so that any PodFqdn redirect from Start-Authentication is honoured
                        $OobPinAuthRequest['Uri'] = "$($ISPSSSession.tenant_url)/Security/AdvanceAuthentication"
                        $OobPinAuthRequest['WebSession'] = $ISPSSSession.WebSession
                        $OobPinAuthRequest['Body'] = @{
                            SessionId   = $IDSession.IdpLoginSessionId
                            MechanismId = 'OOBAUTHPIN'
                            Action      = 'Answer'
                            Answer      = Unprotect-Answer $Pin
                        } | ConvertTo-Json

                        try {

                            $IDSession = Invoke-IDRestMethod @OobPinAuthRequest

                            if ($IDSession.Summary -ne 'LoginSuccess' -or -not $IDSession.Token) {

                                throw 'Failed to complete OOB IdP authentication with PIN code'

                            }

                        } catch {

                            #Cleanup Authentication on any error at the PIN submission stage
                            Clear-AdvanceAuthentication

                            throw $PSItem

                        }

                    } else {

                        $OobAuthStatusRequest = @{ }
                        $OobAuthStatusRequest['Method'] = 'POST'
                        #Undocumented endpoint for checking the IdpLoginSessionId's status. Sniffed out from the ark-sdk-python project
                        #Use the session tenant_url so that any PodFqdn redirect from Start-Authentication is honoured
                        $OobAuthStatusRequest['Uri'] = "$($ISPSSSession.tenant_url)/Security/OobAuthStatus"
                        #We need the cookies the server provides in the same response it provides the IdpAuth information
                        $OobAuthStatusRequest['WebSession'] = $ISPSSSession.WebSession
                        $OobAuthStatusRequest['Body'] = @{SessionId = $IDSession.IdpLoginSessionId} | ConvertTo-Json

                        $IDSession = Invoke-IDRestMethod @OobAuthStatusRequest

                        while ($IDSession.State -ne 'Success') {
                            Start-Sleep 2

                            $IDSession = Invoke-IDRestMethod @OobAuthStatusRequest
                        }

                    }

                    break
                }

                #The MFA Bit - keep a reference to $IDSession for the MFA Package
                $ThisSession = $IDSession
                for ($Challenge = 0; $Challenge -lt $(($ThisSession.Challenges).Count); $Challenge++) {

                    #Iterate through presented challenges
                    if ($($IDSession.Summary) -eq 'NewPackage') {

                        #Initialise loop and $ThisSession if NewPackage Challenges are presented
                        $Challenge = 0
                        $ThisSession = $IDSession
                        if ($null -ne $ThisSession.EventDescription) { Write-Warning -Message $ThisSession.EventDescription }

                    }

                    #Get Current Challenge Mechanisms
                    $Mechanisms = $ThisSession.Challenges[$Challenge] | Select-Object -ExpandProperty Mechanisms

                    #select challenge mechanism
                    $Mechanism = Select-ChallengeMechanism -Mechanisms $Mechanisms

                    try {

                        #answer challenge mechanism
                        $Answer = Get-MechanismAnswer -Mechanism $Mechanism -Credential $Credential

                        #*Advance Authentication
                        $IDSession = $LogonRequest | Start-AdvanceAuthentication -Mechanism $Mechanism -Answer $Answer

                    } catch {

                        throw $PSItem

                    }

                    if ($($IDSession.Summary) -eq 'NewPackage') {

                        #New Package Recieved, decrement counter so we go round the loop again to evaluate.
                        $Challenge--

                    }

                }

                break
            }

            'SAML' {
                #*Get Saml ID Token
                $IDSession = $LogonRequest | Complete-SamlAuthentication
                break
            }
        }

        switch ($IDSession.Summary) {

            'NoncommitalSuccess' {
                Write-Host $IDSession.ClientMessage
                break
            }

            default {

                if ($null -ne $IDSession) {

                    $result = $IDSession | Select-Object -Last 1 | Add-CustomType -Type IdCmd.ID.Session

                    #Add GetWebSession ScriptMethod
                    $result | Add-Member -MemberType ScriptMethod -Name GetWebSession -Value {

                        (Get-IDSession).WebSession

                    } -Force

                    #Add GetToken ScriptMethod to output Bearer Token
                    $result | Add-Member -MemberType ScriptMethod -Name GetToken -Value {

                        Write-Output @{Authorization = "Bearer $($this.Token)" }

                    } -Force

                    #Record authenticated User name, Session Start Time & add Authorization header
                    $ISPSSSession.User = $result.User
                    $ISPSSSession.StartTime = Get-Date
                    $ISPSSSession.WebSession.Headers.Add('Authorization', "Bearer $($result.Token)")

                    #Return the result
                    $result

                }

                break

            }

        }

    } #process

    End { } #end

}