Private/Entra/Checks/Invoke-EntraCAChecks.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Invoke-EntraCAChecks {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$AuditData
    )

    $checkDefs = Get-AuditCategoryDefinitions -Category 'EntraCAChecks'
    $findings = [System.Collections.Generic.List[PSCustomObject]]::new()

    foreach ($check in $checkDefs.checks) {
        $funcName = "Test-Infiltration$($check.id -replace '-', '')"
        if (Get-Command $funcName -ErrorAction SilentlyContinue) {
            try {
                $finding = & $funcName -AuditData $AuditData -CheckDefinition $check
                if ($finding) { $findings.Add($finding) }
            } catch {
                $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' `
                    -CurrentValue "Check failed: $_"))
            }
        } else {
            $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' `
                -CurrentValue 'Check not yet implemented'))
        }
    }

    return @($findings)
}

# ── EIDCA-001: Full CA Policy Inventory ───────────────────────────────────
function Test-InfiltrationEIDCA001 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = $AuditData.ConditionalAccess.Policies
    if (-not $policies -or $policies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'No Conditional Access policies found' `
            -Details @{ PolicyCount = 0 }
    }

    $enabled = @($policies | Where-Object { $_.state -eq 'enabled' }).Count
    $reportOnly = @($policies | Where-Object { $_.state -eq 'enabledForReportingButNotEnforced' }).Count
    $disabled = @($policies | Where-Object { $_.state -eq 'disabled' }).Count

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "$($policies.Count) policies: $enabled enabled, $reportOnly report-only, $disabled disabled" `
        -Details @{
            TotalPolicies = $policies.Count
            Enabled       = $enabled
            ReportOnly    = $reportOnly
            Disabled      = $disabled
            Policies      = @($policies | ForEach-Object {
                @{
                    Id          = $_.id
                    DisplayName = $_.displayName
                    State       = $_.state
                    CreatedDateTime = $_.createdDateTime
                    ModifiedDateTime = $_.modifiedDateTime
                }
            })
        }
}

# ── EIDCA-002: CA Coverage Gap Analysis ───────────────────────────────────
function Test-InfiltrationEIDCA002 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })
    if ($policies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No enabled CA policies found — all users and apps are unprotected'
    }

    # Check if "All users" is targeted by at least one MFA policy
    $allUsersPolicies = @($policies | Where-Object {
        $_.conditions.users.includeUsers -contains 'All'
    })

    # Check if "All cloud apps" is targeted
    $allAppsPolicies = @($policies | Where-Object {
        $_.conditions.applications.includeApplications -contains 'All'
    })

    # Check for MFA requirement
    $mfaPolicies = @($policies | Where-Object {
        $_.grantControls.builtInControls -contains 'mfa' -or
        $_.grantControls.authenticationStrength -ne $null
    })

    $hasAllUsersMfa = @($mfaPolicies | Where-Object {
        $_.conditions.users.includeUsers -contains 'All'
    }).Count -gt 0

    $gaps = [System.Collections.Generic.List[string]]::new()
    if ($allUsersPolicies.Count -eq 0) { $gaps.Add('No policy targets All Users') }
    if ($allAppsPolicies.Count -eq 0) { $gaps.Add('No policy targets All Cloud Apps') }
    if (-not $hasAllUsersMfa) { $gaps.Add('No MFA policy covers All Users') }

    $status = if ($gaps.Count -eq 0) { 'PASS' }
              elseif ($gaps.Count -le 1) { 'WARN' }
              else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "Coverage gaps: $($gaps.Count). $($gaps -join '; ')" `
        -Details @{
            GapCount          = $gaps.Count
            Gaps              = @($gaps)
            AllUsersPolicies  = $allUsersPolicies.Count
            AllAppsPolicies   = $allAppsPolicies.Count
            MfaPolicies       = $mfaPolicies.Count
            HasAllUsersMfa    = $hasAllUsersMfa
        }
}

# ── EIDCA-003: Report-Only Policies ──────────────────────────────────────
function Test-InfiltrationEIDCA003 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = $AuditData.ConditionalAccess.Policies
    $reportOnly = @($policies | Where-Object { $_.state -eq 'enabledForReportingButNotEnforced' })

    if ($reportOnly.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue 'No policies in report-only mode'
    }

    $status = if ($reportOnly.Count -le 2) { 'WARN' } else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($reportOnly.Count) policies in report-only mode should be reviewed for enforcement" `
        -Details @{
            ReportOnlyCount = $reportOnly.Count
            Policies        = @($reportOnly | ForEach-Object {
                @{ Id = $_.id; DisplayName = $_.displayName; CreatedDateTime = $_.createdDateTime }
            })
        }
}

# ── EIDCA-004: CA Exclusion Group Analysis ────────────────────────────────
function Test-InfiltrationEIDCA004 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })
    $exclusionGroups = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.List[string]]]::new()

    foreach ($policy in $policies) {
        $excludedGroups = @($policy.conditions.users.excludeGroups | Where-Object { $_ })
        $excludedUsers = @($policy.conditions.users.excludeUsers | Where-Object { $_ -and $_ -ne 'GuestsOrExternalUsers' })

        foreach ($groupId in $excludedGroups) {
            if (-not $exclusionGroups.ContainsKey($groupId)) {
                $exclusionGroups[$groupId] = [System.Collections.Generic.List[string]]::new()
            }
            $exclusionGroups[$groupId].Add($policy.displayName)
        }
    }

    if ($exclusionGroups.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue 'No group-based CA exclusions found'
    }

    # Groups excluded from many policies are higher risk
    $highRiskExclusions = @($exclusionGroups.GetEnumerator() | Where-Object { $_.Value.Count -ge 3 })

    $status = if ($highRiskExclusions.Count -gt 0) { 'FAIL' }
              elseif ($exclusionGroups.Count -gt 5) { 'WARN' }
              else { 'PASS' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($exclusionGroups.Count) exclusion groups found, $($highRiskExclusions.Count) excluded from 3+ policies" `
        -Details @{
            ExclusionGroupCount  = $exclusionGroups.Count
            HighRiskCount        = $highRiskExclusions.Count
            ExclusionGroups      = @($exclusionGroups.GetEnumerator() | ForEach-Object {
                @{
                    GroupId       = $_.Key
                    PolicyCount   = $_.Value.Count
                    PolicyNames   = @($_.Value)
                }
            })
        }
}

# ── EIDCA-005: Unprotected Exclusion Groups ──────────────────────────────
function Test-InfiltrationEIDCA005 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    # This check requires group membership data which may not be available
    # in the initial data collection. Flag exclusion groups for review.
    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $allExcludedGroups = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    foreach ($policy in $policies) {
        foreach ($groupId in @($policy.conditions.users.excludeGroups | Where-Object { $_ })) {
            [void]$allExcludedGroups.Add($groupId)
        }
    }

    if ($allExcludedGroups.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue 'No groups used in CA exclusions'
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
        -CurrentValue "$($allExcludedGroups.Count) groups used in CA exclusions — review for excessive membership and owner controls" `
        -Details @{
            ExcludedGroupIds = @($allExcludedGroups)
            ReviewRequired   = $true
        }
}

# ── EIDCA-006: Break-Glass Account CA Exclusion Validation ───────────────
function Test-InfiltrationEIDCA006 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })
    if ($policies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'No enabled CA policies to validate'
    }

    # Look for break-glass patterns in excluded users
    # Break-glass accounts are typically excluded from all or most CA policies
    $allExcludedUsers = [System.Collections.Generic.Dictionary[string, int]]::new()

    foreach ($policy in $policies) {
        foreach ($userId in @($policy.conditions.users.excludeUsers | Where-Object { $_ -and $_ -ne 'GuestsOrExternalUsers' })) {
            if ($allExcludedUsers.ContainsKey($userId)) {
                $allExcludedUsers[$userId]++
            } else {
                $allExcludedUsers[$userId] = 1
            }
        }
    }

    # Accounts excluded from most policies are likely break-glass
    $threshold = [Math]::Max(1, [int]($policies.Count * 0.7))
    $potentialBreakGlass = @($allExcludedUsers.GetEnumerator() | Where-Object { $_.Value -ge $threshold })

    if ($potentialBreakGlass.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'No accounts found excluded from most CA policies — verify break-glass accounts exist and are properly excluded' `
            -Details @{ PotentialBreakGlassCount = 0 }
    }

    $status = if ($potentialBreakGlass.Count -ge 2) { 'PASS' } else { 'WARN' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($potentialBreakGlass.Count) potential break-glass accounts detected (excluded from $threshold+ of $($policies.Count) policies)" `
        -Details @{
            PotentialBreakGlassCount = $potentialBreakGlass.Count
            Accounts                 = @($potentialBreakGlass | ForEach-Object {
                @{ UserId = $_.Key; ExcludedFromPolicies = $_.Value; TotalPolicies = $policies.Count }
            })
        }
}

# ── EIDCA-007: MFA Enforcement Coverage ──────────────────────────────────
function Test-InfiltrationEIDCA007 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $mfaPolicies = @($policies | Where-Object {
        $_.grantControls.builtInControls -contains 'mfa' -or
        $_.grantControls.authenticationStrength -ne $null
    })

    if ($mfaPolicies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No CA policies require MFA' `
            -Details @{ MfaPolicyCount = 0 }
    }

    # Check if MFA is required for all users on all cloud apps
    $universalMfa = @($mfaPolicies | Where-Object {
        $_.conditions.users.includeUsers -contains 'All' -and
        $_.conditions.applications.includeApplications -contains 'All'
    })

    $status = if ($universalMfa.Count -gt 0) { 'PASS' }
              elseif ($mfaPolicies.Count -gt 0) { 'WARN' }
              else { 'FAIL' }

    $currentValue = if ($universalMfa.Count -gt 0) {
        "MFA enforced for all users on all apps via $($universalMfa.Count) policy(ies)"
    } else {
        "$($mfaPolicies.Count) MFA policies found but none cover all users + all apps"
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue $currentValue `
        -Details @{
            MfaPolicyCount   = $mfaPolicies.Count
            UniversalMfa     = $universalMfa.Count -gt 0
            MfaPolicies      = @($mfaPolicies | ForEach-Object {
                @{
                    Id              = $_.id
                    DisplayName     = $_.displayName
                    IncludeUsers    = @($_.conditions.users.includeUsers)
                    IncludeApps     = @($_.conditions.applications.includeApplications)
                    GrantControls   = @($_.grantControls.builtInControls)
                }
            })
        }
}

# ── EIDCA-008: Legacy Authentication Blocking ────────────────────────────
function Test-InfiltrationEIDCA008 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    # Look for policies that block legacy auth
    $legacyAuthBlockPolicies = @($policies | Where-Object {
        ($_.conditions.clientAppTypes -contains 'exchangeActiveSync' -or
         $_.conditions.clientAppTypes -contains 'other') -and
        $_.grantControls.builtInControls -contains 'block'
    })

    # Also check for policies using the newer client app filter
    $legacyAuthBlockPolicies += @($policies | Where-Object {
        $_.conditions.clientAppTypes -contains 'exchangeActiveSync' -and
        $_.grantControls.operator -eq 'OR' -and
        $_.grantControls.builtInControls -contains 'block'
    })

    $legacyAuthBlockPolicies = @($legacyAuthBlockPolicies | Select-Object -Unique -Property id)

    if ($legacyAuthBlockPolicies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No CA policy blocks legacy authentication protocols' `
            -Details @{ BlockPolicyCount = 0 }
    }

    # Check if blocking covers all users
    $universalBlock = @($policies | Where-Object {
        ($_.conditions.clientAppTypes -contains 'exchangeActiveSync' -or
         $_.conditions.clientAppTypes -contains 'other') -and
        $_.grantControls.builtInControls -contains 'block' -and
        $_.conditions.users.includeUsers -contains 'All'
    })

    $status = if ($universalBlock.Count -gt 0) { 'PASS' } else { 'WARN' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($legacyAuthBlockPolicies.Count) legacy auth blocking policies found" `
        -Details @{
            BlockPolicyCount = $legacyAuthBlockPolicies.Count
            CoversAllUsers   = $universalBlock.Count -gt 0
        }
}

# ── EIDCA-009: Device Compliance Requirement ─────────────────────────────
function Test-InfiltrationEIDCA009 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $compliancePolicies = @($policies | Where-Object {
        $_.grantControls.builtInControls -contains 'compliantDevice' -or
        $_.grantControls.builtInControls -contains 'domainJoinedDevice'
    })

    if ($compliancePolicies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No CA policy requires device compliance or domain join'
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "$($compliancePolicies.Count) CA policies require device compliance" `
        -Details @{
            CompliancePolicyCount = $compliancePolicies.Count
            Policies              = @($compliancePolicies | ForEach-Object {
                @{ Id = $_.id; DisplayName = $_.displayName; GrantControls = @($_.grantControls.builtInControls) }
            })
        }
}

# ── EIDCA-010: Location-Based Policies ───────────────────────────────────
function Test-InfiltrationEIDCA010 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $locationPolicies = @($policies | Where-Object {
        $_.conditions.locations -ne $null -and
        ($_.conditions.locations.includeLocations -or $_.conditions.locations.excludeLocations)
    })

    $status = if ($locationPolicies.Count -gt 0) { 'PASS' } else { 'WARN' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($locationPolicies.Count) CA policies use location-based conditions" `
        -Details @{
            LocationPolicyCount = $locationPolicies.Count
            Policies            = @($locationPolicies | ForEach-Object {
                @{
                    Id               = $_.id
                    DisplayName      = $_.displayName
                    IncludeLocations = @($_.conditions.locations.includeLocations)
                    ExcludeLocations = @($_.conditions.locations.excludeLocations)
                }
            })
        }
}

# ── EIDCA-011: Named Locations Review ────────────────────────────────────
function Test-InfiltrationEIDCA011 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $namedLocations = $AuditData.ConditionalAccess.NamedLocations
    if (-not $namedLocations -or $namedLocations.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'No named locations configured — location-based CA policies cannot be applied'
    }

    $ipLocations = @($namedLocations | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.ipNamedLocation' })
    $countryLocations = @($namedLocations | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.countryNamedLocation' })
    $trustedLocations = @($namedLocations | Where-Object { $_.isTrusted -eq $true })

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "$($namedLocations.Count) named locations: $($ipLocations.Count) IP-based, $($countryLocations.Count) country-based, $($trustedLocations.Count) trusted" `
        -Details @{
            TotalLocations    = $namedLocations.Count
            IpLocations       = $ipLocations.Count
            CountryLocations  = $countryLocations.Count
            TrustedLocations  = $trustedLocations.Count
            Locations         = @($namedLocations | ForEach-Object {
                @{
                    Id          = $_.id
                    DisplayName = $_.displayName
                    Type        = $_.'@odata.type'
                    IsTrusted   = $_.isTrusted
                }
            })
        }
}

# ── EIDCA-012: Sign-in Risk-Based Policies ───────────────────────────────
function Test-InfiltrationEIDCA012 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $riskPolicies = @($policies | Where-Object {
        $_.conditions.signInRiskLevels -and $_.conditions.signInRiskLevels.Count -gt 0
    })

    if ($riskPolicies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No CA policies configured for sign-in risk levels'
    }

    $riskLevels = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    foreach ($policy in $riskPolicies) {
        foreach ($level in $policy.conditions.signInRiskLevels) {
            [void]$riskLevels.Add($level)
        }
    }

    $status = if ($riskLevels.Contains('high') -and $riskLevels.Contains('medium')) { 'PASS' }
              elseif ($riskLevels.Contains('high')) { 'WARN' }
              else { 'WARN' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($riskPolicies.Count) sign-in risk policies covering levels: $($riskLevels -join ', ')" `
        -Details @{
            RiskPolicyCount = $riskPolicies.Count
            CoveredLevels   = @($riskLevels)
        }
}

# ── EIDCA-013: User Risk-Based Policies ──────────────────────────────────
function Test-InfiltrationEIDCA013 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $riskPolicies = @($policies | Where-Object {
        $_.conditions.userRiskLevels -and $_.conditions.userRiskLevels.Count -gt 0
    })

    if ($riskPolicies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No CA policies configured for user risk levels'
    }

    $riskLevels = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    foreach ($policy in $riskPolicies) {
        foreach ($level in $policy.conditions.userRiskLevels) {
            [void]$riskLevels.Add($level)
        }
    }

    $status = if ($riskLevels.Contains('high')) { 'PASS' } else { 'WARN' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($riskPolicies.Count) user risk policies covering levels: $($riskLevels -join ', ')" `
        -Details @{
            RiskPolicyCount = $riskPolicies.Count
            CoveredLevels   = @($riskLevels)
        }
}

# ── EIDCA-014: Session Controls Audit ────────────────────────────────────
function Test-InfiltrationEIDCA014 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })

    $sessionPolicies = @($policies | Where-Object {
        $_.sessionControls -ne $null -and
        ($_.sessionControls.signInFrequency -ne $null -or
         $_.sessionControls.persistentBrowser -ne $null -or
         $_.sessionControls.cloudAppSecurity -ne $null -or
         $_.sessionControls.applicationEnforcedRestrictions -ne $null)
    })

    # Check for persistent browser policies (should disable persistence)
    $persistentBrowserPolicies = @($sessionPolicies | Where-Object {
        $_.sessionControls.persistentBrowser.mode -eq 'never'
    })

    # Check for sign-in frequency policies
    $signInFreqPolicies = @($sessionPolicies | Where-Object {
        $_.sessionControls.signInFrequency.isEnabled -eq $true
    })

    $status = if ($sessionPolicies.Count -gt 0) { 'PASS' } else { 'WARN' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($sessionPolicies.Count) session control policies ($($signInFreqPolicies.Count) sign-in frequency, $($persistentBrowserPolicies.Count) persistent browser)" `
        -Details @{
            SessionPolicyCount       = $sessionPolicies.Count
            SignInFrequencyPolicies   = $signInFreqPolicies.Count
            PersistentBrowserPolicies = $persistentBrowserPolicies.Count
        }
}

# ── EIDCA-015: CA What-If Simulation ─────────────────────────────────────
function Test-InfiltrationEIDCA015 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = @($AuditData.ConditionalAccess.Policies | Where-Object { $_.state -eq 'enabled' })
    if ($policies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'No enabled policies for simulation'
    }

    # Simulate common attack scenarios
    $scenarios = @(
        @{
            Name        = 'External attacker with stolen credentials (no MFA)'
            HasMfaBlock = ($policies | Where-Object {
                $_.conditions.users.includeUsers -contains 'All' -and
                $_.grantControls.builtInControls -contains 'mfa'
            }).Count -gt 0
        },
        @{
            Name        = 'Legacy auth protocol abuse'
            HasBlock    = ($policies | Where-Object {
                ($_.conditions.clientAppTypes -contains 'exchangeActiveSync' -or
                 $_.conditions.clientAppTypes -contains 'other') -and
                $_.grantControls.builtInControls -contains 'block'
            }).Count -gt 0
        },
        @{
            Name        = 'Compromised user (high risk sign-in)'
            HasBlock    = ($policies | Where-Object {
                $_.conditions.signInRiskLevels -contains 'high'
            }).Count -gt 0
        },
        @{
            Name        = 'Guest user lateral movement'
            HasBlock    = ($policies | Where-Object {
                $_.conditions.users.includeGuestsOrExternalUsers -ne $null -or
                $_.conditions.users.includeUsers -contains 'GuestsOrExternalUsers'
            }).Count -gt 0
        }
    )

    $protected = @($scenarios | Where-Object { $_.HasMfaBlock -or $_.HasBlock }).Count
    $unprotected = @($scenarios | Where-Object { -not ($_.HasMfaBlock -or $_.HasBlock) })

    $status = if ($protected -eq $scenarios.Count) { 'PASS' }
              elseif ($protected -ge 2) { 'WARN' }
              else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$protected of $($scenarios.Count) common attack scenarios have CA protection" `
        -Details @{
            ScenariosProtected   = $protected
            ScenariosTotal       = $scenarios.Count
            UnprotectedScenarios = @($unprotected | ForEach-Object { $_.Name })
            Scenarios            = @($scenarios | ForEach-Object {
                @{ Name = $_.Name; Protected = [bool]($_.HasMfaBlock -or $_.HasBlock) }
            })
        }
}

# ── EIDCA-016: CA Policy Documentation Export ────────────────────────────
function Test-InfiltrationEIDCA016 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $policies = $AuditData.ConditionalAccess.Policies
    if (-not $policies -or $policies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'No policies to document'
    }

    $export = @($policies | ForEach-Object {
        @{
            Id                  = $_.id
            DisplayName         = $_.displayName
            State               = $_.state
            CreatedDateTime     = $_.createdDateTime
            ModifiedDateTime    = $_.modifiedDateTime
            Conditions          = @{
                Users           = @{
                    IncludeUsers  = @($_.conditions.users.includeUsers)
                    ExcludeUsers  = @($_.conditions.users.excludeUsers)
                    IncludeGroups = @($_.conditions.users.includeGroups)
                    ExcludeGroups = @($_.conditions.users.excludeGroups)
                    IncludeRoles  = @($_.conditions.users.includeRoles)
                }
                Applications    = @{
                    IncludeApplications = @($_.conditions.applications.includeApplications)
                    ExcludeApplications = @($_.conditions.applications.excludeApplications)
                }
                ClientAppTypes  = @($_.conditions.clientAppTypes)
                Locations       = $_.conditions.locations
                Platforms       = $_.conditions.platforms
                SignInRiskLevels = @($_.conditions.signInRiskLevels)
                UserRiskLevels  = @($_.conditions.userRiskLevels)
            }
            GrantControls       = @{
                Operator         = $_.grantControls.operator
                BuiltInControls  = @($_.grantControls.builtInControls)
                CustomControls   = @($_.grantControls.customAuthenticationFactors)
            }
            SessionControls     = $_.sessionControls
        }
    })

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "Exported $($export.Count) CA policy configurations" `
        -Details @{
            PolicyCount = $export.Count
            Export      = $export
        }
}