modules/Devolutions.CIEM.EffectivePermissions/Private/ResolveCIEMEffectivePermissionActionDescription.ps1
|
function GetCIEMEffectivePermissionDescriptionCatalog { [CmdletBinding()] param() $ErrorActionPreference = 'Stop' if (-not $script:CIEMEffectivePermissionDescriptionCatalog) { $catalogPath = Join-Path $script:EffectivePermissionsRoot 'Data/effective_permission_descriptions.json' if (-not (Test-Path -LiteralPath $catalogPath)) { throw "Effective permission description catalog not found: $catalogPath" } $script:CIEMEffectivePermissionDescriptionCatalog = Get-Content -LiteralPath $catalogPath -Raw | ConvertFrom-Json -ErrorAction Stop } $script:CIEMEffectivePermissionDescriptionCatalog } function ConvertCIEMAnchoredLiteralRegexPattern { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Pattern ) $ErrorActionPreference = 'Stop' if (-not ($Pattern.StartsWith('^') -and $Pattern.EndsWith('$'))) { return $null } $body = $Pattern.Substring(1, $Pattern.Length - 2) $literal = [System.Text.StringBuilder]::new() $regexOperators = '.^$*+?()[]{}|' for ($i = 0; $i -lt $body.Length; $i++) { $character = $body[$i] if ($character -eq '\') { $i++ if ($i -ge $body.Length) { throw "Invalid action description regex pattern '$Pattern'." } [void]$literal.Append($body[$i]) continue } if ($regexOperators.Contains($character)) { return $null } [void]$literal.Append($character) } $literal.ToString() } function GetCIEMEffectivePermissionActionDescriptionIndex { [CmdletBinding()] param() $ErrorActionPreference = 'Stop' if (-not $script:CIEMEffectivePermissionActionDescriptionIndex) { $catalog = GetCIEMEffectivePermissionDescriptionCatalog $exactDescriptions = [System.Collections.Hashtable]::new([System.StringComparer]::OrdinalIgnoreCase) $patternDescriptions = [System.Collections.Generic.List[object]]::new() foreach ($entry in @($catalog.actionDescriptions)) { $provider = [string]$entry.provider $literalAction = ConvertCIEMAnchoredLiteralRegexPattern -Pattern ([string]$entry.pattern) if ($literalAction) { if (-not $exactDescriptions.ContainsKey($provider)) { $exactDescriptions[$provider] = [System.Collections.Hashtable]::new([System.StringComparer]::OrdinalIgnoreCase) } if ($exactDescriptions[$provider].ContainsKey($literalAction)) { throw "Duplicate effective permission action description for provider '$provider' and action '$literalAction'." } $exactDescriptions[$provider][$literalAction] = [string]$entry.description continue } [void]$patternDescriptions.Add($entry) } $script:CIEMEffectivePermissionActionDescriptionIndex = [pscustomobject]@{ ExactDescriptions = $exactDescriptions PatternDescriptions = @($patternDescriptions) } } $script:CIEMEffectivePermissionActionDescriptionIndex } function GetCIEMEffectivePermissionCatalogValue { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Map, [Parameter(Mandatory)] [string]$Key ) $ErrorActionPreference = 'Stop' $property = $Map.PSObject.Properties[$Key] if ($property) { return [string]$property.Value } $null } function ConvertCIEMPermissionIdentifierToWords { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Value ) $ErrorActionPreference = 'Stop' ($Value -creplace '([a-z0-9])([A-Z])', '$1 $2' -replace '[-_]', ' ' -replace '\s+', ' ').Trim().ToLowerInvariant() } function ResolveCIEMEffectivePermissionScopeDescription { [CmdletBinding()] param( [Parameter()] [AllowNull()] [string]$TargetType ) $ErrorActionPreference = 'Stop' $catalog = GetCIEMEffectivePermissionDescriptionCatalog if ([string]::IsNullOrWhiteSpace($TargetType)) { throw "Effective permission description mapping missing for target scope '$TargetType'." } $scope = GetCIEMEffectivePermissionCatalogValue -Map $catalog.scopeDescriptions -Key $TargetType if ($scope) { return $scope } throw "Effective permission description mapping missing for target scope '$TargetType'." } function ResolveCIEMEffectivePermissionResourceDescription { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ResourcePath ) $ErrorActionPreference = 'Stop' $catalog = GetCIEMEffectivePermissionDescriptionCatalog $key = $ResourcePath.ToLowerInvariant() $description = GetCIEMEffectivePermissionCatalogValue -Map $catalog.resourceDescriptions -Key $key if ($description) { return $description } throw "Effective permission description mapping missing for Azure resource path '$ResourcePath'." } function ResolveCIEMGenericAzureActionDescription { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$NativeAction, [Parameter(Mandatory)] [string]$ScopeDescription ) $ErrorActionPreference = 'Stop' $segments = @($NativeAction -split '/' | Where-Object { $_ }) if ($segments.Count -eq 0) { throw 'NativeAction cannot be empty.' } if ($NativeAction -eq '*') { return "Can manage all resources in $ScopeDescription" } $operation = $segments[-1] if ($segments.Count -eq 1) { $operationLabel = ConvertCIEMPermissionIdentifierToWords -Value $operation return "Can perform $operationLabel in $ScopeDescription" } $resourceSegmentEnd = $segments.Count - 2 $actionLabel = $null if ($operation -eq 'action' -and $segments.Count -gt 2) { $actionLabel = ConvertCIEMPermissionIdentifierToWords -Value $segments[-2] $resourceSegmentEnd = $segments.Count - 3 } $resourcePath = ($segments[0..$resourceSegmentEnd] -join '/') $resourceDescription = ResolveCIEMEffectivePermissionResourceDescription -ResourcePath $resourcePath switch ($operation.ToLowerInvariant()) { '*' { return "Can manage $resourceDescription in $ScopeDescription" } 'read' { return "Can read $resourceDescription in $ScopeDescription" } 'write' { return "Can create or modify $resourceDescription in $ScopeDescription" } 'delete' { return "Can delete $resourceDescription in $ScopeDescription" } 'action' { return "Can run the $actionLabel action on $resourceDescription in $ScopeDescription" } default { $operationLabel = ConvertCIEMPermissionIdentifierToWords -Value $operation return "Can perform $operationLabel on $resourceDescription in $ScopeDescription" } } } function ResolveCIEMTargetActionDescription { [CmdletBinding()] param( [Parameter()] [AllowNull()] [string]$TargetType, [Parameter(Mandatory)] [string]$NativeAction ) $ErrorActionPreference = 'Stop' $catalog = GetCIEMEffectivePermissionDescriptionCatalog if (-not [string]::IsNullOrWhiteSpace($TargetType)) { $description = GetCIEMEffectivePermissionCatalogValue -Map $catalog.targetActionDescriptions -Key $TargetType if ($description) { return $description } } throw "Effective permission description mapping missing for target type '$TargetType' and native action '$NativeAction'." } function ResolveCIEMDirectoryRoleActionDescription { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$DirectoryRoleName ) $ErrorActionPreference = 'Stop' $catalog = GetCIEMEffectivePermissionDescriptionCatalog $description = GetCIEMEffectivePermissionCatalogValue -Map $catalog.directoryRoleDescriptions -Key $DirectoryRoleName if ($description) { return $description } throw "Effective permission description mapping missing for Microsoft Entra directory role '$DirectoryRoleName'." } function ResolveCIEMGraphPermissionActionDescription { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$PermissionSubject, [Parameter(Mandatory)] [string]$PermissionVerb ) $ErrorActionPreference = 'Stop' $catalog = GetCIEMEffectivePermissionDescriptionCatalog $subject = GetCIEMEffectivePermissionCatalogValue -Map $catalog.graphPermissionSubjects -Key $PermissionSubject if (-not $subject) { throw "Effective permission description mapping missing for Microsoft Graph permission subject '$PermissionSubject'." } $verb = GetCIEMEffectivePermissionCatalogValue -Map $catalog.graphPermissionVerbs -Key $PermissionVerb if (-not $verb) { throw "Effective permission description mapping missing for Microsoft Graph permission verb '$PermissionVerb'." } "Can $verb Microsoft Graph $subject data" } function ResolveCIEMEffectivePermissionActionDescription { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [CIEMEffectivePermissionProvider]$Provider, [Parameter(Mandatory)] [string]$NativeAction, [Parameter()] [AllowNull()] [string]$TargetType ) $ErrorActionPreference = 'Stop' if ([string]::IsNullOrWhiteSpace($NativeAction)) { throw 'NativeAction cannot be empty.' } $index = GetCIEMEffectivePermissionActionDescriptionIndex $providerKey = [string]$Provider $description = $null if ($index.ExactDescriptions.ContainsKey($providerKey)) { $description = $index.ExactDescriptions[$providerKey][$NativeAction] } if ($description) { if ($description -match '\{scope\}') { $scopeDescription = ResolveCIEMEffectivePermissionScopeDescription -TargetType $TargetType return $description.Replace('{scope}', $scopeDescription) } return $description } foreach ($entry in @($index.PatternDescriptions)) { if ([string]$entry.provider -ne [string]$Provider) { continue } if ([regex]::IsMatch($NativeAction, [string]$entry.pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)) { $description = [string]$entry.description if ($description -match '\{scope\}') { $scopeDescription = ResolveCIEMEffectivePermissionScopeDescription -TargetType $TargetType return $description.Replace('{scope}', $scopeDescription) } return $description } } if ($TargetType -eq 'EntraDirectoryRole') { return ResolveCIEMDirectoryRoleActionDescription -DirectoryRoleName $NativeAction } if ($NativeAction -match '^([A-Za-z0-9-]+)\.([A-Za-z][A-Za-z0-9]*)(\.|$)') { return ResolveCIEMGraphPermissionActionDescription -PermissionSubject $Matches[1] -PermissionVerb $Matches[2] } if ($TargetType -match '^Entra') { return ResolveCIEMTargetActionDescription -TargetType $TargetType -NativeAction $NativeAction } if ($Provider -eq [CIEMEffectivePermissionProvider]::Azure -and $NativeAction -match '/') { $scopeDescription = ResolveCIEMEffectivePermissionScopeDescription -TargetType $TargetType return ResolveCIEMGenericAzureActionDescription -NativeAction $NativeAction -ScopeDescription $scopeDescription } if ($Provider -eq [CIEMEffectivePermissionProvider]::Azure) { $scopeDescription = ResolveCIEMEffectivePermissionScopeDescription -TargetType $TargetType return ResolveCIEMAzureRoleNameActionDescription -RoleName $NativeAction -ScopeDescription $scopeDescription } ResolveCIEMTargetActionDescription -TargetType $TargetType -NativeAction $NativeAction } function ResolveCIEMAzureRoleNameActionDescription { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$RoleName, [Parameter(Mandatory)] [string]$ScopeDescription ) $ErrorActionPreference = 'Stop' $catalog = GetCIEMEffectivePermissionDescriptionCatalog $description = GetCIEMEffectivePermissionCatalogValue -Map $catalog.azureRoleNameDescriptions -Key $RoleName if ($description) { return $description.Replace('{scope}', $ScopeDescription) } throw "Effective permission description mapping missing for Azure role name '$RoleName'." } |