SHELL/5.3.2.ps1

$CheckId = "5.3.2"
$Title = "Ensure 'Access reviews' for Guest Users are configured"
$Level = "L1"
$BenchmarkType = "Automated"
$Uri = "https://graph.microsoft.com/v1.0/identityGovernance/accessReviews/definitions"

function Get-PropValue {
    param(
        [AllowNull()]$Object,
        [string]$Name
    )

    if ($null -eq $Object) {
        return $null
    }

    if ($Object -is [hashtable]) {
        foreach ($Key in $Object.Keys) {
            if ([string]$Key -ieq $Name) {
                return $Object[$Key]
            }
        }
    }

    if ($Object.PSObject -and $Object.PSObject.Properties) {
        foreach ($Property in $Object.PSObject.Properties) {
            if ([string]$Property.Name -ieq $Name) {
                return $Property.Value
            }
        }
    }

    return $null
}

function Get-GuestScopeQueries {
    param([AllowNull()]$Review)

    $Queries = @()
    $Scope = Get-PropValue -Object $Review -Name "scope"
    if ($Scope) {
        $DirectScopeQuery = Get-PropValue -Object $Scope -Name "query"
        if ($DirectScopeQuery) {
            $Queries += [string]$DirectScopeQuery
        }

        $PrincipalScopes = Get-PropValue -Object $Scope -Name "principalScopes"
        foreach ($PrincipalScope in @($PrincipalScopes)) {
            $PrincipalQuery = Get-PropValue -Object $PrincipalScope -Name "query"
            if ($PrincipalQuery) {
                $Queries += [string]$PrincipalQuery
            }
        }
    }

    return @($Queries | Where-Object { $_ })
}

try {
    $Response = Invoke-MgGraphRequest -Uri $Uri -Method GET -ErrorAction Stop
    $Definitions = @()

    if ($Response) {
        $Definitions = @(Get-PropValue -Object $Response -Name "value")
    }

    if ($Definitions.Count -eq 0) {
        [pscustomobject]@{
            CheckId = $CheckId
            Title = $Title
            Level = $Level
            BenchmarkType = $BenchmarkType
            Status = "FAIL"
            Pass = $false
            Evidence = [pscustomobject]@{
                ReviewCount = 0
                GuestReviewCount = 0
                CompliantGuestReviewCount = 0
                AccessReviewReport = @()
                SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
            }
            Error = "No access review definitions were returned."
            Timestamp = Get-Date
        }
        return
    }

    $GuestReviews = @()
    foreach ($Review in $Definitions) {
        $Queries = Get-GuestScopeQueries -Review $Review
        if (($Queries -join " || ") -match "(?i)userType\s+eq\s+'Guest'|guest") {
            $GuestReviews += $Review
        }
    }

    $AccessReviewReport = foreach ($Review in $GuestReviews) {
        $Settings = Get-PropValue -Object $Review -Name "settings"
        $Recurrence = Get-PropValue -Object $Settings -Name "recurrence"
        $Pattern = Get-PropValue -Object $Recurrence -Name "pattern"
        $RecurrenceType = [string](Get-PropValue -Object $Pattern -Name "type")

        $Status = [string](Get-PropValue -Object $Review -Name "status")
        $Mail = [bool](Get-PropValue -Object $Settings -Name "mailNotificationsEnabled")
        $Reminder = [bool](Get-PropValue -Object $Settings -Name "reminderNotificationsEnabled")
        $Justification = [bool](Get-PropValue -Object $Settings -Name "justificationRequiredOnApproval")
        $AutoApply = [bool](Get-PropValue -Object $Settings -Name "autoApplyDecisionsEnabled")
        $DefaultDecision = [string](Get-PropValue -Object $Settings -Name "defaultDecision")

        $RecurrencePass = $RecurrenceType -eq "absoluteMonthly" -or $RecurrenceType -eq "weekly"
        $IsCISCompliant = $Status -eq "InProgress" -and $Mail -and $Reminder -and $Justification -and $RecurrencePass -and $AutoApply -and ($DefaultDecision -eq "Deny")

        [pscustomobject]@{
            Name = [string](Get-PropValue -Object $Review -Name "displayName")
            Status = $Status
            mailNotificationsEnabled = $Mail
            Reminders = $Reminder
            justificationRequiredOnApproval = $Justification
            Frequency = $RecurrenceType
            autoApplyDecisionsEnabled = $AutoApply
            defaultDecision = $DefaultDecision
            IsCISCompliant = $IsCISCompliant
        }
    }

    $CompliantCount = @($AccessReviewReport | Where-Object { $_.IsCISCompliant }).Count
    $Pass = $CompliantCount -gt 0
    $Status = if ($Pass) { "PASS" } else { "FAIL" }

    if ($GuestReviews.Count -eq 0) {
        $Pass = $false
        $Status = "FAIL"
    }

    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = $Status
        Pass = $Pass
        Evidence = [pscustomobject]@{
            ReviewCount = $Definitions.Count
            GuestReviewCount = $GuestReviews.Count
            CompliantGuestReviewCount = $CompliantCount
            AccessReviewReport = @($AccessReviewReport)
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = if ($Pass) { $null } else { "No guest-user access review matched all CIS-required settings, or no guest-user access review was found." }
        Timestamp = Get-Date
    }
}
catch {
    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = "ERROR"
        Pass = $null
        Evidence = [pscustomobject]@{
            RequestUri = $Uri
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = $_.Exception.Message
        Timestamp = Get-Date
    }
}