Skip to content

Commit 0439ff3

Browse files
authored
Merge pull request KelvinTegelaar#1443 from kris6673/feat-jit-admin-all-tenants-list
Feat: List JIT admin support for all tenants
2 parents 63e6f41 + 732ad86 commit 0439ff3

File tree

3 files changed

+212
-38
lines changed

3 files changed

+212
-38
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
function Push-ExecJITAdminListAllTenants {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
#>
6+
param($Item)
7+
8+
$Tenant = Get-Tenants -TenantFilter $Item.customerId
9+
$DomainName = $Tenant.defaultDomainName
10+
$Table = Get-CIPPTable -TableName CacheJITAdmin
11+
12+
try {
13+
# Get schema extensions
14+
$Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } | Select-Object -First 1
15+
16+
# Query users with JIT Admin enabled
17+
$Query = @{
18+
TenantFilter = $DomainName # Use $DomainName for the current tenant
19+
Endpoint = 'users'
20+
Parameters = @{
21+
'$count' = 'true'
22+
'$select' = "id,accountEnabled,displayName,userPrincipalName,$($Schema.id)"
23+
'$filter' = "$($Schema.id)/jitAdminEnabled eq true or $($Schema.id)/jitAdminEnabled eq false" # Fetches both states to cache current status
24+
}
25+
}
26+
$Users = Get-GraphRequestList @Query | Where-Object { $_.id }
27+
28+
if ($Users) {
29+
# Get role memberships
30+
$BulkRequests = $Users | ForEach-Object { @(
31+
@{
32+
id = $_.id
33+
method = 'GET'
34+
url = "users/$($_.id)/memberOf/microsoft.graph.directoryRole/?`$select=id,displayName"
35+
}
36+
)
37+
}
38+
# Ensure $BulkRequests is not empty or null before making the bulk request
39+
if ($BulkRequests -and $BulkRequests.Count -gt 0) {
40+
$RoleResults = New-GraphBulkRequest -tenantid $DomainName -Requests @($BulkRequests)
41+
42+
# Format the data
43+
$Results = $Users | ForEach-Object {
44+
$currentUser = $_ # Capture current user in the loop
45+
$MemberOf = @() # Initialize as empty array
46+
if ($RoleResults) {
47+
$userRoleResult = $RoleResults | Where-Object -Property id -EQ $currentUser.id
48+
if ($userRoleResult -and $userRoleResult.body -and $userRoleResult.body.value) {
49+
$MemberOf = $userRoleResult.body.value | Select-Object displayName, id
50+
}
51+
}
52+
53+
$jitAdminData = $currentUser.($Schema.id)
54+
$jitAdminEnabled = if ($jitAdminData -and $jitAdminData.PSObject.Properties['jitAdminEnabled']) { $jitAdminData.jitAdminEnabled } else { $false }
55+
$jitAdminExpiration = if ($jitAdminData -and $jitAdminData.PSObject.Properties['jitAdminExpiration']) { $jitAdminData.jitAdminExpiration } else { $null }
56+
57+
[PSCustomObject]@{
58+
id = $currentUser.id
59+
displayName = $currentUser.displayName
60+
userPrincipalName = $currentUser.userPrincipalName
61+
accountEnabled = $currentUser.accountEnabled
62+
jitAdminEnabled = $jitAdminEnabled
63+
jitAdminExpiration = $jitAdminExpiration
64+
memberOf = ($MemberOf | ConvertTo-Json -Depth 5 -Compress)
65+
}
66+
}
67+
68+
# Add to Azure Table
69+
foreach ($result in $Results) {
70+
$GUID = (New-Guid).Guid
71+
Write-Host ($result | ConvertTo-Json -Depth 10 -Compress)
72+
$GraphRequest = @{
73+
JITAdminUser = [string]($result | ConvertTo-Json -Depth 10 -Compress)
74+
RowKey = [string]$GUID
75+
PartitionKey = 'JITAdminUser'
76+
Tenant = [string]$DomainName
77+
UserId = [string]$result.id # Add UserId for easier querying if needed
78+
UserUPN = [string]$result.userPrincipalName # Add UserUPN for easier querying
79+
}
80+
Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null
81+
}
82+
} else {
83+
# No users with JIT Admin attributes found, or no users at all
84+
Write-Host "No JIT Admin users or no users found to process for tenant $DomainName."
85+
}
86+
} else {
87+
Write-Host "No users found for tenant $DomainName."
88+
}
89+
90+
} catch {
91+
$GUID = (New-Guid).Guid
92+
$ErrorMessage = "Could not process JIT Admin users for Tenant: $($DomainName). Error: $($_.Exception.Message)"
93+
if ($_.ScriptStackTrace) {
94+
$ErrorMessage += " StackTrace: $($_.ScriptStackTrace)"
95+
}
96+
$ErrorJson = ConvertTo-Json -InputObject @{
97+
Tenant = $DomainName
98+
Error = $ErrorMessage
99+
Exception = ($_.Exception.Message | ConvertTo-Json -Depth 3 -Compress)
100+
Timestamp = (Get-Date).ToString('s')
101+
}
102+
$GraphRequest = @{
103+
JITAdminUser = [string]$ErrorJson
104+
RowKey = [string]$GUID
105+
PartitionKey = 'JITAdminUser'
106+
Tenant = [string]$DomainName
107+
}
108+
Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null
109+
Write-Error ('Error processing JIT Admin for {0}: {1}' -f $DomainName, $_.Exception.Message)
110+
}
111+
}

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1

