Providers/FabricProbeProvider.ps1
|
function New-FabricProbeProvider { <# .SYNOPSIS Constructs the Microsoft Fabric RBAC probe provider. .DESCRIPTION Internal factory. Fabric has no action-based RBAC model and its REST errors (InsufficientPrivileges / Unauthorized) do not name a required role, so error-parsing cannot derive a requirement. Access is governed by the four workspace roles - Admin > Member > Contributor > Viewer - plus item permissions and tenant settings. This provider resolves requirements from the knowledge base and evaluates access by listing workspace role assignments via the Fabric REST API (GET /v1/workspaces/{id}/roleAssignments). The required role is satisfied by any role at or above it in the hierarchy. .OUTPUTS PSCustomObject (PSAutoRBAC.Provider) #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Factory; constructs and returns a provider object, makes no state change.')] [OutputType([psobject])] param() $resourceUrl = 'https://api.fabric.microsoft.com' # Higher number = more privilege. Used so a higher role satisfies a lower requirement. $rank = @{ 'Viewer' = 1; 'Contributor' = 2; 'Member' = 3; 'Admin' = 4 } [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Provider' Name = 'Microsoft Fabric' Aliases = @('Fabric', 'PowerBI', 'Power BI') SupportsLiveProbe = $false MatchScope = { param($Assignment, $Scope) $true } ResolveRequirement = { param($Command, $Context, $Options) Write-PSFMessage -Level Verbose -Message "Fabric: resolving requirement for '$Command'." -Tag 'PSAutoRBAC', 'Provider', 'Fabric' $mapPath = if ($Options -and $Options.ContainsKey('MapPath')) { $Options.MapPath } else { $null } $map = if ($mapPath) { Get-RBACKnowledgeBase -Path $mapPath } else { Get-RBACKnowledgeBase -Name 'CommandRoleMap' } $commands = $map['Microsoft Fabric'] $commandKey = $commands.Keys | Where-Object { $_ -eq $Command } | Select-Object -First 1 $isKnown = [bool]$commandKey if (-not $commandKey) { $commandKey = '*Default' } $entry = $commands[$commandKey] [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Requirement' Platform = 'Microsoft Fabric' Command = $Command Roles = @($entry.Roles) Permissions = @($entry.Actions) ScopeLevel = $entry.ScopeLevel Notes = $entry.Notes IsKnown = $isKnown Source = 'KnowledgeBase' } } TestAccess = { param($CallerId, $RequiredRole, $Scope, $Context, $Options) Write-PSFMessage -Level Verbose -Message "Fabric: testing '$CallerId' for [$(@($RequiredRole) -join ', ')] on workspace '$Scope'." -Tag 'PSAutoRBAC', 'Provider', 'Fabric' $rank = @{ 'Viewer' = 1; 'Contributor' = 2; 'Member' = 3; 'Admin' = 4 } $resourceUrl = 'https://api.fabric.microsoft.com' # Determine the caller's effective workspace role. $heldRoles = @() $evaluated = $true if ($Options -and $Options.ContainsKey('RoleAssignment')) { $heldRoles = @($Options.RoleAssignment | ForEach-Object { if ($_ -is [string]) { $_ } elseif ($_.Role) { $_.Role } elseif ($_.RoleDefinitionName) { $_.RoleDefinitionName } }) Write-PSFMessage -Level Debug -Message "Fabric: evaluating against supplied role(s) [$($heldRoles -join ', ')] (offline)." -Tag 'PSAutoRBAC', 'Provider', 'Fabric' } elseif ($Scope -and $Scope -ne '/' -and $Scope -ne 'Tenant') { $uri = "$resourceUrl/v1/workspaces/$Scope/roleAssignments" Write-PSFMessage -Level Debug -Message "Fabric: listing role assignments for workspace '$Scope'." -Tag 'PSAutoRBAC', 'Provider', 'Fabric' $res = Invoke-RBACRestRequest -Context $Context -ResourceUrl $resourceUrl -Uri $uri if ($res.Success) { $heldRoles = @($res.Content.value | Where-Object { $_.principal.id -eq $CallerId -or $_.principal.userDetails.userPrincipalName -eq $CallerId } | ForEach-Object { $_.role }) Write-PSFMessage -Level Debug -Message "Fabric: '$CallerId' holds workspace role(s) [$($heldRoles -join ', ')]." -Tag 'PSAutoRBAC', 'Provider', 'Fabric' } elseif ($res.IsAuthorizationError) { # Cannot even read assignments -> caller almost certainly lacks the role. Write-PSFMessage -Level Verbose -Message "Fabric: authorization error reading assignments for '$Scope'; treating as no access." -Tag 'PSAutoRBAC', 'Provider', 'Fabric' $heldRoles = @() } else { $evaluated = $false Write-PSFMessage -Level Warning -Message "Fabric: could not evaluate access for workspace '$Scope': $($res.Message)" -Tag 'PSAutoRBAC', 'Provider', 'Fabric' } } else { $evaluated = $false Write-PSFMessage -Level Warning -Message 'Fabric: access evaluation needs a workspace id in -Scope, or -RoleAssignment for offline checks.' -Tag 'PSAutoRBAC', 'Provider', 'Fabric' } $heldRank = ($heldRoles | ForEach-Object { [int]$rank[$_] } | Measure-Object -Maximum).Maximum if (-not $heldRank) { $heldRank = 0 } foreach ($role in $RequiredRole) { $needRank = [int]$rank[$role] $has = if (-not $evaluated) { $null } elseif ($needRank -eq 0) { ($heldRoles -contains $role) } else { $heldRank -ge $needRank } [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.AccessState' Platform = 'Microsoft Fabric' CallerId = $CallerId Role = $role Scope = $Scope HasAccess = $has } } } ProbeLive = { param($Command, $ArgumentList, $Scope, $Context) $map = Get-RBACKnowledgeBase -Name 'CommandRoleMap' $commands = $map['Microsoft Fabric'] $key = $commands.Keys | Where-Object { $_ -eq $Command } | Select-Object -First 1 if (-not $key) { $key = '*Default' } $entry = $commands[$key] [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Requirement' Platform = 'Microsoft Fabric'; Command = $Command Roles = @($entry.Roles); Permissions = @($entry.Actions); ScopeLevel = $entry.ScopeLevel Notes = 'Fabric REST errors do not name the required role; requirement resolved from the knowledge base, not a live failure.' IsKnown = [bool]$key; Source = 'KnowledgeBase' } } NewGrantScript = { param($CallerId, $Role, $Scope, $Options) $workspace = if ($Options -and $Options.ContainsKey('WorkspaceId')) { $Options.WorkspaceId } else { $Scope } $add = @" # PSAutoRBAC: grant Fabric workspace role '$Role' to '$CallerId' on workspace '$workspace'. # Run as a workspace Admin. Idempotent. CallerId must be the principal's object id. `$base = 'https://api.fabric.microsoft.com/v1/workspaces/$workspace/roleAssignments' `$token = (Get-AzAccessToken -ResourceUrl 'https://api.fabric.microsoft.com').Token `$headers = @{ Authorization = "Bearer `$token" } `$existing = (Invoke-RestMethod -Uri `$base -Headers `$headers).value | Where-Object { `$_.principal.id -eq '$CallerId' } if (`$existing) { Write-Host "Already has '`$(`$existing.role)' on workspace '$workspace'." } else { `$body = @{ principal = @{ id = '$CallerId'; type = 'User' }; role = '$Role' } | ConvertTo-Json Invoke-RestMethod -Uri `$base -Method POST -Headers `$headers -Body `$body -ContentType 'application/json' Write-Host "Granted '$Role' to '$CallerId' on workspace '$workspace'." } "@ $remove = @" # PSAutoRBAC: remove Fabric workspace role for '$CallerId' from workspace '$workspace'. # Run as a workspace Admin. Idempotent. `$base = 'https://api.fabric.microsoft.com/v1/workspaces/$workspace/roleAssignments' `$token = (Get-AzAccessToken -ResourceUrl 'https://api.fabric.microsoft.com').Token `$headers = @{ Authorization = "Bearer `$token" } `$existing = (Invoke-RestMethod -Uri `$base -Headers `$headers).value | Where-Object { `$_.principal.id -eq '$CallerId' } if (`$existing) { Invoke-RestMethod -Uri "`$base/`$(`$existing.id)" -Method DELETE -Headers `$headers Write-Host "Removed workspace role from '$CallerId'." } else { Write-Host "No workspace role for '$CallerId' on '$workspace'." } "@ @{ AddScript = $add; RemoveScript = $remove } } } } Register-RBACProvider -Provider (New-FabricProbeProvider) |