Private/Authentication/Microsoft/Get-PartnerAccessToken.ps1
function Get-PartnerAccessToken { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$TenantId, [Parameter(Mandatory)] [string]$Scopes, [Parameter()] [ValidateSet('Application', 'Delegated')] [string]$FlowType = 'Delegated', [Parameter()] [switch]$Force ) begin { try { if (!$script:PartnerCredentials) { $script:PartnerCredentials = Get-PartnerCredentials } } catch { Write-ModuleLog -Message "Failed to get partner credentials" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_ throw [TokenOperationException]::new( 'Initialization', 'Failed to get partner credentials', $_ ) } } process { try { $cacheKey = "$TenantId|$Scopes|$FlowType" $now = Get-Date $refreshThreshold = $now.AddMinutes($script:TokenCacheConfig.RefreshBuffer) # Validate inputs if (-not [Guid]::TryParse($TenantId, [ref][Guid]::Empty)) { Write-ModuleLog -Message "Invalid token format detected" -Level Error -Component 'PartnerAccessToken' ` -ThrowError -ErrorOperation 'ValidateTenantId' -ErrorMessage 'Invalid TenantId format' } # Check cache for valid token if (!$Force -and $script:TokenCache.ContainsKey($cacheKey)) { $cachedToken = $script:TokenCache[$cacheKey] # Token is still valid if ($cachedToken.ExpirationDateTime -gt $refreshThreshold) { Write-ModuleLog -Message "Using cached token for $cacheKey" -Level Verbose -Component 'PartnerAccessToken' return $cachedToken } # Token needs refresh elseif ($cachedToken.ExpirationDateTime -gt $now -and $FlowType -eq 'Delegated') { Write-ModuleLog -Message "Token for $cacheKey is expired, attempting refresh" -Level Verbose -Component 'PartnerAccessToken' try { $newToken = Get-RefreshedToken -ExistingToken $cachedToken -TenantId $TenantId -Scopes $Scopes $script:TokenCache[$cacheKey] = $newToken Save-TokenCache return $newToken } catch { Write-ModuleLog -Message "Token refresh failed for $cacheKey" -Level Warning -Component 'PartnerAccessToken' -ErrorRecord $_ # Fall through to get new token } } } Write-ModuleLog -Message "Getting new token for $cacheKey" -Level Verbose -Component 'PartnerAccessToken' # Prepare token request $body = @{ client_id = $script:PartnerCredentials.ApplicationId client_secret = $script:PartnerCredentials.ApplicationSecret scope = $Scopes } if ($FlowType -eq 'Application') { $body.grant_type = "client_credentials" } else { if ([string]::IsNullOrEmpty($script:PartnerCredentials.RefreshToken)) { Write-ModuleLog -Message "No refresh token available for delegated flow" -Level Error -Component 'PartnerAccessToken' ` -ThrowError -ErrorOperation 'TokenAcquisition' -ErrorMessage 'No refresh token available for delegated flow' } $body.grant_type = "refresh_token" $body.refresh_token = $script:PartnerCredentials.RefreshToken } # Get new token try { $response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" ` -Method POST ` -Body $body ` -ContentType 'application/x-www-form-urlencoded' } catch { $detailedError = switch -Regex ($_.Exception.Message) { 'AADSTS700016' { 'Application not found or not authorized for tenant' } 'AADSTS7000215' { 'Invalid client secret provided' } 'AADSTS9000410' { 'Invalid scope requested' } 'AADSTS50034' { 'Invalid tenant ID' } default { $_.Exception.Message } } Write-ModuleLog -Message "Failed to acquire token: $detailedError" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_ } try { $token = ConvertFrom-JwtToken -Token $response.access_token # Store refresh token if available (for delegated flow) if ($FlowType -eq 'Delegated' -and $response.refresh_token) { $token | Add-Member -NotePropertyName 'refresh_token' -NotePropertyValue $response.refresh_token } } catch { Write-ModuleLog -Message "Failed to process acquired token" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_ } # Manage cache try { if ($script:TokenCache.Count -ge $script:TokenCacheConfig.MaxSize) { $oldestTokens = $script:TokenCache.GetEnumerator() | Sort-Object { $_.Value.ExpirationDateTime } | Select-Object -First ($script:TokenCache.Count - $script:TokenCacheConfig.MaxSize + 1) foreach ($oldToken in $oldestTokens) { $script:TokenCache.Remove($oldToken.Key) } } $script:TokenCache[$cacheKey] = $token Save-TokenCache } catch { Write-ModuleLog -Message "Failed to manage token cache" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_ } return $token } catch [TokenOperationException] { throw } catch { Write-ModuleLog -Message "An unexpected error occurred during token operation" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_ } } } |