tests/Test-Assessment.21784.ps1

<#
.SYNOPSIS
Checks that all users are required to use phishing-resistant authentication methods through Conditional Access policies.

Pass/Fail Hook:
- Test checks for enabled Conditional Access policies that apply to all users
- Policies must require authentication strength with phishing-resistant methods
- Passes if such policies exist without significant user exclusions
- Fails if no policies found or policies have coverage gaps due to exclusions
#>


function Test-Assessment-21784{
    [ZtTest(
        Category = 'Access control',
        ImplementationCost = 'Medium',
        Pillar = 'Identity',
        RiskLevel = 'Medium',
        SfiPillar = 'Protect identities and secrets',
        TenantType = ('Workforce','External'),
        TestId = 21784,
        Title = 'All user sign in activity uses phishing-resistant authentication methods',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param()

    # Helper function to render policy table
    function Get-PolicyTable {
        param(
            [array]$Policies,
            [array]$PhishingResistantPolicies,
            [string]$TableTitle,
            [switch]$ShowIssues
        )

        if (-not $Policies -or $Policies.Count -eq 0) {
            return ""
        }

        $tableMarkdown = "## $TableTitle`n`n"
        $tableMarkdown += "| Policy | Authentication strength | Included Users | Excluded Users |`n"
        $tableMarkdown += "| :---------- | :---------------------- | :------------- | :------------- |`n"

        foreach ($policy in $Policies) {
            $portalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($policy.id)"
            $authStrengthLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/AuthStrengths/menuId//fromNav/Identity"

            # Get authentication strength name
            $strengthPolicy = $PhishingResistantPolicies | Where-Object { $_.id -eq $policy.grantControls.authenticationStrength.id }
            $authStrengthName = if ($strengthPolicy) {
                "[$(Get-SafeMarkdown($strengthPolicy.displayName))]($authStrengthLink)"
            } else {
                "None"
            }

            # Format included users
            $includedUsers = if ($policy.conditions.users.includeUsers -contains 'All') {
                "All Users"
            } elseif ($policy.conditions.users.includeUsers.Count -gt 0) {
                "$($policy.conditions.users.includeUsers.Count) users"
            } else {
                "None"
            }

            # Format excluded users with warning if showing issues
            $excludedUsers = if ($policy.conditions.users.excludeUsers.Count -gt 0) {
                if ($ShowIssues) {
                    "⚠️ $($policy.conditions.users.excludeUsers.Count) users"
                } else {
                    "$($policy.conditions.users.excludeUsers.Count) users"
                }
            } else {
                "None"
            }

            $tableMarkdown += "| [$(Get-SafeMarkdown($policy.displayName))]($portalLink) | $authStrengthName | $includedUsers | $excludedUsers |`n"
        }
        $tableMarkdown += "`n"
        return $tableMarkdown
    }

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

    $activity = 'Checking phishing-resistant authentication methods'

    # Get enabled Conditional Access policies
    $policies = @()
    try {
        Write-ZtProgress -Activity $activity -Status 'Getting policies'
        $enabledPolicies = Invoke-ZtGraphRequest -RelativeUri 'identity/conditionalAccess/policies' -Filter "state eq 'enabled'" -ApiVersion beta
        $policies = $enabledPolicies
    }
    catch {
        Write-PSFMessage 'Failed to get CA policies' -Level Warning -ErrorRecord $_
        return $false
    }

    # Get authentication strength policies
    $strengthPolicies = @()
    try {
        $authStrengthPolicies = Invoke-ZtGraphRequest -RelativeUri 'identity/conditionalAccess/authenticationStrength/policies' -ApiVersion beta
        $strengthPolicies = $authStrengthPolicies
    }
    catch {
        Write-PSFMessage 'Failed to get auth strength policies' -Level Warning -ErrorRecord $_
        return $false
    }

    # Define phishing-resistant methods
    $phishingResistantMethods = @(
        'windowsHelloForBusiness',
        'fido2',
        'x509CertificateMultiFactor',
        'certificateBasedAuthenticationPki'
    )

    # Find policies with phishing-resistant methods
    $phishingResistantPolicies = $strengthPolicies | Where-Object {
        $_.allowedCombinations | Where-Object { $phishingResistantMethods -contains $_ }
    }

    if (-not $phishingResistantPolicies) {
        Write-PSFMessage 'No phishing-resistant auth policies found' -Level Warning
        return $false
    }

    # Find policies that apply to all users
    $relevantPolicies = $policies | Where-Object {
        ($_.conditions.users.includeUsers -contains 'All') -and
        ($_.grantControls.authenticationStrength.id -in $phishingResistantPolicies.id)
    }

    $result = $false
    $details = [System.Collections.ArrayList]::new()

    if ($relevantPolicies) {
        foreach ($policy in $relevantPolicies) {
            [void]$details.Add("Policy: $($policy.displayName)")
            [void]$details.Add("- ID: $($policy.id)")
            [void]$details.Add("- Authentication Strength: $($policy.grantControls.authenticationStrength.id)")
            if ($policy.conditions.users.excludeUsers.Count -gt 0) {
                [void]$details.Add("- Excluded Users: $($policy.conditions.users.excludeUsers -join ', ')")
            }
            [void]$details.Add("")
        }

        # Check for coverage gaps due to exclusions
        $hasSignificantExclusions = $relevantPolicies | Where-Object {
            $_.conditions.users.excludeUsers.Count -gt 0
        }

        $result = -not $hasSignificantExclusions
    }

    $testResultMarkdown = ''
    if ($result) {
        $testResultMarkdown = @"
✅ All users are protected by Conditional Access policies requiring phishing-resistant authentication methods.

"@

        # Add policy details using helper function
        if ($relevantPolicies) {
            $testResultMarkdown += Get-PolicyTable -Policies $relevantPolicies -PhishingResistantPolicies $phishingResistantPolicies -TableTitle 'Conditional Access Policies with Phishing-Resistant Authentication'
        }
    } else {
        $failReason = if (-not $relevantPolicies) {
            'No Conditional Access policies found that require phishing-resistant authentication for all users'
        } else {
            'Found policies with user exclusions that create coverage gaps'
        }

        $testResultMarkdown = @"
❌ Not all users are protected by Conditional Access policies requiring phishing-resistant authentication methods.

**Reason**: $failReason

"@

        # Add policy details even for failures
        if ($relevantPolicies) {
            $testResultMarkdown += Get-PolicyTable -Policies $relevantPolicies -PhishingResistantPolicies $phishingResistantPolicies -TableTitle "Conditional Access Policies with Phishing-Resistant Authentication (Issues Found)" -ShowIssues
        } else {
            # Show available authentication strength policies even if none are applied to all users
            if ($phishingResistantPolicies) {
                $testResultMarkdown += "## Available Authentication Strength Policies`n`n"
                $testResultMarkdown += "| Authentication Strength Policy | Allowed Methods |`n"
                $testResultMarkdown += "| :----------------------------- | :-------------- |`n"

                foreach ($strengthPolicy in $phishingResistantPolicies) {
                    $allowedMethods = $strengthPolicy.allowedCombinations -join ', '
                    $testResultMarkdown += "| $($strengthPolicy.displayName) | $allowedMethods |`n"
                }
                $testResultMarkdown += "`n*Note: These authentication strength policies exist but are not applied to all users via Conditional Access.*`n`n"
            }
        }
    }
        $passed = $result

        $params = @{
        TestId             = '21784'
        Status             = $passed
        Result             = $testResultMarkdown

    }
    Add-ZtTestResultDetail @params
    Write-PSFMessage '🟦 End' -Tag Test -Level VeryVerbose

}