Skip to content

Commit fd24542

Browse files
committed
fix(app-approval): use linked permission set data
Add `Get-CIPPAppApprovalPermissions` to centralize template permission resolution and prefer the linked `PermissionSetId` as the source of truth, with fallback to template-stored permissions for legacy or missing sets. Update app and delegated permission grant paths to use this helper, and fix template creation by merging duplicate `resourceAppId` entries and deduplicating permission claims so delegated and application scopes are not lost.
1 parent 9126252 commit fd24542

4 files changed

Lines changed: 72 additions & 12 deletions

File tree

Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ function Add-CIPPApplicationPermission {
3131
} else {
3232
if (!$RequiredResourceAccess -and $TemplateId) {
3333
Write-Information "Adding application permissions for template $TemplateId"
34-
$TemplateTable = Get-CIPPTable -TableName 'templates'
35-
$Filter = "RowKey eq '$TemplateId' and PartitionKey eq 'AppApprovalTemplate'"
36-
$Template = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $Filter).JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
37-
$ApplicationId = $Template.AppId
38-
$Permissions = $Template.Permissions
34+
$TemplatePermissions = Get-CIPPAppApprovalPermissions -TemplateId $TemplateId
35+
$ApplicationId = $TemplatePermissions.ApplicationId
36+
$Permissions = $TemplatePermissions.Permissions
3937
$RequiredResourceAccess = [System.Collections.Generic.List[object]]::new()
4038
foreach ($AppId in $Permissions.PSObject.Properties.Name) {
4139
$AppPermissions = @($Permissions.$AppId.applicationPermissions)

Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,9 @@ function Add-CIPPDelegatedPermission {
4545
} else {
4646
if (!$RequiredResourceAccess -and $TemplateId) {
4747
Write-Information "Adding delegated permissions for template $TemplateId"
48-
$TemplateTable = Get-CIPPTable -TableName 'templates'
49-
$Filter = "RowKey eq '$TemplateId' and PartitionKey eq 'AppApprovalTemplate'"
50-
$Template = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $Filter).JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
51-
$ApplicationId = $Template.AppId
52-
$Permissions = $Template.Permissions
48+
$TemplatePermissions = Get-CIPPAppApprovalPermissions -TemplateId $TemplateId
49+
$ApplicationId = $TemplatePermissions.ApplicationId
50+
$Permissions = $TemplatePermissions.Permissions
5351
$NoTranslateRequired = $true
5452
$RequiredResourceAccess = [System.Collections.Generic.List[object]]::new()
5553
foreach ($AppId in $Permissions.PSObject.Properties.Name) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function Get-CIPPAppApprovalPermissions {
2+
<#
3+
.SYNOPSIS
4+
Resolves the effective permissions for an App Approval template.
5+
.DESCRIPTION
6+
App Approval templates persist a copy of the permission set at the time the template was saved.
7+
That copy goes stale the moment the linked permission set is edited, so when a template links to
8+
a permission set the set is treated as the source of truth. Templates without a PermissionSetId
9+
(older or hand-built ones) fall back to the copy stored on the template.
10+
.PARAMETER TemplateId
11+
RowKey of the AppApprovalTemplate to resolve.
12+
#>
13+
[CmdletBinding()]
14+
param(
15+
[Parameter(Mandatory = $true)]
16+
[string]$TemplateId
17+
)
18+
19+
$TemplateTable = Get-CIPPTable -TableName 'templates'
20+
$Filter = "RowKey eq '$TemplateId' and PartitionKey eq 'AppApprovalTemplate'"
21+
$Template = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $Filter).JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
22+
23+
if (!$Template) {
24+
Write-Information "App approval template $TemplateId not found"
25+
return $null
26+
}
27+
28+
$Permissions = $Template.Permissions
29+
30+
if ($Template.PermissionSetId) {
31+
$PermissionsTable = Get-CIPPTable -TableName 'AppPermissions'
32+
$SetFilter = "PartitionKey eq 'Templates' and RowKey eq '$($Template.PermissionSetId)'"
33+
$PermissionSet = Get-CIPPAzDataTableEntity @PermissionsTable -Filter $SetFilter
34+
35+
if ($PermissionSet.Permissions) {
36+
$SetPermissions = $PermissionSet.Permissions | ConvertFrom-Json -ErrorAction SilentlyContinue
37+
if ($SetPermissions) {
38+
$Permissions = $SetPermissions
39+
} else {
40+
Write-Information "Permission set $($Template.PermissionSetId) for template $TemplateId could not be parsed, falling back to the permissions stored on the template"
41+
}
42+
} else {
43+
Write-Information "Permission set $($Template.PermissionSetId) for template $TemplateId not found, falling back to the permissions stored on the template"
44+
}
45+
}
46+
47+
return [PSCustomObject]@{
48+
ApplicationId = $Template.AppId
49+
Permissions = $Permissions
50+
}
51+
}

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecCreateAppTemplate.ps1

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,17 @@ function Invoke-ExecCreateAppTemplate {
236236
$PermissionSetId = $null
237237
$PermissionSetName = "$DisplayName (Auto-created)"
238238

239+
# The service principal fallback above emits a separate entry for delegated access
240+
# (oauth2PermissionGrants) and application access (appRoleAssignments), so a resource that has
241+
# both appears twice. $CIPPPermissions is keyed by resourceAppId, so without merging here the
242+
# second entry overwrites the first and one of the two permission types is silently discarded.
243+
$Permissions = @($Permissions | Group-Object -Property resourceAppId | ForEach-Object {
244+
[PSCustomObject]@{
245+
resourceAppId = $_.Name
246+
resourceAccess = @($_.Group.resourceAccess)
247+
}
248+
})
249+
239250
if ($Permissions -and $Permissions.Count -gt 0) {
240251
# Build bulk requests to get all service principals efficiently using object IDs from cached list
241252
$BulkRequests = [System.Collections.Generic.List[object]]::new()
@@ -334,9 +345,11 @@ function Invoke-ExecCreateAppTemplate {
334345
}
335346
}
336347

348+
# A resource can carry several oauth2PermissionGrants rows (an AllPrincipals grant plus
349+
# per-user grants), which repeats the same scope once merged, so dedupe on the claim value.
337350
$CIPPPermissions[$ResourceAppId] = [PSCustomObject]@{
338-
applicationPermissions = @($AppPerms)
339-
delegatedPermissions = @($DelegatedPerms)
351+
applicationPermissions = @($AppPerms | Sort-Object -Property value -Unique)
352+
delegatedPermissions = @($DelegatedPerms | Sort-Object -Property value -Unique)
340353
}
341354
}
342355

0 commit comments

Comments
 (0)