Private/Authentication/Acronis/Get-AcronisO365AuthorizationCode.ps1
|
function Get-AcronisO365AuthorizationCode { <# .SYNOPSIS Headlessly mints a fresh OAuth authorization code for a customer M365 tenant using the cached partner session cookie + prompt=none (no browser). Returns @{ Code; SessionState } or throws a descriptive error. .PARAMETER TenantId The CUSTOMER M365 tenant GUID. Must be used as the authority (NOT /organizations/). .PARAMETER Cookie The AAD session Cookie header value (from Get-AcronisO365SessionCookie). #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$TenantId, [Parameter(Mandatory)][string]$Cookie ) $state = 'eyJkY0lkIjoibzM2NXdvcmxkd2lkZSJ9' $redirectEncoded = [uri]::EscapeDataString('https://cloud.acronis.com/api/1/oauth_o365_cb') $scopeEncoded = [uri]::EscapeDataString('offline_access https://graph.microsoft.com/.default') # IMPORTANT: target the CUSTOMER tenant as the authority and always build the # URL with -f (the "$Var?" interpolation bug truncates query strings). $authorizeUrl = 'https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize?client_id={1}&prompt=none&redirect_uri={2}&response_mode=query&response_type=code&scope={3}&state={4}' -f $TenantId, '912b3045-82b8-40a4-9da9-cff4dc5290af', $redirectEncoded, $scopeEncoded, $state Write-ModuleLog -Message "Minting authorization code for tenant $TenantId via prompt=none" -Level Verbose -Component 'AcronisO365Auth' $handler = [System.Net.Http.HttpClientHandler]::new() $handler.AllowAutoRedirect = $false $client = [System.Net.Http.HttpClient]::new($handler) $client.Timeout = [TimeSpan]::FromSeconds(60) $status = 0 $loc = '' try { $req = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, $authorizeUrl) $null = $req.Headers.TryAddWithoutValidation('Cookie', $Cookie) $null = $req.Headers.TryAddWithoutValidation('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') $resp = $client.SendAsync($req).Result $status = [int]$resp.StatusCode if ($resp.Headers.Location) { $loc = $resp.Headers.Location.AbsoluteUri } $resp.Dispose() } catch { throw "Authorization request for tenant $TenantId failed: $($_.Exception.Message)" } finally { $client.Dispose() $handler.Dispose() } if (-not $loc) { throw "Authorization for tenant $TenantId returned no redirect (HTTP $status)." } $q = @{} foreach ($pair in ([uri]$loc).Query.TrimStart('?').Split('&')) { if ($pair) { $kv = $pair -split '=', 2 if ($kv.Count -eq 2) { $q[$kv[0]] = [uri]::UnescapeDataString($kv[1]) } } } if ($q.ContainsKey('code') -and $q['code']) { Write-ModuleLog -Message "Got authorization code for tenant $TenantId" -Level Verbose -Component 'AcronisO365Auth' return [PSCustomObject]@{ Code = $q['code']; SessionState = $q['session_state'] } } if ($q.ContainsKey('error')) { $err = $q['error'] $desc = $q['error_description'] switch ($err) { 'interaction_required' { throw "No silent session for tenant $TenantId - the partner GA may need to sign in once at this tenant, or the session cookie is expired. Re-run Save-AcronisO365Session -ForceRefresh. ($desc)" } 'consent_required' { throw "Admin consent for the Acronis app is not granted in tenant $TenantId. ($desc)" } 'login_required' { throw "Not signed in - re-run Save-AcronisO365Session to refresh the session cookie. ($desc)" } default { throw "Authorization failed for tenant ${TenantId}: $err - $desc" } } } throw "Authorization for tenant ${TenantId} returned an unexpected redirect: $loc" } |