Skip to content

Commit 1b34dd9

Browse files
committed
Add per-request user roles cache and improve timing logic
Introduces an AsyncLocal-based per-request cache for user roles in Test-CIPPAccessUserRole and initializes it in New-CippCoreRequest to reduce redundant lookups. Also refines stopwatch timing logic in profile.ps1 to ensure accurate measurement and avoid errors when Application Insights is not configured.
1 parent 4e6385b commit 1b34dd9

File tree

3 files changed

+70
-49
lines changed

3 files changed

+70
-49
lines changed

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

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,59 +21,75 @@ function Test-CIPPAccessUserRole {
2121
)
2222
$Roles = @()
2323

24-
try {
25-
$Table = Get-CippTable -TableName cacheAccessUserRoles
26-
$Filter = "PartitionKey eq 'AccessUser' and RowKey eq '$($User.userDetails)' and Timestamp ge datetime'$((Get-Date).AddMinutes(-15).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))'"
27-
$UserRole = Get-CIPPAzDataTableEntity @Table -Filter $Filter
28-
} catch {
29-
Write-Information "Could not access cached user roles table. $($_.Exception.Message)"
30-
$UserRole = $null
31-
}
32-
if ($UserRole) {
33-
Write-Information "Found cached user role for $($User.userDetails)"
34-
$Roles = $UserRole.Role | ConvertFrom-Json
24+
# Check AsyncLocal cache first (per-request cache)
25+
if ($script:CippUserRolesStorage -and $script:CippUserRolesStorage.Value -and $script:CippUserRolesStorage.Value.ContainsKey($User.userDetails)) {
26+
$Roles = $script:CippUserRolesStorage.Value[$User.userDetails]
3527
} else {
28+
# Check table storage cache (persistent cache)
3629
try {
37-
$uri = "https://graph.microsoft.com/beta/users/$($User.userDetails)/transitiveMemberOf"
38-
$Memberships = New-GraphGetRequest -uri $uri -NoAuthCheck $true -AsApp $true | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' }
39-
if ($Memberships) {
40-
Write-Information "Found group memberships for $($User.userDetails)"
41-
} else {
42-
Write-Information "No group memberships found for $($User.userDetails)"
43-
}
30+
$Table = Get-CippTable -TableName cacheAccessUserRoles
31+
$Filter = "PartitionKey eq 'AccessUser' and RowKey eq '$($User.userDetails)' and Timestamp ge datetime'$((Get-Date).AddMinutes(-15).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))'"
32+
$UserRole = Get-CIPPAzDataTableEntity @Table -Filter $Filter
4433
} catch {
45-
Write-Information "Could not get user roles for $($User.userDetails). $($_.Exception.Message)"
46-
return $User
34+
Write-Information "Could not access cached user roles table. $($_.Exception.Message)"
35+
$UserRole = $null
4736
}
37+
if ($UserRole) {
38+
Write-Information "Found cached user role for $($User.userDetails)"
39+
$Roles = $UserRole.Role | ConvertFrom-Json
40+
41+
# Store in AsyncLocal cache for this request
42+
if ($script:CippUserRolesStorage -and $script:CippUserRolesStorage.Value) {
43+
$script:CippUserRolesStorage.Value[$User.userDetails] = $Roles
44+
}
45+
} else {
46+
try {
47+
$uri = "https://graph.microsoft.com/beta/users/$($User.userDetails)/transitiveMemberOf"
48+
$Memberships = New-GraphGetRequest -uri $uri -NoAuthCheck $true -AsApp $true | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' }
49+
if ($Memberships) {
50+
Write-Information "Found group memberships for $($User.userDetails)"
51+
} else {
52+
Write-Information "No group memberships found for $($User.userDetails)"
53+
}
54+
} catch {
55+
Write-Information "Could not get user roles for $($User.userDetails). $($_.Exception.Message)"
56+
return $User
57+
}
4858

49-
$AccessGroupsTable = Get-CippTable -TableName AccessRoleGroups
50-
$AccessGroups = Get-CIPPAzDataTableEntity @AccessGroupsTable -Filter "PartitionKey eq 'AccessRoleGroups'"
51-
$CustomRolesTable = Get-CippTable -TableName CustomRoles
52-
$CustomRoles = Get-CIPPAzDataTableEntity @CustomRolesTable -Filter "PartitionKey eq 'CustomRoles'"
53-
$BaseRoles = @('superadmin', 'admin', 'editor', 'readonly')
59+
$AccessGroupsTable = Get-CippTable -TableName AccessRoleGroups
60+
$AccessGroups = Get-CIPPAzDataTableEntity @AccessGroupsTable -Filter "PartitionKey eq 'AccessRoleGroups'"
61+
$CustomRolesTable = Get-CippTable -TableName CustomRoles
62+
$CustomRoles = Get-CIPPAzDataTableEntity @CustomRolesTable -Filter "PartitionKey eq 'CustomRoles'"
63+
$BaseRoles = @('superadmin', 'admin', 'editor', 'readonly')
5464

55-
$Roles = foreach ($AccessGroup in $AccessGroups) {
56-
if ($Memberships.id -contains $AccessGroup.GroupId -and ($CustomRoles.RowKey -contains $AccessGroup.RowKey -or $BaseRoles -contains $AccessGroup.RowKey)) {
57-
$AccessGroup.RowKey
65+
$Roles = foreach ($AccessGroup in $AccessGroups) {
66+
if ($Memberships.id -contains $AccessGroup.GroupId -and ($CustomRoles.RowKey -contains $AccessGroup.RowKey -or $BaseRoles -contains $AccessGroup.RowKey)) {
67+
$AccessGroup.RowKey
68+
}
5869
}
59-
}
6070

61-
$Roles = @($Roles) + @($User.userRoles)
71+
$Roles = @($Roles) + @($User.userRoles)
6272

63-
if ($Roles) {
64-
Write-Information "Roles determined for $($User.userDetails): $($Roles -join ', ')"
65-
}
73+
if ($Roles) {
74+
Write-Information "Roles determined for $($User.userDetails): $($Roles -join ', ')"
75+
}
6676

67-
if (($Roles | Measure-Object).Count -gt 2) {
68-
try {
69-
$UserRole = [PSCustomObject]@{
70-
PartitionKey = 'AccessUser'
71-
RowKey = [string]$User.userDetails
72-
Role = [string](ConvertTo-Json -Compress -InputObject $Roles)
77+
if (($Roles | Measure-Object).Count -gt 2) {
78+
try {
79+
$UserRole = [PSCustomObject]@{
80+
PartitionKey = 'AccessUser'
81+
RowKey = [string]$User.userDetails
82+
Role = [string](ConvertTo-Json -Compress -InputObject $Roles)
83+
}
84+
Add-CIPPAzDataTableEntity @Table -Entity $UserRole -Force
85+
} catch {
86+
Write-Information "Could not cache user roles for $($User.userDetails). $($_.Exception.Message)"
7387
}
74-
Add-CIPPAzDataTableEntity @Table -Entity $UserRole -Force
75-
} catch {
76-
Write-Information "Could not cache user roles for $($User.userDetails). $($_.Exception.Message)"
88+
}
89+
90+
# Store in AsyncLocal cache for this request
91+
if ($script:CippUserRolesStorage -and $script:CippUserRolesStorage.Value) {
92+
$script:CippUserRolesStorage.Value[$User.userDetails] = $Roles
7793
}
7894
}
7995
}

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/New-CippCoreRequest.ps1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ function New-CippCoreRequest {
2222
if (-not $script:CippAllowedGroupsStorage) {
2323
$script:CippAllowedGroupsStorage = [System.Threading.AsyncLocal[object]]::new()
2424
}
25+
if (-not $script:CippUserRolesStorage) {
26+
$script:CippUserRolesStorage = [System.Threading.AsyncLocal[hashtable]]::new()
27+
}
28+
29+
# Initialize user roles cache for this request
30+
if (-not $script:CippUserRolesStorage.Value) {
31+
$script:CippUserRolesStorage.Value = @{}
32+
}
2533

2634
# Set InvocationId in AsyncLocal storage for console logging correlation
2735
if ($global:TelemetryClient -and $TriggerMetadata.InvocationId) {

profile.ps1

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ if ($hasAppInsights) {
1717
} catch {
1818
Write-Warning "Failed to load Application Insights SDK: $($_.Exception.Message)"
1919
}
20+
$SwAppInsights.Stop()
21+
$Timings['AppInsightsSDK'] = $SwAppInsights.Elapsed.TotalMilliseconds
2022
}
21-
if (!$hasAppInsights) {
22-
Write-Information 'Application Insights not configured; skipping SDK load'
23-
}
24-
$SwAppInsights.Stop()
25-
$Timings['AppInsightsSDK'] = $SwAppInsights.Elapsed.TotalMilliseconds
2623

2724
# Import modules
2825
$SwModules = [System.Diagnostics.Stopwatch]::StartNew()
@@ -66,9 +63,9 @@ if ($hasAppInsights -and -not $global:TelemetryClient) {
6663
} catch {
6764
Write-Warning "Failed to initialize TelemetryClient: $($_.Exception.Message)"
6865
}
66+
$SwTelemetry.Stop()
67+
$Timings['TelemetryClient'] = $SwTelemetry.Elapsed.TotalMilliseconds
6968
}
70-
$SwTelemetry.Stop()
71-
$Timings['TelemetryClient'] = $SwTelemetry.Elapsed.TotalMilliseconds
7269

7370
$SwDurableSDK = [System.Diagnostics.Stopwatch]::StartNew()
7471
if ($env:ExternalDurablePowerShellSDK -eq $true) {

0 commit comments

Comments
 (0)