Client/PVEConfiguration.ps1

#
# Proxmox VE
# Generated module to access all Proxmox VE Api Endpoints
# Version: 0.3
# Contact: amna.wolf@gmail.com
# Generated by OpenAPI Generator: https://openapi-generator.tech
#

<#
.SYNOPSIS

Get the configuration object 'PVEConfiguration' from file.

.DESCRIPTION

Get the configuration object 'PVEConfiguration' from file.

.OUTPUTS

System.Collections.Hashtable
#>

function Get-PVEConfigurationFromFile{
    if((Test-Path ($env:USERPROFILE + '\PVESettings.txt'))){
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR((Get-Content ($env:USERPROFILE + '\PVESettings.txt') | ConvertTo-SecureString))
        $Unsecure = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
        $FileConfig = $Unsecure | ConvertFrom-Json -AsHashtable
        $Credential = New-Object System.Management.Automation.PSCredential($FileConfig["Credential"].UserName, (ConvertTo-SecureString $FileConfig["Credential"].Password))
        $FileConfig["Credential"] = $Credential
        return $FileConfig
    }else{
        return $false
    }
}
<#
.SYNOPSIS

Initialize 'PVE'.

.DESCRIPTION

Loads an existing configuration if exists from current userprofile, performs login.

.OUTPUTS

boolean
#>

function Initialize-PVE{
    $FileConfig = Get-PVEConfigurationFromFile
    if($FileConfig){
        $Script:Configuration = $FileConfig
        return Invoke-PVELogin -Silent
    }else{
        Invoke-PVELogin
    }
}

<#
.SYNOPSIS

Get the configuration object 'PVEConfiguration'.

.DESCRIPTION

Get the configuration object 'PVEConfiguration'.

.OUTPUTS

System.Collections.Hashtable
#>

function Get-PVEConfiguration{

    if(($Script:Configuration.Count -eq 0) -and (Test-Path ($env:USERPROFILE + '\PVESettings.txt'))){
        Initialize-PVE
    }

    $Configuration = $Script:Configuration

    if ([string]::IsNullOrEmpty($Configuration["BaseUrl"])){
        $Configuration["BaseUrl"] = "http://localhost";
    }

    if (!$Configuration.containsKey("Credential")){
        $Configuration["Credential"] = $null
    }

    if (!$Configuration.containsKey("TokenId")){
        $Configuration["TokenId"] = $null
    }

    if (!$Configuration.containsKey("ApiToken")){
        $Configuration["ApiToken"] = $null
    }

    if (!$Configuration["DefaultHeaders"]){
        $Configuration["DefaultHeaders"] = @{}
    }

    if (!$Configuration.containsKey("SkipCertificateCheck")){
        $Configuration["SkipCertificateCheck"] = $false
    }

    if (!$Configuration.containsKey("Proxy")){
        $Configuration["Proxy"] = $null
    }
    
    Return $Configuration
}

<#
.SYNOPSIS

Set the configuration.

.DESCRIPTION

Set the configuration.

.PARAMETER BaseUrl
Base URL of the HTTP endpoints

.PARAMETER ApiToken
API Key for authentication/authorization

.PARAMETER Cookie
Cookie for authentication/authorization

.PARAMETER AccessToken
Access token for authentication/authorization

.PARAMETER SkipCertificateCheck
Skip certificate verification

.PARAMETER DefaultHeaders
Default HTTP headers to be included in the HTTP request

.PARAMETER Proxy
Proxy setting in the HTTP request, e.g.

$proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials

.PARAMETER PassThru
Return an object of the Configuration

.OUTPUTS

System.Collections.Hashtable
#>

