SHELL/helpers/fabric_settings_helpers.ps1

function Get-Root365FabricValue {
    param(
        [AllowNull()][object]$Object,
        [Parameter(Mandatory = $true)][string]$Name
    )

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

    if ($Object -is [System.Collections.IDictionary]) {
        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 Convert-Root365FabricBool {
    param([AllowNull()]$Value)

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

    if ($Value -is [bool]) {
        return [bool]$Value
    }

    $Text = ([string]$Value).Trim()
    if ($Text -match '^(?i:true|1|enabled|yes)$') {
        return $true
    }

    if ($Text -match '^(?i:false|0|disabled|no)$') {
        return $false
    }

    return $null
}

function Convert-Root365SecureTokenToText {
    param([AllowNull()]$Token)

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

    if ($Token -is [string]) {
        return $Token
    }

    if ($Token -is [System.Security.SecureString]) {
        $Ptr = [System.IntPtr]::Zero
        try {
            $Ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token)
            return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($Ptr)
        }
        finally {
            if ($Ptr -ne [System.IntPtr]::Zero) {
                [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Ptr)
            }
        }
    }

    return [string]$Token
}

function Get-Root365FabricToken {
    if (-not [string]::IsNullOrWhiteSpace([string]$env:ROOT365_FABRIC_ACCESS_TOKEN)) {
        return [string]$env:ROOT365_FABRIC_ACCESS_TOKEN
    }

    if (Get-Command -Name Get-AzAccessToken -ErrorAction SilentlyContinue) {
        try {
            $AccessTokenResult = Get-AzAccessToken -ResourceUrl 'https://api.fabric.microsoft.com' -ErrorAction Stop
            $TokenValue = Convert-Root365SecureTokenToText -Token (Get-Root365FabricValue -Object $AccessTokenResult -Name 'Token')
            if (-not [string]::IsNullOrWhiteSpace($TokenValue)) {
                return $TokenValue
            }
        }
        catch {
            # Continue to fallback options.
        }
    }

    if (Get-Command -Name Get-PowerBIAccessToken -ErrorAction SilentlyContinue) {
        try {
            $PowerBIToken = [string](Get-PowerBIAccessToken -AsString -ErrorAction Stop)
            if ($PowerBIToken -match '^(?i)Bearer\s+(?<token>.+)$') {
                return $Matches.token
            }
            if (-not [string]::IsNullOrWhiteSpace($PowerBIToken)) {
                return $PowerBIToken
            }
        }
        catch {
            # Continue to error below.
        }
    }

    throw "Unable to acquire Fabric API token. Use Connect-AzAccount (Az.Accounts) or Connect-PowerBIServiceAccount, or set ROOT365_FABRIC_ACCESS_TOKEN."
}

function Get-Root365FabricTenantSettings {
    if ($global:ROOT365_FabricTenantSettingsCache -and @($global:ROOT365_FabricTenantSettingsCache).Count -gt 0) {
        return @($global:ROOT365_FabricTenantSettingsCache)
    }

    $Token = Get-Root365FabricToken
    $Headers = @{
        Authorization = "Bearer $Token"
        "Content-Type" = "application/json"
    }

    $Response = Invoke-RestMethod -Uri 'https://api.fabric.microsoft.com/v1/admin/tenantsettings' -Method Get -Headers $Headers -ErrorAction Stop
    $TenantSettings = @($Response.tenantSettings)

    if ($TenantSettings.Count -eq 0) {
        throw "Fabric API returned no tenant settings."
    }

    $global:ROOT365_FabricTenantSettingsCache = @($TenantSettings)
    return @($TenantSettings)
}

function Get-Root365FabricSetting {
    param(
        [Parameter(Mandatory = $true)][array]$Settings,
        [Parameter(Mandatory = $true)][string]$SettingName,
        [AllowNull()][string]$Title
    )

    $ByName = @($Settings | Where-Object {
            ([string](Get-Root365FabricValue -Object $_ -Name 'settingName')) -eq $SettingName
        } | Select-Object -First 1)

    if ($ByName.Count -gt 0) {
        return $ByName[0]
    }

    if (-not [string]::IsNullOrWhiteSpace($Title)) {
        $ByTitle = @($Settings | Where-Object {
                ([string](Get-Root365FabricValue -Object $_ -Name 'title')) -eq $Title
            } | Select-Object -First 1)

        if ($ByTitle.Count -gt 0) {
            return $ByTitle[0]
        }
    }

    return $null
}

function Get-Root365FabricEnabledGroups {
    param([AllowNull()]$Setting)

    $GroupsRaw = @(Get-Root365FabricValue -Object $Setting -Name 'enabledSecurityGroups')
    $Groups = foreach ($Group in $GroupsRaw) {
        $Name = [string](Get-Root365FabricValue -Object $Group -Name 'name')
        if (-not [string]::IsNullOrWhiteSpace($Name)) {
            $Name
            continue
        }

        $Id = [string](Get-Root365FabricValue -Object $Group -Name 'id')
        if (-not [string]::IsNullOrWhiteSpace($Id)) {
            $Id
        }
    }

    return @($Groups | Select-Object -Unique)
}

function Invoke-Root365FabricControl {
    param(
        [Parameter(Mandatory = $true)][string]$CheckId,
        [Parameter(Mandatory = $true)][string]$Title,
        [Parameter(Mandatory = $true)][string]$Level,
        [Parameter(Mandatory = $true)][string]$SettingName,
        [AllowNull()][string]$SettingTitle,
        [Parameter(Mandatory = $true)][ValidateSet('Disable', 'RestrictOrDisable', 'Enable')][string]$Mode,
        [Parameter(Mandatory = $true)][ValidateSet('Pass', 'Fail')][string]$MissingSettingBehavior,
        [string]$BenchmarkType = 'Automated',
        [AllowNull()][string[]]$AuditCommands,
        [AllowNull()][string]$ExpectedStateDescription
    )

    try {
        $Settings = @(Get-Root365FabricTenantSettings)
        $Setting = Get-Root365FabricSetting -Settings $Settings -SettingName $SettingName -Title $SettingTitle

        $Pass = $false
        $Status = 'FAIL'
        $ErrorMessage = $null
        $Enabled = $null
        $EnabledGroups = @()

        if ($null -eq $Setting) {
            $Pass = ($MissingSettingBehavior -eq 'Pass')
            $Status = if ($Pass) { 'PASS' } else { 'FAIL' }
            $ErrorMessage = if ($Pass) {
                $null
            }
            else {
                "Setting '$SettingName' was not found in Fabric tenant settings."
            }
        }
        else {
            $Enabled = Convert-Root365FabricBool -Value (Get-Root365FabricValue -Object $Setting -Name 'enabled')
            $EnabledGroups = @(Get-Root365FabricEnabledGroups -Setting $Setting)

            switch ($Mode) {
                'Disable' {
                    $Pass = ($Enabled -eq $false)
                    if (-not $Pass) {
                        $ErrorMessage = "Setting '$SettingName' is enabled; expected disabled."
                    }
                }
                'RestrictOrDisable' {
                    $Pass = ($Enabled -eq $false) -or (($Enabled -eq $true) -and ($EnabledGroups.Count -gt 0))
                    if (-not $Pass) {
                        $ErrorMessage = "Setting '$SettingName' is enabled for the entire organization and is not restricted to security groups."
                    }
                }
                'Enable' {
                    $Pass = ($Enabled -eq $true)
                    if (-not $Pass) {
                        $ErrorMessage = "Setting '$SettingName' is not enabled."
                    }
                }
            }

            $Status = if ($Pass) { 'PASS' } else { 'FAIL' }
        }

        [pscustomobject]@{
            CheckId = $CheckId
            Title = $Title
            Level = $Level
            BenchmarkType = $BenchmarkType
            Status = $Status
            Pass = $Pass
            Evidence = [pscustomobject]@{
                AuditCommands = $AuditCommands
                SettingName = $SettingName
                SettingTitle = $SettingTitle
                SettingFound = ($null -ne $Setting)
                Enabled = $Enabled
                EnabledSecurityGroups = $EnabledGroups
                Mode = $Mode
                MissingSettingBehavior = $MissingSettingBehavior
                ExpectedState = $ExpectedStateDescription
                RetrievedSettingCount = @($Settings).Count
                SourceDocument = 'CIS_Microsoft_365_Foundations_Benchmark_v6.0.1'
            }
            Error = $ErrorMessage
            Timestamp = Get-Date
        }
    }
    catch {
        $ExceptionMessage = [string]$_.Exception.Message
        $IsAuthOrPermissionIssue =
            ($ExceptionMessage -match '(?i)Unable to acquire Fabric API token') -or
            ($ExceptionMessage -match '(?i)forbidden') -or
            ($ExceptionMessage -match '(?i)unauthorized') -or
            ($ExceptionMessage -match '(?i)403')

        [pscustomobject]@{
            CheckId = $CheckId
            Title = $Title
            Level = $Level
            BenchmarkType = $BenchmarkType
            Status = if ($IsAuthOrPermissionIssue) { 'MANUAL_REVIEW' } else { 'ERROR' }
            Pass = $null
            Evidence = [pscustomobject]@{
                AuditCommands = $AuditCommands
                SettingName = $SettingName
                SettingTitle = $SettingTitle
                SourceDocument = 'CIS_Microsoft_365_Foundations_Benchmark_v6.0.1'
            }
            Error = $ExceptionMessage
            Timestamp = Get-Date
        }
    }
}