SHELL/helpers/ca_policy_helpers.ps1

function Get-Root365Value {
    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 Get-Root365PathValue {
    param(
        [AllowNull()][object]$Object,
        [Parameter(Mandatory = $true)][string[]]$Path
    )

    $Current = $Object
    foreach ($Segment in $Path) {
        if ($null -eq $Current) {
            return $null
        }

        $Current = Get-Root365Value -Object $Current -Name $Segment
    }

    return $Current
}

function Convert-Root365StringArray {
    param([AllowNull()]$Value)

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

    $Items = @()

    if ($Value -is [string]) {
        $Items = @($Value)
    }
    elseif ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
        $Items = @($Value)
    }
    else {
        $Items = @($Value)
    }

    $Result = foreach ($Item in $Items) {
        if ($null -eq $Item) {
            continue
        }

        $Text = [string]$Item
        if (-not [string]::IsNullOrWhiteSpace($Text)) {
            $Text.Trim()
        }
    }

    return @($Result | Where-Object { $_ } | Select-Object -Unique)
}

function Convert-Root365NullableBool {
    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|yes|enabled)$') {
        return $true
    }

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

    return $null
}

function Get-Root365CaPoliciesRaw {
    if (-not (Get-Command -Name Invoke-MgGraphRequest -ErrorAction SilentlyContinue)) {
        throw "Invoke-MgGraphRequest cmdlet is unavailable in the current session."
    }

    $Uri = 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies?$top=500'
    $Policies = [System.Collections.Generic.List[object]]::new()

    while (-not [string]::IsNullOrWhiteSpace($Uri)) {
        $Response = Invoke-MgGraphRequest -Uri $Uri -Method GET -ErrorAction Stop
        $Values = @(Get-Root365Value -Object $Response -Name 'value')
        foreach ($Policy in $Values) {
            $Policies.Add($Policy)
        }

        $Uri = [string](Get-Root365Value -Object $Response -Name '@odata.nextLink')
    }

    return @($Policies)
}

function Get-Root365CaPoliciesNormalized {
    $Policies = @(Get-Root365CaPoliciesRaw)
    $Normalized = [System.Collections.Generic.List[object]]::new()

    foreach ($Policy in $Policies) {
        $Users = Get-Root365PathValue -Object $Policy -Path @('conditions', 'users')
        $Applications = Get-Root365PathValue -Object $Policy -Path @('conditions', 'applications')
        $GrantControls = Get-Root365PathValue -Object $Policy -Path @('grantControls')
        $SessionControls = Get-Root365PathValue -Object $Policy -Path @('sessionControls')
        $SignInFrequency = Get-Root365PathValue -Object $SessionControls -Path @('signInFrequency')
        $PersistentBrowser = Get-Root365PathValue -Object $SessionControls -Path @('persistentBrowser')

        $State = [string](Get-Root365Value -Object $Policy -Name 'state')

        $Normalized.Add([pscustomobject]@{
            Id = [string](Get-Root365Value -Object $Policy -Name 'id')
            DisplayName = [string](Get-Root365Value -Object $Policy -Name 'displayName')
            State = $State
            IsEnabled = ($State -eq 'enabled')

            IncludeUsers = Convert-Root365StringArray -Value (Get-Root365Value -Object $Users -Name 'includeUsers')
            ExcludeUsers = Convert-Root365StringArray -Value (Get-Root365Value -Object $Users -Name 'excludeUsers')
            IncludeRoles = Convert-Root365StringArray -Value (Get-Root365Value -Object $Users -Name 'includeRoles')
            ExcludeRoles = Convert-Root365StringArray -Value (Get-Root365Value -Object $Users -Name 'excludeRoles')
            IncludeGroups = Convert-Root365StringArray -Value (Get-Root365Value -Object $Users -Name 'includeGroups')
            ExcludeGroups = Convert-Root365StringArray -Value (Get-Root365Value -Object $Users -Name 'excludeGroups')

            IncludeApplications = Convert-Root365StringArray -Value (Get-Root365Value -Object $Applications -Name 'includeApplications')
            ExcludeApplications = Convert-Root365StringArray -Value (Get-Root365Value -Object $Applications -Name 'excludeApplications')
            IncludeUserActions = Convert-Root365StringArray -Value (Get-Root365Value -Object $Applications -Name 'includeUserActions')

            ClientAppTypes = Convert-Root365StringArray -Value (Get-Root365PathValue -Object $Policy -Path @('conditions', 'clientAppTypes'))
            UserRiskLevels = Convert-Root365StringArray -Value (Get-Root365PathValue -Object $Policy -Path @('conditions', 'userRiskLevels'))
            SignInRiskLevels = Convert-Root365StringArray -Value (Get-Root365PathValue -Object $Policy -Path @('conditions', 'signInRiskLevels'))
            AuthenticationFlowTransferMethods = Convert-Root365StringArray -Value (Get-Root365PathValue -Object $Policy -Path @('conditions', 'authenticationFlows', 'transferMethods'))

            GrantBuiltInControls = Convert-Root365StringArray -Value (Get-Root365Value -Object $GrantControls -Name 'builtInControls')
            GrantOperator = [string](Get-Root365Value -Object $GrantControls -Name 'operator')
            AuthenticationStrengthId = [string](Get-Root365PathValue -Object $GrantControls -Path @('authenticationStrength', 'id'))
            AuthenticationStrengthDisplayName = [string](Get-Root365PathValue -Object $GrantControls -Path @('authenticationStrength', 'displayName'))

            SignInFrequencyEnabled = Convert-Root365NullableBool -Value (Get-Root365Value -Object $SignInFrequency -Name 'isEnabled')
            SignInFrequencyValue = Get-Root365Value -Object $SignInFrequency -Name 'value'
            SignInFrequencyType = [string](Get-Root365Value -Object $SignInFrequency -Name 'type')
            SignInFrequencyInterval = [string](Get-Root365Value -Object $SignInFrequency -Name 'frequencyInterval')

            PersistentBrowserEnabled = Convert-Root365NullableBool -Value (Get-Root365Value -Object $PersistentBrowser -Name 'isEnabled')
            PersistentBrowserMode = [string](Get-Root365Value -Object $PersistentBrowser -Name 'mode')
        })
    }

    return @($Normalized)
}