function Set-PVEConfiguration{

    [CmdletBinding()]
    Param(
        [string]$BaseUrl,
        [PSCredential] $Credential,
        [ValidateSet('token','ticket')]
        [string] $LoginMethod,
        [AllowEmptyString()]
        [string]$Cookie,
        [switch]$SkipCertificateCheck,
        [hashtable]$DefaultHeaders,
        [System.Object]$Proxy,
        [switch]$PassThru,
        [switch]$Persistent
    )

    Process{

        if($BaseUrl){
            # validate URL
            $URL = $BaseUrl -as [System.URI]
            if (!($null -ne $URL.AbsoluteURI -and $URL.Scheme -match '[http|https]')){
                throw "Invalid URL '$($BaseUrl)' cannot be used in the base URL."
            }
            $Script:Configuration["BaseUrl"] = $BaseUrl
        }

        if($Credential){
            $Script:Configuration['Credential'] = $Credential
        }

        if($LoginMethod){
            $Script:Configuration['LoginMethod'] = $LoginMethod
        }

        if ($DefaultHeaders){
            $Script:Configuration['DefaultHeaders'] = $DefaultHeaders
        }

        if ($Proxy -ne $null){
            if ($Proxy.GetType().FullName -ne "System.Net.SystemWebProxy" -and $Proxy.GetType().FullName -ne "System.Net.WebRequest+WebProxyWrapperOpaque"){
                throw "Incorrect Proxy type '$($Proxy.GetType().FullName)'. Must be System.Net.SystemWebProxy or System.Net.WebRequest+WebProxyWrapperOpaque."
            }
            $Script:Configuration['Proxy'] = $Proxy
        } else{
            $Script:Configuration['Proxy'] = $null
        }
        if ($Persistent){
            $SaveConfig = $Script:Configuration
            $SaveConfig["Credential"] = @{
                UserName = $Script:Configuration["Credential"].UserName
                Password = (ConvertFrom-SecureString -SecureString $Script:Configuration["Credential"].Password)
            }
            $SaveConfig | ConvertTo-Json -Compress | ConvertTo-SecureString -AsPlainText | ConvertFrom-SecureString  | set-content ($env:USERPROFILE + '\PVESettings.txt')
        }

        if ($PassThru){
            $Script:Configuration
        }
    }
}

