Skip to content

Commit c8a180c

Browse files
authored
Merge pull request #76 from KelvinTegelaar/master
[pull] master from KelvinTegelaar:master
2 parents cda0eed + f1b2c93 commit c8a180c

24 files changed

Lines changed: 233 additions & 18 deletions
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
function Select-CippAllowedTenantData {
2+
<#
3+
.SYNOPSIS
4+
Narrow a set of cached rows to the tenants the current caller is allowed to see.
5+
6+
.DESCRIPTION
7+
A family of cached List*/Exec*List endpoints reads a global cache table by PartitionKey
8+
and returns the rows directly. When a tenant-restricted custom role calls one of these
9+
with tenantFilter=AllTenants, the read must still be narrowed to the caller's allowed
10+
tenants - the same scope Get-Tenants already applies via $script:CippAllowedTenantsStorage.
11+
A direct cache-table read never touches Get-Tenants on a cache hit, so it would otherwise
12+
leak every managed tenant's data. Pipe those rows through this function at the point they
13+
become the response to close that gap.
14+
15+
This function MUST live in CIPPCore. The $script:CippAllowedTenantsStorage AsyncLocal slot
16+
is CIPPCore module-scoped; a copy defined in CIPPHTTP would read that module's own empty
17+
variable and silently filter nothing (see Get-CippRequestContext).
18+
19+
The stored scope is a list of customerIds (or $null = unrestricted). Cache rows identify
20+
their tenant by domain name (defaultDomainName, stored on a 'Tenant' property) and/or by
21+
customerId, so allowed customerIds are expanded to every identifier form an allowed tenant
22+
might present, mirroring the match logic in Invoke-ListLogs.
23+
24+
.PARAMETER InputObject
25+
The rows to filter. Accepts pipeline input.
26+
27+
.PARAMETER TenantProperty
28+
The property name(s) on each row that identify its tenant. Defaults to 'Tenant' and
29+
'TenantId'. A row is kept when any of these properties matches an allowed tenant. For the
30+
Lighthouse aggregate use 'organizationId'.
31+
32+
.PARAMETER AllowPartner
33+
Also keep rows whose Tenant equals 'CIPP' (system/partner rows), mirroring Invoke-ListLogs.
34+
35+
.EXAMPLE
36+
$Rows = $Rows | Select-CippAllowedTenantData -TenantProperty 'Tenant'
37+
38+
.FUNCTIONALITY
39+
Internal
40+
#>
41+
[CmdletBinding()]
42+
param(
43+
[Parameter(ValueFromPipeline = $true)]
44+
[AllowNull()]
45+
$InputObject,
46+
47+
[string[]]$TenantProperty = @('Tenant', 'TenantId'),
48+
49+
[switch]$AllowPartner
50+
)
51+
52+
begin {
53+
# $null / empty stored scope means the caller is unrestricted - pass everything through
54+
# with zero overhead (no Get-Tenants call).
55+
$AllowedCustomerIds = if ($script:CippAllowedTenantsStorage) { $script:CippAllowedTenantsStorage.Value } else { $null }
56+
$Unrestricted = -not ($AllowedCustomerIds | Where-Object { $_ })
57+
58+
if (-not $Unrestricted) {
59+
# Build a case-insensitive set of every identifier a row might carry for an allowed
60+
# tenant. Get-Tenants is already narrowed to the caller's scope by the storage filter.
61+
$AllowedSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
62+
foreach ($Id in $AllowedCustomerIds) {
63+
if ($Id) { [void]$AllowedSet.Add([string]$Id) }
64+
}
65+
foreach ($Tenant in (Get-Tenants -IncludeErrors)) {
66+
foreach ($Value in @($Tenant.customerId, $Tenant.defaultDomainName, $Tenant.initialDomainName)) {
67+
if ($Value) { [void]$AllowedSet.Add([string]$Value) }
68+
}
69+
}
70+
if ($AllowPartner) { [void]$AllowedSet.Add('CIPP') }
71+
}
72+
}
73+
74+
process {
75+
foreach ($Item in $InputObject) {
76+
if ($null -eq $Item) { continue }
77+
if ($Unrestricted) {
78+
$Item
79+
continue
80+
}
81+
foreach ($Prop in $TenantProperty) {
82+
$Value = $Item.$Prop
83+
if ($Value -and $AllowedSet.Contains([string]$Value)) {
84+
$Item
85+
break
86+
}
87+
}
88+
}
89+
}
90+
}

Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function Test-CIPPAccess {
136136
$swUserBranch = [System.Diagnostics.Stopwatch]::StartNew()
137137
$User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json
138138

139-
if ($User.claims -and [string]::IsNullOrWhiteSpace($User.userDetails)) {
139+
if ($User.claims -and [string]::IsNullOrWhiteSpace($User.userDetails)) {
140140
$Claims = @($User.claims)
141141
$Upn = ($Claims | Where-Object { $_.typ -in @('preferred_username', 'upn', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', 'email', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress') } | Select-Object -First 1).val
142142
if ([string]::IsNullOrWhiteSpace($Upn)) { $Upn = $Request.Headers.'x-ms-client-principal-name' }
@@ -159,7 +159,7 @@ function Test-CIPPAccess {
159159

160160
$swIPCheck = [System.Diagnostics.Stopwatch]::StartNew()
161161
if (-not $User.userRoles) {
162-
throw 'Access denied: unable to resolve roles for the authenticated principal.'
162+
throw 'Access denied: unable to resolve roles for the authenticated principal'
163163
}
164164
$AllowedIPRanges = Get-CIPPRoleIPRanges -Roles $User.userRoles
165165

Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogIngestionV2.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ function Start-AuditLogIngestionV2 {
2929

3030
# --- Download tenants: searches awaiting download (State = Created, due) ---
3131
$DownloadTenants = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
32-
foreach ($Row in @(Get-CIPPAzDataTableEntity @Ledger -Filter "State eq 'Created'" -Property @('PartitionKey', 'NextAttemptUtc'))) {
32+
foreach ($Row in @(Get-CIPPAzDataTableEntity @Ledger -Filter "State eq 'Created'" -Property @('PartitionKey', 'RowKey', 'NextAttemptUtc'))) {
3333
if ($Row.NextAttemptUtc -and ([datetimeoffset]$Row.NextAttemptUtc).UtcDateTime -gt $Now) { continue }
3434
if ($Row.PartitionKey) { [void]$DownloadTenants.Add([string]$Row.PartitionKey) }
3535
}
3636

3737
# --- Process-only tenants: rows pending in the webhook cache (downloaded, not yet processed) ---
3838
$CacheTable = Get-CippTable -TableName 'CacheWebhooks'
3939
$CacheTenants = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
40-
foreach ($Row in @(Get-CIPPAzDataTableEntity @CacheTable -Property @('PartitionKey'))) {
40+
foreach ($Row in @(Get-CIPPAzDataTableEntity @CacheTable -Property @('PartitionKey', 'RowKey'))) {
4141
if ($Row.PartitionKey) { [void]$CacheTenants.Add([string]$Row.PartitionKey) }
4242
}
4343

Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-SchedulerOrchestrator.ps1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ function Start-SchedulerOrchestrator {
1212
$Table = Get-CIPPTable -TableName SchedulerConfig
1313
$Tenants = Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -NE 'WebhookAlert'
1414

15+
$ValidTypes = @('CIPPNotifications', 'webhookcreation')
16+
1517
$Tasks = foreach ($Tenant in $Tenants) {
18+
if ($Tenant.type -notin $ValidTypes) {
19+
if ($Tenant.PartitionKey -eq 'Alert') {
20+
Write-Information "Scheduler: removing legacy classic-alert row for '$($Tenant.tenant)'"
21+
Remove-AzDataTableEntity -Force @Table -Entity $Tenant
22+
} else {
23+
Write-Information "Scheduler: skipping row $($Tenant.PartitionKey)/$($Tenant.RowKey) - no handler for type '$($Tenant.type)'"
24+
}
25+
continue
26+
}
1627
if ($Tenant.tenant -ne 'AllTenants') {
1728
[pscustomobject]@{
1829
Tenant = $Tenant.tenant

Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
function Get-CIPPAuthentication {
33
[CmdletBinding()]
4+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Wraps a resolved tenant GUID in a SecureString only to satisfy Set-CippKeyVaultSecret; the value is written to Azure Key Vault (encrypted at rest)')]
45
param (
56
$APIName = 'Get Keyvault Authentication',
67
[switch]$Force

Modules/CIPPCore/Public/Get-CippKeyVaultSecret.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function Get-CippKeyVaultSecret {
2323
Get-CippKeyVaultSecret -VaultName 'mykeyvault' -Name 'RefreshToken' -AsPlainText
2424
#>
2525
[CmdletBinding()]
26+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Wraps the value returned by the Key Vault REST call in a SecureString to match the Get-AzKeyVaultSecret return shape')]
2627
param(
2728
[Parameter(Mandatory = $false)]
2829
[string]$VaultName,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function Get-AuthorisedRequest {
1313
$TenantID = $env:TenantID
1414
}
1515

16-
if ($Uri -like 'https://graph.microsoft.com/beta/contracts*' -or $Uri -like '*/customers/*' -or $Uri -eq 'https://graph.microsoft.com/v1.0/me/sendMail' -or $Uri -like '*/tenantRelationships/*' -or $Uri -like '*/security/partner/*' -or $Uri -like '*/organization') {
16+
if ($Uri -like 'https://graph.microsoft.com/beta/contracts*' -or $Uri -like '*/customers/*' -or $Uri -eq 'https://graph.microsoft.com/v1.0/me/sendMail' -or $Uri -like '*/tenantRelationships/*' -or $Uri -like '*/security/partner/*' -or $Uri -match '/organization(\?|$)') {
1717
return $true
1818
}
1919
$Tenant = Get-Tenants -IncludeErrors -TenantFilter $TenantID | Where-Object { $_.Excluded -eq $false }

Modules/CIPPCore/Public/Set-CIPPNotificationConfig.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
function Set-CIPPNotificationConfig {
22
[CmdletBinding()]
3+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Wraps a webhook auth config value in a SecureString only to satisfy Set-CippKeyVaultSecret; the value is written to Azure Key Vault (encrypted at rest)')]
4+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '', Justification = 'webhookAuthUsername/webhookAuthPassword are stored config values for an outbound webhook integration, not interactive credentials')]
35
param (
46
$email,
57
$webhook,

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecBackupReplicationConfig.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ function Invoke-ExecBackupReplicationConfig {
66
CIPP.AppSettings.ReadWrite
77
#>
88
[CmdletBinding()]
9+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Wraps a backup SAS URL in a SecureString only to satisfy Set-CippKeyVaultSecret; the value is written to Azure Key Vault (encrypted at rest)')]
910
param($Request, $TriggerMetadata)
1011

1112
$Table = Get-CIPPTable -TableName Config

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ListMailboxRules.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function Invoke-ListMailboxRules {
8888
$Metadata = [PSCustomObject]@{
8989
QueueId = $RunningQueue.RowKey ?? $null
9090
}
91-
$GraphRequest = $Rows | ForEach-Object {
91+
$GraphRequest = $Rows | Select-CippAllowedTenantData -TenantProperty 'Tenant' | ForEach-Object {
9292
$NewObj = $_.Rules | ConvertFrom-Json -ErrorAction SilentlyContinue
9393
$NewObj | Add-Member -NotePropertyName 'Tenant' -NotePropertyValue $_.Tenant -Force
9494
$NewObj

0 commit comments

Comments
 (0)