Private/Google/Get-GoogleAccessToken.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Get-GoogleAccessToken { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ServiceAccountKeyPath, [Parameter(Mandatory)] [string]$AdminEmail, [string[]]$Scopes = @('https://www.googleapis.com/auth/admin.reports.audit.readonly'), [string]$ImpersonateUser, [switch]$ForceRefresh ) # Build cache key from sorted scopes + impersonation target $impersonateEmail = if ($ImpersonateUser) { $ImpersonateUser } else { $AdminEmail } $cacheKey = (($Scopes | Sort-Object) -join '|') + '|' + $impersonateEmail # Initialize per-scope token cache if (-not $script:TokenCache) { $script:TokenCache = @{} } # Check cached token for this scope set if (-not $ForceRefresh -and $script:TokenCache.ContainsKey($cacheKey)) { $cached = $script:TokenCache[$cacheKey] if ([DateTimeOffset]::UtcNow.ToUnixTimeSeconds() -lt ($cached.Expiry - 60)) { Write-Verbose "Using cached access token for scopes: $($Scopes -join ', ')" return $cached.Token } } # Backward compat: also check legacy single-token cache for default scope if (-not $ForceRefresh -and -not $ImpersonateUser -and $Scopes.Count -eq 1 -and $Scopes[0] -eq 'https://www.googleapis.com/auth/admin.reports.audit.readonly' -and $script:CachedAccessToken -and $script:TokenExpiry -and [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() -lt ($script:TokenExpiry - 60)) { Write-Verbose 'Using cached access token (legacy cache)' return $script:CachedAccessToken } # Read service account JSON if (-not (Test-Path $ServiceAccountKeyPath)) { throw "Service account key file not found: $ServiceAccountKeyPath" } $serviceAccount = Get-Content -Path $ServiceAccountKeyPath -Raw | ConvertFrom-Json if (-not $serviceAccount.client_email -or -not $serviceAccount.private_key) { throw "Invalid service account key file: missing client_email or private_key" } # Create JWT $jwt = New-GoogleJwt ` -ServiceAccountEmail $serviceAccount.client_email ` -PrivateKeyPem $serviceAccount.private_key ` -Scopes $Scopes ` -ImpersonateUser $impersonateEmail # Exchange JWT for access token $tokenUri = 'https://oauth2.googleapis.com/token' $body = @{ grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer' assertion = $jwt } try { $response = Invoke-RestMethod -Uri $tokenUri -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' } catch { $statusCode = $_.Exception.Response.StatusCode.value__ throw "Google OAuth2 token exchange failed (HTTP $statusCode): $($_.ErrorDetails.Message ?? $_.Exception.Message)" } if (-not $response.access_token) { throw "Google OAuth2 response did not contain an access_token" } # Cache in per-scope cache $script:TokenCache[$cacheKey] = @{ Token = $response.access_token Expiry = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $response.expires_in } # Also update legacy cache for backward compatibility if (-not $ImpersonateUser) { $script:CachedAccessToken = $response.access_token $script:TokenExpiry = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $response.expires_in } Write-Verbose "Access token obtained, expires in $($response.expires_in) seconds" return $response.access_token } |