Modules/businessdev.ALbuild.Marketplace/Public/New-BcMarketplaceAuthContext.ps1
|
function New-BcMarketplaceAuthContext { <# .SYNOPSIS Acquires an OAuth2 access token for the Microsoft Marketplace / Partner Center APIs. .DESCRIPTION Client-credentials OAuth2 against Azure AD, returning an auth context for the other Marketplace cmdlets. The Partner Center ingestion API and the Graph product-ingestion API use different scopes - pass the appropriate -Scope. The context carries the numeric PublisherId (Seller ID), which the Partner Center ingestion API requires as the 'x-ms-publisherId' header, and the client id/secret so the token can be renewed (Update-BcMarketplaceAuthContext) during long submissions. Mirrors the proven Publish-AppSourceApp / New-AppSourceAuthContext implementation. .PARAMETER TenantId Azure AD tenant id (or verified domain). .PARAMETER ClientId Azure AD application (client) id. .PARAMETER ClientSecret Client secret (SecureString or plain string). .PARAMETER Scope OAuth2 scope. Default 'https://api.partner.microsoft.com/.default'. Use 'https://graph.microsoft.com/.default' for product-ingestion (Get-BcMarketplaceProduct). .PARAMETER PublisherId Numeric publisher/Seller ID (Partner Center > Settings > Account settings > Identifiers). Sent as the required 'x-ms-publisherId' header on Partner Center ingestion calls. .OUTPUTS PSCustomObject: AccessToken, TokenType, ExpiresOn, ClientId, ClientSecret, TenantId, Scope, Authority, PublisherId. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Acquires an OAuth token; it does not change system state.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The pipeline supplies the client secret as plain text (a task input/secret variable); it must be wrapped as a SecureString to carry it securely in the auth context.')] [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [string] $TenantId, [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [object] $ClientSecret, [string] $Scope = 'https://api.partner.microsoft.com/.default', [string] $PublisherId ) $secure = if ($ClientSecret -is [System.Security.SecureString]) { $ClientSecret } elseif ($ClientSecret -is [string]) { ConvertTo-SecureString $ClientSecret -AsPlainText -Force } else { throw 'ClientSecret must be a string or SecureString.' } $authority = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" $token = Get-BcMarketplaceAccessToken -ClientId $ClientId -ClientSecret $secure -Scope $Scope -AuthorityUrl $authority return [PSCustomObject]@{ PSTypeName = 'BcMarketplace.AuthContext' AccessToken = $token.access_token TokenType = $token.token_type ExpiresOn = (Get-Date).AddSeconds([int]$token.expires_in) ClientId = $ClientId ClientSecret = $secure TenantId = $TenantId Scope = $Scope Authority = $authority PublisherId = $PublisherId } } function Get-BcMarketplaceAccessToken { # Internal: client-credentials token request; surfaces the AAD error body on failure. [CmdletBinding()] param( [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [System.Security.SecureString] $ClientSecret, [Parameter(Mandatory)] [string] $Scope, [Parameter(Mandatory)] [string] $AuthorityUrl ) $plain = [System.Net.NetworkCredential]::new('', $ClientSecret).Password $body = @{ client_id = $ClientId; client_secret = $plain; scope = $Scope; grant_type = 'client_credentials' } try { return Invoke-RestMethod -Uri $AuthorityUrl -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop } catch { $d = Get-BcMarketplaceErrorDetail -ErrorRecord $_; throw "Marketplace authentication failed: $($_.Exception.Message)$(if ($d) { " - $d" })" } finally { $plain = $null } } function Update-BcMarketplaceAuthContext { # Internal: refresh the token when within 5 minutes of expiry (safe for long submissions). [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Refreshes an in-memory OAuth token; changes no external state.')] [CmdletBinding()] [OutputType([PSCustomObject])] param([Parameter(Mandatory)] [PSCustomObject] $AuthContext) if ($AuthContext.ExpiresOn -gt (Get-Date).AddMinutes(5)) { return $AuthContext } $token = Get-BcMarketplaceAccessToken -ClientId $AuthContext.ClientId -ClientSecret $AuthContext.ClientSecret -Scope $AuthContext.Scope -AuthorityUrl $AuthContext.Authority $AuthContext.AccessToken = $token.access_token $AuthContext.TokenType = $token.token_type $AuthContext.ExpiresOn = (Get-Date).AddSeconds([int]$token.expires_in) return $AuthContext } |