Private/ConvertFrom-AzAuthorizationError.ps1
|
function ConvertFrom-AzAuthorizationError { <# .SYNOPSIS Parses an Azure Resource Manager AuthorizationFailed error into actions + scope. .DESCRIPTION Internal. Azure (ARM) is the only one of the four supported platforms whose authorization error reliably names the missing action and scope, e.g.: "The client 'x' with object id 'y' does not have authorization to perform action 'Microsoft.Storage/storageAccounts/write' over scope '/subscriptions/.../resourceGroups/rg' or the scope is invalid." or the LinkedAuthorizationFailed / structured-error variants that carry an explicit 'action'/'scope' field. This helper extracts every action and scope it can find from either an ErrorRecord, an Exception, or a raw string. Graph / Fabric / Purview errors do NOT carry this detail, which is why only the Azure provider calls this. .PARAMETER InputObject An ErrorRecord, Exception, or string containing the ARM error text. .OUTPUTS PSCustomObject: Actions [string[]], Scopes [string[]], IsAuthorizationError [bool], Message [string]. #> [CmdletBinding()] [OutputType([psobject])] param( [Parameter(Mandatory, ValueFromPipeline)] [AllowNull()] [object]$InputObject ) process { $text = switch ($InputObject) { { $_ -is [System.Management.Automation.ErrorRecord] } { $parts = @() if ($_.Exception) { $parts += $_.Exception.Message } if ($_.ErrorDetails -and $_.ErrorDetails.Message) { $parts += $_.ErrorDetails.Message } $parts -join "`n" } { $_ -is [System.Exception] } { $_.Message } default { [string]$_ } } if ([string]::IsNullOrWhiteSpace($text)) { return [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.ParsedError' Actions = @() Scopes = @() IsAuthorizationError = $false Message = '' } } $isAuth = $text -match 'AuthorizationFailed|does not have authorization to perform action|LinkedAuthorizationFailed|RoleAssignmentDoesNotExist' # 1) Prose form: ...perform action 'Provider/op/...'... $actions = @() $proseActions = [regex]::Matches($text, "perform action '([^']+)'") foreach ($m in $proseActions) { $actions += $m.Groups[1].Value } # 2) Structured form: "action": "Provider/op" (ARM error JSON) and DataAction variants. $jsonActions = [regex]::Matches($text, '"(?:action|dataAction)"\s*:\s*"([^"]+)"') foreach ($m in $jsonActions) { $actions += $m.Groups[1].Value } # Scopes: prose "over scope '...'" and structured "scope": "...". $scopes = @() foreach ($m in [regex]::Matches($text, "over scope '([^']+)'")) { $scopes += $m.Groups[1].Value } foreach ($m in [regex]::Matches($text, '"scope"\s*:\s*"([^"]+)"')) { $scopes += $m.Groups[1].Value } $uniqueActions = @($actions | Where-Object { $_ } | Select-Object -Unique) $uniqueScopes = @($scopes | Where-Object { $_ } | Select-Object -Unique) Write-PSFMessage -Level Debug -Message "Parsed authorization error (IsAuthError=$isAuth, actions=[$($uniqueActions -join ', ')], scopes=[$($uniqueScopes -join ', ')])." -Tag 'PSAutoRBAC', 'ErrorParse' [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.ParsedError' Actions = $uniqueActions Scopes = $uniqueScopes IsAuthorizationError = [bool]$isAuth Message = $text.Trim() } } } |