Skip to content

Commit bf2c2c0

Browse files
committed
feat: Add Get-CIPPAlertGlobalAdminAllowList function and corresponding tests
1 parent f22afac commit bf2c2c0

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
function Get-CIPPAlertGlobalAdminAllowList {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
#>
6+
[CmdletBinding()]
7+
param (
8+
[Parameter(Mandatory = $false)]
9+
[Alias('input')]
10+
$InputValue,
11+
$TenantFilter
12+
)
13+
try {
14+
$AllowedAdmins = @()
15+
$AlertEachAdmin = $false
16+
if ($InputValue -is [hashtable] -or $InputValue -is [pscustomobject]) {
17+
$AlertEachAdmin = [bool]($InputValue['AlertEachAdmin'])
18+
$ApprovedValue = if ($InputValue.ContainsKey('ApprovedGlobalAdmins') -or ($InputValue.PSObject.Properties.Name -contains 'ApprovedGlobalAdmins')) {
19+
$InputValue['ApprovedGlobalAdmins']
20+
} else {
21+
$null
22+
}
23+
$InputValue = $ApprovedValue
24+
}
25+
if ($null -ne $InputValue) {
26+
if ($InputValue -is [string]) {
27+
$AllowedAdmins = $InputValue -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
28+
} elseif ($InputValue -is [System.Collections.IEnumerable]) {
29+
$AllowedAdmins = $InputValue | ForEach-Object { $_.ToString().Trim() } | Where-Object { $_ }
30+
} else {
31+
$AllowedAdmins = @("$InputValue")
32+
}
33+
}
34+
$AllowedLookup = $AllowedAdmins | ForEach-Object { $_.ToLowerInvariant() } | Select-Object -Unique
35+
36+
if (-not $AllowedLookup -or $AllowedLookup.Count -eq 0) {
37+
return
38+
}
39+
40+
$GlobalAdmins = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members?`$select=id,displayName,userPrincipalName" -tenantid $TenantFilter -AsApp $true -ErrorAction Stop | Where-Object {
41+
$_.'@odata.type' -eq '#microsoft.graph.user' -and $_.displayName -ne 'On-Premises Directory Synchronization Service Account'
42+
}
43+
44+
$UnapprovedAdmins = foreach ($admin in $GlobalAdmins) {
45+
if ([string]::IsNullOrWhiteSpace($admin.userPrincipalName)) { continue }
46+
$UpnPrefix = ($admin.userPrincipalName -split '@')[0].ToLowerInvariant()
47+
if ($AllowedLookup -notcontains $UpnPrefix) {
48+
[PSCustomObject]@{
49+
Admin = $admin
50+
UpnPrefix = $UpnPrefix
51+
}
52+
}
53+
}
54+
55+
if ($UnapprovedAdmins) {
56+
if ($AlertEachAdmin) {
57+
$AlertData = foreach ($item in $UnapprovedAdmins) {
58+
$admin = $item.Admin
59+
$UpnPrefix = $item.UpnPrefix
60+
[PSCustomObject]@{
61+
Message = "$($admin.userPrincipalName) has Global Administrator role but is not in the approved allow list (prefix '$UpnPrefix')."
62+
DisplayName = $admin.displayName
63+
UserPrincipalName = $admin.userPrincipalName
64+
Id = $admin.id
65+
AllowedList = if ($AllowedAdmins) { $AllowedAdmins -join ', ' } else { 'Not provided' }
66+
Tenant = $TenantFilter
67+
}
68+
}
69+
} else {
70+
$NonCompliantUpns = @($UnapprovedAdmins.Admin.userPrincipalName)
71+
$AlertData = @([PSCustomObject]@{
72+
Message = "Found $($NonCompliantUpns.Count) Global Administrator account(s) not in the approved allow list."
73+
NonCompliantUsers = $NonCompliantUpns
74+
ApprovedPrefixes = if ($AllowedAdmins) { $AllowedAdmins -join ', ' } else { 'Not provided' }
75+
Tenant = $TenantFilter
76+
})
77+
}
78+
79+
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData
80+
}
81+
} catch {
82+
Write-AlertMessage -tenant $TenantFilter -message "Failed to check approved Global Admins: $(Get-NormalizedError -message $_.Exception.Message)"
83+
}
84+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Pester tests for Get-CIPPAlertGlobalAdminAllowList
2+
# Verifies prefix-based allow list handling and alert emission
3+
4+
BeforeAll {
5+
$RepoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSCommandPath))
6+
$AlertPath = Join-Path $RepoRoot 'Modules/CIPPCore/Public/Alerts/Get-CIPPAlertGlobalAdminAllowList.ps1'
7+
8+
# Provide minimal stubs so Mock has commands to replace during tests
9+
function New-GraphGetRequest { param($uri, $tenantid, $AsApp) }
10+
function Write-AlertTrace { param($cmdletName, $tenantFilter, $data) }
11+
function Write-AlertMessage { param($tenant, $message) }
12+
13+
. $AlertPath
14+
}
15+
16+
Describe 'Get-CIPPAlertGlobalAdminAllowList' {
17+
BeforeEach {
18+
$script:CapturedData = $null
19+
$script:CapturedTenant = $null
20+
21+
Mock -CommandName New-GraphGetRequest -MockWith {
22+
@(
23+
[pscustomobject]@{
24+
'@odata.type' = '#microsoft.graph.user'
25+
displayName = 'Allowed Admin'
26+
userPrincipalName = '[email protected]'
27+
id = 'id-allowed'
28+
},
29+
[pscustomobject]@{
30+
'@odata.type' = '#microsoft.graph.user'
31+
displayName = 'Unapproved Admin'
32+
userPrincipalName = '[email protected]'
33+
id = 'id-unapproved'
34+
}
35+
)
36+
}
37+
38+
Mock -CommandName Write-AlertTrace -MockWith {
39+
param($cmdletName, $tenantFilter, $data)
40+
$script:CapturedData = $data
41+
$script:CapturedTenant = $tenantFilter
42+
}
43+
44+
Mock -CommandName Write-AlertMessage {}
45+
}
46+
47+
It 'emits per-admin alerts when AlertEachAdmin is true' {
48+
$allowInput = @{ ApprovedGlobalAdmins = 'breakglass'; AlertEachAdmin = $true }
49+
50+
Get-CIPPAlertGlobalAdminAllowList -TenantFilter 'contoso.onmicrosoft.com' -InputValue $allowInput
51+
52+
$CapturedData | Should -Not -BeNullOrEmpty
53+
$CapturedData.UserPrincipalName | Should -Contain '[email protected]'
54+
$CapturedData.UserPrincipalName | Should -Not -Contain '[email protected]'
55+
$CapturedTenant | Should -Be 'contoso.onmicrosoft.com'
56+
}
57+
58+
It 'emits single aggregated alert when AlertEachAdmin is false (default)' {
59+
Get-CIPPAlertGlobalAdminAllowList -TenantFilter 'contoso.onmicrosoft.com' -InputValue 'breakglass'
60+
61+
$CapturedData | Should -Not -BeNullOrEmpty
62+
$CapturedData.Count | Should -Be 1
63+
$CapturedData[0].NonCompliantUsers | Should -Contain '[email protected]'
64+
$CapturedData[0].NonCompliantUsers | Should -Not -Contain '[email protected]'
65+
}
66+
67+
It 'suppresses alert when UPN prefix is approved (comma separated list)' {
68+
$allowInput = @{ ApprovedGlobalAdmins = 'breakglass,otheradmin'; AlertEachAdmin = $true }
69+
Get-CIPPAlertGlobalAdminAllowList -TenantFilter 'contoso.onmicrosoft.com' -InputValue $allowInput
70+
71+
$CapturedData | Should -BeNullOrEmpty
72+
}
73+
74+
It 'accepts ApprovedGlobalAdmins property when provided as hashtable' {
75+
$allowInput = @{ ApprovedGlobalAdmins = 'breakglass,otheradmin' }
76+
Get-CIPPAlertGlobalAdminAllowList -TenantFilter 'contoso.onmicrosoft.com' -InputValue $allowInput
77+
78+
$CapturedData | Should -BeNullOrEmpty
79+
}
80+
}

0 commit comments

Comments
 (0)