Authentication.ps1
|
$script:ServicePointSessionStorePath = Join-Path -Path $PSScriptRoot -ChildPath '.servicePointSessions' if (-not (Test-Path -Path $script:ServicePointSessionStorePath -PathType Container)) { $null = New-Item -Path $script:ServicePointSessionStorePath -ItemType Directory -Force } New-Variable -Scope Global -Name 'ServicePointSessions' -Value @{} -ErrorAction SilentlyContinue function Lock-FileMutex { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Path, [int]$TimeoutSeconds = 15 ) begin { Write-Verbose "LOCK: Attempting [$Path]" $stopWatch = [Diagnostics.Stopwatch]::StartNew() } process { while ($stopWatch.Elapsed -lt [TimeSpan]::FromSeconds($TimeoutSeconds)) { try { $fs = [IO.File]::Open($Path, 'OpenOrCreate', 'ReadWrite', 'None') Write-Verbose "LOCK: Acquired [$Path]" return $fs } catch [IO.IOException] { Write-Verbose 'LOCK: Busy, retrying...' Start-Sleep -Milliseconds 88 } } throw "LOCK: Timeout acquiring [$Path]" } } function Get-SNSessionPath { [CmdletBinding()] param( [string]$Name = 'Default' ) process { Join-Path -Path $script:ServicePointSessionStorePath -ChildPath ("{0}.json" -f $Name) } } function New-SNAuthorizationHeader { [CmdletBinding()] param( [Parameter(Mandatory)] [pscredential]$Credential ) process { $plainText = '{0}:{1}' -f $Credential.UserName, $Credential.GetNetworkCredential().Password 'Basic {0}' -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($plainText)) } } function New-SNSessionObject { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory)] [string]$Server, [Parameter(Mandatory)] [string]$Authorization, [Parameter(Mandatory)] [string]$UserName, [switch]$AllowInsecureSSL, [int]$SessionTtlHours = 8 ) process { $headers = @{ Authorization = $Authorization Accept = 'application/json' 'Content-Type' = 'application/json' } $webSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new() foreach ($entry in $headers.GetEnumerator()) { $webSession.Headers[$entry.Key] = $entry.Value } [pscustomobject]@{ PSTypeName = 'ServicePoint.Session' Name = $Name Server = $Server.TrimEnd('/') UserName = $UserName Created = Get-Date ExpirationDate = (Get-Date).AddHours($SessionTtlHours) AllowInsecureSSL = $AllowInsecureSSL.IsPresent Headers = $headers WebSession = $webSession } } } function Save-SNSession { [CmdletBinding()] param( [Parameter(Mandatory)] [pscustomobject]$Session ) process { $path = Get-SNSessionPath -Name $Session.Name $persisted = [pscustomobject]@{ Name = $Session.Name Server = $Session.Server UserName = $Session.UserName Created = $Session.Created ExpirationDate = $Session.ExpirationDate AllowInsecureSSL = $Session.AllowInsecureSSL Headers = $Session.Headers } $persisted | ConvertTo-Json -Depth 6 | Set-Content -Path $path } } function Restore-SNSession { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Path ) process { $saved = Get-Content -Path $Path -Raw | ConvertFrom-Json -ErrorAction Stop $webSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new() foreach ($header in $saved.Headers.psobject.Properties) { $webSession.Headers[$header.Name] = [string]$header.Value } [pscustomobject]@{ PSTypeName = 'ServicePoint.Session' Name = $saved.Name Server = $saved.Server UserName = $saved.UserName Created = [datetime]$saved.Created ExpirationDate = [datetime]$saved.ExpirationDate AllowInsecureSSL = [bool]$saved.AllowInsecureSSL Headers = @{} WebSession = $webSession } | ForEach-Object { foreach ($header in $saved.Headers.psobject.Properties) { $_.Headers[$header.Name] = [string]$header.Value } $_ } } } function Test-SNSession { [CmdletBinding()] param( [AllowNull()] [pscustomobject]$Session, [string]$Server, [string]$UserName ) process { if ($null -eq $Session) { return $false } if (-not $Session.Headers.Authorization) { return $false } if ((Get-Date) -ge [datetime]$Session.ExpirationDate) { return $false } if ($Server -and $Session.Server.TrimEnd('/') -ne $Server.TrimEnd('/')) { return $false } if ($UserName -and $Session.UserName -ne $UserName) { return $false } return $true } } function Get-SNSession { [CmdletBinding()] param( [string]$Name = 'Default', [string]$Server, [pscredential]$Credential ) begin { $userName = $Credential.UserName } process { $globalSession = $Global:ServicePointSessions[$Name] if (Test-SNSession -Session $globalSession -Server $Server -UserName $userName) { return $globalSession } if ($globalSession) { $Global:ServicePointSessions.Remove($Name) } $path = Get-SNSessionPath -Name $Name if (-not (Test-Path -Path $path -PathType Leaf)) { return $null } try { $restored = Restore-SNSession -Path $path if (Test-SNSession -Session $restored -Server $Server -UserName $userName) { $Global:ServicePointSessions[$Name] = $restored return $restored } } catch { Write-Verbose "Failed to restore ServicePoint session: $_" } return $null } } function New-SNSession { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Server, [Parameter(Mandatory)] [pscredential]$Credential, [string]$Name = 'Default', [switch]$AllowInsecureSSL, [switch]$Force, [switch]$PassThru, [int]$SessionTtlHours = 8 ) begin { $normalizedServer = if ($Server -match '^https?://') { $Server } else { 'https://{0}' -f $Server } if (-not $Force.IsPresent) { $existingSession = Get-SNSession -Name $Name -Server $normalizedServer -Credential $Credential } } process { if ($existingSession) { return $PassThru.IsPresent ? $existingSession : $null } $lockName = '{0}.lock' -f $Name $lockPath = Join-Path -Path $script:ServicePointSessionStorePath -ChildPath $lockName $lock = $null try { $lock = Lock-FileMutex -Path $lockPath if (-not $Force.IsPresent) { $retrySession = Get-SNSession -Name $Name -Server $normalizedServer -Credential $Credential if ($retrySession) { return $PassThru.IsPresent ? $retrySession : $null } } $authorization = New-SNAuthorizationHeader -Credential $Credential $session = New-SNSessionObject -Name $Name -Server $normalizedServer -Authorization $authorization -UserName $Credential.UserName -AllowInsecureSSL:$AllowInsecureSSL -SessionTtlHours $SessionTtlHours Save-SNSession -Session $session $Global:ServicePointSessions[$Name] = $session if ($PassThru.IsPresent) { return $session } } finally { if ($lock) { $lock.Dispose() } } } } function Invoke-SNRequest { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')] [string]$Method, [Parameter(Mandatory)] [string]$Uri, [object]$Body, [string]$SessionName = 'Default', [string]$Server, [pscredential]$Credential, [switch]$AllowInsecureSSL, [int]$TimeoutSec = 60 ) begin { $session = Get-SNSession -Name $SessionName -Server $Server -Credential $Credential if (-not $session) { if (-not $Server -or -not $Credential) { throw 'A reusable ServicePoint session was not found. Provide -Server and -Credential, or create a session first with New-SNSession.' } $session = New-SNSession -Name $SessionName -Server $Server -Credential $Credential -AllowInsecureSSL:$AllowInsecureSSL -PassThru } } process { $requestSplat = @{ Method = $Method Uri = $Uri WebSession = $session.WebSession Headers = $session.Headers ContentType = 'application/json' TimeoutSec = $TimeoutSec SkipCertificateCheck = $session.AllowInsecureSSL StatusCodeVariable = 'statusCode' ErrorAction = 'Stop' } if ($PSBoundParameters.ContainsKey('Body')) { $requestSplat.Body = $Body | ConvertTo-Json -Depth 10 } try { $response = Invoke-RestMethod @requestSplat [pscustomobject]@{ Status = 'success' HttpStatusCode = [int]$statusCode Session = $session Body = $response } } catch { $status = $null $rawBody = $null $parsedBody = $null if ($_.Exception.Response) { try { $status = [int]$_.Exception.Response.StatusCode } catch { $status = $null } try { $reader = [IO.StreamReader]::new($_.Exception.Response.GetResponseStream()) $rawBody = $reader.ReadToEnd() $reader.Dispose() } catch { $rawBody = $null } } if ($rawBody) { try { $parsedBody = $rawBody | ConvertFrom-Json -ErrorAction Stop } catch { $parsedBody = $rawBody } } $errorCode = $null $detail = $_.Exception.Message $message = $null if ($parsedBody -and $parsedBody.error) { $errorCode = $parsedBody.error.message $detail = $parsedBody.error.detail $message = $parsedBody.error.message } elseif ($parsedBody -is [string]) { $detail = $parsedBody } [pscustomobject]@{ Status = 'failure' HttpStatusCode = $status ErrorCode = $errorCode Message = $message Detail = $detail Body = $parsedBody Session = $session } } } } |