Last active
January 23, 2026 22:12
-
-
Save 7effrey89/876f855c9c8cb07a9753ee6cc50886b9 to your computer and use it in GitHub Desktop.
OneLake Security Rest API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [CmdletBinding()] | |
| # Summary: | |
| # Standalone validation utility that provisions a Fabric OneLake data access role | |
| # (default: GoldReader) on the specified lakehouse and assigns a Microsoft Entra | |
| # user to it using the documented preview endpoints. Useful for testing role | |
| # creation/membership independently of the full workspace automation. | |
| #how to run script: | |
| #Onelake security must be enabled on the target lakehouse for this script to work. | |
| #pwsh -NoLogo -NoProfile -Command "& 'c:/Git/Fabric_powershell/Standalone scripts/Test-OneLakeRoleAssignment.ps1' -WorkspaceName '001onelakesec' -LakehouseName 'Gold' -TenantId 'guid-guid-guid' -UserObjectId 'guid-guid-guid' -RoleName 'GoldReader'" | |
| param( | |
| [Parameter(Mandatory=$true)][string]$WorkspaceName, | |
| [Parameter(Mandatory=$true)][string]$LakehouseName, | |
| [Parameter(Mandatory=$true)][string]$TenantId, | |
| [Parameter(Mandatory=$true)][string]$UserObjectId, | |
| [string]$RoleName = "GoldReader", | |
| [string[]]$PathScope = @("*"), | |
| [string[]]$ActionScope = @("Read"), | |
| [string]$ClientId = "", | |
| [string]$ClientSecret = "" | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| $global:baseUrl = "https://api.fabric.microsoft.com/v1" | |
| $global:resourceUrl = "https://api.fabric.microsoft.com" | |
| $global:fabricHeaders = @{} | |
| function ConvertSecureStringToPlainText { | |
| param([System.Security.SecureString]$SecureString) | |
| $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) | |
| try { | |
| return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr) | |
| } | |
| finally { | |
| [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr) | |
| } | |
| } | |
| function GetSecureTokenForUserPrincipal { | |
| if (-not (Get-AzContext -ErrorAction SilentlyContinue)) { | |
| Write-Host "Connecting to Azure interactively..." -ForegroundColor Cyan | |
| Connect-AzAccount -TenantId $TenantId | Out-Null | |
| } | |
| Write-Host "Acquiring Fabric access token (user)..." -ForegroundColor Cyan | |
| return (Get-AzAccessToken -AsSecureString -ResourceUrl $global:resourceUrl).Token | |
| } | |
| function GetSecureTokenForServicePrincipal { | |
| if ([string]::IsNullOrWhiteSpace($ClientSecret)) { | |
| throw "ClientSecret must be provided when using ClientId." | |
| } | |
| $secureSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force | |
| $credential = New-Object System.Management.Automation.PSCredential ($ClientId, $secureSecret) | |
| Write-Host "Connecting with Service Principal..." -ForegroundColor Cyan | |
| Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $credential | Out-Null | |
| Write-Host "Acquiring Fabric access token (service principal)..." -ForegroundColor Cyan | |
| return (Get-AzAccessToken -AsSecureString -ResourceUrl $global:resourceUrl).Token | |
| } | |
| function SetFabricHeaders { | |
| $useServicePrincipal = -not [string]::IsNullOrWhiteSpace($ClientId) | |
| $secureToken = if ($useServicePrincipal) { GetSecureTokenForServicePrincipal } else { GetSecureTokenForUserPrincipal } | |
| $token = ConvertSecureStringToPlainText -SecureString $secureToken | |
| $global:fabricHeaders = @{ | |
| "Content-Type" = "application/json" | |
| "Authorization" = "Bearer $token" | |
| } | |
| Write-Host "Fabric headers initialized." -ForegroundColor Green | |
| } | |
| function GetErrorResponse { | |
| param([System.Management.Automation.ErrorRecord]$ErrorRecord) | |
| if ($ErrorRecord.ErrorDetails -and $ErrorRecord.ErrorDetails.Message) { | |
| return $ErrorRecord.ErrorDetails.Message | |
| } | |
| $exception = $ErrorRecord.Exception | |
| if (-not $exception -or -not $exception.Response) { | |
| return $exception?.Message | |
| } | |
| try { | |
| $stream = $exception.Response.GetResponseStream() | |
| if (-not $stream) { | |
| return $exception.Message | |
| } | |
| $reader = [System.IO.StreamReader]::new($stream) | |
| $reader.BaseStream.Position = 0 | |
| $reader.DiscardBufferedData() | |
| return $reader.ReadToEnd() | |
| } | |
| catch { | |
| return $exception.Message | |
| } | |
| } | |
| function GetWorkspaceByName { | |
| param([string]$Name) | |
| $url = "$global:baseUrl/workspaces" | |
| $response = Invoke-RestMethod -Headers $global:fabricHeaders -Uri $url -Method GET | |
| $workspace = $response.value | Where-Object { $_.displayName -eq $Name } | Select-Object -First 1 | |
| if (-not $workspace) { | |
| throw "Workspace '$Name' not found." | |
| } | |
| Write-Host "Workspace '$Name' resolved to ID $($workspace.id)." -ForegroundColor Green | |
| return $workspace | |
| } | |
| function GetLakehouseByName { | |
| param([string]$WorkspaceId, [string]$Name) | |
| $itemsUrl = "$global:baseUrl/workspaces/$WorkspaceId/items" | |
| $items = Invoke-RestMethod -Headers $global:fabricHeaders -Uri $itemsUrl -Method GET | |
| $lakehouse = $items.value | Where-Object { $_.displayName -eq $Name -and $_.type -eq "Lakehouse" } | Select-Object -First 1 | |
| if (-not $lakehouse) { | |
| throw "Lakehouse '$Name' not found in workspace $WorkspaceId." | |
| } | |
| Write-Host "Lakehouse '$Name' resolved to ID $($lakehouse.id)." -ForegroundColor Green | |
| return $lakehouse | |
| } | |
| function NewOneLakeRoleDefinition { | |
| param( | |
| [string]$RoleName, | |
| [string[]]$PathScope, | |
| [string[]]$ActionScope | |
| ) | |
| if (-not $PathScope -or $PathScope.Count -eq 0) { | |
| $PathScope = @("*") | |
| } | |
| if (-not $ActionScope -or $ActionScope.Count -eq 0) { | |
| $ActionScope = @("Read") | |
| } | |
| return @{ | |
| name = $RoleName | |
| decisionRules = @( | |
| @{ | |
| effect = "Permit" | |
| permission = @( | |
| @{ | |
| attributeName = "Path" | |
| attributeValueIncludedIn = $PathScope | |
| }, | |
| @{ | |
| attributeName = "Action" | |
| attributeValueIncludedIn = $ActionScope | |
| } | |
| ) | |
| } | |
| ) | |
| members = @{ | |
| microsoftEntraMembers = @() | |
| fabricItemMembers = @() | |
| } | |
| } | |
| } | |
| function GetOneLakeDataAccessRoles { | |
| param([string]$WorkspaceId, [string]$LakehouseId) | |
| $url = "$global:baseUrl/workspaces/$WorkspaceId/items/$LakehouseId/dataAccessRoles?preview=true" | |
| try { | |
| $response = Invoke-RestMethod -Headers $global:fabricHeaders -Uri $url -Method GET | |
| $roles = if ($response.value) { $response.value } else { $response } | |
| if (-not $roles) { | |
| return @() | |
| } | |
| return @($roles) | |
| } | |
| catch { | |
| $err = GetErrorResponse -ErrorRecord $_ | |
| if ($err -and $err -match "UniversalSecurityFeatureDisabledForWorkspace") { | |
| throw [System.InvalidOperationException] "OneLakeSecurityDisabled" | |
| } | |
| throw | |
| } | |
| } | |
| function SetOneLakeDataAccessRoles { | |
| param([string]$WorkspaceId, [string]$LakehouseId, [object[]]$Roles) | |
| $payload = @{ value = @($Roles) } | ConvertTo-Json -Depth 30 | |
| $url = "$global:baseUrl/workspaces/$WorkspaceId/items/$LakehouseId/dataAccessRoles?preview=true" | |
| try { | |
| Invoke-RestMethod -Headers $global:fabricHeaders -Uri $url -Method PUT -Body $payload | Out-Null | |
| } | |
| catch { | |
| $err = GetErrorResponse -ErrorRecord $_ | |
| if ($err -and $err -match "UniversalSecurityFeatureDisabledForWorkspace") { | |
| throw [System.InvalidOperationException] "OneLakeSecurityDisabled" | |
| } | |
| throw "Failed to persist OneLake data access roles: $err" | |
| } | |
| } | |
| function GetOneLakeDataAccessRoleDefinition { | |
| param([string]$WorkspaceId, [string]$LakehouseId, [string]$RoleName) | |
| $roles = GetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId | |
| return $roles | Where-Object { $_.name -eq $RoleName } | Select-Object -First 1 | |
| } | |
| function EnsureOneLakeDataAccessRole { | |
| param( | |
| [string]$WorkspaceId, | |
| [string]$LakehouseId, | |
| [string]$RoleName, | |
| [string[]]$PathScope, | |
| [string[]]$ActionScope | |
| ) | |
| $roles = @() | |
| try { | |
| $roles = GetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId | |
| } | |
| catch [System.InvalidOperationException] { | |
| if ($_.Exception.Message -eq "OneLakeSecurityDisabled") { | |
| Write-Warning "OneLake security preview is disabled for this lakehouse." | |
| return $false | |
| } | |
| throw | |
| } | |
| $existingRole = $roles | Where-Object { $_.name -eq $RoleName } | Select-Object -First 1 | |
| if ($existingRole) { | |
| Write-Host "Role '$RoleName' already exists." -ForegroundColor Yellow | |
| return $true | |
| } | |
| $definition = NewOneLakeRoleDefinition -RoleName $RoleName -PathScope $PathScope -ActionScope $ActionScope | |
| $updatedRoles = @($roles) | |
| $updatedRoles += $definition | |
| try { | |
| SetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId -Roles $updatedRoles | |
| Write-Host "Provisioned OneLake data access role '$RoleName'." -ForegroundColor Green | |
| return $true | |
| } | |
| catch [System.InvalidOperationException] { | |
| if ($_.Exception.Message -eq "OneLakeSecurityDisabled") { | |
| Write-Warning "OneLake security preview is disabled for this lakehouse." | |
| return $false | |
| } | |
| throw | |
| } | |
| } | |
| function UpdateOneLakeRoleMembers { | |
| param( | |
| [string]$WorkspaceId, | |
| [string]$LakehouseId, | |
| [psobject]$RoleDefinition, | |
| [string]$UserObjectId, | |
| [string]$TenantId | |
| ) | |
| # Fetch entire role collection to keep server-side state consistent. | |
| $roles = @() | |
| try { | |
| $roles = GetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId | |
| } | |
| catch [System.InvalidOperationException] { | |
| if ($_.Exception.Message -eq "OneLakeSecurityDisabled") { | |
| Write-Warning "OneLake security preview is disabled for this lakehouse." | |
| return | |
| } | |
| throw | |
| } | |
| $roles = @($roles) | |
| $targetRole = $roles | Where-Object { $_.name -eq $RoleDefinition.name } | Select-Object -First 1 | |
| if (-not $targetRole) { | |
| $targetRole = $RoleDefinition | |
| $roles += $targetRole | |
| } | |
| if (-not $targetRole.members) { | |
| $targetRole | Add-Member -MemberType NoteProperty -Name members -Value @{} -Force | |
| } | |
| $entraMembers = @() | |
| if ($targetRole.members.microsoftEntraMembers) { | |
| $entraMembers = @($targetRole.members.microsoftEntraMembers) | |
| } | |
| if ($entraMembers | Where-Object { $_.objectId -eq $UserObjectId }) { | |
| Write-Host "User already assigned to role '$($targetRole.name)'." -ForegroundColor Yellow | |
| return | |
| } | |
| $entraMembers += @{ | |
| objectId = $UserObjectId | |
| objectType = "User" | |
| tenantId = $TenantId | |
| } | |
| $targetRole.members.microsoftEntraMembers = $entraMembers | |
| if (-not $targetRole.members.fabricItemMembers) { | |
| $targetRole.members.fabricItemMembers = @() | |
| } | |
| SetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId -Roles $roles | |
| Write-Host "Assigned user to role '$($targetRole.name)'." -ForegroundColor Green | |
| } | |
| function Invoke-OneLakeRoleTest { | |
| Write-Host "\n--- OneLake Role Test ---" -ForegroundColor Cyan | |
| Write-Host "Workspace: $WorkspaceName" -ForegroundColor Cyan | |
| Write-Host "Lakehouse: $LakehouseName" -ForegroundColor Cyan | |
| Write-Host "Role: $RoleName" -ForegroundColor Cyan | |
| SetFabricHeaders | |
| $workspace = GetWorkspaceByName -Name $WorkspaceName | |
| $lakehouse = GetLakehouseByName -WorkspaceId $workspace.id -Name $LakehouseName | |
| $provisioned = EnsureOneLakeDataAccessRole -WorkspaceId $workspace.id -LakehouseId $lakehouse.id -RoleName $RoleName -PathScope $PathScope -ActionScope $ActionScope | |
| if (-not $provisioned) { | |
| Write-Warning "Stopping test because OneLake security is disabled." | |
| return | |
| } | |
| $roleDefinition = GetOneLakeDataAccessRoleDefinition -WorkspaceId $workspace.id -LakehouseId $lakehouse.id -RoleName $RoleName | |
| if (-not $roleDefinition) { | |
| $roleDefinition = NewOneLakeRoleDefinition -RoleName $RoleName -PathScope $PathScope -ActionScope $ActionScope | |
| } | |
| if ($roleDefinition.PSObject.Properties.Name -contains "id") { | |
| Write-Host "Role '$RoleName' id: $($roleDefinition.id)" -ForegroundColor Gray | |
| } | |
| UpdateOneLakeRoleMembers -WorkspaceId $workspace.id -LakehouseId $lakehouse.id -RoleDefinition $roleDefinition -UserObjectId $UserObjectId -TenantId $TenantId | |
| Write-Host "OneLake role test complete." -ForegroundColor Green | |
| } | |
| Invoke-OneLakeRoleTest |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment