Private/Entra/Checks/Invoke-M365SharePointChecks.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-M365SharePointChecks {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$AuditData
    )

    $checkDefs = Get-AuditCategoryDefinitions -Category 'M365SharePointChecks'
    $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)
}

# ── M365SPO-001: External Sharing Settings ───────────────────────────
function Test-InfiltrationM365SPO001 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $sp = $AuditData.M365Services.SharePoint
    if (-not $sp -or $null -eq $sp.SharingCapability) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'SharePoint sharing capability data not available (SPO admin module not connected)'
    }

    $sharingCapability = $sp.SharingCapability

    $status = switch ($sharingCapability) {
        'Disabled'                          { 'PASS' }
        'ExistingExternalUserSharingOnly'   { 'PASS' }
        'ExternalUserSharingOnly'           { 'PASS' }
        'ExternalUserAndGuestSharing'       { 'FAIL' }
        default                             { 'WARN' }
    }

    $description = switch ($sharingCapability) {
        'Disabled'                          { 'External sharing is completely disabled' }
        'ExistingExternalUserSharingOnly'   { 'Sharing restricted to existing external users only' }
        'ExternalUserSharingOnly'           { 'Sharing restricted to authenticated external users only' }
        'ExternalUserAndGuestSharing'       { 'External sharing allows anonymous guest links — consider restricting' }
        default                             { "Unrecognized sharing capability: $sharingCapability" }
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue $description `
        -Details @{
            SharingCapability = $sharingCapability
            Status = $status
        }
}

# ── M365SPO-002: Guest Access Expiration ─────────────────────────────
function Test-InfiltrationM365SPO002 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $sp = $AuditData.M365Services.SharePoint
    if (-not $sp -or $null -eq $sp.ExternalUserExpireInDays) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'SharePoint guest expiration data not available (SPO admin module not connected)'
    }

    $expireDays = $sp.ExternalUserExpireInDays

    $status = if ($expireDays -gt 0 -and $expireDays -le 90) { 'PASS' }
              elseif ($expireDays -gt 90) { 'WARN' }
              else { 'FAIL' }

    $description = if ($expireDays -le 0) {
        'Guest access expiration is NOT configured — external users retain access indefinitely'
    } elseif ($expireDays -le 90) {
        "Guest access expires after $expireDays days (within recommended 90-day window)"
    } else {
        "Guest access expires after $expireDays days (exceeds recommended 90-day maximum)"
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue $description `
        -Details @{
            ExternalUserExpireInDays = $expireDays
            RecommendedMaxDays = 90
        }
}

# ── M365SPO-003: Default Sharing Link Type ───────────────────────────
function Test-InfiltrationM365SPO003 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $sp = $AuditData.M365Services.SharePoint
    if (-not $sp -or $null -eq $sp.DefaultSharingLinkType) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'SharePoint default sharing link type data not available (SPO admin module not connected)'
    }

    $linkType = $sp.DefaultSharingLinkType

    $status = switch ($linkType) {
        'Direct'           { 'PASS' }
        'SpecificPeople'   { 'PASS' }
        'Internal'         { 'WARN' }
        'AnonymousAccess'  { 'FAIL' }
        default            { 'WARN' }
    }

    $description = switch ($linkType) {
        'Direct'           { 'Default sharing link scoped to specific people (most restrictive)' }
        'SpecificPeople'   { 'Default sharing link scoped to specific people' }
        'Internal'         { 'Default sharing link scoped to organization — consider restricting to specific people' }
        'AnonymousAccess'  { 'Default sharing link allows anonymous access — strongly recommend restricting' }
        default            { "Unrecognized default sharing link type: $linkType" }
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue $description `
        -Details @{
            DefaultSharingLinkType = $linkType
        }
}

# ── M365SPO-004: Site Creation Restrictions ──────────────────────────
function Test-InfiltrationM365SPO004 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $sp = $AuditData.M365Services.SharePoint
    if (-not $sp) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'SharePoint tenant data not available (SPO admin module not connected)'
    }

    # Check SelfServiceSiteCreationDisabled or equivalent property
    $siteCreationDisabled = $sp.SelfServiceSiteCreationDisabled
    $siteCreationManagedPath = $sp.SelfServiceSiteCreationManagedPath

    if ($null -eq $siteCreationDisabled) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'Site creation restriction data not available'
    }

    $status = if ($siteCreationDisabled -eq $true) { 'PASS' } else { 'WARN' }

    $description = if ($siteCreationDisabled -eq $true) {
        'Self-service site creation is disabled — only admins can create sites'
    } else {
        'Self-service site creation is enabled — users can create SharePoint sites without admin approval'
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue $description `
        -Details @{
            SelfServiceSiteCreationDisabled = $siteCreationDisabled
            SelfServiceSiteCreationManagedPath = $siteCreationManagedPath
        }
}

# ── M365SPO-005: DLP Policy Configuration ────────────────────────────
function Test-InfiltrationM365SPO005 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $dlpPolicies = $AuditData.M365Services.DlpPolicies
    if (-not $dlpPolicies) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'DLP policy data not available (Purview compliance module not connected)'
    }

    if ($dlpPolicies.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
            -CurrentValue 'No DLP policies configured — sensitive data in SharePoint/OneDrive is unprotected' `
            -Details @{ PolicyCount = 0 }
    }

    # Check for policies that cover SharePoint/OneDrive workloads
    $spoPolicies = @($dlpPolicies | Where-Object {
        $_.Workload -match 'SharePoint' -or
        $_.Workload -match 'OneDriveForBusiness' -or
        $_.Workload -match 'SPO'
    })

    $enabledPolicies = @($dlpPolicies | Where-Object { $_.Mode -eq 'Enable' -or $_.Enabled -eq $true })

    $status = if ($spoPolicies.Count -gt 0 -and $enabledPolicies.Count -gt 0) { 'PASS' }
              elseif ($dlpPolicies.Count -gt 0) { 'WARN' }
              else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($dlpPolicies.Count) DLP policies found ($($spoPolicies.Count) covering SharePoint/OneDrive, $($enabledPolicies.Count) enabled)" `
        -Details @{
            TotalPolicyCount = $dlpPolicies.Count
            SharePointPolicyCount = $spoPolicies.Count
            EnabledPolicyCount = $enabledPolicies.Count
            Policies = @($dlpPolicies | Select-Object -First 20 | ForEach-Object {
                @{
                    Name = $_.Name
                    Mode = $_.Mode
                    Workload = $_.Workload
                    Enabled = $_.Enabled
                }
            })
        }
}