Skip to content

Commit 38afad4

Browse files
authored
Merge branch 'KelvinTegelaar:master' into master
2 parents f76b2f0 + 0ec258d commit 38afad4

5 files changed

Lines changed: 128 additions & 141 deletions

File tree

Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ function Get-CippSamPermissions {
1212
The effective set returned in .Permissions is therefore always manifest ∪ extras. Each permission
1313
is annotated with a 'required' boolean so the UI can lock the manifest-defined defaults.
1414
15-
Unless -NoDiff is used, the function also pulls the live CIPP-SAM application registration from the
16-
partner tenant and diffs its requiredResourceAccess against the effective set, surfacing
17-
permissions that need to be added to (MissingPermissions) and removed from (PartnerAppDiff) the app.
15+
Unless -NoDiff is used, the function also reads what is actually granted on the CIPP-SAM enterprise
16+
application (service principal) in the partner tenant - appRoleAssignments (application/Role) and
17+
oauth2PermissionGrants (delegated/Scope) - and diffs those grants against the effective set,
18+
surfacing permissions that need to be granted (MissingPermissions) and grants that are present but
19+
not in the effective set (PartnerAppDiff). The app registration's requiredResourceAccess is not used.
1820
1921
.EXAMPLE
2022
Get-CippSamPermissions
@@ -195,58 +197,44 @@ function Get-CippSamPermissions {
195197
}
196198
}
197199

