Skip to content

Commit ee059e5

Browse files
authored
Merge pull request #1067 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents d8541cb + 127bdc6 commit ee059e5

6 files changed

Lines changed: 158 additions & 162 deletions

File tree

Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/BEC/Push-BECRun.ps1

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,41 @@ function Push-BECRun {
2020
$startDate = (Get-Date).AddDays(-7).ToUniversalTime()
2121
$endDate = (Get-Date)
2222
Write-Information 'Getting audit logs'
23-
$auditLog = (New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled
24-
$7dayslog = if ($auditLog -eq $false) {
25-
$ExtractResult = 'AuditLog is disabled. Cannot perform full analysis'
26-
} else {
27-
$sessionid = Get-Random -Minimum 10000 -Maximum 99999
28-
$operations = @(
29-
'Remove-MailboxPermission',
30-
'Add-MailboxPermission',
31-
'UpdateCalendarDelegation',
32-
'AddFolderPermissions',
33-
'MailboxLogin',
34-
'UserLoggedIn'
35-
)
36-
$startDate = (Get-Date).AddDays(-7)
37-
$endDate = (Get-Date)
38-
$SearchParam = @{
39-
SessionCommand = 'ReturnLargeSet'
40-
Operations = $operations
41-
sessionid = $sessionid
42-
startDate = $startDate
43-
endDate = $endDate
23+
try {
24+
$auditLog = (New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled
25+
$7dayslog = if ($auditLog -eq $false) {
26+
$ExtractResult = 'AuditLog is disabled. Cannot perform full analysis'
27+
} else {
28+
$sessionid = Get-Random -Minimum 10000 -Maximum 99999
29+
$operations = @(
30+
'Remove-MailboxPermission',
31+
'Add-MailboxPermission',
32+
'UpdateCalendarDelegation',
33+
'AddFolderPermissions',
34+
'MailboxLogin',
35+
'UserLoggedIn'
36+
)
37+
$startDate = (Get-Date).AddDays(-7)
38+
$endDate = (Get-Date)
39+
$SearchParam = @{
40+
SessionCommand = 'ReturnLargeSet'
41+
Operations = $operations
42+
sessionid = $sessionid
43+
startDate = $startDate
44+
endDate = $endDate
45+
}
46+
do {
47+
$logsTenant = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Search-unifiedAuditLog' -cmdParams $SearchParam -Anchor $Username
48+
Write-Information "Retrieved $($logsTenant.count) logs"
49+
$logsTenant
50+
} while ($LogsTenant.count % 5000 -eq 0 -and $LogsTenant.count -ne 0)
51+
$ExtractResult = 'Successfully extracted logs from auditlog'
4452
}
45-
do {
46-
New-ExoRequest -tenantid $TenantFilter -cmdlet 'Search-unifiedAuditLog' -cmdParams $SearchParam -Anchor $Username
47-
Write-Information "Retrieved $($logsTenant.count) logs"
48-
$logsTenant
49-
} while ($LogsTenant.count % 5000 -eq 0 -and $LogsTenant.count -ne 0)
50-
$ExtractResult = 'Successfully extracted logs from auditlog'
53+
} catch {
54+
$7dayslog = @()
55+
$CippAuditError = Get-CippException -Exception $_
56+
$ExtractResult = "Could not retrieve audit logs: $($CippAuditError.NormalizedError)"
57+
Write-LogMessage -API 'BECRun' -message "Failed to retrieve audit logs for $($UserName): $($CippAuditError.NormalizedError)" -tenant $TenantFilter -sev Warning -LogData $CippAuditError
5158
}
5259
Write-Information 'Getting last sign-in'
5360
try {

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

Lines changed: 29 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -197,88 +197,44 @@ function Get-CippSamPermissions {
197197
}
198198
}
199199

200-
# Diff the effective set against what is actually GRANTED on the partner CIPP-SAM enterprise
201-
# application (service principal): appRoleAssignments for application (Role) permissions and
202-
# oauth2PermissionGrants for delegated (Scope) permissions. The app registration's
203-
# requiredResourceAccess is intentionally NOT used - permissions are applied as SP grants, so the
204-
# grants are the real source of truth for what the app can do.
205-
# MissingPermissions = effective perms not yet granted on the SP (need to be added).
206-
# PartnerAppDiff also surfaces extra grants on the SP that are not in the effective set.
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.
207205
$MissingPermissions = @{}
208206
$PartnerAppDiff = @{}
209207
if (!$NoDiff.IsPresent) {
210-
try {
211-
$PartnerSP = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($env:ApplicationID)')?`$select=id" -tenantid $env:TenantID -NoAuthCheck $true
212-
$AppRoleAssignments = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($PartnerSP.id)/appRoleAssignments?`$top=999" -tenantid $env:TenantID -NoAuthCheck $true
213-
$OAuthGrants = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($PartnerSP.id)/oauth2PermissionGrants?`$top=999" -tenantid $env:TenantID -NoAuthCheck $true
214-
215-
# Grants reference the resource SP's object id; map it back to the resource appId the
216-
# effective set is keyed on. Use $UsedServicePrincipals - it carries both id and appId
217-
# ($ServicePrincipals is selected without id, so its .id is null).
218-
$ResourceIdToAppId = @{}
219-
foreach ($SP in $UsedServicePrincipals) { if ($SP.id) { $ResourceIdToAppId[$SP.id] = $SP.appId } }
220-
221-
# Granted application roles (GUIDs) per resource appId.
222-
$GrantedRoleIdsByApp = @{}
223-
foreach ($Assignment in $AppRoleAssignments) {
224-
$ResAppId = $ResourceIdToAppId[$Assignment.resourceId]
225-
if (!$ResAppId -or !$Assignment.appRoleId) { continue }
226-
if (-not $GrantedRoleIdsByApp.ContainsKey($ResAppId)) { $GrantedRoleIdsByApp[$ResAppId] = [System.Collections.Generic.List[string]]::new() }
227-
$GrantedRoleIdsByApp[$ResAppId].Add([string]$Assignment.appRoleId)
228-
}
208+
foreach ($AppId in $AllAppIds) {
209+
$ManifestApp = $ManifestPermissions.$AppId
210+
$SavedApp = $SavedPermissions.$AppId
229211

230-
# Granted delegated scope NAMES per resource appId (oauth2 grants store space-delimited names).
231-
$GrantedScopesByApp = @{}
232-
foreach ($Grant in $OAuthGrants) {
233-
$ResAppId = $ResourceIdToAppId[$Grant.resourceId]
234-
if (!$ResAppId) { continue }
235-
if (-not $GrantedScopesByApp.ContainsKey($ResAppId)) { $GrantedScopesByApp[$ResAppId] = [System.Collections.Generic.List[string]]::new() }
236-
foreach ($ScopeName in @(($Grant.scope -split ' ') | Where-Object { $_ })) { $GrantedScopesByApp[$ResAppId].Add($ScopeName) }
237-
}
212+
$SavedAppIds = @($SavedApp.applicationPermissions.id)
213+
$SavedDelIds = @($SavedApp.delegatedPermissions.id)
238214

239-
foreach ($AppId in $AllAppIds) {
240-
$ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId
241-
$GrantedRoleIds = @($GrantedRoleIdsByApp[$AppId] | Where-Object { $_ })
242-
$GrantedScopeNames = @($GrantedScopesByApp[$AppId] | Where-Object { $_ })
243-
244-
# Application (Role) permissions compare by GUID against appRoleAssignments.
245-
$EffApp = @($EffectivePermissions.$AppId.applicationPermissions | Where-Object { $_.id -match $GuidRegex })
246-
# Delegated (Scope) permissions compare by NAME (value) against oauth2 grant scopes -
247-
# this covers both GUID-resolved scopes and the string-named AdditionalPermissions.
248-
$EffDel = @($EffectivePermissions.$AppId.delegatedPermissions)
249-
$EffAppIds = @($EffApp.id)
250-
$EffDelNames = @($EffDel.value)
251-
252-
$MissingApp = @(foreach ($Permission in $EffApp) { if ($GrantedRoleIds -notcontains $Permission.id) { $Permission } })
253-
$MissingDel = @(foreach ($Permission in $EffDel) { if ($Permission.value -and $GrantedScopeNames -notcontains $Permission.value) { $Permission } })
254-
$ExtraApp = @(foreach ($Id in ($GrantedRoleIds | Sort-Object -Unique)) {
255-
if ($EffAppIds -notcontains $Id) {
256-
[PSCustomObject]@{ id = $Id; value = (($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Id).value) ?? $Id }
257-
}
258-
})
259-
$ExtraDel = @(foreach ($Name in ($GrantedScopeNames | Sort-Object -Unique)) {
260-
if ($EffDelNames -notcontains $Name) {
261-
[PSCustomObject]@{ id = $Name; value = $Name }
262-
}
263-
})
264-
265-
if ($MissingApp.Count -gt 0 -or $MissingDel.Count -gt 0) {
266-
$MissingPermissions.$AppId = @{
267-
applicationPermissions = $MissingApp
268-
delegatedPermissions = $MissingDel
215+
$MissingApp = @(foreach ($Permission in $ManifestApp.applicationPermissions) {
216+
if ($Permission.id -and $SavedAppIds -notcontains $Permission.id) {
217+
[PSCustomObject]@{ id = $Permission.id; value = $Permission.value }
269218
}
270-
}
271-
if ($MissingApp.Count -gt 0 -or $MissingDel.Count -gt 0 -or $ExtraApp.Count -gt 0 -or $ExtraDel.Count -gt 0) {
272-
$PartnerAppDiff.$AppId = @{
273-
missingApplicationPermissions = $MissingApp
274-
missingDelegatedPermissions = $MissingDel
275-
extraApplicationPermissions = $ExtraApp
276-
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 }
277223
}
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 = @()
278236
}
279237
}
280-
} catch {
281-
Write-Information "Failed to retrieve partner enterprise app grants for permission diff: $($_.Exception.Message)"
282238
}
283239
}
284240

Modules/CIPPCore/Public/SAMManifest/Update-CippSamPermissions.ps1

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
function Update-CippSamPermissions {
22
<#
33
.SYNOPSIS
4-
Reconciles the saved CIPP-SAM additional-permission set in the AppPermissions table.
4+
Reconciles the applied CIPP-SAM permission set in the AppPermissions table.
55
.DESCRIPTION
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".
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
1212
It deliberately does NOT write the partner CIPP-SAM app registration's requiredResourceAccess.
1313
Permissions reach the CIPP-SAM service principal(s) - partner and clients - through the grant
1414
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).
15+
through the app registration.
1716
.PARAMETER UpdatedBy
1817
The user or system that is performing the update. Defaults to 'CIPP-API'.
1918
.OUTPUTS
@@ -26,69 +25,68 @@ function Update-CippSamPermissions {
2625
)
2726

2827
try {
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.
28+
# Manifest base - the always-required permissions.
3129
$ManifestPermissions = (Get-CippSamPermissions -ManifestOnly).Permissions
3230

3331
$Table = Get-CIPPTable -TableName 'AppPermissions'
3432
$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.'
37-
}
38-
39-
try {
40-
$Saved = $SavedRow.Permissions | ConvertFrom-Json -ErrorAction Stop
41-
} catch {
42-
return 'Saved additional permissions could not be parsed; nothing to reconcile.'
33+
$Saved = $null
34+
if ($SavedRow.Permissions) {
35+
try {
36+
$Saved = $SavedRow.Permissions | ConvertFrom-Json -ErrorAction Stop
37+
} catch {
38+
$Saved = $null
39+
}
4340
}
4441

45-
# Keep only the entries the manifest does NOT already cover.
46-
$Extras = @{}
47-
$RemovedCount = 0
48-
foreach ($AppId in $Saved.PSObject.Properties.Name) {
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) {
4946
$ManifestApp = $ManifestPermissions.$AppId
47+
$SavedApp = $Saved.$AppId
5048
$ManifestAppIds = @($ManifestApp.applicationPermissions.id)
5149
$ManifestDelIds = @($ManifestApp.delegatedPermissions.id)
5250

53-
$ExtraApp = [System.Collections.Generic.List[object]]::new()
54-
foreach ($Permission in $Saved.$AppId.applicationPermissions) {
51+
$AppPerms = [System.Collections.Generic.List[object]]::new()
52+
$DelPerms = [System.Collections.Generic.List[object]]::new()
53+
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) {
5563
if ($Permission.id -and $ManifestAppIds -notcontains $Permission.id) {
56-
$ExtraApp.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
57-
} else {
58-
$RemovedCount++
64+
$AppPerms.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
5965
}
6066
}
61-
$ExtraDel = [System.Collections.Generic.List[object]]::new()
62-
foreach ($Permission in $Saved.$AppId.delegatedPermissions) {
67+
foreach ($Permission in $SavedApp.delegatedPermissions) {
6368
if ($Permission.id -and $ManifestDelIds -notcontains $Permission.id) {
64-
$ExtraDel.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
65-
} else {
66-
$RemovedCount++
69+
$DelPerms.Add([PSCustomObject]@{ id = $Permission.id; value = $Permission.value })
6770
}
6871
}
6972

70-
if ($ExtraApp.Count -gt 0 -or $ExtraDel.Count -gt 0) {
71-
$Extras.$AppId = @{
72-
applicationPermissions = @($ExtraApp)
73-
delegatedPermissions = @($ExtraDel)
73+
if ($AppPerms.Count -gt 0 -or $DelPerms.Count -gt 0) {
74+
$Applied.$AppId = @{
75+
applicationPermissions = @($AppPerms)
76+
delegatedPermissions = @($DelPerms)
7477
}
7578
}
7679
}
7780

78-
if ($RemovedCount -eq 0) {
79-
return 'Saved additional permissions already reconciled; no manifest-covered entries to remove.'
80-
}
81-
8281
$Entity = @{
8382
'PartitionKey' = 'CIPP-SAM'
8483
'RowKey' = 'CIPP-SAM'
85-
'Permissions' = [string]([PSCustomObject]$Extras | ConvertTo-Json -Depth 10 -Compress)
84+
'Permissions' = [string]([PSCustomObject]$Applied | ConvertTo-Json -Depth 10 -Compress)
8685
'UpdatedBy' = $UpdatedBy
8786
}
8887
$null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force
8988

90-
$Plural = if ($RemovedCount -eq 1) { 'entry' } else { 'entries' }
91-
return "Reconciled saved additional permissions: removed $RemovedCount $Plural now covered by the CIPP manifest."
89+
return 'CIPP-SAM permissions reconciled: the applied permission table now contains the CIPP manifest permissions plus any additional permissions.'
9290
} catch {
9391
throw "Failed to reconcile permissions: $($_.Exception.Message)"
9492
}

0 commit comments

Comments
 (0)