tests/Test-Assessment.25408.ps1

<#
.SYNOPSIS
    Checks that Global Secure Access web content filtering is enabled and configured
.DESCRIPTION
    Verifies that Web Content Filtering policies are configured and applied either through the Baseline Profile
    or through Security Profiles linked to active Conditional Access policies. This ensures that organizations
    control access to websites based on categories, domains, or URLs to reduce exposure to malicious or
    inappropriate content.
 
.NOTES
    Test ID: 25408
    Category: Global Secure Access
    Required API: networkAccess/filteringPolicies, networkAccess/filteringProfiles, conditionalAccess/policies
#>


function Test-Assessment-25408 {
    [ZtTest(
        Category = 'Global Secure Access',
        ImplementationCost = 'Medium',
        MinimumLicense = ('Entra_Premium_Internet_Access'),
        Pillar = 'Network',
        RiskLevel = 'Medium',
        SfiPillar = 'Protect networks',
        TenantType = ('Workforce','External'),
        TestId = '25408',
        Title = 'Global Secure Access web content filtering is enabled and configured',
        UserImpact = 'Medium'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
    $activity = 'Checking Global Secure Access web content filtering configuration'
    Write-ZtProgress -Activity $activity -Status 'Querying Web Content Filtering policies'

    # Q1: Get all Web Content Filtering policies (excluding "All Websites")
    $allFilteringPolicies = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringPolicies' -ApiVersion beta
    $wcfPolicies = $allFilteringPolicies | Where-Object { $_.name -ne 'All websites' }

    Write-ZtProgress -Activity $activity -Status 'Querying filtering profiles'

    # Q2: Get all filtering profiles with their policies and priority
    $filteringProfilesQueryParams = @{
        '$select' = 'id,name,description,state,version,priority'
        '$expand' = 'policies($select=id,state;$expand=policy($select=id,name,version))'
    }
    $filteringProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringProfiles' -QueryParameters $filteringProfilesQueryParams -ApiVersion beta

    Write-ZtProgress -Activity $activity -Status 'Querying Conditional Access policies'

    # Q3 prep: Get all Conditional Access policies with session controls
    $caPolicies = Get-ZtConditionalAccessPolicy
    #endregion Data Collection

    #region Assessment Logic
    # Initialize test variables
    $testResultMarkdown = ''
    $passed = $false
    $appliedPolicies = @()

    # Check if any Web Content Filtering policies exist (excluding "All Websites")
    if (-not $wcfPolicies -or $wcfPolicies.Count -eq 0) {
        $testResultMarkdown = '❌ Web Content Filtering policy is not configured.'
        $passed = $false
    }
    else {
        # Check if WCF policies are linked to profiles
        foreach ($wcfPolicy in $wcfPolicies) {
            $policyId = $wcfPolicy.id
            $policyName = $wcfPolicy.name
            $policyAction = $wcfPolicy.action

            # Find profiles that have this policy linked
            $linkedProfiles = @()

            foreach ($filteringProfile in $filteringProfiles) {
                # Check if this profile contains the WCF policy
                $policyLink = $filteringProfile.policies | Where-Object {
                    $_.'@odata.type' -eq '#microsoft.graph.networkaccess.filteringPolicyLink' -and
                    $_.policy.id -eq $policyId -and
                    $_.state -eq 'enabled'
                }

                if ($policyLink) {
                    # Determine profile type based on priority
                    $priority = $filteringProfile.priority
                    $profileType = if ($priority -eq 65000) { 'Baseline Profile' } elseif ($null -ne $priority -and $priority -lt 65000) { 'Security Profile' }

                    $profileInfo = [PSCustomObject]@{
                        ProfileId       = $filteringProfile.id
                        ProfileName     = $filteringProfile.name
                        ProfileType     = $profileType
                        ProfileState    = $filteringProfile.state
                        ProfilePriority = $filteringProfile.priority
                        PolicyLinkState = $policyLink.state
                        IsApplied       = $false
                        CAPolicy        = $null
                    }

                    # If Baseline Profile and enabled, it's automatically applied
                    if ($profileType -eq 'Baseline Profile' -and $filteringProfile.state -eq 'enabled') {
                        $profileInfo.IsApplied = $true
                    }
                    # If Security Profile, check if it's linked to an active CA policy
                    elseif ($profileType -eq 'Security Profile' -and $filteringProfile.state -eq 'enabled') {
                        # Step 4: Check for Conditional Access policy linkage
                        $linkedCAPolicies = $caPolicies | Where-Object {
                            $_.state -eq 'enabled' -and
                            $null -ne $_.sessionControls.globalSecureAccessFilteringProfile -and
                            $_.sessionControls.globalSecureAccessFilteringProfile.profileId -eq $filteringProfile.id -and
                            $_.sessionControls.globalSecureAccessFilteringProfile.isEnabled -eq $true
                        }

                        if ($linkedCAPolicies) {
                            $profileInfo.IsApplied = $true
                            $profileInfo.CAPolicy = $linkedCAPolicies
                        }
                    }

                    $linkedProfiles += $profileInfo
                }
            }

            # If this policy is applied through at least one profile, add it to applied policies
            if ($linkedProfiles | Where-Object { $_.IsApplied -eq $true }) {
                $appliedPolicies += [PSCustomObject]@{
                    PolicyId       = $policyId
                    PolicyName     = $policyName
                    PolicyAction   = $policyAction
                    LinkedProfiles = $linkedProfiles
                }
            }
        }

        # Determine pass/fail
        if ($appliedPolicies.Count -gt 0) {
            $passed = $true
            $testResultMarkdown = "✅ Web Content Filtering policy is enabled. `n`n%TestResult%"
        }
        else {
            $passed = $false
            $testResultMarkdown = "❌ Web Content Filtering policy is not applied to users. `n`n%TestResult%"
        }
    }
    #endregion Assessment Logic

    #region Report Generation
    # Build detailed markdown information
    $mdInfo = ''

    if ($wcfPolicies -and $wcfPolicies.Count -gt 0) {
        # Check if there are any applied policies to determine table structure
        if ($appliedPolicies.Count -gt 0) {
            # Add table title for applied policies
            $mdInfo += "### Applied web content filtering policies`n`n"

            # table for applied policies
            $mdInfo += "| Linked profile name | Linked profile priority | Linked policy name | Policy state | Profile state | Policy action | CA policy name | CA policy state |`n"
            $mdInfo += "|---------------------|-------------------------|--------------------|--------------|---------------|---------------|----------------|-----------------|`n"

            foreach ($wcfPolicy in $wcfPolicies | Sort-Object -Property name) {
                $safePolicyName = Get-SafeMarkdown $wcfPolicy.name
                $policyAction = $wcfPolicy.action
                $appliedPolicy = $appliedPolicies | Where-Object { $_.PolicyId -eq $wcfPolicy.id }

                if ($appliedPolicy) {
                    # Get applied profiles for this policy
                    $appliedProfiles = $appliedPolicy.LinkedProfiles | Where-Object { $_.IsApplied -eq $true }

                    foreach ($profileInfo in $appliedProfiles) {
                        $safeProfileName = Get-SafeMarkdown $profileInfo.ProfileName
                        $profilePriority = $profileInfo.ProfilePriority
                        $profileState = $profileInfo.ProfileState
                        $policyLinkState = $profileInfo.PolicyLinkState

                        # Create blade links
                        $profileBladeLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditProfileMenuBlade.MenuView/~/basics/profileId/$($profileInfo.ProfileId)"
                        $profileNameWithLink = "[$safeProfileName]($profileBladeLink)"

                        $policyBladeLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/WebFilteringPolicy.ReactView"
                        $policyNameWithLink = "[$safePolicyName]($policyBladeLink)"

                        # If there are CA policies, create a row for each one
                        if ($profileInfo.CAPolicy -and $profileInfo.CAPolicy.Count -gt 0) {
                            foreach ($caPolicy in $profileInfo.CAPolicy) {
                                $caPolicyPortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($caPolicy.id)"
                                $safeCAPolicyName = Get-SafeMarkdown $caPolicy.displayName
                                $caPolicyNameWithLink = "[$safeCAPolicyName]($caPolicyPortalLink)"
                                $caPolicyState = $caPolicy.state

                                $mdInfo += "| $profileNameWithLink | $profilePriority | $policyNameWithLink | $policyLinkState | $profileState | $policyAction | $caPolicyNameWithLink | $caPolicyState |`n"
                            }
                        }
                        else {
                            # Baseline profile or profile without CA policy
                            $mdInfo += "| $profileNameWithLink | $profilePriority | $policyNameWithLink | $policyLinkState | $profileState | $policyAction | Not applicable | Not applicable |`n"
                        }
                    }
                }
            }
        }
        else {
            # Add table title with blade link for unapplied policies
            $mdInfo += "### [Web content filtering policies](https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/WebFilteringPolicy.ReactView)`n`n"

            # table for unapplied policies
            $mdInfo += "The following web content filtering policies are configured but not applied to users.`n`n"
            $mdInfo += "| Policy name | Policy action |`n"
            $mdInfo += "|-------------|---------------|`n"

            foreach ($wcfPolicy in $wcfPolicies | Sort-Object -Property name) {
                $safePolicyName = Get-SafeMarkdown $wcfPolicy.name
                $policyAction = $wcfPolicy.action
                $mdInfo += "| $safePolicyName | $policyAction |`n"
            }
        }
    }

    # Replace the placeholder with detailed information
    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '25408'
        Title  = 'Global Secure Access web content filtering is enabled and configured'
        Status = $passed
        Result = $testResultMarkdown
    }

    # Add test result details
    Add-ZtTestResultDetail @params
}