198-
# Diff the effective set against the live CIPP-SAM application registration in the partner tenant.
199-
# MissingPermissions = effective perms not yet on the app (need to be added).
200-
# PartnerAppDiff also surfaces extra perms on the app that are not in the effective set (need to be removed).
200+
# Diff the manifest-required base against the saved AppPermissions table. The table records what has
201+
# been applied to the CIPP-SAM app - the repair/update flow persists it as manifest ∪ extras - so it
202+
# stands in for the "current" permission set and no partner-tenant Graph call is needed here.
203+
# MissingPermissions = manifest-required perms not yet present in the table (a Permissions repair is needed).
204+
# PartnerAppDiff mirrors MissingPermissions in the shape the SAM permissions page expects.
201205
$MissingPermissions = @{}
202206
$PartnerAppDiff = @{}
203207
if (!$NoDiff.IsPresent) {
204-
try {
205-
$PartnerApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/applications(appId='$($env:ApplicationID)')?`$select=requiredResourceAccess" -tenantid $env:TenantID -NoAuthCheck $true
206-
foreach ($AppId in $AllAppIds) {
207-
$ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId
208-
$AppRegResource = $PartnerApp.requiredResourceAccess | Where-Object -Property resourceAppId -EQ $AppId
209-
$AppRegRoleIds = @(($AppRegResource.resourceAccess | Where-Object { $_.type -eq 'Role' }).id)
210-
$AppRegScopeIds = @(($AppRegResource.resourceAccess | Where-Object { $_.type -eq 'Scope' }).id)
211-
212-
# Only GUID-based permissions live in the app registration's requiredResourceAccess.
213-
# String-named scopes (e.g. the .Sdp AdditionalPermissions) are applied as direct grants,
214-
# so excluding them here avoids permanent false-positive "missing" entries.
215-
$EffApp = @($EffectivePermissions.$AppId.applicationPermissions | Where-Object { $_.id -match $GuidRegex })
216-
$EffDel = @($EffectivePermissions.$AppId.delegatedPermissions | Where-Object { $_.id -match $GuidRegex })
217-
$EffAppIds = @($EffApp.id)
218-
$EffDelIds = @($EffDel.id)
219-
220-
$MissingApp = @(foreach ($Permission in $EffApp) { if ($AppRegRoleIds -notcontains $Permission.id) { $Permission } })
221-
$MissingDel = @(foreach ($Permission in $EffDel) { if ($AppRegScopeIds -notcontains $Permission.id) { $Permission } })
222-
$ExtraApp = @(foreach ($Id in $AppRegRoleIds) {
223-
if ($EffAppIds -notcontains $Id) {
224-
[PSCustomObject]@{ id = $Id; value = (($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Id).value) ?? $Id }
225-
}
226-
})
227-
$ExtraDel = @(foreach ($Id in $AppRegScopeIds) {
228-
if ($EffDelIds -notcontains $Id) {
229-
[PSCustomObject]@{ id = $Id; value = (($ServicePrincipal.publishedPermissionScopes | Where-Object -Property id -EQ $Id).value) ?? $Id }
230-
}
231-
})
232-
233-
if ($MissingApp.Count -gt 0 -or $MissingDel.Count -gt 0) {
234-
$MissingPermissions.$AppId = @{
235-
applicationPermissions = $MissingApp
236-
delegatedPermissions = $MissingDel
208+
foreach ($AppId in $AllAppIds) {
209+
$ManifestApp = $ManifestPermissions.$AppId
210+
$SavedApp = $SavedPermissions.$AppId
211+
212+
$SavedAppIds = @($SavedApp.applicationPermissions.id)
213+
$SavedDelIds = @($SavedApp.delegatedPermissions.id)
214+
215+
$MissingApp = @(foreach ($Permission in $ManifestApp.applicationPermissions) {
216+
if ($Permission.id -and $SavedAppIds -notcontains $Permission.id) {
217+
[PSCustomObject]@{ id = $Permission.id; value = $Permission.value }
237218
}
238-
}
239-
if ($MissingApp.Count -gt 0 -or $MissingDel.Count -gt 0 -or $ExtraApp.Count -gt 0 -or $ExtraDel.Count -gt 0) {
240-
$PartnerAppDiff.$AppId = @{
241-
missingApplicationPermissions = $MissingApp
242-
missingDelegatedPermissions = $MissingDel
243-
extraApplicationPermissions = $ExtraApp
244-
extraDelegatedPermissions = $ExtraDel
219+
})
220+
$MissingDel = @(foreach ($Permission in $ManifestApp.delegatedPermissions) {
221+
if ($Permission.id -and $SavedDelIds -notcontains $Permission.id) {
222+
[PSCustomObject]@{ id = $Permission.id; value = $Permission.value }
245223
}
224+
})
225+
226+
if ($MissingApp.Count -gt 0 -or $MissingDel.Count -gt 0) {
227+
$MissingPermissions.$AppId = @{
228+
applicationPermissions = $MissingApp
229+
delegatedPermissions = $MissingDel
230+
}
231+
$PartnerAppDiff.$AppId = @{
232+
missingApplicationPermissions = $MissingApp
233+
missingDelegatedPermissions = $MissingDel
234+
extraApplicationPermissions = @()
235+
extraDelegatedPermissions = @()
246236
}
247237
}
248-
} catch {
249-
Write-Information "Failed to retrieve partner app registration for permission diff: $($_.Exception.Message)"
250238
}
251239
}
252240

Lines changed: 57 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
function Update-CippSamPermissions {
22
<#
33
.SYNOPSIS
4-
Repairs the CIPP-SAM app registration permissions in the partner tenant.
4+
Reconciles the applied CIPP-SAM permission set in the AppPermissions table.
55
.DESCRIPTION
6-
Diffs the effective CIPP-SAM permission set (manifest defaults + saved extras) against the live
7-
CIPP-SAM application registration in the partner tenant and ADDS any missing permissions to the
8-
app registration's requiredResourceAccess. This is additive only: it never removes permissions,
9-
so it cannot strip a legitimately-configured entry. Extra permissions found on the app that are
10-
not part of the effective set are reported back so an admin can review/remove them manually.
6+
Writes the full applied permission set - the SAM manifest base PLUS any admin-configured extra
7+
permissions - into the AppPermissions table, so the table always reflects everything the
8+
CIPP-SAM app is expected to have. Get-CippSamPermissions diffs the manifest against this table
9+
to decide when a Permissions repair is needed, so persisting the manifest here is what lets that
10+
check clear after a repair.
1111
12-
Pushing these permissions out to customer tenants is handled separately by the CPV refresh.
12+
It deliberately does NOT write the partner CIPP-SAM app registration's requiredResourceAccess.
13+
Permissions reach the CIPP-SAM service principal(s) - partner and clients - through the grant
14+
flow (Add-CIPPApplicationPermission / Add-CIPPDelegatedPermission, which read this table), not
15+
through the app registration.
1316
.PARAMETER UpdatedBy
1417
The user or system that is performing the update. Defaults to 'CIPP-API'.
1518
.OUTPUTS
@@ -22,87 +25,69 @@ function Update-CippSamPermissions {
2225
)
2326

2427
try {
25-
$CurrentPermissions = Get-CippSamPermissions
26-
$PartnerAppDiff = $CurrentPermissions.PartnerAppDiff
27-
$MissingPermissions = $CurrentPermissions.MissingPermissions
28+
# Manifest base - the always-required permissions.
29+
$ManifestPermissions = (Get-CippSamPermissions -ManifestOnly).Permissions
2830

29-
$MissingAppIds = @($MissingPermissions.PSObject.Properties.Name)
30-
$ExtraAppIds = @($PartnerAppDiff.PSObject.Properties.Name | Where-Object {
31-
($PartnerAppDiff.$_.extraApplicationPermissions | Measure-Object).Count -gt 0 -or
32-
($PartnerAppDiff.$_.extraDelegatedPermissions | Measure-Object).Count -gt 0
33-
})
34-
35-
if ($MissingAppIds.Count -eq 0) {
36-
if ($ExtraAppIds.Count -gt 0) {
37-
$ExtraSummary = foreach ($AppId in $ExtraAppIds) {
38-
$Names = @($PartnerAppDiff.$AppId.extraApplicationPermissions.value) + @($PartnerAppDiff.$AppId.extraDelegatedPermissions.value)
39-
"$AppId ($($Names -join ', '))"
40-
}
41-
return "No missing permissions to add. The following extra permissions are present on the app and should be reviewed/removed manually: $($ExtraSummary -join '; ')"
31+
$Table = Get-CIPPTable -TableName 'AppPermissions'
32+
$SavedRow = Get-CippAzDataTableEntity @Table -Filter "PartitionKey eq 'CIPP-SAM' and RowKey eq 'CIPP-SAM'"
33+
$Saved = $null
34+
if ($SavedRow.Permissions) {
35+
try {
36+
$Saved = $SavedRow.Permissions | ConvertFrom-Json -ErrorAction Stop
37+
} catch {
38+
$Saved = $null
4239
}
43-
return 'No permissions to update'
4440
}
4541

46-
# Retrieve the live CIPP-SAM application registration in the partner tenant.
47-
$PartnerApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/applications(appId='$($env:ApplicationID)')?`$select=id,requiredResourceAccess" -tenantid $env:TenantID -NoAuthCheck $true
42+
# Build the full applied set = manifest base ∪ admin extras, keyed by resource appId.
43+
$Applied = @{}
44+
$AppIds = @(@($ManifestPermissions.PSObject.Properties.Name) + @($Saved.PSObject.Properties.Name)) | Where-Object { $_ } | Sort-Object -Unique
45+
foreach ($AppId in $AppIds) {
46+
$ManifestApp = $ManifestPermissions.$AppId
47+
$SavedApp = $Saved.$AppId
48+
$ManifestAppIds = @($ManifestApp.applicationPermissions.id)
49+
$ManifestDelIds = @($ManifestApp.delegatedPermissions.id)
4850

49-
$RequiredResourceAccess = [System.Collections.Generic.List[object]]::new()
50-
foreach ($Resource in $PartnerApp.requiredResourceAccess) {
51-
$ResourceAccess = [System.Collections.Generic.List[object]]::new()
52-
foreach ($Access in $Resource.resourceAccess) {
53-
$ResourceAccess.Add(@{ id = $Access.id; type = $Access.type })
54-
}
55-
$RequiredResourceAccess.Add([PSCustomObject]@{
56-
resourceAppId = $Resource.resourceAppId
57-
resourceAccess = $ResourceAccess
58-
})
59-
}
51+
$AppPerms = [System.Collections.Generic.List[object]]::new()
52+
$DelPerms = [System.Collections.Generic.List[object]]::new()
6053

61-
$AddedPermissions = [System.Collections.Generic.List[string]]::new()
62-
foreach ($AppId in $MissingAppIds) {
63-
$Resource = $RequiredResourceAccess | Where-Object -Property resourceAppId -EQ $AppId | Select-Object -First 1
64-
if (!$Resource) {
65-
$Resource = [PSCustomObject]@{
66-
resourceAppId = $AppId
67-
resourceAccess = [System.Collections.Generic.List[object]]::new()
54+
# Manifest base (always applied).
55+
foreach ($Permission in $ManifestApp.applicationPermissions) {
56+
$AppPerms.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
57+
}
58+
foreach ($Permission in $ManifestApp.delegatedPermissions) {
59+
$DelPerms.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
60+
}
61+
# Admin extras (anything the manifest does not already cover).
62+
foreach ($Permission in $SavedApp.applicationPermissions) {
63+
if ($Permission.id -and $ManifestAppIds -notcontains $Permission.id) {
64+
$AppPerms.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
6865
}
69-
$RequiredResourceAccess.Add($Resource)
7066
}
71-
$ExistingIds = @($Resource.resourceAccess.id)
72-
73-
foreach ($Permission in $MissingPermissions.$AppId.applicationPermissions) {
74-
if ($Permission.id -and $ExistingIds -notcontains $Permission.id) {
75-
$Resource.resourceAccess.Add(@{ id = $Permission.id; type = 'Role' })
76-
$AddedPermissions.Add("$($Permission.value) (Application)")
67+
foreach ($Permission in $SavedApp.delegatedPermissions) {
68+
if ($Permission.id -and $ManifestDelIds -notcontains $Permission.id) {
69+
$DelPerms.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
7770
}
7871
}
79-
foreach ($Permission in $MissingPermissions.$AppId.delegatedPermissions) {
80-
if ($Permission.id -and $ExistingIds -notcontains $Permission.id) {
81-
$Resource.resourceAccess.Add(@{ id = $Permission.id; type = 'Scope' })
82-
$AddedPermissions.Add("$($Permission.value) (Delegated)")
72+
73+
if ($AppPerms.Count -gt 0 -or $DelPerms.Count -gt 0) {
74+
$Applied.$AppId = @{
75+
applicationPermissions = @($AppPerms)
76+
delegatedPermissions = @($DelPerms)
8377
}
8478
}
8579
}
8680

87-
if ($AddedPermissions.Count -eq 0) {
88-
return 'No permissions to update'
81+
$Entity = @{
82+
'PartitionKey' = 'CIPP-SAM'
83+
'RowKey' = 'CIPP-SAM'
84+
'Permissions' = [string]([PSCustomObject]$Applied | ConvertTo-Json -Depth 10 -Compress)
85+
'UpdatedBy' = $UpdatedBy
8986
}
87+
$null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force
9088

91-
$PatchBody = @{ requiredResourceAccess = @($RequiredResourceAccess) } | ConvertTo-Json -Depth 10 -Compress
92-
$null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/applications/$($PartnerApp.id)" -tenantid $env:TenantID -body $PatchBody -type PATCH -NoAuthCheck $true
93-
94-
Write-LogMessage -API 'UpdateCippSamPermissions' -message "CIPP-SAM app registration permissions repaired by $UpdatedBy" -Sev 'Info' -LogData @{ Added = $AddedPermissions }
95-
96-
$Result = "Added $($AddedPermissions.Count) missing permission(s) to the CIPP-SAM app registration: $($AddedPermissions -join ', '). Run a CPV refresh to apply these to customer tenants."
97-
if ($ExtraAppIds.Count -gt 0) {
98-
$ExtraSummary = foreach ($AppId in $ExtraAppIds) {
99-
$Names = @($PartnerAppDiff.$AppId.extraApplicationPermissions.value) + @($PartnerAppDiff.$AppId.extraDelegatedPermissions.value)
100-
"$AppId ($($Names -join ', '))"
101-
}
102-
$Result += " Extra permissions present on the app that should be reviewed/removed manually: $($ExtraSummary -join '; ')."
103-
}
104-
return $Result
89+
return 'CIPP-SAM permissions reconciled: the applied permission table now contains the CIPP manifest permissions plus any additional permissions.'
10590
} catch {
106-
throw "Failed to update permissions: $($_.Exception.Message)"
91+
throw "Failed to reconcile permissions: $($_.Exception.Message)"
10792
}
10893
}

0 commit comments

Comments
 (0)