function Invoke-PVELogin {
    [CmdletBinding()]
    Param(
        [switch]$Silent,
        [ValidateSet('ticket','token')]
        [string]$LoginMethod
    )

    if(!$Script:Configuration["BaseUrl"]){
        if($Silent){
            throw "Error: BaseUrl not set in Configuration"
        }else{
            $Script:Configuration["BaseUrl"] = Read-Host "BaseUrl not set. Please insert the BaseUrl to your PVE Api. e.G. https://pve.local:8006/api2/json"
        }
    }
    $oldLoginMethod = $script:Configuration["LoginMethod"]
    if($LoginMethod){
        $script:Configuration["LoginMethod"] = $LoginMethod
    }elseif($script:Configuration["LoginMethod"]){
        $LoginMethod = $Script:Configuration["LoginMethod"]
        if(!$Silent){
            Write-Host "Using login method $LoginMethod set via configuration."
        }
    }else{
        if($Silent){
            throw "Error: Login Method not given in Configuration but needed in Silent mode. Please set the LoginMethod with the Set-Configuration cmdlet."
        }
        $AskMethod = $true
        $AskCount = 0
        while($AskMethod){
            $UserChoice = Read-Host -Prompt "LoginMethod not set, please choose one of the following by inserting: [token]|ticket"
            $LoginMethod = (!$UserChoice) ? "token" : $UserChoice
            
            $AskMethod = $LoginMethod -notmatch "token|ticket"
            if($AskMethod){
                Write-Host "Wrong input. Please type: 'token' or 'ticket'. Leave it blank to choose token."
                if(++$AskCount -eq 5){
                    Write-Host "Aborting."
                    return
                }
            }else{
                $Script:Configuration["LoginMethod"] = $LoginMethod
            }
        }
    }
    $UserChoice = "y"
    if($script:Configuration["Credential"] -and !$Silent -and $oldLoginMethod -eq $LoginMethod){
        $AskCount = 0
        $AskCreds = $true
        while($AskCreds){
            $UserChoice = Read-Host -Prompt "Credential already given in Configuration, insert new one? [y]|n"
            $UserChoice = (!$UserChoice) ? "y" : $UserChoice
            $AskCreds = $UserChoice -notmatch "y|n|yes|no"
            if($AskCreds){
                Write-Host "Wrong input. Please type: 'y' for yes or 'n' for no. Leave it blank to choose yes."
                if(++$AskCount -eq 5){
                    Write-Host "Aborting."
                    return
                }
            }
        }
    }
    if($Silent){
        $UserChoice = "n"
    }
    if($UserChoice -eq "y"){
        switch($LoginMethod){
            { $_ -eq "token" }{
                Write-Host "Login method 'Token' chosen. Please insert your Proxmox TokenId <USER@REALM>!<GROUP> as Username and your Proxmox VE ApiToken as Password."
                Write-Host "https://pve.proxmox.com/wiki/Proxmox_VE_API#Example:_Use_API_Token"
                break
            }
            { $_ -eq "ticket" }{
                Write-Host "Login method 'Ticket' chosen. Please insert your Proxmox Credentials."
                Write-Host "https://pve.proxmox.com/wiki/Proxmox_VE_API#Ticket_Cookie"
                break
            }
        }
        $script:Configuration["Credential"] = (Get-Credential -Message "Proxmox VE $($LoginMethod -replace "^t","T") Authentication:")
    }
        
    if($LoginMethod -eq "ticket"){
        try{
            $LoginUri = "$($Script:Configuration["BaseUrl"])/access/ticket"
            $crds = $Script:Configuration["Credential"]
            $LoginResponse = Invoke-WebRequest `
                                -Uri $LoginUri `
                                -Method Post `
                                -Body @{ 
                                    username = $crds.UserName
                                    password = (DecryptSecureString -SecureString $crds.Password)
                                } `
                                -ContentType "application/x-www-form-urlencoded" `
                                -ErrorAction Stop
            if($LoginResponse.StatusCode -eq 200){
                $LoginData = $LoginResponse.Content | ConvertFrom-Json
                $script:AuthData["Ticket"] = (ConvertTo-SecureString -String $LoginData.data.ticket -AsPlainText -Force)
                $script:AuthData["CSRFPreventionToken"] = (ConvertTo-SecureString -String $LoginData.data.CSRFPreventionToken -AsPlainText -Force)
            }else{
                if($Silent){
                    throw "Login failed: $($LoginResponse.Content)"
                }else{
                    Write-Host "Login failed:" -ForegroundColor Red
                    Write-Host $LoginResponse.Content -ForegroundColor Red
                    return
                }
            }
        }catch{
            if($Silent){
                throw "Login failed: $($_.Exception.Message)"
            }else{
                Write-Host "Login failed:" -ForegroundColor Red
                Write-Host $_.Exception.Message -ForegroundColor Red
                return
            }
        }
    }
    if($LoginMethod -eq "token"){
        $AuthHeaders = @{
            Authorization = ("PVEAPIToken {0}={1}" -f $Script:Configuration["Credential"].UserName,(DecryptSecureString -SecureString $Script:Configuration["Credential"].Password))
        }
        try{
            $LoginResponse = Invoke-WebRequest -Uri $Script:Configuration["BaseUrl"] -Method Get -Headers $AuthHeaders
            if($LoginResponse.StatusCode -eq 200){
                $Script:AuthData["PVEAPIToken"] = (ConvertTo-SecureString -String $AuthHeaders.Authorization -AsPlainText -Force)
            }else{
                if($Silent){
                    throw "Login failed: $($LoginResponse.Content)"
                }else{
                    Write-Host "Login failed:" -ForegroundColor Red
                    Write-Host $LoginResponse.Content -ForegroundColor Red
                    return
                }
            }
        }catch{
            if($Silent){
                throw "Login failed: $($_.Exception.Message)"
            }else{
                Write-Host "Login failed:" -ForegroundColor Red
                Write-Host $_.Exception.Message -ForegroundColor Red
                return
            }
        }
    }
    $Script:AuthData["LoggedIn"] = $true
    if(!$Silent){
        Write-Host -ForegroundColor Green "Login successful"
        $SaveLoginData = Read-Host -Prompt "Save login data to persistent Configuration? y|[n]"
        if(!$SaveLoginData){ $SaveLoginData = "n" }
        $AskAgain = $SaveLoginData -notmatch "y|n"
        $AskCount = 0
        if($AskAgain){
            Write-Host "Wrong input. Please type: 'y' for yes or 'n' for no. Leave it blank to choose 'no'."
            if(++$AskCount -eq 5){
                Write-Host "Abort. Didn't save login data in Configuration"
                return
            }
        }
        if($SaveLoginData -eq "y"){
            Set-PVEConfiguration -Persistent
            Write-Host "done."
        }
    }else{
        return $true
    }
}


<#
.SYNOPSIS
Sets the configuration for http signing.
.DESCRIPTION

Sets the configuration for the HTTP signature security scheme.
The HTTP signature security scheme is used to sign HTTP requests with a key
which is in possession of the API client.
An 'Authorization' header is calculated by creating a hash of select headers,
and optionally the body of the HTTP request, then signing the hash value using
a key. The 'Authorization' header is added to outbound HTTP requests.

Ref: https://openapi-generator.tech

.PARAMETER KeyId
KeyId for HTTP signing

.PARAMETER KeyFilePath
KeyFilePath for HTTP signing

.PARAMETER KeyPassPhrase
KeyPassPhrase, if the HTTP signing key is protected

.PARAMETER HttpSigningHeader
HttpSigningHeader list of HTTP headers used to calculate the signature. The two special signature headers '(request-target)' and '(created)'
SHOULD be included.
    The '(created)' header expresses when the signature was created.
    The '(request-target)' header is a concatenation of the lowercased :method, an
    ASCII space, and the :path pseudo-headers.
If no headers are specified then '(created)' sets as default.

.PARAMETER HashAlgorithm
HashAlgorithm to calculate the hash, Supported values are "sha256" and "sha512"

.PARAMETER SigningAlgorithm
SigningAlgorithm specifies the signature algorithm, supported values are "RSASSA-PKCS1-v1_5" and "RSASSA-PSS"
RSA key : Supported values "RSASSA-PKCS1-v1_5" and "RSASSA-PSS", for ECDSA key this parameter is not applicable

.PARAMETER SignatureValidityPeriod
SignatureValidityPeriod specifies the signature maximum validity time in seconds. It accepts integer value

.OUTPUTS

System.Collections.Hashtable
#>

function Set-PVEConfigurationHttpSigning{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$KeyId,
        [Parameter(Mandatory = $true)]
        [string]$KeyFilePath,
        [Parameter(Mandatory = $false)]
        [securestring]$KeyPassPhrase,
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]] $HttpSigningHeader = @("(created)"),
        [Parameter(Mandatory = $false)]
        [ValidateSet("sha256", "sha512")]
        [string] $HashAlgorithm = "sha256",
        [Parameter(Mandatory = $false)]
        [ValidateSet("RSASSA-PKCS1-v1_5", "RSASSA-PSS")]
        [string]$SigningAlgorithm ,
        [Parameter(Mandatory = $false)]
        [int]$SignatureValidityPeriod
    )

    Process{
        $httpSignatureConfiguration = @{ }

        if (Test-Path -Path $KeyFilePath){
            $httpSignatureConfiguration["KeyId"] = $KeyId
            $httpSignatureConfiguration["KeyFilePath"] = $KeyFilePath
        }
        else{
            throw "Private key file path does not exist"
        }

        $keyType = Get-PVEKeyTypeFromFile -KeyFilePath $KeyFilePath
        if ([String]::IsNullOrEmpty($SigningAlgorithm)){
            if ($keyType -eq "RSA"){
                $SigningAlgorithm = "RSASSA-PKCS1-v1_5"
            }
        }

        if ($keyType -eq "RSA" -and
            ($SigningAlgorithm -ne "RSASSA-PKCS1-v1_5" -and $SigningAlgorithm -ne "RSASSA-PSS" )){
            throw "Provided Key and SigningAlgorithm : $SigningAlgorithm is not compatible."
        }

        if ($HttpSigningHeader -contains "(expires)" -and $SignatureValidityPeriod -le 0){
            throw "SignatureValidityPeriod must be greater than 0 seconds."
        }

        if ($HttpSigningHeader -contains "(expires)"){
            $httpSignatureConfiguration["SignatureValidityPeriod"] = $SignatureValidityPeriod
        }
        if ($null -ne $HttpSigningHeader -and $HttpSigningHeader.Length -gt 0){
            $httpSignatureConfiguration["HttpSigningHeader"] = $HttpSigningHeader
        }

        if ($null -ne $HashAlgorithm ){
            $httpSignatureConfiguration["HashAlgorithm"] = $HashAlgorithm
        }

        if ($null -ne $SigningAlgorithm){
            $httpSignatureConfiguration["SigningAlgorithm"] = $SigningAlgorithm
        }

        if ($null -ne $KeyPassPhrase){
            $httpSignatureConfiguration["KeyPassPhrase"] = $KeyPassPhrase
        }

        $Script:Configuration["HttpSigning"] = New-Object -TypeName PSCustomObject -Property $httpSignatureConfiguration
    }
}

<#
.SYNOPSIS

Get the configuration object 'PVEConfigurationHttpSigning'.

.DESCRIPTION

Get the configuration object 'PVEConfigurationHttpSigning'.

.OUTPUTS

[PSCustomObject]
#>

function Get-PVEConfigurationHttpSigning{

    $httpSignatureConfiguration = $Script:Configuration["HttpSigning"]
    return $httpSignatureConfiguration
}

<#
.SYNOPSIS
Decrypts a SecureString and returns a String.

.DESCRIPTION
Decrypts a SecureString and returns a String.

.OUTPUTS
[string]
#>

function DecryptSecureString {
    Param(
        [SecureString]$SecureString
    )
    $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SecureString)
    $Value = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
    $Value
}