Testing/Unit/PowerShell/Providers/AADProvider/AADRiskyPermissionsHelper/Get-ServicePrincipalsWithRiskyPermissions.Tests.ps1

$ModulesPath = "../../../../../../Modules"
$AADRiskyPermissionsHelper = "$($ModulesPath)/Providers/ProviderHelpers/AADRiskyPermissionsHelper.psm1"
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath $AADRiskyPermissionsHelper)

InModuleScope AADRiskyPermissionsHelper {
    Describe "Get-ServicePrincipalsWithRiskyPermissions" {
        $PermissionsModule = "../../../../../../Modules/Permissions/PermissionsHelper.psm1"
        Import-Module (Join-Path -Path $PSScriptRoot -ChildPath $PermissionsModule) -Function Get-CyberAssessmentPermissions
        BeforeAll {
            # Import mock data
            $MockServicePrincipals = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "../RiskyPermissionsSnippets/MockServicePrincipals.json") | ConvertFrom-Json
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'MockServicePrincipalAppRoleAssignments')]
            $MockServicePrincipalAppRoleAssignments = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "../RiskyPermissionsSnippets/MockServicePrincipalAppRoleAssignments.json") | ConvertFrom-Json
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'MockSafePermissions')]
            $MockSafePermissions = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "../RiskyPermissionsSnippets/MockSafePermissions.json") | ConvertFrom-Json
            $MockResourcePermissionCacheJson = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "../RiskyPermissionsSnippets/MockResourcePermissionCache.json") | ConvertFrom-Json
            $MockResourcePermissionCache = @{}
            foreach ($prop in $MockResourcePermissionCacheJson.PSObject.Properties) {
                $MockResourcePermissionCache[$prop.Name] = $prop.Value
            }

            function New-MockMgGraphResponseAppRoleAssignments {
                param (
                    [int] $Size,
                    [array] $MockBody
                )

                $data = @()
                for ($i = 1; $i -le $Size; $i++) {
                    $id = "00000000-0000-0000-0000-0000000000{0:D2}" -f ($i * 10)
                    $mockResponse = @{
                        id     = $id
                        status = 200
                        body   = @{
                            value = $MockBody
                        }
                    }
                    $data += $mockResponse
                }

                return $data
            }

            Mock Get-ServicePrincipalAll { $MockServicePrincipals }

            Mock Invoke-GraphDirectly {
                return @{
                    "value" = $MockServicePrincipals
                    "@odata.context" = "https://graph.microsoft.com/beta/$metadata#ServicePrincipal"
                }
            } -ParameterFilter { $commandlet -eq "Get-MgBetaServicePrincipal" -or $Uri -match "/serviceprincipals" } -ModuleName AADRiskyPermissionsHelper
            function Get-MockMgGraphResponse {
                param (
                    [int] $Size,
                    [array] $MockBody
                )

                $data = @()
                for ($i = 1; $i -le $Size; $i++) {
                    $id = "00000000-0000-0000-0000-0000000000{0:D2}" -f ($i * 10)
                    $mockResponse = @{
                        id     = $id
                        status = 200
                        body   = @{
                            value = $MockBody
                        }
                    }
                    $data += $mockResponse
                }

                return $data
            }

            Mock Invoke-GraphDirectly {
                return $MockResourcePermissionCache
            }
        }

        It "returns a list of service principals with valid properties" {
            $MockAppRoleAssignmentResponses = New-MockMgGraphResponseAppRoleAssignments -Size 5 -MockBody $MockServicePrincipalAppRoleAssignments

            Mock Invoke-MgGraphRequest {
                return @{
                    responses = $MockAppRoleAssignmentResponses
                }
            }

            $RiskySPs = Get-ServicePrincipalsWithRiskyPermissions -M365Environment "gcc" -ResourcePermissionCache $MockResourcePermissionCache
            $RiskySPs | Should -HaveCount 5

            $RiskySPs[0].DisplayName | Should -Match "Test SP 1"
            $RiskySPs[0].ObjectId | Should -Match "00000000-0000-0000-0000-000000000010"
            $RiskySPs[0].AppId | Should -Match "10000000-0000-0000-0000-000000000000"
            $RiskySPs[0].KeyCredentials | Should -HaveCount 1
            $RiskySPs[0].PasswordCredentials | Should -HaveCount 1
            $RiskySPs[0].FederatedCredentials | Should -BeNullOrEmpty
            $RiskySPs[0].Permissions | Should -HaveCount 8

            $RiskySPs[1].DisplayName | Should -Match "Test SP 2"
            $RiskySPs[1].ObjectId | Should -Match "00000000-0000-0000-0000-000000000020"
            $RiskySPs[1].AppId | Should -Match "20000000-0000-0000-0000-000000000000"
            $RiskySPs[1].KeyCredentials | Should -HaveCount 1
            $RiskySPs[1].PasswordCredentials | Should -BeNullOrEmpty
            $RiskySPs[1].FederatedCredentials | Should -BeNullOrEmpty
            $RiskySPs[1].Permissions | Should -HaveCount 8

            $RiskySPs[2].DisplayName | Should -Match "Test SP 3"
            $RiskySPs[2].ObjectId | Should -Match "00000000-0000-0000-0000-000000000030"
            $RiskySPs[2].AppId | Should -Match "40000000-0000-0000-0000-000000000000"
            $RiskySPs[2].KeyCredentials | Should -BeNullOrEmpty
            $RiskySPs[2].PasswordCredentials | Should -BeNullOrEmpty
            $RiskySPs[2].FederatedCredentials | Should -BeNullOrEmpty
            $RiskySPs[2].Permissions | Should -HaveCount 8

            $RiskySPs[3].DisplayName | Should -Match "Test SP 4"
            $RiskySPs[3].ObjectId | Should -Match "00000000-0000-0000-0000-000000000040"
            $RiskySPs[3].AppId | Should -Match "50000000-0000-0000-0000-000000000000"
            $RiskySPs[3].KeyCredentials | Should -BeNullOrEmpty
            $RiskySPs[3].PasswordCredentials | Should -HaveCount 2
            $RiskySPs[3].FederatedCredentials | Should -BeNullOrEmpty
            $RiskySPs[3].Permissions | Should -HaveCount 8

            $RiskySPs[4].DisplayName | Should -Match "Test SP 5"
            $RiskySPs[4].ObjectId | Should -Match "00000000-0000-0000-0000-000000000050"
            $RiskySPs[4].AppId | Should -Match "60000000-0000-0000-0000-000000000000"
            $RiskySPs[4].KeyCredentials | Should -HaveCount 1
            $RiskySPs[4].PasswordCredentials | Should -BeNullOrEmpty
            $RiskySPs[4].FederatedCredentials | Should -BeNullOrEmpty
            $RiskySPs[4].Permissions | Should -HaveCount 8
        }

        It "excludes service principals with no risky permissions" {
            # Set to $SafePermissions instead of $MockServicePrincipalAppRoleAssignments
            # to simulate service principals assigned to safe permissions
            $MockAppRoleAssignmentResponses = New-MockMgGraphResponseAppRoleAssignments -Size 5 -MockBody $MockSafePermissions
            Mock Invoke-MgGraphRequest {
                return @{
                    responses = $MockAppRoleAssignmentResponses
                }
            }

            $RiskySPs = @()
            foreach ($SP in Get-ServicePrincipalsWithRiskyPermissions -M365Environment "gcc" -ResourcePermissionCache $MockResourcePermissionCache) {
                $RiskyPerms = $SP.Permissions | Where-Object { $_.IsRisky }
                if ($RiskyPerms.Count -gt 0) {
                    $RiskySPs += $SP
                }
            }
            $RiskySPs | Should -BeNullOrEmpty
        }

        It "excludes permissions not included in the RiskyPermissions.json mapping" {
            $MockServicePrincipalAppRoleAssignments += $MockSafePermissions
            $MockServicePrincipalAppRoleAssignments | Should -HaveCount 11

            $MockAppRoleAssignmentResponses = New-MockMgGraphResponseAppRoleAssignments -Size 5 -MockBody $MockServicePrincipalAppRoleAssignments
            Mock Invoke-MgGraphRequest {
                return @{
                    responses = $MockAppRoleAssignmentResponses
                }
            }

            $RiskySPs = @()
            foreach ($SP in Get-ServicePrincipalsWithRiskyPermissions -M365Environment "gcc" -ResourcePermissionCache $MockResourcePermissionCache) {
                $RiskyPerms = $SP.Permissions | Where-Object { $_.IsRisky }
                if ($RiskyPerms.Count -gt 0) {
                    $RiskySPs += $SP
                }
            }
            $RiskySPs[0].DisplayName | Should -Match "Test SP 1"
            $RiskySPs[0].ObjectId | Should -Match "00000000-0000-0000-0000-000000000010"
            $RiskySPs[0].AppId | Should -Match "10000000-0000-0000-0000-000000000000"
            $RiskySPs[0].KeyCredentials | Should -HaveCount 1
            $RiskySPs[0].PasswordCredentials | Should -HaveCount 1
            $RiskySPs[0].FederatedCredentials | Should -BeNullOrEmpty
            $RiskySPs[0].Permissions | Where-Object { $_.IsRisky } | Should -HaveCount 8
        }
    }
}

AfterAll {
    Remove-Module AADRiskyPermissionsHelper -Force -ErrorAction 'SilentlyContinue'
}