tests/Test-Assessment.21820.ps1

<#
.SYNOPSIS
    Checks that activation alerts are configured for all privileged role assignments.
#>


function Test-Assessment-21820 {
    [ZtTest(
        Category = 'Privileged access',
        ImplementationCost = 'Medium',
        MinimumLicense = ('P2'),
        Pillar = 'Identity',
        RiskLevel = 'Low',
        SfiPillar = 'Protect identities and secrets',
        TenantType = ('Workforce'),
        TestId = 21820,
        Title = 'Activation alert for all privileged role assignments',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param(
        $Database
    )

    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

    if ( -not (Get-ZtLicense EntraIDP2) ) {
        Add-ZtTestResultDetail -SkippedBecause NotLicensedEntraIDP2
        return
    }

    #region Data Collection
    $activity = 'Checking activation alerts for privileged role assignments'
    Write-ZtProgress -Activity $activity -Status 'Getting privileged role definitions'

    # Query 1: Get all highly privileged role definitions from database
    $sql = @"
    SELECT id, displayName
    FROM RoleDefinition
    WHERE isPrivileged = 1
"@


    $privilegedRoles = Invoke-DatabaseQuery -Database $Database -Sql $sql -AsCustomObject

    Write-PSFMessage "Found $($privilegedRoles.Count) privileged role definitions" -Level Verbose

    # Query 2: Get all PIM role management policy assignments for directory roles (single API call)
    Write-ZtProgress -Activity $activity -Status 'Getting PIM policy assignments'

    $filter = "scopeId eq '/' and scopeType eq 'DirectoryRole'"
    $allPolicyAssignments = Invoke-ZtGraphRequest -RelativeUri 'policies/roleManagementPolicyAssignments' -Filter $filter -ApiVersion beta

    # Build hashtable mapping roleDefinitionId -> policyId for quick lookup
    $policyIdByRoleId = @{}
    foreach ($assignment in $allPolicyAssignments) {
        $policyIdByRoleId[$assignment.roleDefinitionId] = $assignment.policyId
    }

    # Track roles with missing or misconfigured alerts
    $rolesWithIssues = @()
    # Flag to control early exit when first issue is found
    $exitLoop = $false
    $passed = $true
    $totalRoles = $privilegedRoles.Count
    $currentRole = 0

    foreach ($role in $privilegedRoles) {
        $currentRole++
        Write-ZtProgress -Activity $activity -Status "Checking alerts for $($role.displayName) ($currentRole of $totalRoles)"

        Write-PSFMessage "Checking PIM policy for role: $($role.displayName) (ID: $($role.id))" -Level Verbose

        # Lookup policy ID from hashtable
        # id is a GUID, so use Guid property for lookup
        $policyId = $policyIdByRoleId[$role.id.Guid]

        if (-not $policyId) {
            Write-PSFMessage "No PIM policy assignment found for role: $($role.displayName)" -Level Verbose

            $rolesWithIssues += @{
                Role                       = $role
                Issue                      = 'No PIM policy assignment found'
                IsDefaultRecipientsEnabled = 'N/A'
                NotificationRecipients     = 'N/A'
            }
            continue
        }

        Write-PSFMessage "Found policy ID: $policyId for role: $($role.displayName)" -Level Verbose

        # Query 3: Get activation notification rules for this policy
        $notificationRuleUri = "policies/roleManagementPolicies/$policyId/rules/Notification_Requestor_EndUser_Assignment"

        $notificationRule = Invoke-ZtGraphRequest -RelativeUri $notificationRuleUri -ApiVersion beta

        $isDefaultRecipientsEnabled = $notificationRule.isDefaultRecipientsEnabled
        $notificationRecipients = $notificationRule.notificationRecipients

        Write-PSFMessage "Role: $($role.displayName) - isDefaultRecipientsEnabled: $isDefaultRecipientsEnabled, Recipients: $($notificationRecipients -join ', ')" -Level Verbose

        # Check if alert is properly configured
        # Fail if: (isDefaultRecipientsEnabled is true AND notificationRecipients is empty) OR (isDefaultRecipientsEnabled is false AND no custom recipients)
        if (($isDefaultRecipientsEnabled -eq $true -and ([string]::IsNullOrEmpty($notificationRecipients) -or $notificationRecipients.Count -eq 0))) {

            $passed = $false
            Write-PSFMessage "Alert misconfigured for role: $($role.displayName) - Default recipients enabled but no recipients configured" -Level Verbose

            $rolesWithIssues += @{
                Role                       = $role
                IsDefaultRecipientsEnabled = $isDefaultRecipientsEnabled
                NotificationRecipients     = 'N/A'
            }

            $exitLoop = $true
        }
        else {
            Write-PSFMessage "Alert properly configured for role: $($role.displayName)" -Level Verbose
        }

        if ($exitLoop) {
            break
        }
    }
    #endregion Data Collection

    #region Assessment Logic
    if ($rolesWithIssues.Count -eq 0) {
        $passed = $true
        $testResultMarkdown = 'Activation alerts are configured for privileged role assignments.'
    }
    else {
        $passed = $false
        $testResultMarkdown = 'Activation alerts are missing or improperly configured for privileged roles.'
    }
    #endregion Assessment Logic

    #region Report Generation
    # Build markdown output
    $mdInfo = ""

    if ($rolesWithIssues.Count -gt 0) {
        $mdInfo += "`n## Roles with missing or misconfigured alerts`n`n"
        $mdInfo += "| Role display name | Default recipients | Additional recipients |`n"
        $mdInfo += "| :---------------- | :----------------- | :------------------- |`n"

        foreach ($roleIssue in $rolesWithIssues) {
            $role = $roleIssue.Role
            $roleLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RolesManagementMenuBlade/~/AllRoles"
            $displayName = $role.displayName
            $displayNameLink = "[$displayName]($roleLink)"

            $defaultRecipientsStatus = if ($roleIssue.IsDefaultRecipientsEnabled -eq $true) {
                'Enabled'
            }
            else {
                'Disabled'
            }
            $recipients = $roleIssue.NotificationRecipients

            $mdInfo += "| $displayNameLink | $defaultRecipientsStatus | $recipients |`n"
        }
        $mdInfo += "`n`n*Not all misconfigured roles may be listed. For performance reasons, this assessment stops at the first detected issue.*`n"
    }

    # Append details to the test result
    $testResultMarkdown += $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '21820'
        Status = $passed
        Result = $testResultMarkdown
    }

    Add-ZtTestResultDetail @params
}