tests/Test-Assessment.27000.ps1

<#
.SYNOPSIS
    Checks if high-risk web content filtering categories (Criminal activity, Hacking, Illegal software) are blocked.
 
.DESCRIPTION
    This check evaluates whether Global Secure Access web content filtering policies block three
    high-risk Liability categories: Criminal activity, Hacking, and Illegal software. It validates
    that the blocking is effective through either the baseline profile or security profiles with
    active Conditional Access enforcement.
 
.NOTES
    Test ID: 27000
    Category: Global Secure Access
    Risk Level: High
#>


function Test-Assessment-27000 {
    [ZtTest(
        Category = 'Global Secure Access',
        ImplementationCost = 'Low',
        MinimumLicense = ('Entra_Premium_Internet_Access'),
        Pillar = 'Network',
        RiskLevel = 'High',
        SfiPillar = 'Protect networks',
        TenantType = ('Workforce'),
        TestId = 27000,
        Title = 'High-risk WCF categories (Criminal activity, Hacking, Illegal software) are blocked',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param()

    #region Helper Functions
    function New-FailedCategoryResults {
        <#
        .SYNOPSIS
            Creates failed category results when policies or profiles are missing.
        #>

        param(
            [Parameter(Mandatory)]
            [array]$RequiredCategories,

            [Parameter(Mandatory)]
            [hashtable]$CategoryDisplayNames
        )

        $results = @()
        foreach ($catName in $RequiredCategories) {
            $results += [PSCustomObject]@{
                Category   = $CategoryDisplayNames[$catName]
                EnforcedBy = 'None'
                CAEnforced = 'N/A'
                Status     = 'Not Blocked'
            }
        }
        return $results
    }
    #endregion Helper Functions

    #region Data Collection
    Write-PSFMessage '🟦 Start high-risk WCF category block evaluation' -Tag Test -Level VeryVerbose
    $activity = 'Checking high-risk WCF categories are blocked'

    $filteringPolicies = $null
    $filteringProfiles = $null
    $caPolicies = $null
    $errorMsg = $null

    try {
        # Q1: Get all filtering policies with policy rules
        Write-ZtProgress -Activity $activity -Status 'Getting filtering policies'
        $filteringPolicies = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringPolicies' -QueryParameters @{ '$expand' = 'policyRules' } -ApiVersion beta -ErrorAction Stop
        Write-PSFMessage "Found $($filteringPolicies.Count) filtering policies" -Level Verbose

        # Q2: Get all filtering profiles with expanded policies (following 25407 pattern)
        if ($filteringPolicies -and $filteringPolicies.Count -gt 0) {
            Write-ZtProgress -Activity $activity -Status 'Getting filtering profiles'
            $filteringProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringProfiles' -QueryParameters @{ '$expand' = 'policies($expand=policy)' } -ApiVersion beta -ErrorAction Stop
            Write-PSFMessage "Found $($filteringProfiles.Count) filtering profiles" -Level Verbose
        }

        # Q3: Get CA policies (following 25407 pattern)
        if ($filteringPolicies -and $filteringPolicies.Count -gt 0 -and
            $filteringProfiles -and $filteringProfiles.Count -gt 0) {
            Write-ZtProgress -Activity $activity -Status 'Getting Conditional Access policies'
            $caPolicies = Get-ZtConditionalAccessPolicy
        }
    }
    catch {
        $errorMsg = $_
        Write-PSFMessage "Failed to retrieve data: $_" -Level Error
    }
    #endregion Data Collection

    #region Assessment Logic
    # Required categories to check
    $requiredCategories = @('CriminalActivity', 'Hacking', 'IllegalSoftware')
    $categoryDisplayNames = @{
        'CriminalActivity' = 'Criminal activity'
        'Hacking'          = 'Hacking'
        'IllegalSoftware'  = 'Illegal software'
    }

    # Early validation: Check if we have the required data to proceed
    $passed = $false
    $categoryResults = @()

    if($errorMsg) {
        Write-PSFMessage "Error during data collection: $errorMsg" -Level Error
        $testResultMarkdown = "❌ Failed to retrieve necessary data for assessment.`n`nError: $errorMsg"
    }
    elseif(-not $filteringPolicies -or $filteringPolicies.Count -eq 0 ){
        Write-PSFMessage "No WCF policies found" -Level Warning
        $categoryResults = New-FailedCategoryResults -RequiredCategories $requiredCategories -CategoryDisplayNames $categoryDisplayNames
        $blockedCount = 0
        $notBlockedCount = $requiredCategories.Count
    }
    elseif (-not $filteringProfiles -or $filteringProfiles.Count -eq 0) {
        Write-PSFMessage "No filtering profiles found" -Level Warning
        $categoryResults = New-FailedCategoryResults -RequiredCategories $requiredCategories -CategoryDisplayNames $categoryDisplayNames
        $blockedCount = 0
        $notBlockedCount = $requiredCategories.Count
    }
    else {
        [int]$BASELINE_PROFILE_PRIORITY = 65000

        # Evaluate each category
        foreach ($catName in $requiredCategories) {
            $catDisplay = $categoryDisplayNames[$catName]

            # Find all policies that cover this category using filtering
            $policiesCoveringCategory = @($filteringPolicies | Where-Object {
                $policy = $_
                $webCatRules = @($policy.policyRules | Where-Object { $_.ruleType -eq 'webCategory' })
                $webCatRules | Where-Object {
                    $_.destinations | Where-Object { $_.name -eq $catName }
                }
            })

            # Find all profiles linked to these policies using Find-ZtProfilesLinkedToPolicy
            $profileCandidates = @()
            foreach ($policy in $policiesCoveringCategory) {
                $findParams = @{
                    PolicyId          = $policy.id
                    FilteringProfiles = $filteringProfiles
                    CAPolicies        = $caPolicies
                    BaselinePriority  = $BASELINE_PROFILE_PRIORITY
                    PolicyLinkType    = 'filteringPolicyLink'
                    PolicyRules       = @($policy.policyRules)
                }
                $linkedProfiles = Find-ZtProfilesLinkedToPolicy @findParams

                # For each linked profile, get the policy link priority and action
                foreach ($linkedProfile in $linkedProfiles) {
                    $filteringProfile = $filteringProfiles | Where-Object { $_.id -eq $linkedProfile.ProfileId }
                    if (-not $filteringProfile -or $filteringProfile.state -ne 'enabled') { continue }

                    # Find the policy link and get its priority and action from the expanded policy
                    foreach ($policyLink in $filteringProfile.policies) {
                        if ($policyLink.policy.id -ne $policy.id) { continue }

                        # Skip disabled policy links
                        if ($policyLink.state -ne 'enabled') {
                            Write-PSFMessage "Skipping disabled policy link in profile '$($filteringProfile.name)' for policy '$($policy.name)'" -Level Verbose
                            continue
                        }

                        $linkPriority = try { [int]$policyLink.priority } catch { [int]::MaxValue }

                        # Get action from the expanded policy object
                        $linkAction = if ($policyLink.policy.action) {
                            $policyLink.policy.action.ToString().ToLower()
                        }
                        else {
                            # Default to block for WCF policies
                            'block'
                        }

                        $profileCandidates += [PSCustomObject]@{
                            ProfileId      = $linkedProfile.ProfileId
                            ProfileName    = $linkedProfile.ProfileName
                            ProfilePriority= $linkedProfile.ProfilePriority
                            IsBaseline     = ($linkedProfile.ProfileType -eq 'Baseline Profile')
                            PolicyAction   = $linkAction
                            PolicyPriority = $linkPriority
                            PassesCriteria = $linkedProfile.PassesCriteria
                        }
                    }
                }
            }

            # Sort by profile priority, then policy priority
            $profileCandidates = @($profileCandidates | Sort-Object ProfilePriority, PolicyPriority)

            # Find effective profile per spec logic
            $effectiveProfileName = 'None'
            $caEnforced = 'N/A'  # Default to N/A when no profile found
            $status = 'Not blocked'

            foreach ($pc in $profileCandidates) {
                if ($pc.IsBaseline) {
                    # Baseline profile is always effective
                    $effectiveProfileName = $pc.ProfileName
                    $caEnforced = 'N/A'
                    $status = if ($pc.PolicyAction -eq 'block') { 'Blocked' } else { 'Not blocked' }
                    break
                }
                else {
                    # Security profile - check if it passes CA enforcement criteria
                    if ($pc.PassesCriteria) {
                        $effectiveProfileName = $pc.ProfileName
                        $caEnforced = 'Yes'
                        $status = if ($pc.PolicyAction -eq 'block') { 'Blocked' } else { 'Not blocked' }
                        break
                    }
                }
            }

            $categoryResults += [PSCustomObject]@{
                Category   = $catDisplay
                EnforcedBy = $effectiveProfileName
                CAEnforced = $caEnforced
                Status     = $status
            }
        }

        # Determine pass/fail
        $blockedCount = @($categoryResults | Where-Object { $_.Status -eq 'Blocked' }).Count
        $notBlockedCount = $requiredCategories.Count - $blockedCount
        $passed = $blockedCount -eq $requiredCategories.Count
    }


    #endregion Assessment Logic

    #region Report Generation
    # Only generate full report if we have category results
    if ($errorMsg) {
        # Error message already set, no table needed
    }
    else {
        if ($passed) {
            $testResultMarkdown = "✅ High-risk web content filtering categories (Criminal activity, Hacking, Illegal software) are blocked across enabled security profiles, protecting users from liability risks and malicious content.`n`n%TestResult%"
        }
        else {
            $testResultMarkdown = "❌ One or more high-risk web content filtering categories (Criminal activity, Hacking, Illegal software) are not blocked. Configure web content filtering policies to block these Liability categories to protect against security risks and policy violations.`n`n%TestResult%"
        }

        $reportTitle = 'Web Content Filtering – Category block status'
        $portalLink = 'https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/WebFilteringPolicy.ReactView'

        # Build table with exactly 4 columns as per spec
        $tableRows = ''
        foreach ($row in $categoryResults) {
            $categoryName = Get-SafeMarkdown -Text $row.Category
            $enforcedBy = Get-SafeMarkdown -Text $row.EnforcedBy
            $statusIcon = if ($row.Status -eq 'Blocked') { '✅ Blocked' } else { '❌ Not blocked' }

            $tableRows += "| $categoryName | $enforcedBy | $($row.CAEnforced) | $statusIcon |`n"
        }

        $formatTemplate = @'
 
## [{0}]({1})
 
| Category | Enforced by | CA enforced | Status |
| :------- | :---------- | :---------- | :----- |
{2}
 
**Summary:**
- Total required categories: {3}
- Categories blocked: {4}
- Categories not blocked: {5}
'@


        $mdInfo = $formatTemplate -f $reportTitle, $portalLink, $tableRows, $requiredCategories.Count, $blockedCount, $notBlockedCount
        $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    }
    #endregion Report Generation

    $params = @{
        TestId = '27000'
        Title  = 'High-risk WCF categories (Criminal activity, Hacking, Illegal software) are blocked'
        Status = $passed
        Result = $testResultMarkdown
    }

    Add-ZtTestResultDetail @params
}