tests/Test-Assessment.35011.ps1

<#
.SYNOPSIS
    Super user membership is configured for Azure Information Protection
 
.DESCRIPTION
    Evaluates whether the Azure Information Protection (AIP) super user feature is properly configured.
 
    The cmdlets require the AipService module (v3.0+) which is only supported on Windows PowerShell 5.1. A PowerShell 7 subprocess workaround is automatically employed if running under PowerShell Core.
 
.NOTES
    Test ID: 35011
    Pillar: Data
    Risk Level: Medium
#>


function Test-Assessment-35011 {
    [ZtTest(
        Category = 'Advanced Label Features',
        ImplementationCost = 'Medium',
        MinimumLicense = ('Microsoft 365 E5'),
        Pillar = 'Data',
        RiskLevel = 'Medium',
        SfiPillar = 'Protect tenants and production systems',
        TenantType = ('Workforce','External'),
        TestId = 35011,
        Title = 'Super user membership is configured for Azure Information Protection',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

    $activity = 'Checking Azure Information Protection Super User Configuration'
    Write-ZtProgress -Activity $activity -Status 'Querying AIP super user settings'

    $superUserFeatureEnabled = $null
    $superUsers = @()
    $superUserObjects = @()
    $errorMsg = $null

    try {
        # Note: AipService must be authenticated in Connect-ZtAssessment first
        # This test only performs queries against the authenticated service

        # Query Q1: Check if super user feature is enabled
        $superUserFeatureRaw = Get-AipServiceSuperUserFeature -ErrorAction Stop
        $superUserFeatureEnabled = $superUserFeatureRaw.Value

        # Query Q2: Get list of configured super users
        $superUsers = Get-AipServiceSuperUser -ErrorAction Stop

        # Process superusers and create PSObjects with type classification
        foreach ($superUser in $superUsers) {
            # Split superuser on @ to extract the appId (for service principals) or email (for users)
            $userIdentifier = $superUser -split '@' | Select-Object -First 1

            # Check if the identifier is a GUID (service principal) or email/UPN (user)
            $parsedGuid = [Guid]::Empty
            $isGuid = [Guid]::TryParse($userIdentifier, [ref]$parsedGuid)
            $accountType = if ($isGuid) { 'Service Principal' } else { 'User' }

            # For users, display the full email; for service principals, will be updated with lookup result
            $displayName = if ($isGuid) { $userIdentifier } else { $superUser }
            $servicePrincipalName = $null
            $objectId = $null

            # If it's a service principal, lookup the display name via Graph API
            if ($isGuid) {
                try {
                    $spDetails = Invoke-ZtGraphRequest -RelativeUri 'servicePrincipals' -ApiVersion 'beta' -Filter "appId eq '$userIdentifier'" -Select 'id,displayName,appDisplayName,servicePrincipalNames' -ErrorAction SilentlyContinue

                    if ($spDetails) {
                        # Prefer appDisplayName, then displayName, then the GUID
                        $servicePrincipalName = $spDetails.appDisplayName
                        if (-not $servicePrincipalName) {
                            $servicePrincipalName = $spDetails.displayName
                        }

                        if ($servicePrincipalName) {
                            $displayName = $servicePrincipalName
                        }
                        else {
                            $displayName = $userIdentifier
                        }

                        # Capture the service principal's object ID
                        if ($spDetails.id) {
                            $objectId = $spDetails.id
                        }
                    }
                }
                catch {
                    Write-PSFMessage "Warning: Could not lookup service principal details for $userIdentifier : $_" -Tag Test -Level Warning
                }
            }

            $superUserObj = [PSCustomObject]@{
                DisplayName        = $displayName
                AccountType        = $accountType
                AppId              = $userIdentifier
                Id                 = $objectId
                IsServicePrincipal = $isGuid
            }

            $superUserObjects += $superUserObj
        }
    }
    catch {
        $errorMsg = $_
        Write-PSFMessage "Error querying AIP Super User configuration: $_" -Level Error
    }
    #endregion Data Collection

    #region Assessment Logic
    $passed = $false
    $investigateFlag = $false
    $superUserCount = if ($superUsers) { @($superUsers).Count } else { 0 }

    if ($errorMsg) {
        $investigateFlag = $true
    }
    else {
        # Evaluation logic:
        # - Pass: Feature is DISABLED and count >= 1 (secure state with emergency access pre-configured)
        # - Fail: Feature is DISABLED and count = 0 (no emergency access capability)
        # - Fail: Feature is ENABLED and count = 0 (feature active but no members configured)
        # - Investigate: Feature is ENABLED and count >= 1 (feature actively in use)

        if ($superUserFeatureEnabled -eq "Disabled") {
            if ($superUserCount -ge 1) {
                $passed = $true
            }
            else {
                $passed = $false
            }
        }
        elseif ($superUserFeatureEnabled -eq "Enabled") {
            if ($superUserCount -ge 1) {
                $investigateFlag = $true
            }
            else {
                $passed = $false
            }
        }
    }
    #endregion Assessment Logic

    #region Report Generation

    $testResultMarkdown = ""
    $mdInfo = ""
    $customStatus = $null

    if ($investigateFlag) {
        if ($errorMsg) {
            $testResultMarkdown = "⚠️ Unable to determine super user configuration due to permissions or connection issues.`n`n%TestResult%"
        }
        else {
            $testResultMarkdown = "⚠️ Super user feature is enabled with members configured (review accounts and consider using Azure PIM for just-in-time access instead of permanent enablement).`n`n%TestResult%"
        }
        $customStatus = 'Investigate'
    }
    elseif ($passed) {
        $testResultMarkdown = "✅ Super user feature is disabled with at least one member pre-configured for emergency access (review members to ensure they're still needed).`n`n%TestResult%"
    }
    else {
        $testResultMarkdown = "❌ Super user feature is disabled with no members configured, OR feature is enabled with no members.`n`n%TestResult%"
    }

    # Build detailed information section
    $mdInfo = "## Azure Information Protection Super User Configuration`n`n"

    if ($errorMsg) {
        # Show explicit "Unknown" values when an error occurred
        $mdInfo += "**Super User Feature:** Unknown (unable to query)`n`n"
        $mdInfo += "**Super Users Configured:** Unknown`n`n"
    }
    else {
        $mdInfo += "**Super User Feature:** $superUserFeatureEnabled`n`n"
        $mdInfo += "**Super Users Configured:** $superUserCount`n`n"
    }

    if (-not $errorMsg -and $superUserCount -gt 0) {
        $mdInfo += "| Super User Display | Account Type |`n"
        $mdInfo += "| :--- | :--- |`n"

        foreach ($superUserObj in $superUserObjects) {
            if ($superUserObj.IsServicePrincipal -and $superUserObj.Id) {
                # Only create portal link when we have a valid object ID
                $spPortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Overview/objectId/$($superUserObj.Id)/appId/$($superUserObj.AppId)"
                $displayText = "[$(Get-SafeMarkdown $superUserObj.DisplayName)]($spPortalLink)"
            }
            else {
                $displayText = Get-SafeMarkdown $superUserObj.DisplayName
            }
            $mdInfo += "| $displayText | $($superUserObj.AccountType) |`n"
        }

        $mdInfo += "`n"
    }

    $mdInfo += "**Note:** Super user configuration is not available through the Azure portal and must be managed via PowerShell using the AipService module.`n"

    # Replace placeholder with actual detailed info
    $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '35011'
        Status = $passed
        Result = $testResultMarkdown
    }
    if ($customStatus) {
        $params.CustomStatus = $customStatus
    }
    Add-ZtTestResultDetail @params
}