WSO2-Auth.psm1

<#
    .NOTES
    --------------------------------------------------------------------------------
     Code generated by: SAPIEN Technologies, Inc., PowerShell Studio 2021 v5.8.183
     Generated on: 3/19/2021 4:56 PM
     Generated by: dz053479
    --------------------------------------------------------------------------------
    .DESCRIPTION
        Script generated by PowerShell Studio 2021
#>



    <#
        ===========================================================================
         Created with: SAPIEN Technologies, Inc., PowerShell Studio 2020 v5.7.173
         Created on: 7/23/2020 12:32 PM
         Created by: Dakota Zinn
         Organization:
         Filename: WSO2-Auth.psm1
        -------------------------------------------------------------------------
         Module Name: WSO2-Auth
        ===========================================================================
    #>

    
    function Connect-WSO2
    {
        [CmdletBinding(DefaultParameterSetName = 'Using Kerberos')]
        param
        (
            [Parameter(ParameterSetName = 'JWT',
                       Mandatory = $true)]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string]$HostName,
            [Parameter(ParameterSetName = 'JWT')]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [int]$TokenPort = 8243,
            [Parameter(ParameterSetName = 'JWT')]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [int]$APIPort = 9443,
            [Parameter(ParameterSetName = 'JWT',
                       Mandatory = $false)]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string]$ClientId,
            [Parameter(ParameterSetName = 'JWT',
                       Mandatory = $false)]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string]$ClientSecret,
            [Parameter(ParameterSetName = 'JWT')]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string]$Base64Key,
            [Parameter(ParameterSetName = 'JWT')]
            [string]$JWT,
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string]$KerberosToken,
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string]$KerberosRealm,
            [Parameter(ParameterSetName = 'Username/Password')]
            [string]$Username,
            [Parameter(ParameterSetName = 'Username/Password')]
            [string]$Password,
            [Parameter(ParameterSetName = 'RefreshToken')]
            [string]$RefreshToken,
            [Parameter(ParameterSetName = 'JWT')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [string[]]$Scopes,
            [Parameter(ParameterSetName = 'JWT')]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [switch]$PassThru,
            [Parameter(ParameterSetName = 'JWT')]
            [Parameter(ParameterSetName = 'RefreshToken')]
            [Parameter(ParameterSetName = 'Username/Password')]
            [Parameter(ParameterSetName = 'Using Kerberos')]
            [Alias('ValidFor')]
            [int]$TokenValidityPeriod,
            [switch]$NoAuthorization
        )
        
        process
        {
            if (!$NoAuthorization.IsPresent)
            {
                if ($base64Key -eq "" -and ("" -in @($ClientId, $ClientSecret)))
                {
                    $ErrorRecord = New-Object System.Management.Automation.ErrorRecord("An application identifier is required!`r`n(i.e. ConsumerKey/Secret, Base64Key)", "", [System.Management.Automation.ErrorCategory]::InvalidArgument, $null)
                    $PSCmdlet.ThrowTerminatingError($ErrorRecord)
                }
                elseif ($ClientId -and $ClientSecret)
                { $base64Key = "$ClientID`:$ClientSecret" | ConvertTo-Base64 }
                if ($base64Key)
                { <#Do NOT perform any transformations on the data#> }
                
                $OneAuthError = New-Object System.Management.Automation.ErrorRecord("Only one authentication method is allowed!`r`n(Kerberos, Refresh Token, or JWT.)", "", [System.Management.Automation.ErrorCategory]::InvalidArgument, $PSBoundParameters)
                switch ($PSBoundParameters.RefreshToken)
                {
                    { $_ -eq $null }{ break }
                    { $KerberosToken -ne "" } {
                        Write-Debug "Token Refresh: Kerberos Token"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $JWT -ne "" } {
                        Write-Debug "Token Refresh: JWT"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $Username -ne "" } {
                        Write-Debug "Token Refresh: Username"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    default { $Body = "grant_type=refresh_token&refresh_token=$RefreshToken" }
                }
                switch ($PSBoundParameters.KerberosToken)
                {
                    { $_ -eq $null }{ break }
                    { $JWT -ne "" } {
                        Write-Debug "Kerberos Autnentication"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $RefreshToken -ne "" } {
                        Write-Debug "Kerberos Autnentication"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $Username -ne "" } {
                        Write-Debug "Kerberos Autnentication"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $KerberosRealm -eq "" } {
                        $KerbError = New-Object System.Management.Automation.ErrorRecord("Cannot use Kerberos Authentication; KerberosRealm not provided", "", [System.Management.Automation.ErrorCategory]::InvalidArgument, $null)
                        $PSCmdlet.ThrowTerminatingError($KerbError)
                    }
                    default{
                        $body = "grant_type=kerberos&kerberos_realm=$KerberosRealm&kerberos_token=$KerberosToken"
                    }
                }
                
                switch ($PSBoundParameters.JWT)
                {
                    { $_ -eq $null }{ break }
                    { $KerberosToken -ne "" }{
                        Write-Debug "JWT Autnentication"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $RefreshToken -ne "" }{
                        Write-Debug "JWT Autnentication"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $Username -ne "" } {
                        Write-Debug "JWT Autnentication"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    default { $body = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT" }
                }
                
                switch ($PSBoundParameters.Username)
                {
                    { $_ -eq $null }{ break }
                    { $KerberosToken -ne "" } {
                        Write-Debug "Username : Has KerberosToken"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $JWT -ne "" } {
                        Write-Debug "Username : Has JWT"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $RefreshToken -ne "" } {
                        Write-Debug "Username : Has RefreshToken"
                        $PSCmdlet.ThrowTerminatingError($OneAuthError)
                    }
                    { $Password -ne "" } {
                        Write-Debug "Username/Password Authentication"
                        $Body = "grant_type=password&username=$Username&password=$Password"
                    }
                    default {
                        $UserError = New-Object System.Management.Automation.ErrorRecord("Cannot use Username Authentication; Password not provided", "", [System.Management.Automation.ErrorCategory]::InvalidArgument, $null)
                        $PSCmdlet.ThrowTerminatingError($UserError)
                    }
                }
                if (!$body)
                {
                    Write-Debug "Client Credentials"
                    $Body = "grant_type=client_credentials"
                }
                
                if ($Scopes)
                {
                    Write-Verbose "Using Scopes: $($Scopes -join ", ")"
                    $Body += "&scope=$($Scopes -join " ")"
                }
                
                $invocation = @{
                    Uri = "https://$HostName`:$TokenPort/token"
                    Headers = @{ Authorization = "Basic $base64Key" }
                    ContentType = "application/x-www-form-urlencoded"
                    Body = $Body
                    Method = "POST"
                }
                
            }
            
            if ($isLinux -or $isMacOS)
            { $TokenPath = "$env:HOME" }
            elseif ($isWindows)
            { $TokenPath = "$env:LOCALAPPDATA" }
            else
            { $TokenPath = "$env:LOCALAPPDATA" }
            
            $TokenFolder = "$TokenPath/WSO2"
            if (!(Test-Path $TokenFolder -ErrorAction SilentlyContinue))
            { New-Item -Path $TokenFolder -ItemType directory | Out-Null }
            
            $TokenFile = "$TokenFolder/Token"
            
            try
            {
                if ($NoAuthorization.IsPresent)
                {
                    $token = [pscustomobject]@{
                        HostName = "$HostName"
                        APIPort  = $APIPort
                    }
                }
                else
                {
                    $token = Invoke-RestMethod @invocation
                    if ($token.expires_in -lt 31557600)
                    { $token | Add-Member -MemberType NoteProperty -Name expire_time -Value ((Get-Date).AddSeconds($token.expires_in)) }
                    else { $token | Add-Member -MemberType NoteProperty -Name expire_time -Value ((Get-Date).AddYears(15)) }
                    #$token | Add-Member -MemberType NoteProperty -Name expires_in -Value $token
                    $token | Add-Member -MemberType NoteProperty -Name "HostName" -Value $HostName
                    $token | Add-Member -MemberType NoteProperty -Name "APIPort" -Value $APIPort
                    $token | Add-Member -MemberType NoteProperty -Name "TokenPort" -Value $TokenPort
                    $token | Add-Member -MemberType NoteProperty -Name key -Value $base64Key
                    $token | Add-Member -MemberType NoteProperty -Name "ApplicationToken" -Value ($Body -like "grant_type=client_credentials*")
                }
                $token | Convert-ObjectToBase64 | Out-File $TokenFile -Force
                
                
                if ($PassThru)
                {
                    if (!$NoAuthorization)
                    {
                        $token = $token | Select-Object * -ExcludeProperty key, token_type
                    }
                    return $token
                }
                
            }
            catch
            {
                if ($PSVersionTable.PSCompatibleVersions.major -contains 7)
                {
                    $StatusCode = $_.Exception.Response.StatusCode.value__
                    $ErrCategory = [system.management.automation.errorcategory]::InvalidOperation
                    $ErrorRecord = New-Object System.Management.Automation.ErrorRecord($_.Exception.Message, "$statuscode", $ErrCategory, $invocation)
                    $PSCmdlet.ThrowTerminatingError($ErrorRecord)
                }
                else
                {
                    $ErrorRecord = $_.Exception.Response.GetResponseStream() | Get-ErrorResult -Invocation $Invocation -StatusCode $_.Exception.Response.StatusCode.value__
                    $PSCmdlet.ThrowTerminatingError($ErrorRecord)
                }
            }
            
        }
    }
    
    function Disconnect-WSO2
    {
        [CmdletBinding(DefaultParameterSetName = 'Using ClientID/Secret')]
        param
        (
            [Parameter(Mandatory = $false,
                       ValueFromPipelineByPropertyName = $true,
                       Position = 1)]
            [Alias('Refresh_Token')]
            [string]$RefreshToken,
            [Parameter(ParameterSetName = 'Using Base64',
                       Mandatory = $false,
                       ValueFromPipelineByPropertyName = $true)]
            [Alias('Key')]
            [string]$Base64Key,
            [Parameter(ValueFromPipelineByPropertyName = $true,
                       Position = 1)]
            [Alias('access_token')]
            [string]$AccessToken,
            [Parameter(ParameterSetName = 'Using ClientID/Secret')]
            [string]$ClientID,
            [Parameter(ParameterSetName = 'Using ClientID/Secret')]
            [string]$ClientSecret
        )
        begin
        {
            
            if ($isLinux -or $isMacOS)
            { $TokenPath = "$env:HOME" }
            if ($isWindows)
            { $TokenPath = "$env:LOCALAPPDATA" }
            if (!$tokenPath)
            { $TokenPath = "$env:LOCALAPPDATA" }
            $TokenFolder = "$TokenPath/WSO2"
            $TokenFile = "$TokenFolder/Token"
        }
        process
        {
            $TokenInfo = Get-Content $TokenFile | ConvertFrom-Base64 | ConvertFrom-Json
            $TokenPort = $TokenInfo.TokenPort
            $HostName = $TokenInfo.HostName
            $tokenAPI = "https://$HostName`:$TokenPort/revoke"
            
            if (!$PSBoundParameters.Base64Key -and !$PSBoundParameters.ClientID -and !$PSBoundParameters.ClientSecret)
            {
                $Token = (Get-Content $TokenFile | ConvertFrom-Base64 | ConvertFrom-Json)
                $Base64Key = $Token.key
                $AccessToken = $Token.access_token
            }
            if ($PSBoundParameters.ClientID -and $PSBoundParameters.ClientSecret)
            { $Base64Key = "$($PSBoundParameters.ClientID)`:$($PSBoundParameters.ClientSecret)" | ConvertTo-Base64 }
            if ($Base64Key)
            {
                if ($PSBoundParameters.AccessToken)
                { $Body = "token=$($PSBoundParameters.AccessToken)&token_type_hint=access_token" }
                elseif ($PSBoundParameters.RefreshToken)
                { $Body = "token=$($PSBoundParameters.RefreshToken)&token_type_hint=refresh_token" }
                $header = @{ Authorization = "Basic $Base64Key" }
                $invocation = @{
                    Uri            = $tokenAPI
                    Headers        = $header
                    ContentType = "application/x-www-form-urlencoded"
                    Body        = $Body
                    Method        = "POST"
                }
                
                try
                {
                    
                    $response = Invoke-RestMethod @invocation
                    if ($PSBoundParameters.AccessToken)
                    {
                        if ($TokenInfo.access_token -eq $PSBoundParameters.AccessToken)
                        {
                            $TokenInfo.access_token = ""
                            $TokenInfo.refresh_token = ""
                            $TokenInfo | ConvertTo-Json | ConvertTo-Base64 | Out-File $TokenFile -Force
                        }
                    }
                    elseif ($PSBoundParameters.RefreshToken)
                    {
                        if ($TokenInfo.refresh_token -eq $PSBoundParameters.RefreshToken)
                        {
                            $TokenInfo.access_token = ""
                            $TokenInfo.refresh_token = ""
                            $TokenInfo | ConvertTo-Json | ConvertTo-Base64 | Out-File $TokenFile -Force
                        }
                    }
                    #Remove-Item -Path $TokenFile -Force -ErrorAction Ignore
                    Write-Output "Token Revoked"
                }
                catch
                {
                    $result = $_.Exception.Response.GetResponseStream()
                    $reader = New-Object System.IO.StreamReader($result)
                    $reader.BaseStream.Position = 0
                    $reader.DiscardBufferedData()
                    $response = $reader.ReadToEnd();
                    $ErrorRecord = New-Object System.Management.Automation.ErrorRecord("$response", "", [system.management.automation.errorcategory]::InvalidResult, $invocation)
                    $PSCmdlet.ThrowTerminatingError($ErrorRecord)
                }
            }
        }
    }
    
    function Get-WSO2Token
    {
        [CmdletBinding()]
        param ()
        
        if ($isLinux -or $isMacOS)
        { $TokenPath = "$env:HOME" }
        elseif ($isWindows)
        { $TokenPath = "$env:LOCALAPPDATA" }
        else
        { $TokenPath = "$env:LOCALAPPDATA" }
        
        $TokenFolder = "$TokenPath/WSO2"
        if (!(Test-Path $TokenFolder -ErrorAction SilentlyContinue))
        { New-Item -Path $TokenFolder -ItemType directory | Out-Null }
        
        $TokenFile = "$TokenFolder/Token"
        
        if (!(Test-Path $TokenFile -ErrorAction SilentlyContinue))
        { throw "Could not get local token, use Connect-WSO2 to get a new token" }
        
        #if (!(Test-Path "$TokenFile" -ErrorAction SilentlyContinue))
        #{ $PSCmdlet.ThrowTerminatingError($_) }
        
        $Token = gc $TokenFile | ConvertFrom-Base64 | ConvertFrom-JSON
        if ($Token.access_token)
        {
            $AccessToken = $Token | select access_token, refresh_token, hostname, expire_time, apiport, tokenport, scope, expires_in
            $AccessToken.expire_time = ([datetime]$AccessToken.expire_time).toLocalTime()
            $AccessToken.expires_in = $([math]::Round((New-TimeSpan -Start (Get-Date) -End $AccessToken.expire_time).TotalSeconds))
            if ($Token.id_token)
            { $AccessToken | Add-Member -MemberType NoteProperty -Name "id_token" -Value $Token.id_token }
            if ($AccessToken.expires_in -le 0)
            {
                Write-Verbose "Token has expired"
                #Token has expired
                if ($Token.applicationtoken -eq $true)
                {
                    write-verbose "Attempting to retreive new Application Token"
                    try { Connect-WSO2 -HostName $Token.hostname -TokenPort $Token.tokenport -Base64Key $Token.key }
                    catch { $PSCmdlet.ThrowTerminatingError($_) }
                }
                elseif ($Token.key -and $Token.refresh_token -notin @("",$null))
                {
                    Write-Verbose "Attempting to automatically refresh the token"
                    try
                    {
                        $Token = Connect-WSO2 -HostName $Token.hostname -TokenPort $Token.tokenport -Base64Key $Token.key -RefreshToken $Token.refresh_token -PassThru
                        return $Token
                    }
                    catch { $PSCmdlet.ThrowTerminatingError($_) }
                }
                
                else
                {
                    $ErrorRecord = New-Object System.Management.Automation.ErrorRecord("Access Token has expired.", "", [System.Management.Automation.ErrorCategory]::SecurityError, $null)
                    $PSCmdlet.ThrowTerminatingError($ErrorRecord)
                }
            }
            else { return $AccessToken }
        }
        else { return $Token }
    }
    
    function Register-DynamicClient
    {
    <#
        .SYNOPSIS
            Register a client to use admin/publisher/devportal APIs
         
        .DESCRIPTION
            Register a client to use admin/publisher/devportal APIs
         
        .PARAMETER Credentials
            Credentials to register a dynamic client. This is typically the Admin user credentials
         
        .PARAMETER DCREndpoint
            The full dynamic client registration url
            e.g. https://localhost:9443/client-registration/v0.16/register
         
        .PARAMETER callbackUrl
            The URL callback for the application, this will be where the user is redirected after signing in.
         
        .PARAMETER ClientName
            Name of the Application/Client
         
        .PARAMETER Owner
            Owner of the Client, should match the user in the credentials
         
        .PARAMETER AllowedGrantTypes
            List of grant types allowed to be used with the client
         
        .PARAMETER SaaSApp
            Switch that indicates whether the client is a SaaS App
         
        .EXAMPLE
            PS C:\> Register-DynamicClient -DCREndpoint 'https://localhost:9443/client-registration/v0.16/register' -ClientName 'admin_app' `
            -Owner 'admin' -AllowedGrantTypes "client_credentials refresh_token"
         
    #>

        
        [CmdletBinding()]
        param
        (
            [pscredential]$Credentials = (Get-Credential),
            [Parameter(Mandatory = $false)]
            [string]$DCREndpoint,
            [string]$callbackUrl,
            [Parameter(Mandatory = $true)]
            [Alias('AppName', 'ServiceProviderName')]
            [string]$ClientName,
            [Parameter(Mandatory = $true)]
            [string]$Owner,
            [Parameter(Mandatory = $true)]
            [string[]]$AllowedGrantTypes,
            [switch]$SaaSApp
        )
        
        $JSON = [pscustomobject]@{
            callbackUrl = $PSBoundParameters.callbackurl
            clientName  = $PSBoundParameters.clientName
            owner        = $PSBoundParameters.owner
            grantType   = ($PSBoundParameters.AllowedGrantTypes -join " ")
            saasApp        = $SaaSApp.IsPresent
        }
        
        $body = $JSON | ConvertTo-Json
        
        try
        {
            Invoke-RestMethod -Headers @{ Authorization = "Basic $("$($credentials.UserName):$($Credentials.GetNetworkCredential().Password)" | ConvertTo-Base64)" } `
                              -Method POST -Uri $DCREndpoint -Body $body -ContentType "Application/JSON"
        }
        catch
        {
            if ($PSVersionTable.PSCompatibleVersions.major -contains 7)
            {
                $StatusCode = $_.Exception.Response.StatusCode.value__
                $ErrCategory = [system.management.automation.errorcategory]::InvalidOperation
                $ErrorRecord = New-Object System.Management.Automation.ErrorRecord($_.Exception.Message, "$statuscode", $ErrCategory, $invocation)
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
            else
            {
                $ErrorRecord = $_.Exception.Response.GetResponseStream() | Get-ErrorResult -Invocation $Invocation -StatusCode $_.Exception.Response.StatusCode.value__
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
        }
    }
    
    function Unregister-DynamicClient
    {
    <#
        .SYNOPSIS
            Delete an Oauth application
         
        .DESCRIPTION
            Delete an Oauth application
         
        .PARAMETER Credentials
            A description of the Credentials parameter.
         
        .PARAMETER DCRendpoint
            A description of the DCRendpoint parameter.
         
        .PARAMETER ClientId
            A description of the ClientId parameter.
         
        .EXAMPLE
            PS C:\> Unregister-DynamicClient -Credentials $Credentials -DCREndpoint 'https://localhost:9443/api/identity/oauth2/dcr/v1.1/register/' -ClientId 's6BhdRkqt3'
         
        .NOTES
            Additional information about the function.
    #>

        
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory = $true)]
            [pscredential]$Credentials,
            [Parameter(Mandatory = $true)]
            [string]$DCREndpoint,
            [Parameter(Mandatory = $true)]
            [string]$ClientId
        )
        
        try
        {
            Invoke-RestMethod -Headers @{ Authorization = "Basic $("$($credentials.UserName):$($Credentials.GetNetworkCredential().Password)" | ConvertTo-Base64)" } `
                              -Method DELETE -Uri "$DCREndpoint/$ClientId" -ContentType "Application/JSON"
        }
        catch
        {
            if ($PSVersionTable.PSCompatibleVersions.major -contains 7)
            {
                $StatusCode = $_.Exception.Response.StatusCode.value__
                $ErrCategory = [system.management.automation.errorcategory]::InvalidOperation
                $ErrorRecord = New-Object System.Management.Automation.ErrorRecord($_.Exception.Message, "$statuscode", $ErrCategory, $invocation)
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
            else
            {
                $ErrorRecord = $_.Exception.Response.GetResponseStream() | Get-ErrorResult -Invocation $Invocation -StatusCode $_.Exception.Response.StatusCode.value__
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
        }
    }
    
    #region Conversions
    function Convert-FileToBase64
    {
        param
        (
            [Parameter(ValueFromPipeline = $true)]
            $Filepath
        )
        process
        {
            $bufferSize = 9000 # should be a multiplier of 3
            $buffer = New-Object byte[] $bufferSize
            
            $reader = [System.IO.File]::OpenRead($Filepath)
            $writer = ""
            $bytesRead = 0
            do
            {
                $bytesRead = $reader.Read($buffer, 0, $bufferSize);
                $writer += ([Convert]::ToBase64String($buffer, 0, $bytesRead))
            }
            while ($bytesRead -eq $bufferSize);
            
            $reader.Dispose()
            return $writer
        }
        
    }
    
    
    function Convert-Base64ToFile
    {
        param
        (
            $B64String,
            $OutFilePath
        )
        $bytes = [Convert]::FromBase64String($B64String)
        [IO.File]::WriteAllBytes($OutFilePath, $bytes)
    }
    
    
    function Convert-ObjectToBase64
    {
        [CmdletBinding()]
        [OutputType([string])]
        param
        (
            [Parameter(Mandatory = $true,
                       ValueFromPipeline = $true)]
            $psobject
        )
        
        process
        {
            $ObjString = [string]($psobject | ConvertTo-Json -Depth 10 -Compress)
            $String = [System.Text.Encoding]::UTF8.GetBytes($ObjString)
            [System.convert]::ToBase64String($string)
        }
    }
    
    
    function ConvertTo-Base64
    {
        param
        (
            [Parameter(Mandatory = $true,
                       ValueFromPipeline = $true)]
            [string]$String
        )
        
        Process
        {
            $BytesString = [System.Text.Encoding]::UTF8.GetBytes($String)
            [System.convert]::ToBase64String($BytesString)
        }
    }
    
    
    function ConvertFrom-Base64
    {
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory = $true,
                       ValueFromPipeline = $true)]
            [string]$base64String
        )
        
        process
        {
            $bytes = [System.Convert]::FromBase64String($base64String)
            [System.Text.Encoding]::UTF8.GetString($bytes)
        }
    }
    
    
    function ConvertFrom-EPOCHDate
    {
        [OutputType([datetime])]
        param
        (
            [Parameter(ValueFromPipeline = $true)]
            $date
        )
        begin { $UTCOffset = [System.TimeZoneInfo]::Local.GetUtcOffset((get-date)).totalseconds }
        process
        {
            if ($date)
            {
                $newdate = (get-date -Date "1/1/1970").AddSeconds($date).AddSeconds($UTCOffset)
                return $newdate
            }
            else { return $null }
        }
    }
    
    
    function ConvertTo-EPOCHDate
    {
        param
        (
            [Parameter(ValueFromPipeline = $true)]
            [datetime]$Date
        )
        begin { $UTCOffset = (([System.TimeZoneInfo]::Local.GetUtcOffset((get-date)).totalseconds) * -1) }
        process
        { [System.Math]::Round((New-TimeSpan (get-date -Date "1/1/1970") ($Date).AddSeconds($UTCOffset)).TotalSeconds) }
    }
    
    
    function ConvertFrom-Hashtable
    {
        param (
            [Parameter(
                       Position = 0,
                       Mandatory = $true,
                       ValueFromPipeline = $true,
                       ValueFromPipelineByPropertyName = $true
                       )]
            [object[]]$hashtable
        );
        
        begin { $i = 0; }
        
        process
        {
            foreach ($myHashtable in $hashtable)
            {
                if ($myHashtable.GetType().Name -eq 'hashtable')
                {
                    $output = New-Object -TypeName PsObject;
                    Add-Member -InputObject $output -MemberType ScriptMethod -Name AddNote -Value {
                        Add-Member -InputObject $this -MemberType NoteProperty -Name $args[0] -Value $args[1];
                    };
                    $myHashtable.Keys | Sort-Object | ForEach-Object {
                        $output.AddNote($_, $myHashtable.$_);
                    }
                    $output;
                }
                else
                {
                    Write-Warning "Index $i is not of type [hashtable]";
                }
                $i += 1;
            }
        }
    }
    
    
    function ConvertTo-HashTable
    {
        param (
            [Parameter(
                       Position = 0,
                       Mandatory = $true,
                       ValueFromPipeline = $true,
                       ValueFromPipelineByPropertyName = $true
                       )]
            [object[]]$psCustomObject
        );
        
        process
        {
            foreach ($myPsObject in $psObject)
            {
                $output = @{ };
                $myPsObject | Get-Member -MemberType *Property | ForEach-Object {
                    $output.($_.name) = $myPsObject.($_.name);
                }
                $output;
            }
        }
    }
    
    
    function ConvertFrom-Jwt
    {
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory = $true,
                       ValueFromPipeline = $true)]
            [string]$Token,
            [switch]$IncludeHeader
        )
        process
        {
            # Validate as per https://tools.ietf.org/html/rfc7519
            # Access and ID tokens are fine, Refresh tokens will not work
            if (!$Token.Contains(".") -or !$Token.StartsWith("eyJ")) { Write-Error "Invalid token" -ErrorAction Stop }
            
            # Extract header and payload
            $tokenheader, $tokenPayload = $Token.Split(".").Replace('-', '+').Replace('_', '/')[0 .. 1]
            
            # Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
            while ($tokenheader.Length % 4) { Write-Debug "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "=" }
            while ($tokenPayload.Length % 4) { Write-Debug "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
            
            Write-Debug "Base64 encoded (padded) header:`n$tokenheader"
            Write-Debug "Base64 encoded (padded) payoad:`n$tokenPayload"
            
            # Convert header from Base64 encoded string to PSObject all at once
            $header = [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json
            Write-Debug "Decoded header:`n$header"
            
            # Convert payload to string array
            $tokenArray = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($tokenPayload))
            Write-Debug "Decoded array in JSON format:`n$tokenArray"
            
            # Convert from JSON to PSObject
            $tokobj = $tokenArray | ConvertFrom-Json
            Write-Debug "Decoded Payload:"
            
            if ($IncludeHeader) { $header }
            return $tokobj
        }
        
    }
    #endregion Conversions
    
    
    function Set-UseUnsafeHeaderParsing
    {
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "This is required for Kerberos Auth, not to be used by end-user")]
        param (
            [Parameter(Mandatory, ParameterSetName = 'Enable')]
            [switch]$Enable,
            [Parameter(Mandatory, ParameterSetName = 'Disable')]
            [switch]$Disable
        )
        
        $ShouldEnable = $PSCmdlet.ParameterSetName -eq 'Enable'
        
        $netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection])
        
        if ($netAssembly)
        {
            $bindingFlags = [Reflection.BindingFlags] 'Static,GetProperty,NonPublic'
            $settingsType = $netAssembly.GetType('System.Net.Configuration.SettingsSectionInternal')
            
            $instance = $settingsType.InvokeMember('Section', $bindingFlags, $null, $null, @())
            
            if ($instance)
            {
                $bindingFlags = 'NonPublic', 'Instance'
                $useUnsafeHeaderParsingField = $settingsType.GetField('useUnsafeHeaderParsing', $bindingFlags)
                
                if ($useUnsafeHeaderParsingField)
                { $useUnsafeHeaderParsingField.SetValue($instance, $ShouldEnable) }
            }
        }
    }
    
    function Get-ErrorResult
    {
        [CmdletBinding()]
        param
        (
            [Parameter(ValueFromPipeline = $true)]
            $Result,
            $Invocation,
            [int]$statuscode
        )
        process
        {
            $reader = New-Object System.IO.StreamReader($result)
            $reader.BaseStream.Position = 0
            $reader.DiscardBufferedData()
            $response = $reader.ReadToEnd();
            
            switch -Wildcard ($response)
            {
                "*Missing parameters*"{ $ErrCategory = [system.management.automation.errorcategory]::InvalidArgument }
                "*Forbidden*"{ $ErrCategory = [system.management.automation.errorcategory]::PermissionDenied }
                default { $ErrCategory = [system.management.automation.errorcategory]::InvalidOperation }
            }
            $ErrorRecord = New-Object System.Management.Automation.ErrorRecord("$response", "$statuscode", $ErrCategory, $invocation)
            return $ErrorRecord
        }
    }
    
    
    function Get-AzureJWT
    {
        [CmdletBinding()]
        param
        (
            [string]$ClientId,
            [string]$TenantId,
            [switch]$ForcePrompt
        )
        
            Import-Module MSAL.PS
            $Splat = @{
                ClientId = $PSBoundParameters.ClientID
                TenantId = $TenantId
                RedirectURI = "http://localhost"
            }
            try
            {
                if ($PSBoundParameters.ForcePrompt)
                {
                    Write-Verbose "Acquiring ID token in forground through MSAL"
                    $AzureToken = Get-MsalToken @splat -Interactive
                }
                else
                {
                    Write-Verbose "Acquiring ID token silently through MSAL"
                    $AzureToken = Get-MsalToken @splat -IntegratedWindowsAuth
                }
            }
            catch [Microsoft.Identity.Client.MsalClientException]{
                $AzureError = $_.exception.message
            }
            
            if ($AzureError)
            {
                switch -Wildcard ($AzureError)
                {
                    "Failed to get user*"{
                        Try
                        {
                            Write-Verbose "Acquiring ID Token Interactively through MSAL"
                            $AzureToken = Get-MsalToken @splat -Interactive
                        }
                        catch
                        {
                            $ErrCategory = [system.management.automation.errorcategory]::AuthenticationError
                            $ErrorRecord = New-Object System.Management.Automation.ErrorRecord("Could not acquire a token interactively or silently.'", "", $ErrCategory, $null)
                            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
                        }
                    }
                }
            }
            
            if ($AzureToken)
            { return $AzureToken.IdToken }
    }