From 5e484fead0dbef183e4c8bf04a10f4150b71b7ae Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Fri, 5 Dec 2025 16:52:40 -0800 Subject: [PATCH 1/9] Remove ApiKey usage --- .../templates/steps/create-apireview.yml | 12 ++-- eng/common/scripts/Create-APIReview.ps1 | 63 +++++++++++++++---- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/eng/common/pipelines/templates/steps/create-apireview.yml b/eng/common/pipelines/templates/steps/create-apireview.yml index e5deb551a382..b65251c1e2ab 100644 --- a/eng/common/pipelines/templates/steps/create-apireview.yml +++ b/eng/common/pipelines/templates/steps/create-apireview.yml @@ -26,6 +26,9 @@ parameters: - name: PackageInfoFiles type: object default: [] + - name: AzureServiceConnection + type: string + default: 'APIView prod deployment' steps: # Automatic API review is generated for a package when pipeline runs irrespective of how pipeline gets triggered. @@ -37,16 +40,18 @@ steps: parameters: WorkingDirectory: ${{ parameters.SourceRootPath }} - - task: Powershell@2 + - task: AzureCLI@2 inputs: - filePath: ${{ parameters.SourceRootPath }}/eng/common/scripts/Create-APIReview.ps1 + azureSubscription: ${{ parameters.AzureServiceConnection }} + scriptType: pscore + scriptLocation: scriptPath + scriptPath: ${{ parameters.SourceRootPath }}/eng/common/scripts/Create-APIReview.ps1 # PackageInfoFiles example: @('a/file1.json','a/file2.json') arguments: > -PackageInfoFiles @('${{ join(''',''', parameters.PackageInfoFiles) }}') -ArtifactList ('${{ convertToJson(parameters.Artifacts) }}' | ConvertFrom-Json | Select-Object Name) -ArtifactPath '${{parameters.ArtifactPath}}' -ArtifactName ${{ parameters.ArtifactName }} - -APIKey '$(azuresdk-apiview-apikey)' -PackageName '${{parameters.PackageName}}' -SourceBranch '$(Build.SourceBranchName)' -DefaultBranch '$(DefaultBranch)' @@ -54,7 +59,6 @@ steps: -BuildId '$(Build.BuildId)' -RepoName '$(Build.Repository.Name)' -MarkPackageAsShipped $${{parameters.MarkPackageAsShipped}} - pwsh: true displayName: Create API Review condition: >- and( diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index ec76326d9992..c9b27d8acde3 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -4,7 +4,7 @@ Param ( [array] $ArtifactList, [Parameter(Mandatory=$True)] [string] $ArtifactPath, - [Parameter(Mandatory=$True)] + [Parameter(Mandatory=$False)] [string] $APIKey, [string] $SourceBranch, [string] $DefaultBranch, @@ -12,17 +12,40 @@ Param ( [string] $BuildId, [string] $PackageName = "", [string] $ConfigFileDir = "", - [string] $APIViewUri = "https://apiview.dev/AutoReview", + [string] $APIViewUri = "https://apiview.dev/autoreview", [string] $ArtifactName = "packages", [bool] $MarkPackageAsShipped = $false, [Parameter(Mandatory=$False)] - [array] $PackageInfoFiles + [array] $PackageInfoFiles, + [string] $APIViewAudience = "api://apiview" ) Set-StrictMode -Version 3 . (Join-Path $PSScriptRoot common.ps1) . (Join-Path $PSScriptRoot Helpers ApiView-Helpers.ps1) +# Get Bearer token for APIView authentication +# Uses Azure CLI to get an access token for the specified audience +function Get-ApiViewBearerToken($audience) +{ + try { + Write-Host "Acquiring access token for audience: $audience" + $tokenResponse = az account get-access-token --resource $audience --output json | ConvertFrom-Json + if ($tokenResponse -and $tokenResponse.accessToken) { + Write-Host "Successfully acquired access token" + return $tokenResponse.accessToken + } + else { + Write-Host "Failed to acquire access token - no token in response" -ForegroundColor Yellow + return $null + } + } + catch { + Write-Host "Failed to acquire access token: $($_.Exception.Message)" -ForegroundColor Yellow + return $null + } +} + # Submit API review request and return status whether current revision is approved or pending or failed to create review function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVersion, $packageType) { @@ -78,10 +101,19 @@ function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVer Write-Host "Request param, compareAllRevisions: true" } - $uri = "${APIViewUri}/UploadAutoReview" - $headers = @{ - "ApiKey" = $apiKey; - "content-type" = "multipart/form-data" + $uri = "${APIViewUri}/upload" + + # Get Bearer token for authentication (preferred) or fall back to API key + $bearerToken = Get-ApiViewBearerToken $APIViewAudience + if ($bearerToken) { + $headers = @{ + "Authorization" = "Bearer $bearerToken"; + "content-type" = "multipart/form-data" + } + } + else { + Write-Host "ERROR: No authentication method available. Either Azure CLI login or APIKey is required." -ForegroundColor Red + return 401 } try @@ -115,20 +147,29 @@ function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $review if($MarkPackageAsShipped) { $params += "&setReleaseTag=true" } - $uri = "${APIViewUri}/CreateApiReview?${params}" + $uri = "${APIViewUri}/create?${params}" if ($releaseStatus -and ($releaseStatus -ne "Unreleased")) { $uri += "&compareAllRevisions=true" } Write-Host "Request to APIView: $uri" - $headers = @{ - "ApiKey" = $APIKey; + + # Get Bearer token for authentication (preferred) or fall back to API key + $bearerToken = Get-ApiViewBearerToken $APIViewAudience + if ($bearerToken) { + $headers = @{ + "Authorization" = "Bearer $bearerToken" + } + } + else { + Write-Host "ERROR: No authentication method available. Either Azure CLI login or APIKey is required." -ForegroundColor Red + return 401 } try { - $Response = Invoke-WebRequest -Method 'GET' -Uri $uri -Headers $headers + $Response = Invoke-WebRequest -Method 'POST' -Uri $uri -Headers $headers Write-Host "API review: $($Response.Content)" $StatusCode = $Response.StatusCode } From 457476ac7dd79ba1348fa36e1da0216f101a437e Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 10:31:46 -0800 Subject: [PATCH 2/9] Add -TestAuth flag to verify Bearer token authentication --- eng/common/scripts/Create-APIReview.ps1 | 66 ++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index c9b27d8acde3..74c524a14630 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -2,7 +2,7 @@ Param ( [Parameter(Mandatory=$False)] [array] $ArtifactList, - [Parameter(Mandatory=$True)] + [Parameter(Mandatory=$False)] [string] $ArtifactPath, [Parameter(Mandatory=$False)] [string] $APIKey, @@ -17,10 +17,72 @@ Param ( [bool] $MarkPackageAsShipped = $false, [Parameter(Mandatory=$False)] [array] $PackageInfoFiles, - [string] $APIViewAudience = "api://apiview" + [string] $APIViewAudience = "api://apiview", + [switch] $TestAuth ) Set-StrictMode -Version 3 + +# Test authentication mode - just verify Bearer token works and exit +if ($TestAuth) { + Write-Host "=== APIView Authentication Test Mode ===" -ForegroundColor Cyan + Write-Host "Testing Bearer token authentication against APIView..." + Write-Host "" + + try { + Write-Host "Step 1: Acquiring access token for audience: $APIViewAudience" + $tokenResponse = az account get-access-token --resource $APIViewAudience --output json 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "FAILED: Could not acquire token. Error: $tokenResponse" -ForegroundColor Red + Write-Host "Make sure you are logged in with 'az login'" -ForegroundColor Yellow + exit 1 + } + $parsed = $tokenResponse | ConvertFrom-Json + Write-Host "SUCCESS: Token acquired! Expires: $($parsed.expiresOn)" -ForegroundColor Green + Write-Host "" + + Write-Host "Step 2: Testing authenticated request to APIView..." + $headers = @{ + "Authorization" = "Bearer $($parsed.accessToken)" + } + + # Make a simple GET request to verify the token is accepted + # Using the reviews endpoint which should return 200 or redirect if auth works + $testUri = "https://apiview.dev/api/reviews" + Write-Host "Calling: GET $testUri" + + try { + $response = Invoke-WebRequest -Uri $testUri -Headers $headers -Method GET -MaximumRedirection 0 -ErrorAction Stop + Write-Host "SUCCESS: API responded with status $($response.StatusCode)" -ForegroundColor Green + } + catch { + $statusCode = $_.Exception.Response.StatusCode.Value__ + if ($statusCode -eq 401 -or $statusCode -eq 403) { + Write-Host "FAILED: Authentication rejected (HTTP $statusCode)" -ForegroundColor Red + Write-Host "The token was acquired but APIView rejected it." -ForegroundColor Yellow + Write-Host "This may indicate the service principal doesn't have access." -ForegroundColor Yellow + exit 1 + } + elseif ($statusCode -ge 200 -and $statusCode -lt 400) { + Write-Host "SUCCESS: API responded with status $statusCode" -ForegroundColor Green + } + else { + Write-Host "WARNING: API responded with status $statusCode" -ForegroundColor Yellow + Write-Host "This may be expected depending on the endpoint. Auth likely worked." -ForegroundColor Yellow + } + } + + Write-Host "" + Write-Host "=== Authentication Test Complete ===" -ForegroundColor Cyan + Write-Host "Bearer token authentication is working!" -ForegroundColor Green + exit 0 + } + catch { + Write-Host "FAILED: Unexpected error: $($_.Exception.Message)" -ForegroundColor Red + exit 1 + } +} + . (Join-Path $PSScriptRoot common.ps1) . (Join-Path $PSScriptRoot Helpers ApiView-Helpers.ps1) From ead0c2a8d56253ac0c46b46dc2134148c311bc8c Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 11:15:52 -0800 Subject: [PATCH 3/9] TEMP: Enable TestAuthOnly for pipeline testing --- .../templates/steps/create-apireview.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/eng/common/pipelines/templates/steps/create-apireview.yml b/eng/common/pipelines/templates/steps/create-apireview.yml index b65251c1e2ab..e6289f60e25a 100644 --- a/eng/common/pipelines/templates/steps/create-apireview.yml +++ b/eng/common/pipelines/templates/steps/create-apireview.yml @@ -29,11 +29,26 @@ parameters: - name: AzureServiceConnection type: string default: 'APIView prod deployment' + - name: TestAuthOnly + type: boolean + default: true # TEMPORARY: Set to true for testing Bearer auth, revert to false before merging steps: + # Test authentication mode - just verify Bearer token works + - ${{ if eq(parameters.TestAuthOnly, true) }}: + - task: AzureCLI@2 + inputs: + azureSubscription: ${{ parameters.AzureServiceConnection }} + scriptType: pscore + scriptLocation: scriptPath + scriptPath: ${{ parameters.SourceRootPath }}/eng/common/scripts/Create-APIReview.ps1 + arguments: -TestAuth + displayName: Test APIView Bearer Token Authentication + condition: succeededOrFailed() + # Automatic API review is generated for a package when pipeline runs irrespective of how pipeline gets triggered. # Below condition ensures that API review is generated only for manual pipeline runs when flag GenerateApiReviewForManualOnly is set to true. - - ${{ if or(ne(parameters.GenerateApiReviewForManualOnly, true), eq(variables['Build.Reason'], 'Manual')) }}: + - ${{ if and(ne(parameters.TestAuthOnly, true), or(ne(parameters.GenerateApiReviewForManualOnly, true), eq(variables['Build.Reason'], 'Manual'))) }}: # ideally this should be done as initial step of a job in caller template # We can remove this step later once it is added in caller - template: /eng/common/pipelines/templates/steps/set-default-branch.yml From 9a9560fbcc84da32671c29bd051898d7a2ca7d8d Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 15:52:53 -0800 Subject: [PATCH 4/9] Remove testing logs --- .../templates/steps/create-apireview.yml | 17 +---- eng/common/scripts/Create-APIReview.ps1 | 64 +------------------ 2 files changed, 2 insertions(+), 79 deletions(-) diff --git a/eng/common/pipelines/templates/steps/create-apireview.yml b/eng/common/pipelines/templates/steps/create-apireview.yml index e6289f60e25a..b65251c1e2ab 100644 --- a/eng/common/pipelines/templates/steps/create-apireview.yml +++ b/eng/common/pipelines/templates/steps/create-apireview.yml @@ -29,26 +29,11 @@ parameters: - name: AzureServiceConnection type: string default: 'APIView prod deployment' - - name: TestAuthOnly - type: boolean - default: true # TEMPORARY: Set to true for testing Bearer auth, revert to false before merging steps: - # Test authentication mode - just verify Bearer token works - - ${{ if eq(parameters.TestAuthOnly, true) }}: - - task: AzureCLI@2 - inputs: - azureSubscription: ${{ parameters.AzureServiceConnection }} - scriptType: pscore - scriptLocation: scriptPath - scriptPath: ${{ parameters.SourceRootPath }}/eng/common/scripts/Create-APIReview.ps1 - arguments: -TestAuth - displayName: Test APIView Bearer Token Authentication - condition: succeededOrFailed() - # Automatic API review is generated for a package when pipeline runs irrespective of how pipeline gets triggered. # Below condition ensures that API review is generated only for manual pipeline runs when flag GenerateApiReviewForManualOnly is set to true. - - ${{ if and(ne(parameters.TestAuthOnly, true), or(ne(parameters.GenerateApiReviewForManualOnly, true), eq(variables['Build.Reason'], 'Manual'))) }}: + - ${{ if or(ne(parameters.GenerateApiReviewForManualOnly, true), eq(variables['Build.Reason'], 'Manual')) }}: # ideally this should be done as initial step of a job in caller template # We can remove this step later once it is added in caller - template: /eng/common/pipelines/templates/steps/set-default-branch.yml diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 74c524a14630..bd57d9eb1b12 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -16,73 +16,11 @@ Param ( [string] $ArtifactName = "packages", [bool] $MarkPackageAsShipped = $false, [Parameter(Mandatory=$False)] - [array] $PackageInfoFiles, - [string] $APIViewAudience = "api://apiview", - [switch] $TestAuth + [array] $PackageInfoFiles ) Set-StrictMode -Version 3 -# Test authentication mode - just verify Bearer token works and exit -if ($TestAuth) { - Write-Host "=== APIView Authentication Test Mode ===" -ForegroundColor Cyan - Write-Host "Testing Bearer token authentication against APIView..." - Write-Host "" - - try { - Write-Host "Step 1: Acquiring access token for audience: $APIViewAudience" - $tokenResponse = az account get-access-token --resource $APIViewAudience --output json 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Host "FAILED: Could not acquire token. Error: $tokenResponse" -ForegroundColor Red - Write-Host "Make sure you are logged in with 'az login'" -ForegroundColor Yellow - exit 1 - } - $parsed = $tokenResponse | ConvertFrom-Json - Write-Host "SUCCESS: Token acquired! Expires: $($parsed.expiresOn)" -ForegroundColor Green - Write-Host "" - - Write-Host "Step 2: Testing authenticated request to APIView..." - $headers = @{ - "Authorization" = "Bearer $($parsed.accessToken)" - } - - # Make a simple GET request to verify the token is accepted - # Using the reviews endpoint which should return 200 or redirect if auth works - $testUri = "https://apiview.dev/api/reviews" - Write-Host "Calling: GET $testUri" - - try { - $response = Invoke-WebRequest -Uri $testUri -Headers $headers -Method GET -MaximumRedirection 0 -ErrorAction Stop - Write-Host "SUCCESS: API responded with status $($response.StatusCode)" -ForegroundColor Green - } - catch { - $statusCode = $_.Exception.Response.StatusCode.Value__ - if ($statusCode -eq 401 -or $statusCode -eq 403) { - Write-Host "FAILED: Authentication rejected (HTTP $statusCode)" -ForegroundColor Red - Write-Host "The token was acquired but APIView rejected it." -ForegroundColor Yellow - Write-Host "This may indicate the service principal doesn't have access." -ForegroundColor Yellow - exit 1 - } - elseif ($statusCode -ge 200 -and $statusCode -lt 400) { - Write-Host "SUCCESS: API responded with status $statusCode" -ForegroundColor Green - } - else { - Write-Host "WARNING: API responded with status $statusCode" -ForegroundColor Yellow - Write-Host "This may be expected depending on the endpoint. Auth likely worked." -ForegroundColor Yellow - } - } - - Write-Host "" - Write-Host "=== Authentication Test Complete ===" -ForegroundColor Cyan - Write-Host "Bearer token authentication is working!" -ForegroundColor Green - exit 0 - } - catch { - Write-Host "FAILED: Unexpected error: $($_.Exception.Message)" -ForegroundColor Red - exit 1 - } -} - . (Join-Path $PSScriptRoot common.ps1) . (Join-Path $PSScriptRoot Helpers ApiView-Helpers.ps1) From 6ce86bd80b9e8a020383e53f79d40389777d0f33 Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 16:01:28 -0800 Subject: [PATCH 5/9] Additional clean up --- eng/common/scripts/Create-APIReview.ps1 | 46 ++++++++++--------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index bd57d9eb1b12..017630681fee 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -4,8 +4,6 @@ Param ( [array] $ArtifactList, [Parameter(Mandatory=$False)] [string] $ArtifactPath, - [Parameter(Mandatory=$False)] - [string] $APIKey, [string] $SourceBranch, [string] $DefaultBranch, [string] $RepoName, @@ -25,23 +23,21 @@ Set-StrictMode -Version 3 . (Join-Path $PSScriptRoot Helpers ApiView-Helpers.ps1) # Get Bearer token for APIView authentication -# Uses Azure CLI to get an access token for the specified audience -function Get-ApiViewBearerToken($audience) +function Get-ApiViewBearerToken() { + $audience = "api://apiview" try { - Write-Host "Acquiring access token for audience: $audience" $tokenResponse = az account get-access-token --resource $audience --output json | ConvertFrom-Json if ($tokenResponse -and $tokenResponse.accessToken) { - Write-Host "Successfully acquired access token" return $tokenResponse.accessToken } else { - Write-Host "Failed to acquire access token - no token in response" -ForegroundColor Yellow + Write-Error "Failed to acquire access token - no token in response" return $null } } catch { - Write-Host "Failed to acquire access token: $($_.Exception.Message)" -ForegroundColor Yellow + Write-Error "Failed to acquire access token: $($_.Exception.Message)" return $null } } @@ -103,18 +99,16 @@ function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVer $uri = "${APIViewUri}/upload" - # Get Bearer token for authentication (preferred) or fall back to API key - $bearerToken = Get-ApiViewBearerToken $APIViewAudience - if ($bearerToken) { - $headers = @{ - "Authorization" = "Bearer $bearerToken"; - "content-type" = "multipart/form-data" - } - } - else { - Write-Host "ERROR: No authentication method available. Either Azure CLI login or APIKey is required." -ForegroundColor Red + # Get Bearer token for authentication + $bearerToken = Get-ApiViewBearerToken + if (-not $bearerToken) { return 401 } + + $headers = @{ + "Authorization" = "Bearer $bearerToken"; + "content-type" = "multipart/form-data" + } try { @@ -155,17 +149,15 @@ function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $review Write-Host "Request to APIView: $uri" - # Get Bearer token for authentication (preferred) or fall back to API key - $bearerToken = Get-ApiViewBearerToken $APIViewAudience - if ($bearerToken) { - $headers = @{ - "Authorization" = "Bearer $bearerToken" - } - } - else { - Write-Host "ERROR: No authentication method available. Either Azure CLI login or APIKey is required." -ForegroundColor Red + # Get Bearer token for authentication + $bearerToken = Get-ApiViewBearerToken + if (-not $bearerToken) { return 401 } + + $headers = @{ + "Authorization" = "Bearer $bearerToken" + } try { From 03173b308453ae21017d092afa30fbdd8a637282 Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 16:08:29 -0800 Subject: [PATCH 6/9] Keep apikey fallback while migrating --- eng/common/scripts/Create-APIReview.ps1 | 55 ++++++++++++++++--------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 017630681fee..33b1fe391ddf 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -2,8 +2,10 @@ Param ( [Parameter(Mandatory=$False)] [array] $ArtifactList, - [Parameter(Mandatory=$False)] + [Parameter(Mandatory=$True)] [string] $ArtifactPath, + [Parameter(Mandatory=$False)] + [string] $APIKey, [string] $SourceBranch, [string] $DefaultBranch, [string] $RepoName, @@ -27,17 +29,13 @@ function Get-ApiViewBearerToken() { $audience = "api://apiview" try { - $tokenResponse = az account get-access-token --resource $audience --output json | ConvertFrom-Json + $tokenResponse = az account get-access-token --resource $audience --output json 2>$null | ConvertFrom-Json if ($tokenResponse -and $tokenResponse.accessToken) { return $tokenResponse.accessToken } - else { - Write-Error "Failed to acquire access token - no token in response" - return $null - } + return $null } catch { - Write-Error "Failed to acquire access token: $($_.Exception.Message)" return $null } } @@ -99,15 +97,24 @@ function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVer $uri = "${APIViewUri}/upload" - # Get Bearer token for authentication + # Try Bearer token first, fall back to API key $bearerToken = Get-ApiViewBearerToken - if (-not $bearerToken) { - return 401 + if ($bearerToken) { + $headers = @{ + "Authorization" = "Bearer $bearerToken"; + "content-type" = "multipart/form-data" + } } - - $headers = @{ - "Authorization" = "Bearer $bearerToken"; - "content-type" = "multipart/form-data" + elseif ($APIKey) { + Write-Warning "##[warning]Bearer token acquisition failed - falling back to API key." + $headers = @{ + "ApiKey" = $APIKey; + "content-type" = "multipart/form-data" + } + } + else { + Write-Error "No authentication available. Either configure AzureCLI@2 task or provide APIKey." + return 401 } try @@ -149,14 +156,22 @@ function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $review Write-Host "Request to APIView: $uri" - # Get Bearer token for authentication + # Try Bearer token first, fall back to API key $bearerToken = Get-ApiViewBearerToken - if (-not $bearerToken) { - return 401 + if ($bearerToken) { + $headers = @{ + "Authorization" = "Bearer $bearerToken" + } } - - $headers = @{ - "Authorization" = "Bearer $bearerToken" + elseif ($APIKey) { + Write-Warning "##[warning]Bearer token acquisition failed - falling back to API key. Please migrate to using AzureCLI@2 task with service connection." + $headers = @{ + "ApiKey" = $APIKey + } + } + else { + Write-Error "No authentication available. Either configure AzureCLI@2 task or provide APIKey." + return 401 } try From 352c1d8537316c7200b397419fc4286c96031e8a Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 16:10:25 -0800 Subject: [PATCH 7/9] Keep migration to new endpoint --- eng/common/scripts/Create-APIReview.ps1 | 51 +++++++++---------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 33b1fe391ddf..3f1f3174d84f 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -1,11 +1,9 @@ [CmdletBinding()] Param ( - [Parameter(Mandatory=$False)] - [array] $ArtifactList, [Parameter(Mandatory=$True)] - [string] $ArtifactPath, + [array] $ArtifactList, [Parameter(Mandatory=$False)] - [string] $APIKey, + [string] $ArtifactPath, [string] $SourceBranch, [string] $DefaultBranch, [string] $RepoName, @@ -97,25 +95,17 @@ function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVer $uri = "${APIViewUri}/upload" - # Try Bearer token first, fall back to API key + # Get Bearer token for authentication $bearerToken = Get-ApiViewBearerToken - if ($bearerToken) { - $headers = @{ - "Authorization" = "Bearer $bearerToken"; - "content-type" = "multipart/form-data" - } - } - elseif ($APIKey) { - Write-Warning "##[warning]Bearer token acquisition failed - falling back to API key." - $headers = @{ - "ApiKey" = $APIKey; - "content-type" = "multipart/form-data" - } - } - else { - Write-Error "No authentication available. Either configure AzureCLI@2 task or provide APIKey." + if (-not $bearerToken) { + Write-Error "Failed to acquire Bearer token for APIView authentication." return 401 } + + $headers = @{ + "Authorization" = "Bearer $bearerToken"; + "content-type" = "multipart/form-data" + } try { @@ -156,23 +146,16 @@ function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $review Write-Host "Request to APIView: $uri" - # Try Bearer token first, fall back to API key + # Get Bearer token for authentication $bearerToken = Get-ApiViewBearerToken - if ($bearerToken) { - $headers = @{ - "Authorization" = "Bearer $bearerToken" - } - } - elseif ($APIKey) { - Write-Warning "##[warning]Bearer token acquisition failed - falling back to API key. Please migrate to using AzureCLI@2 task with service connection." - $headers = @{ - "ApiKey" = $APIKey - } - } - else { - Write-Error "No authentication available. Either configure AzureCLI@2 task or provide APIKey." + if (-not $bearerToken) { + Write-Error "Failed to acquire Bearer token for APIView authentication." return 401 } + + $headers = @{ + "Authorization" = "Bearer $bearerToken" + } try { From 9f64f19eb7216bc5b687e57178bc2571a91fcd42 Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 16:11:40 -0800 Subject: [PATCH 8/9] Keep migration to new endpoint --- eng/common/scripts/Create-APIReview.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 3f1f3174d84f..9ed95069ed15 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -1,8 +1,8 @@ [CmdletBinding()] Param ( - [Parameter(Mandatory=$True)] - [array] $ArtifactList, [Parameter(Mandatory=$False)] + [array] $ArtifactList, + [Parameter(Mandatory=$True)] [string] $ArtifactPath, [string] $SourceBranch, [string] $DefaultBranch, From a8c58f1a492f2e43fbf008ab7dc8dc38f3c0ba7e Mon Sep 17 00:00:00 2001 From: Alitzel Mendez Date: Mon, 8 Dec 2025 17:20:11 -0800 Subject: [PATCH 9/9] Feedback --- .../templates/steps/create-apireview.yml | 5 +---- eng/common/scripts/Create-APIReview.ps1 | 16 +++++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/eng/common/pipelines/templates/steps/create-apireview.yml b/eng/common/pipelines/templates/steps/create-apireview.yml index b65251c1e2ab..85c4216ad761 100644 --- a/eng/common/pipelines/templates/steps/create-apireview.yml +++ b/eng/common/pipelines/templates/steps/create-apireview.yml @@ -26,9 +26,6 @@ parameters: - name: PackageInfoFiles type: object default: [] - - name: AzureServiceConnection - type: string - default: 'APIView prod deployment' steps: # Automatic API review is generated for a package when pipeline runs irrespective of how pipeline gets triggered. @@ -42,7 +39,7 @@ steps: - task: AzureCLI@2 inputs: - azureSubscription: ${{ parameters.AzureServiceConnection }} + azureSubscription: 'APIView prod deployment' scriptType: pscore scriptLocation: scriptPath scriptPath: ${{ parameters.SourceRootPath }}/eng/common/scripts/Create-APIReview.ps1 diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 9ed95069ed15..86b95ed0e552 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -23,17 +23,19 @@ Set-StrictMode -Version 3 . (Join-Path $PSScriptRoot Helpers ApiView-Helpers.ps1) # Get Bearer token for APIView authentication +# In Azure DevOps, this uses the service connection's Managed Identity/Service Principal function Get-ApiViewBearerToken() { - $audience = "api://apiview" try { - $tokenResponse = az account get-access-token --resource $audience --output json 2>$null | ConvertFrom-Json - if ($tokenResponse -and $tokenResponse.accessToken) { - return $tokenResponse.accessToken + $tokenResponse = az account get-access-token --resource "api://apiview" --output json 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to acquire access token: $tokenResponse" + return $null } - return $null + return ($tokenResponse | ConvertFrom-Json).accessToken } catch { + Write-Error "Failed to acquire access token: $($_.Exception.Message)" return $null } } @@ -99,7 +101,7 @@ function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVer $bearerToken = Get-ApiViewBearerToken if (-not $bearerToken) { Write-Error "Failed to acquire Bearer token for APIView authentication." - return 401 + return [System.Net.HttpStatusCode]::Unauthorized } $headers = @{ @@ -150,7 +152,7 @@ function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $review $bearerToken = Get-ApiViewBearerToken if (-not $bearerToken) { Write-Error "Failed to acquire Bearer token for APIView authentication." - return 401 + return [System.Net.HttpStatusCode]::Unauthorized } $headers = @{