Trelica.psm1
Add-Type -AssemblyName System.Web <# .SYNOPSIS Get or Set Trelica API credentials. Once set, these credentials are persisted to a file for future use. .DESCRIPTION The first time this is run you will be prompted to enter your Trelica API Client ID and Client Secret. The credentials will be stored in a file for future use. By default they will be added to a folder called Trelica in the user's home directory.The StorePath parameter controls the location of this file. If you are running on Windows then the credentials are encrypted and decryted using the current user's logon credentials via the Windows Data Protection API (DPAPI). .INPUTS None. You cannot pipe objects to Initialize-TrelicaCredentials. .OUTPUTS A Trelia.Credentials object @{ PSTypeName = 'Trelica.Credentials' [String]$credential [String]$baseUrl } Typically this is passed to Get-TrelicaContext in order to authenticate and receive an access token. .EXAMPLE PS> Initialize-TrelicaCredentials -Reset Credentials path: C:\Users\Me\Trelica PowerShell credential request Please enter your Trelica API Client ID and Secret when prompted for User and Password respectively User: UaQD5J21ARhKjQyFTYokxkieXqM9SM9RaUMHxQBIPjD Password for user UaQD5J21ARhKjQyFTYokxkieXqM9SM9RaUMHxQBIPjD: ******************************************* credential baseUrl ---------- ------- System.Management.Automation.PSCredential https://app.trelica.com .EXAMPLE PS> Initialize-TrelicaCredentials | Get-TrelicaContext | .\MyScript.ps1 .LINK https://docs.trelica.com/admin/api/creating-an-app #> function Initialize-TrelicaCredentials { [CmdletBinding()] Param ( [Parameter(Mandatory = $false, Position = 1, HelpMessage = "The path to the credential store. Defaults to %Home%\Trelica")] [string]$StorePath, [Parameter(Mandatory = $false, Position = 2, HelpMessage = "Trelica Base Url.")] [string]$BaseUrl = "https://app.trelica.com", [Parameter(Mandatory=$false,Position=3,HelpMessage="Do not prompt for the credentials if they cannot be read and throw an exception.")] [Switch]$DoNotPrompt, [Parameter(Mandatory = $false, Position = 4, HelpMessage = "Reset credentials.")] [Switch]$Reset, [Parameter(Mandatory = $false, Position = 5, HelpMessage = "Delete credentials.")] [Switch]$Delete, [Parameter(Mandatory = $false, Position = 6, HelpMessage = "Output credentials.")] [Switch]$Show, [Parameter(Mandatory = $false, Position = 7, HelpMessage = "Specify credentials name.")] [string]$SecretName = "TrelicaAPI" ) begin { $ErrorActionPreference = "Stop" } process { if ([String]::IsNullOrEmpty($StorePath)) { $StorePath = ($Home | Join-Path -ChildPath "Trelica") Write-Host "Credentials path: $StorePath" } # ensure path exists if (-Not (Test-Path -Path "$StorePath" -PathType Container)) { New-Item -Path $StorePath -ItemType Directory | Out-Null } $filePath = [String]::Format("{0}\{1}.json", $StorePath,$SecretName) $fileExists = Test-Path -Path $filePath -PathType Leaf if ($Delete.IsPresent) { if ($fileExists) { Remove-Item -Path $filePath -Force } Write-Host "Credentials deleted" return; } if ($fileExists -and -not $Reset.IsPresent) { # we can load $userName = Get-Content -Path $clientIdPath $data = Get-Content -Path $filePath | ConvertFrom-Json $baseUrl = $data.baseUrl $userName = $data.clientId $password = $data.clientSecret | ConvertTo-SecureString $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName, $password if ($Show.IsPresent) { Write-Host "Credentials:" Write-Host "------------" Write-Host "$baseUrl" Write-Host "$userName/$($credential.GetNetworkCredential().Password)" } } else { if ($DoNotPrompt.IsPresent) { throw "Stored credentials not found" } if ([String]::IsNullOrEmpty($BaseUrl)) { $BaseUrl = "https://app.trelica.com" Write-Host "Base URL defaulting to $BaseUrl" } $credential = Get-Credential -Message "Please enter your $SecretName Client ID and Secret when prompted for User and Password respectively" if ($null -ne $credential) { # save $data = @{ baseUrl = $BaseUrl clientId = $credential.UserName clientSecret = $credential.Password | ConvertFrom-SecureString } $data | ConvertTo-Json | Out-File $filePath -Force } } return [PSCustomObject]@{ PSTypeName = 'Trelica.Credentials' credential = $credential baseUrl = $baseUrl } } } function Get-TrelicaCredentials { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, HelpMessage = "API Credential.")] [System.Management.Automation.PSCredential]$Credential, [Parameter(Mandatory = $false, Position = 2, HelpMessage = "Trelica Base Url.")] [string]$BaseUrl = "https://app.trelica.com" ) begin { $ErrorActionPreference = "Stop" } process { return [PSCustomObject]@{ PSTypeName = 'Trelica.Credentials' credential = $Credential baseUrl = $BaseUrl } } } <# .SYNOPSIS Connects to Trelica and retrieves an OAuth access token. .DESCRIPTION Uses the OAuth2 Client Credentials flow to get an access token from Trelica with (optionally) a restricted set of scopes. If no scopes are passed, then the full set of scopes assigned to the Trelica OAuth2 client are used. .INPUTS A Trelia.Credentials object @{ PSTypeName = 'Trelica.Credentials' [String]$credential [String]$baseUrl } .OUTPUTS A Trelia.Context object @{ PSTypeName = 'Trelica.Context' [String]$token [String]$baseUrl } Typically this is passed to Invoke-TrelicaRequest in order to make Trelica API requests. .EXAMPLE PS> Initialize-TrelicaCredentials | Get-TrelicaContext -Scopes "Workflows.Read","Workflows.Runs.Read" | Format-List C:\Users\Me\Trelica token : eyJhbGciOiJSUzI1NiIsImtpZCI6IjdD... baseUrl : https://app.trelica.com .EXAMPLE PS> Initialize-TrelicaCredentials | Get-TrelicaContext | Invoke-TrelicaRequest -Path "/api/workflows/v1" | ConvertTo-Json -Depth 4 .LINK https://docs.trelica.com/admin/api/client-credentials-flow #> Function Get-TrelicaContext() { [CmdletBinding()] Param( [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)][PSTypeName('Trelica.Credentials')]$Credentials, [Parameter(Mandatory = $false, Position = 2)][String[]]$Scopes ) begin { $ErrorActionPreference = "Stop" } process { $requestAccessTokenUri = "$($Credentials.baseUrl)/connect/token" $items = @( @("grant_type", "client_credentials"), @("client_id", $Credentials.credential.UserName), @("client_secret", $Credentials.credential.GetNetworkCredential().Password) ) $body = [string]::join("&", ([Array]$items | ForEach-Object { "$($_[0])=$([uri]::EscapeDataString($_[1]))" })) if ($null -ne $Scopes) { $body += "&scopes=$([uri]::EscapeDataString([string]::join(" ", $Scopes)))" } $token = Invoke-RestMethod -Method Post -Uri $requestAccessTokenUri -Body $body -ContentType 'application/x-www-form-urlencoded' return [PSCustomObject]@{ PSTypeName = 'Trelica.Context' token = $token.access_token baseUrl = $Credentials.baseUrl } } } <# .SYNOPSIS Invokes a Trelica API end-point. .DESCRIPTION Invokes a Trelica API end-point using the provided Trelica context. .INPUTS A Trelia.Context object @{ PSTypeName = 'Trelica.Context' [String]$token [String]$baseUrl } .OUTPUTS The result of the API call, or $null in the case of an HTTP 404 (Not found). .EXAMPLE PS> Initialize-TrelicaCredentials | Get-TrelicaContext | Invoke-TrelicaRequest -Path "/api/workflows/v1" .EXAMPLE PS> $context = Initialize-TrelicaCredentials | Get-TrelicaContext PS> $context | Invoke-TrelicaRequest -Path "/api/workflows/v1" | ConvertTo-Json -Depth 4 #> Function Invoke-TrelicaRequest() { [CmdletBinding()] Param( [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)][PSTypeName('Trelica.Context')]$Context, [Parameter(Mandatory = $true)][String]$Path, [Parameter(Mandatory = $false)][System.Collections.Hashtable]$QueryStringParams, [Parameter(Mandatory = $false)][Object]$PostData, [Parameter(Mandatory = $false)][String]$Method = 'GET' ) begin { $ErrorActionPreference = "Stop" } process { $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $headers.Add("Authorization", "Bearer $($Context.token)") $headers.Add("Content-Type", "application/json") if ($Path.StartsWith("https://")) { $uri = $Path } else { $uri = "$($Context.baseUrl.TrimEnd("/"))/$($Path.TrimStart("/"))" } if ($QueryStringParams) { $prepend = "&" if (-not ($uri.ToCharArray() -contains '?')) { $prepend = "?" } foreach ($key in $QueryStringParams.Keys) { $value = $QueryStringParams[$key] if ("System.Boolean" -eq $value.GetType().FullName) { $value = @("true", "false")[($value)] } $encodedValue = [System.Web.HttpUtility]::UrlEncode($value) $uri = $uri + "$prepend$key=$encodedValue" $prepend = "&" } } # we want to return HTTP 404 as a null try { $response = (Invoke-WebRequest -Method $Method -Uri $uri -Body $postData -Headers $headers -ErrorAction Stop) | ConvertFrom-Json } catch { Write-Host "API [$Method] $uri" Write-Host "An exception was caught: $($_.Exception.Message)" if ($_.Exception.Response.StatusCode.value__ -eq 404) { return $null } else { Write-Host "$($_.Exception.Response)" throw } } return $response } } |