function Test-Root365ContainsValue {
    param(
        [AllowNull()]$Collection,
        [Parameter(Mandatory = $true)][string]$Expected
    )

    foreach ($Item in @(Convert-Root365StringArray -Value $Collection)) {
        if ([string]$Item -ieq $Expected) {
            return $true
        }
    }

    return $false
}

function Test-Root365ContainsAllValues {
    param(
        [AllowNull()]$Collection,
        [string[]]$ExpectedValues
    )

    foreach ($Expected in @($ExpectedValues)) {
        if (-not (Test-Root365ContainsValue -Collection $Collection -Expected $Expected)) {
            return $false
        }
    }

    return $true
}

function Convert-Root365SignInFrequencyToHours {
    param(
        [AllowNull()]$Value,
        [string]$Type
    )

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

    $Numeric = 0.0
    if (-not [double]::TryParse(([string]$Value), [ref]$Numeric)) {
        return $null
    }

    switch (($Type | ForEach-Object { $_.ToLowerInvariant() })) {
        'hours' { return $Numeric }
        'days' { return ($Numeric * 24) }
        default { return $null }
    }
}

function Test-Root365SignInFrequencyEveryTime {
    param([Parameter(Mandatory = $true)][object]$Policy)

    if ($Policy.SignInFrequencyEnabled -ne $true) {
        return $false
    }

    if (($Policy.SignInFrequencyInterval | ForEach-Object { $_.ToLowerInvariant() }) -eq 'everytime') {
        return $true
    }

    $Hours = Convert-Root365SignInFrequencyToHours -Value $Policy.SignInFrequencyValue -Type $Policy.SignInFrequencyType
    if ($null -ne $Hours -and $Hours -le 1) {
        return $true
    }

    return $false
}

function Get-Root365CaPolicyEvidenceSummary {
    param([AllowNull()]$Policies)

    return @(
        @($Policies) |
            Select-Object Id, DisplayName, State,
                IncludeUsers, ExcludeUsers, IncludeRoles, ExcludeRoles,
                IncludeApplications, ExcludeApplications, IncludeUserActions,
                ClientAppTypes, UserRiskLevels, SignInRiskLevels, AuthenticationFlowTransferMethods,
                GrantBuiltInControls, GrantOperator, AuthenticationStrengthDisplayName,
                SignInFrequencyEnabled, SignInFrequencyValue, SignInFrequencyType, SignInFrequencyInterval,
                PersistentBrowserEnabled, PersistentBrowserMode
    )
}