11function Update-CippSamPermissions {
22 <#
33 . SYNOPSIS
4- Repairs the CIPP-SAM app registration permissions in the partner tenant .
4+ Reconciles the saved CIPP-SAM additional-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+ The SAM manifest is the immutable permission base and is always layered in at read time by
7+ Get-CippSamPermissions, so the AppPermissions table only ever needs to hold the EXTRA
8+ permissions an admin layered on top . This function keeps that row clean : it drops any saved
9+ entries the manifest now covers (e.g. legacy rows that stored the full manifest+extras set)
10+ so the table stays "extras only" .
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. Refreshing those grants is handled by the caller
16+ (Invoke-ExecPermissionRepair for the partner, the per-tenant permission refresh for clients).
1317 . PARAMETER UpdatedBy
1418 The user or system that is performing the update. Defaults to 'CIPP-API'.
1519 . OUTPUTS
@@ -22,87 +26,70 @@ function Update-CippSamPermissions {
2226 )
2327
2428 try {
25- $CurrentPermissions = Get-CippSamPermissions
26- $PartnerAppDiff = $CurrentPermissions .PartnerAppDiff
27- $MissingPermissions = $CurrentPermissions .MissingPermissions
29+ # Manifest base - always-required permissions that are layered in at read time, so they never
30+ # need to live in the saved extras row.
31+ $ManifestPermissions = ( Get-CippSamPermissions - ManifestOnly).Permissions
2832
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 ' ; ' ) "
42- }
43- return ' No permissions to update'
33+ $Table = Get-CIPPTable - TableName ' AppPermissions'
34+ $SavedRow = Get-CippAzDataTableEntity @Table - Filter " PartitionKey eq 'CIPP-SAM' and RowKey eq 'CIPP-SAM'"
35+ if (-not $SavedRow.Permissions ) {
36+ return ' No additional permissions saved. CIPP default (manifest) permissions are always applied.'
4437 }
4538
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
48-
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- })
39+ try {
40+ $Saved = $SavedRow.Permissions | ConvertFrom-Json - ErrorAction Stop
41+ } catch {
42+ return ' Saved additional permissions could not be parsed; nothing to reconcile.'
5943 }
6044
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()
45+ # Keep only the entries the manifest does NOT already cover.
46+ $Extras = @ {}
47+ $RemovedCount = 0
48+ foreach ($AppId in $Saved.PSObject.Properties.Name ) {
49+ $ManifestApp = $ManifestPermissions .$AppId
50+ $ManifestAppIds = @ ($ManifestApp.applicationPermissions.id )
51+ $ManifestDelIds = @ ($ManifestApp.delegatedPermissions.id )
52+
53+ $ExtraApp = [System.Collections.Generic.List [object ]]::new()
54+ foreach ($Permission in $Saved .$AppId.applicationPermissions ) {
55+ if ($Permission.id -and $ManifestAppIds -notcontains $Permission.id ) {
56+ $ExtraApp.Add ([PSCustomObject ]@ { id = $Permission.id ; value = $Permission.value })
57+ } else {
58+ $RemovedCount ++
6859 }
69- $RequiredResourceAccess.Add ($Resource )
7060 }
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) " )
61+ $ExtraDel = [ System.Collections.Generic.List [ object ]]::new( )
62+ foreach ( $Permission in $Saved . $AppId .delegatedPermissions ) {
63+ if ($Permission.id -and $ManifestDelIds -notcontains $Permission .id ) {
64+ $ExtraDel .Add ([ PSCustomObject ] @ { id = $Permission .id ; value = $Permission.value })
65+ } else {
66+ $RemovedCount ++
7767 }
7868 }
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)" )
69+
70+ if ($ExtraApp.Count -gt 0 -or $ExtraDel.Count -gt 0 ) {
71+ $Extras .$AppId = @ {
72+ applicationPermissions = @ ($ExtraApp )
73+ delegatedPermissions = @ ($ExtraDel )
8374 }
8475 }
8576 }
8677
87- if ($AddedPermissions .Count -eq 0 ) {
88- return ' No permissions to update '
78+ if ($RemovedCount -eq 0 ) {
79+ return ' Saved additional permissions already reconciled; no manifest-covered entries to remove. '
8980 }
9081
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 ' ; ' ) ."
82+ $Entity = @ {
83+ ' PartitionKey' = ' CIPP-SAM'
84+ ' RowKey' = ' CIPP-SAM'
85+ ' Permissions' = [string ]([PSCustomObject ]$Extras | ConvertTo-Json - Depth 10 - Compress)
86+ ' UpdatedBy' = $UpdatedBy
10387 }
104- return $Result
88+ $null = Add-CIPPAzDataTableEntity @Table - Entity $Entity - Force
89+
90+ $Plural = if ($RemovedCount -eq 1 ) { ' entry' } else { ' entries' }
91+ return " Reconciled saved additional permissions: removed $RemovedCount $Plural now covered by the CIPP manifest."
10592 } catch {
106- throw " Failed to update permissions: $ ( $_.Exception.Message ) "
93+ throw " Failed to reconcile permissions: $ ( $_.Exception.Message ) "
10794 }
10895}
0 commit comments