Lines changed: 100 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,114 @@ function Invoke-ExecJITAdmin {
1010
[CmdletBinding()]
1111
param($Request, $TriggerMetadata)
1212

13-
$APIName = 'ExecJITAdmin'
13+
$APIName = $Request.Params.CIPPEndpoint
1414
$User = $Request.Headers
15-
$TenantFilter = $Request.body.TenantFilter.value ? $Request.body.TenantFilter.value : $Request.body.TenantFilter
16-
Write-LogMessage -Headers $User -API $APINAME -message 'Accessed this API' -Sev 'Debug'
15+
$TenantFilter = $Request.Body.tenantFilter.value ? $Request.Body.tenantFilter.value : $Request.Body.tenantFilter
16+
Write-LogMessage -Headers $User -API $APIName -message 'Accessed this API' -Sev 'Debug'
1717

1818
if ($Request.Query.Action -eq 'List') {
1919
$Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } | Select-Object -First 1
20-
$Query = @{
21-
TenantFilter = $Request.Query.TenantFilter
22-
Endpoint = 'users'
23-
Parameters = @{
24-
'$count' = 'true'
25-
'$select' = "id,accountEnabled,displayName,userPrincipalName,$($Schema.id)"
26-
'$filter' = "$($Schema.id)/jitAdminEnabled eq true or $($Schema.id)/jitAdminEnabled eq false"
20+
if ($Request.Query.TenantFilter -ne 'AllTenants') {
21+
# Single tenant logic
22+
$Query = @{
23+
TenantFilter = $Request.Query.TenantFilter
24+
Endpoint = 'users'
25+
Parameters = @{
26+
'$count' = 'true'
27+
'$select' = "id,accountEnabled,displayName,userPrincipalName,$($Schema.id)"
28+
'$filter' = "$($Schema.id)/jitAdminEnabled eq true or $($Schema.id)/jitAdminEnabled eq false"
29+
}
2730
}
28-
}
29-
$Users = Get-GraphRequestList @Query | Where-Object { $_.id }
30-
$BulkRequests = $Users | ForEach-Object { @(
31-
@{
32-
id = $_.id
33-
method = 'GET'
34-
url = "users/$($_.id)/memberOf/microsoft.graph.directoryRole/?`$select=id,displayName"
31+
$Users = Get-GraphRequestList @Query | Where-Object { $_.id }
32+
$BulkRequests = $Users | ForEach-Object { @(
33+
@{
34+
id = $_.id
35+
method = 'GET'
36+
url = "users/$($_.id)/memberOf/microsoft.graph.directoryRole/?`$select=id,displayName"
37+
}
38+
)
39+
}
40+
$RoleResults = New-GraphBulkRequest -tenantid $Request.Query.TenantFilter -Requests @($BulkRequests)
41+
#Write-Information ($RoleResults | ConvertTo-Json -Depth 10 )
42+
$Results = $Users | ForEach-Object {
43+
$MemberOf = ($RoleResults | Where-Object -Property id -EQ $_.id).body.value | Select-Object displayName, id
44+
[PSCustomObject]@{
45+
id = $_.id
46+
displayName = $_.displayName
47+
userPrincipalName = $_.userPrincipalName
48+
accountEnabled = $_.accountEnabled
49+
jitAdminEnabled = $_.($Schema.id).jitAdminEnabled
50+
jitAdminExpiration = $_.($Schema.id).jitAdminExpiration
51+
memberOf = $MemberOf
3552
}
36-
)
37-
}
38-
$RoleResults = New-GraphBulkRequest -tenantid $Request.Query.TenantFilter -Requests @($BulkRequests)
39-
#Write-Information ($RoleResults | ConvertTo-Json -Depth 10 )
40-
$Results = $Users | ForEach-Object {
41-
$MemberOf = ($RoleResults | Where-Object -Property id -EQ $_.id).body.value | Select-Object displayName, id
42-
[PSCustomObject]@{
43-
id = $_.id
44-
displayName = $_.displayName
45-
userPrincipalName = $_.userPrincipalName
46-
accountEnabled = $_.accountEnabled
47-
jitAdminEnabled = $_.($Schema.id).jitAdminEnabled
48-
jitAdminExpiration = $_.($Schema.id).jitAdminExpiration
49-
memberOf = $MemberOf
5053
}
51-
}
5254

53-
#Write-Information ($Results | ConvertTo-Json -Depth 10)
54-
$Body = @{
55-
Results = @($Results)
56-
Metadata = @{
57-
Parameters = $Query.Parameters
55+
#Write-Information ($Results | ConvertTo-Json -Depth 10)
56+
$Body = @{
57+
Results = @($Results)
58+
Metadata = @{
59+
Parameters = $Query.Parameters
60+
}
61+
}
62+
} else {
63+
# AllTenants logic
64+
$Results = [System.Collections.Generic.List[object]]::new()
65+
$Metadata = @{}
66+
$Table = Get-CIPPTable -TableName CacheJITAdmin
67+
$PartitionKey = 'JITAdminUser'
68+
$Filter = "PartitionKey eq '$PartitionKey'"
69+
$Rows = Get-CIPPAzDataTableEntity @Table -filter $Filter | Where-Object -Property Timestamp -GT (Get-Date).AddMinutes(-60)
70+
71+
$QueueReference = '{0}-{1}' -f $Request.Query.TenantFilter, $PartitionKey # $TenantFilter is 'AllTenants'
72+
Write-Information "QueueReference: $QueueReference"
73+
$RunningQueue = Invoke-ListCippQueue | Where-Object { $_.Reference -eq $QueueReference -and $_.Status -notmatch 'Completed' -and $_.Status -notmatch 'Failed' }
74+
75+
if ($RunningQueue) {
76+
$Metadata = [PSCustomObject]@{
77+
QueueMessage = 'Still loading JIT Admin data for all tenants. Please check back in a few more minutes.'
78+
}
79+
} elseif (!$Rows -and !$RunningQueue) {
80+
$TenantList = Get-Tenants -IncludeErrors
81+
$Queue = New-CippQueueEntry -Name 'JIT Admin List - All Tenants' -Link '/identity/administration/jit-admin?tenantFilter=AllTenants' -Reference $QueueReference -TotalTasks ($TenantList | Measure-Object).Count
82+
83+
$Metadata = [PSCustomObject]@{
84+
QueueMessage = 'Loading JIT Admin data for all tenants. Please check back in a few minutes.'
85+
}
86+
$InputObject = [PSCustomObject]@{
87+
OrchestratorName = 'JITAdminOrchestrator'
88+
QueueFunction = @{
89+
FunctionName = 'GetTenants'
90+
QueueId = $Queue.RowKey
91+
TenantParams = @{
92+
IncludeErrors = $true
93+
}
94+
DurableName = 'ExecJITAdminListAllTenants'
95+
}
96+
SkipLog = $true
97+
}
98+
Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
99+
} else {
100+
# There is data in the cache, so we will use that
101+
Write-Information "Found $($Rows.Count) rows in the cache"
102+
foreach ($row in $Rows) {
103+
$UserObject = $row.JITAdminUser | ConvertFrom-Json
104+
$Results.Add(
105+
[PSCustomObject]@{
106+
Tenant = $row.Tenant
107+
id = $UserObject.id
108+
displayName = $UserObject.displayName
109+
userPrincipalName = $UserObject.userPrincipalName
110+
accountEnabled = $UserObject.accountEnabled
111+
jitAdminEnabled = $UserObject.jitAdminEnabled
112+
jitAdminExpiration = $UserObject.jitAdminExpiration
113+
memberOf = $UserObject.memberOf
114+
}
115+
)
116+
}
117+
}
118+
$Body = @{
119+
Results = @($Results)
120+
Metadata = $Metadata
58121
}
59122
}
60123
} else {

Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function Test-CIPPAccessPermissions {
133133
$ApplicationToken = Get-GraphToken -returnRefresh $true -SkipCache $true -AsApp $true
134134
$ApplicationTokenDetails = Read-JwtAccessDetails -Token $ApplicationToken.access_token -erroraction SilentlyContinue | Select-Object
135135

136-
$LastUpdate = [DateTime]::SpecifyKind($GraphPermissions.Timestamp.DateTime, [DateTimeKind]::Utc)
136+
$LastUpdate = [DateTime]::SpecifyKind($GraphPermissions.Timestamp.ToString('yyyy-MM-ddTHH:mm:ssZ'), [DateTimeKind]::Utc)
137137
$CpvTable = Get-CippTable -tablename 'cpvtenants'
138138
$CpvRefresh = Get-CippAzDataTableEntity @CpvTable -Filter "PartitionKey eq 'Tenant'"
139139
$TenantList = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -ne $env:TenantID -and $_.Excluded -eq $false }

0 commit comments

Comments
 (0)