Skip to content

Commit 15cc4b4

Browse files
committed
feat: enhance role permission handling and introduce Get-CippHttpPermissions function
fix issue with stale permissions causing escalation alerts on api client management
1 parent 660a177 commit 15cc4b4

4 files changed

Lines changed: 66 additions & 24 deletions

File tree

Modules/CIPPCore/Public/Authentication/Get-CIPPRolePermissions.ps1

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,25 @@ function Get-CIPPRolePermissions {
1717
$Filter = "RowKey eq '$RoleName'"
1818
$Role = Get-CIPPAzDataTableEntity @Table -Filter $Filter
1919
if ($Role) {
20-
$Permissions = $Role.Permissions | ConvertFrom-Json
20+
$Permissions = ($Role.Permissions | ConvertFrom-Json).PSObject.Properties.Value
21+
# Stored permissions can reference endpoints removed or renamed in later CIPP
22+
# versions; drop those so stale entries don't inflate the role's permission set
23+
# (e.g. failing the Test-CippApiClientRoleGrant subset check). Skip filtering if
24+
# the valid-permission universe can't be resolved, rather than emptying the role.
25+
try {
26+
$ValidPermissions = Get-CippHttpPermissions
27+
if (@($ValidPermissions).Count -gt 0) {
28+
$Permissions = @($Permissions | Where-Object { $ValidPermissions -contains $_ })
29+
}
30+
} catch {
31+
Write-Warning "Unable to resolve valid permissions to filter role '$RoleName': $($_.Exception.Message)"
32+
}
2133
$AllowedTenants = if ($Role.AllowedTenants) { $Role.AllowedTenants | ConvertFrom-Json } else { @() }
2234
$BlockedTenants = if ($Role.BlockedTenants) { $Role.BlockedTenants | ConvertFrom-Json } else { @() }
2335
$BlockedEndpoints = if ($Role.BlockedEndpoints) { $Role.BlockedEndpoints | ConvertFrom-Json } else { @() }
2436
[PSCustomObject]@{
2537
Role = $Role.RowKey
26-
Permissions = $Permissions.PSObject.Properties.Value
38+
Permissions = @($Permissions)
2739
AllowedTenants = @($AllowedTenants)
2840
BlockedTenants = @($BlockedTenants)
2941
BlockedEndpoints = @($BlockedEndpoints)

Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,10 @@ function Get-CippAllowedPermissions {
2222
)
2323

2424
# Get all available permissions and base roles configuration
25-
26-
$Version = if ($env:CIPPNG -eq 'true') {
27-
$env:APP_VERSION
28-
} else {
29-
(Get-Content -Path (Join-Path $env:CIPPRootPath 'version_latest.txt')).Trim()
30-
}
3125
$BaseRoles = Get-Content -Path (Join-Path $env:CIPPRootPath 'Config\cipp-roles.json') | ConvertFrom-Json
3226
$DefaultRoles = @('superadmin', 'admin', 'editor', 'readonly', 'anonymous', 'authenticated')
3327

34-
$AllPermissionCacheTable = Get-CIPPTable -tablename 'cachehttppermissions'
35-
$AllPermissionsRow = Get-CIPPAzDataTableEntity @AllPermissionCacheTable -Filter "PartitionKey eq 'HttpFunctions' and RowKey eq 'HttpFunctions' and Version eq '$($Version)'"
36-
37-
if (-not $AllPermissionsRow.Permissions) {
38-
$AllPermissions = Get-CIPPHttpFunctions -ByRole | Select-Object -ExpandProperty Permission
39-
$Entity = @{
40-
PartitionKey = 'HttpFunctions'
41-
RowKey = 'HttpFunctions'
42-
Version = [string]$Version
43-
Permissions = [string]($AllPermissions | ConvertTo-Json -Compress)
44-
}
45-
Add-CIPPAzDataTableEntity @AllPermissionCacheTable -Entity $Entity -Force
46-
} else {
47-
$AllPermissions = $AllPermissionsRow.Permissions | ConvertFrom-Json
48-
}
28+
$AllPermissions = Get-CippHttpPermissions
4929

5030
$AllowedPermissions = [System.Collections.Generic.List[string]]::new()
5131

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
function Get-CippHttpPermissions {
2+
<#
3+
.SYNOPSIS
4+
Returns the set of API permissions that exist on the current HTTP functions.
5+
6+
.DESCRIPTION
7+
Resolves the full permission universe for the running CIPP version from the
8+
cachehttppermissions table, computing and caching it via Get-CIPPHttpFunctions
9+
on a cache miss. Results are memoized in-process per version so hot paths
10+
(Test-CIPPAccess, Get-CippAllowedPermissions) avoid repeated table reads.
11+
12+
.OUTPUTS
13+
[string[]] of valid permission names, e.g. 'Exchange.Mailbox.ReadWrite'.
14+
15+
.FUNCTIONALITY
16+
Internal
17+
#>
18+
[CmdletBinding()]
19+
param()
20+
21+
$Version = if ($env:CIPPNG -eq 'true') {
22+
$env:APP_VERSION
23+
} else {
24+
(Get-Content -Path (Join-Path $env:CIPPRootPath 'version_latest.txt')).Trim()
25+
}
26+
27+
if ($script:CippHttpPermissions -and $script:CippHttpPermissionsVersion -eq $Version) {
28+
return $script:CippHttpPermissions
29+
}
30+
31+
$AllPermissionCacheTable = Get-CIPPTable -tablename 'cachehttppermissions'
32+
$AllPermissionsRow = Get-CIPPAzDataTableEntity @AllPermissionCacheTable -Filter "PartitionKey eq 'HttpFunctions' and RowKey eq 'HttpFunctions' and Version eq '$($Version)'"
33+
34+
if (-not $AllPermissionsRow.Permissions) {
35+
$AllPermissions = Get-CIPPHttpFunctions -ByRole | Select-Object -ExpandProperty Permission
36+
$Entity = @{
37+
PartitionKey = 'HttpFunctions'
38+
RowKey = 'HttpFunctions'
39+
Version = [string]$Version
40+
Permissions = [string]($AllPermissions | ConvertTo-Json -Compress)
41+
}
42+
Add-CIPPAzDataTableEntity @AllPermissionCacheTable -Entity $Entity -Force
43+
} else {
44+
$AllPermissions = $AllPermissionsRow.Permissions | ConvertFrom-Json
45+
}
46+
47+
$script:CippHttpPermissions = @($AllPermissions)
48+
$script:CippHttpPermissionsVersion = $Version
49+
return $script:CippHttpPermissions
50+
}

Modules/CIPPCore/Public/Authentication/Test-CippApiClientRoleGrant.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function Test-CippApiClientRoleGrant {
102102
# Effective permissions a client holding this role would receive, computed the
103103
# same way Test-CIPPAccess evaluates an API client (single role, no base ceiling).
104104
$RolePermissions = @(Get-CippAllowedPermissions -UserRoles @($TargetRole))
105-
$Escalation = @($RolePermissions | Where-Object { $CallerPermissions -notcontains $_ -and $_ -notmatch 'None$' })
105+
$Escalation = @($RolePermissions | Where-Object { $CallerPermissions -notcontains $_ })
106106

107107
if ($Escalation.Count -gt 0) {
108108
return (New-Denial "You do not have sufficient permissions to manage an API client with the '$TargetRole' role; it grants permissions beyond your own.")

0 commit comments

Comments
 (0)