Classes/AzureDevOpsConnection.ps1
|
# Connection class for Azure DevOps Rest API # # Path: src/DevOpsTools.AzureDevOps.PSRule/Functions/Connection.ps1 # This class contains methods to connect to Azure DevOps Rest API # using a service principal, managed identity, personal access token (PAT), # or Bearer token (OAuth 2.0 access token). It provides an authentication # header which is refreshed automatically when it expires for supported auth types. # -------------------------------------------------- class AzureDevOpsConnection { [string]$Organization [string]$OrganizationId [string]$PAT [string]$ClientId [string]$ClientSecret [string]$TenantId [string]$TokenEndpoint [string]$Token [System.DateTime]$TokenExpires [string]$AuthType [string]$TokenType # Constructor for Service Principal AzureDevOpsConnection( [string]$Organization, [string]$ClientId, [string]$ClientSecret, [string]$TenantId, [string]$TokenType = 'FullAccess' ) { $this.Organization = $Organization $this.ClientId = $ClientId $this.ClientSecret = $ClientSecret $this.TenantId = $TenantId $this.TokenEndpoint = "https://login.microsoftonline.com/$($this.TenantId)/oauth2/v2.0/token" $this.Token = $null $this.TokenExpires = [System.DateTime]::MinValue $this.TokenType = $TokenType $this.AuthType = 'ServicePrincipal' # Get a token for the Azure DevOps REST API $this.GetServicePrincipalToken() } # Constructor for Managed Identity AzureDevOpsConnection( [string]$Organization, [string]$TokenType = 'FullAccess' ) { $this.Organization = $Organization # Get the Managed Identity token endpoint for the Azure DevOps REST API if (-not $env:IDENTITY_ENDPOINT) { $env:IDENTITY_ENDPOINT = "http://169.254.169.254/metadata/identity/oauth2/token" } if ($env:ADO_MSI_CLIENT_ID) { $this.TokenEndpoint = "$($env:IDENTITY_ENDPOINT)?resource=499b84ac-1321-427f-aa17-267ca6975798&api-version=2019-08-01&client_id=$($env:ADO_MSI_CLIENT_ID)" } else { $this.TokenEndpoint = "$($env:IDENTITY_ENDPOINT)?resource=499b84ac-1321-427f-aa17-267ca6975798&api-version=2019-08-01" } $this.Token = $null $this.TokenExpires = [System.DateTime]::MinValue $this.TokenType = $TokenType $this.AuthType = 'ManagedIdentity' # Get a token for the Azure DevOps REST API $this.GetManagedIdentityToken() } # Constructor for Personal Access Token (PAT) AzureDevOpsConnection( [string]$Organization, [string]$PAT, [string]$TokenType = 'FullAccess' ) { $this.Organization = $Organization $this.PAT = $PAT $this.Token = $null $this.TokenExpires = [System.DateTime]::MaxValue $this.TokenType = $TokenType $this.AuthType = 'PAT' # Get a token for the Azure DevOps REST API $this.GetPATToken() } # Constructor for Bearer Token AzureDevOpsConnection( [string]$Organization, [string]$OrganizationId, [string]$AccessToken, [string]$TokenType = 'FullAccess', [switch]$Bearer ) { $this.Organization = $Organization $this.OrganizationId = $OrganizationId $this.Token = "Bearer $AccessToken" $this.TokenExpires = [System.DateTime]::UtcNow.AddHours(1) $this.TokenType = $TokenType $this.AuthType = 'Bearer' # Validate token format if (-not $AccessToken -or $AccessToken -notmatch "^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$") { throw "Invalid Bearer token format. Ensure the token is a valid JWT." } } # Get a token for the Azure DevOps REST API using a service principal [void]GetServicePrincipalToken() { $body = @{ grant_type = "client_credentials" client_id = $this.ClientId client_secret = $this.ClientSecret scope = '499b84ac-1321-427f-aa17-267ca6975798/.default' } $header = @{ 'Content-Type' = 'application/x-www-form-urlencoded' } $response = Invoke-RestMethod -Uri $this.TokenEndpoint -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' -Headers $header $this.Token = "Bearer $($response.access_token)" $this.TokenExpires = [System.DateTime]::UtcNow.AddSeconds($response.expires_in) $this.AuthType = 'ServicePrincipal' } # Get a token for the Azure DevOps REST API using a managed identity [void]GetManagedIdentityToken() { $header = @{} If ($env:IDENTITY_HEADER) { $header = @{ 'X-IDENTITY-HEADER' = "$env:IDENTITY_HEADER" ; Metadata = 'true' } } else { $header = @{ Metadata = 'true' } } $response = Invoke-RestMethod -Uri $this.TokenEndpoint -Method Get -Headers $header $this.Token = "Bearer $($response.access_token)" $this.TokenExpires = [DateTimeOffset]::FromUnixTimeSeconds([long]$response.expires_on).UtcDateTime $this.AuthType = 'ManagedIdentity' } # Get a token for the Azure DevOps REST API using a personal access token (PAT) [void]GetPATToken() { $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((":$($this.PAT)"))) $this.Token = 'Basic ' + $base64AuthInfo $this.AuthType = 'PAT' } # Get the up-to-date authentication header for the Azure DevOps REST API [System.Collections.Hashtable]GetHeader() { # If the token is expired, attempt to refresh (except for Bearer and PAT) if ($this.TokenExpires -lt [System.DateTime]::UtcNow) { switch ($this.AuthType) { 'ServicePrincipal' { $this.GetServicePrincipalToken() } 'ManagedIdentity' { $this.GetManagedIdentityToken() } 'PAT' { # PAT tokens don't expire in this context } 'Bearer' { throw "Bearer token has expired. Please provide a new token via Connect-AzDevOps." } } } $header = @{ Authorization = $this.Token 'Content-Type' = 'application/json' } return $header } } |