From 30a2cd535c8e0979fcbc77e030cf92b46cdc093a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 14 Aug 2024 09:57:08 -0400 Subject: [PATCH 1/2] Cleanup duplicate permissions --- Modules/CIPPCore/Public/SAMManifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 5c24954b1d7e..45df45352603 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -113,7 +113,6 @@ { "id": "1138cb37-bd11-4084-a2b7-9f71582aeddb", "type": "Role" }, { "id": "78145de6-330d-4800-a6ce-494ff2d33d07", "type": "Role" }, { "id": "9241abd9-d0e6-425a-bd4f-47ba86e767a4", "type": "Role" }, - { "id": "5b07b0dd-2377-4e44-a38d-703f09a0dc3c", "type": "Role" }, { "id": "243333ab-4d21-40cb-a475-36241daa0842", "type": "Role" }, { "id": "e330c4f0-4170-414e-a55a-2f022ec2b57b", "type": "Role" }, { "id": "5ac13192-7ace-4fcf-b828-1a26f28068ee", "type": "Role" }, @@ -154,7 +153,8 @@ { "id": "e0a7cdbb-08b0-4697-8264-0069786e9674", "type": "Scope" }, { "id": "19da66cb-0fb0-4390-b071-ebc76a349482", "type": "Role" }, { "id": "818c620a-27a9-40bd-a6a5-d96f7d610b4b", "type": "Scope" }, - { "id": "6931bccd-447a-43d1-b442-00a195474933", "type": "Role" } + { "id": "6931bccd-447a-43d1-b442-00a195474933", "type": "Role" }, + { "id": "c5366453-9fb0-48a5-a156-24f0c49a4b84", "type": "Scope" } ] }, { From d62a47b1ebe119469138aa8d4283dc6062d0734b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 14 Aug 2024 18:49:23 -0400 Subject: [PATCH 2/2] CIPP-SAM permissions - Update CPV functions, set delegated to no translate - Add function to parse SAMManifest.json/AdditionalPermissions.json and table - Add GUID check to ExecServicePrincipals --- .../Public/Add-CIPPApplicationPermission.ps1 | 21 ++- .../Public/Add-CIPPDelegatedPermission.ps1 | 43 +++-- .../Core/Invoke-ExecServicePrincipals.ps1 | 19 ++- .../Settings/Invoke-ExecSAMAppPermissions.ps1 | 74 +-------- .../GraphHelper/Get-CippSamPermissions.ps1 | 154 ++++++++++++++++++ 5 files changed, 223 insertions(+), 88 deletions(-) create mode 100644 Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 index b63db8aeb3f8..b4b6c7d177ca 100644 --- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 @@ -11,7 +11,26 @@ function Add-CIPPApplicationPermission { } Set-Location (Get-Item $PSScriptRoot).FullName if ($RequiredResourceAccess -eq 'CIPPDefaults') { - $RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess + #$RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess + + $Permissions = Get-CippSamPermissions -NoDiff + $RequiredResourceAccess = [System.Collections.Generic.List[object]]::new() + + foreach ($AppId in $Permissions.Permissions.PSObject.Properties.Name) { + $AppPermissions = @($Permissions.Permissions.$AppId.applicationPermissions) + $Resource = @{ + resourceAppId = $AppId + resourceAccess = [System.Collections.Generic.List[object]]::new() + } + foreach ($Permission in $AppPermissions) { + $Resource.ResourceAccess.Add(@{ + id = $Permission.id + type = 'Role' + }) + } + + $RequiredResourceAccess.Add($Resource) + } } $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter -NoAuthCheck $true $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 03be9e2a44d6..37932d98b9e6 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -15,8 +15,24 @@ function Add-CIPPDelegatedPermission { } if ($RequiredResourceAccess -eq 'CIPPDefaults') { - $RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess - $AdditionalPermissions = Get-Content '.\AdditionalPermissions.json' | ConvertFrom-Json + $Permissions = Get-CippSamPermissions -NoDiff + $NoTranslateRequired = $Permissions.Type -eq 'Table' + $RequiredResourceAccess = [System.Collections.Generic.List[object]]::new() + foreach ($AppId in $Permissions.Permissions.PSObject.Properties.Name) { + $DelegatedPermissions = @($Permissions.Permissions.$AppId.delegatedPermissions) + $ResourceAccess = [System.Collections.Generic.List[object]]::new() + foreach ($Permission in $DelegatedPermissions) { + $ResourceAccess.Add(@{ + id = $Permission.value + type = 'Scope' + }) + } + $Resource = @{ + resourceAppId = $AppId + resourceAccess = @($ResourceAccess) + } + $RequiredResourceAccess.Add($Resource) + } if ($Tenantfilter -eq $env:TenantID) { $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) @@ -24,7 +40,6 @@ function Add-CIPPDelegatedPermission { # remove the partner center permission if not pushing to partner tenant $RequiredResourceAccess = $RequiredResourceAccess | Where-Object { $_.resourceAppId -ne 'fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd' } } - $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) } $Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true -NoAuthCheck $true @@ -46,16 +61,22 @@ function Add-CIPPDelegatedPermission { continue } } - $AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess | Where-Object -Property type -EQ 'Scope' + $DelegatedScopes = $App.resourceAccess | Where-Object -Property type -EQ 'Scope' - if ($AdditionalScopes) { - $NewScope = (@(($Translator | Where-Object { $_.id -in $DelegatedScopes.id }).value) + @($AdditionalScopes.id | Select-Object -Unique)) -join ' ' + if ($NoTranslateRequired) { + $NewScope = @($DelegatedScopes | ForEach-Object { $_.id } | Sort-Object -Unique) -join ' ' } else { - if ($NoTranslateRequired) { - $NewScope = @($DelegatedScopes | ForEach-Object { $_.id } | Sort-Object -Unique) -join ' ' - } else { - $NewScope = @(($Translator | Where-Object { $_.id -in $DelegatedScopes.id }).value | Sort-Object -Unique) -join ' ' + $NewScope = foreach ($Scope in $DelegatedScopes.id) { + if ($Scope -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $TranslatedScope = ($Translator | Where-Object -Property id -EQ $Scope).value + if ($TranslatedScope) { + $TranslatedScope + } + } else { + $Scope + } } + $NewScope = (@($NewScope) | Sort-Object -Unique) -join ' ' } $OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id) @@ -83,7 +104,7 @@ function Add-CIPPDelegatedPermission { # Added permissions $Added = ($Compare | Where-Object { $_.SideIndicator -eq '=>' }).InputObject -join ' ' $Removed = ($Compare | Where-Object { $_.SideIndicator -eq '<=' }).InputObject -join ' ' - $Results.add("Successfully updated permissions for $($svcPrincipalId.displayName). $(if ($Added) { "Added: $Added"}) $(if ($Removed) { "Removed: $Removed"})") + $Results.add("Successfully updated permissions for $($svcPrincipalId.displayName). $(if ($Added) { "Added: $Added"}) $(if ($Removed) { "Removed: $Removed"})") } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 index e064d1fba096..c2fe14c59131 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 @@ -16,10 +16,21 @@ function Invoke-ExecServicePrincipals { try { switch ($Request.Query.Action) { 'Create' { - $Body = @{ - 'appId' = $Request.Query.AppId - } | ConvertTo-Json -Compress - $Results = New-GraphPostRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter -type POST -body $Body + $Action = 'Create' + if ($Request.Query.AppId -match '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$') { + $Body = @{ + 'appId' = $Request.Query.AppId + } | ConvertTo-Json -Compress + try { + $Results = New-GraphPostRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter -type POST -body $Body + } catch { + $Results = "Unable to create service principal: $($_.Exception.Message)" + $Success = $false + } + } else { + $Results = 'Invalid AppId' + $Success = $false + } } default { if ($Request.Query.AppId) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 index ba76c1f038e9..a3c442c85656 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 @@ -15,7 +15,7 @@ function Invoke-ExecSAMAppPermissions { $Entity = @{ 'PartitionKey' = 'CIPP-SAM' 'RowKey' = 'CIPP-SAM' - 'Permissions' = [string]($Permissions | ConvertTo-Json -Depth 10 -Compress) + 'Permissions' = [string]($Permissions.Permissions | ConvertTo-Json -Depth 10 -Compress) } $Table = Get-CIPPTable -TableName 'AppPermissions' $null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force @@ -29,77 +29,7 @@ function Invoke-ExecSAMAppPermissions { } } default { - $ModuleBase = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase - $SamManifest = Get-Item "$ModuleBase\Public\SAMManifest.json" - $AdditionalPermissions = Get-Item "$ModuleBase\Public\AdditionalPermissions.json" - - $LastWrite = @{ - 'SAMManifest' = $SamManifest.LastWriteTime - 'AdditionalPermissions' = $AdditionalPermissions.LastWriteTime - } - - $ServicePrincipals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$select=appId,displayName,appRoles,publishedPermissionScopes' -tenantid $env:TenantID -NoAuthCheck $true - $SAMManifest = Get-Content -Path $SamManifest.FullName | ConvertFrom-Json - $AdditionalPermissions = Get-Content -Path $AdditionalPermissions.FullName | ConvertFrom-Json - - $RequiredResources = $SamManifest.requiredResourceAccess - - $AppIds = ($RequiredResources.resourceAppId + $AdditionalPermissions.resourceAppId) | Sort-Object -Unique - - $Permissions = @{} - foreach ($AppId in $AppIds) { - $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId - $AppPermissions = [System.Collections.Generic.List[object]]@() - $ManifestPermissions = ($RequiredResources | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess - $UnpublishedPermissions = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess - - foreach ($Permission in $ManifestPermissions) { - $AppPermissions.Add($Permission) - } - if ($UnpublishedPermissions) { - foreach ($Permission in $UnpublishedPermissions) { - $AppPermissions.Add($Permission) - } - } - - $ApplicationPermissions = [system.collections.generic.list[object]]@() - $DelegatedPermissions = [system.collections.generic.list[object]]@() - foreach ($Permission in $AppPermissions) { - if ($Permission.id -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { - if ($Permission.type -eq 'Role') { - $PermissionName = ($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Permission.id).value - } else { - $PermissionName = ($ServicePrincipal.publishedPermissionScopes | Where-Object -Property id -EQ $Permission.id).value - } - } else { - $PermissionName = $Permission.id - } - - if ($Permission.type -eq 'Role') { - $ApplicationPermissions.Add([PSCustomObject]@{ - id = $Permission.id - value = $PermissionName - - }) - } else { - $DelegatedPermissions.Add([PSCustomObject]@{ - id = $Permission.id - value = $PermissionName - }) - } - } - - $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId - $Permissions.$AppId = @{ - applicationPermissions = @($ApplicationPermissions | Sort-Object -Property label) - delegatedPermissions = @($DelegatedPermissions | Sort-Object -Property label) - } - } - - $Body = @{ - 'Permissions' = $Permissions - 'LastUpdate' = $LastWrite - } + $Body = Get-CippSamPermissions } } diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 new file mode 100644 index 000000000000..cd5a5e7997c8 --- /dev/null +++ b/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 @@ -0,0 +1,154 @@ +function Get-CippSamPermissions { + <# + .SYNOPSIS + This script retrieves the CIPP-SAM permissions. + + .DESCRIPTION + The Get-CippSamManifest function is used to retrieve the CIPP-SAM permissions either from the manifest files or table. + + .EXAMPLE + Get-CippSamManifest + Retrieves the CIPP SAM manifest located in the module root + + .FUNCTIONALITY + Internal + #> + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param( + [Parameter(ParameterSetName = 'ManifestOnly')] + [switch]$ManifestOnly, + [Parameter(ParameterSetName = 'Default')] + [switch]$SavedOnly, + [Parameter(ParameterSetName = 'Diff')] + [switch]$NoDiff + ) + + if (!$SavedOnly.IsPresent) { + $ModuleBase = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase + $SamManifest = Get-Item "$ModuleBase\Public\SAMManifest.json" + $AdditionalPermissions = Get-Item "$ModuleBase\Public\AdditionalPermissions.json" + + $ServicePrincipals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$select=appId,displayName,appRoles,publishedPermissionScopes' -tenantid $env:TenantID -NoAuthCheck $true + $SAMManifest = Get-Content -Path $SamManifest.FullName | ConvertFrom-Json + $AdditionalPermissions = Get-Content -Path $AdditionalPermissions.FullName | ConvertFrom-Json + + $RequiredResources = $SamManifest.requiredResourceAccess + + $AppIds = ($RequiredResources.resourceAppId + $AdditionalPermissions.resourceAppId) | Sort-Object -Unique + + $Permissions = @{} + foreach ($AppId in $AppIds) { + $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId + $AppPermissions = [System.Collections.Generic.List[object]]@() + $ManifestPermissions = ($RequiredResources | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess + $UnpublishedPermissions = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess + + foreach ($Permission in $ManifestPermissions) { + $AppPermissions.Add($Permission) + } + if ($UnpublishedPermissions) { + foreach ($Permission in $UnpublishedPermissions) { + $AppPermissions.Add($Permission) + } + } + + $ApplicationPermissions = [system.collections.generic.list[object]]@() + $DelegatedPermissions = [system.collections.generic.list[object]]@() + foreach ($Permission in $AppPermissions) { + if ($Permission.id -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + if ($Permission.type -eq 'Role') { + $PermissionName = ($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Permission.id).value + } else { + $PermissionName = ($ServicePrincipal.publishedPermissionScopes | Where-Object -Property id -EQ $Permission.id).value + } + } else { + $PermissionName = $Permission.id + } + + if ($Permission.type -eq 'Role') { + $ApplicationPermissions.Add([PSCustomObject]@{ + id = $Permission.id + value = $PermissionName + + }) + } else { + $DelegatedPermissions.Add([PSCustomObject]@{ + id = $Permission.id + value = $PermissionName + }) + } + } + + $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId + $Permissions.$AppId = @{ + applicationPermissions = @($ApplicationPermissions | Sort-Object -Property label) + delegatedPermissions = @($DelegatedPermissions | Sort-Object -Property label) + } + } + } + if ($ManifestOnly) { + return [PSCustomObject]@{ + Permissions = $Permissions + Type = 'Manifest' + } + } + + $Table = Get-CippTable -tablename 'AppPermissions' + $SavedPermissions = Get-CippAzDataTableEntity @Table -Filter "PartitionKey eq 'CIPP-SAM' and RowKey eq 'CIPP-SAM'" + if ($SavedPermissions.Permissions) { + $SavedPermissions.Permissions = $SavedPermissions.Permissions | ConvertFrom-Json + } else { + $SavedPermissions = @{ + Permissions = [PSCustomObject]@{} + } + } + + if ($SavedOnly.IsPresent) { + $SavedPermissions | Add-Member -MemberType NoteProperty -Name Type -Value 'Table' + return $SavedPermissions + } + + if (!$NoDiff -and $SavedPermissions.Permissions) { + $DiffPermissions = @{} + foreach ($AppId in $AppIds) { + $ManifestSpPermissions = $Permissions.$AppId + $SavedSpPermission = $SavedPermissions.Permissions.$AppId + $MissingApp = [System.Collections.Generic.List[object]]::new() + $MissingDelegated = [System.Collections.Generic.List[object]]::new() + foreach ($Permission in $ManifestSpPermissions.applicationPermissions) { + if ($SavedSpPermission.applicationPermissions.id -notcontains $Permission.id) { + $MissingApp.Add($Permission) + } + } + foreach ($Permission in $ManifestSpPermissions.delegatedPermissions) { + if ($SavedSpPermission.delegatedPermissions.id -notcontains $Permission.id) { + $MissingDelegated.Add($Permission) + } + } + if ($MissingApp -or $MissingDelegated) { + $DiffPermissions.$AppId = @{ + applicationPermissions = $MissingApp + delegatedPermissions = $MissingDelegated + } + } + } + } + + $SamAppPermissions = @{} + if (($SavedPermissions.Permissions.PSObject.Properties.Name | Measure-Object).Count -gt 0) { + $SamAppPermissions.Permissions = $SavedPermissions.Permissions + $SamAppPermissions.Type = 'Table' + } else { + $SamAppPermissions.Permissions = $Permissions + $SamAppPermissions.Type = 'Manifest' + } + + if (!$NoDiff.IsPresent) { + $SamAppPermissions.MissingPermissions = $DiffPermissions + } + + $SamAppPermissions = $SamAppPermissions | ConvertTo-Json -Depth 10 -Compress | ConvertFrom-Json + + return $SamAppPermissions +} +