tests/Test-Assessment.21868.ps1

<#
.SYNOPSIS
 
#>


function Test-Assessment-21868 {
    [ZtTest(
    Category = 'External collaboration',
    ImplementationCost = 'Medium',
    MinimumLicense = ('Free'),
    Pillar = 'Identity',
    RiskLevel = 'Medium',
    SfiPillar = 'Protect tenants and isolate production systems',
    TenantType = ('Workforce', 'External'),
    TestId = 21868,
    Title = 'Guests don''t own apps in the tenant',
    UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param(
        $Database
    )

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

    $activity = "Checking Guests don't own apps in the tenant"
    Write-ZtProgress -Activity $activity -Status "Getting applications and service principals"

    $sqlAppOwners = @'
with applicationOwners as (
    select
        id as appObjectId,
        appId,
        displayName as appDisplayName,
        unnest(from_json(
            case
                when json_type(owners) = 'ARRAY' then owners
                else json_array(owners)
            end,
            '[{"id":"VARCHAR"}]'
        )).id as ownerId
    from Application
    where owners is not null
)
select
    users.id,
    users.displayName,
    users.userPrincipalName,
    applicationOwners.appDisplayName,
    applicationOwners.appObjectId,
    applicationOwners.appId
from applicationOwners
inner join User users on users.id = applicationOwners.ownerId
where users.userType = 'Guest'
'@


    $sqlSpOwners = @'
with servicePrincipalOwners as (
    select
        id as spObjectId,
        appId as spAppId,
        displayName as spDisplayName,
        unnest(from_json(
            case
                when json_type(owners) = 'ARRAY' then owners
                else json_array(owners)
            end,
            '[{"id":"VARCHAR"}]'
        )).id as ownerId
    from ServicePrincipal
    where owners is not null
)
select
    users.id,
    users.displayName,
    users.userPrincipalName,
    servicePrincipalOwners.spDisplayName,
    servicePrincipalOwners.spObjectId,
    servicePrincipalOwners.spAppId
from servicePrincipalOwners
inner join User users on users.id = servicePrincipalOwners.ownerId
where users.userType = 'Guest'
'@


    $guestAppOwners = @(Invoke-DatabaseQuery -Database $Database -Sql $sqlAppOwners)
    $guestSpOwners = @(Invoke-DatabaseQuery -Database $Database -Sql $sqlSpOwners)
    #endregion Data Collection

    #region Assessment Logic
    $hasGuestAppOwners = $guestAppOwners.Count -gt 0
    $hasGuestSpOwners = $guestSpOwners.Count -gt 0

    if ($hasGuestAppOwners -or $hasGuestSpOwners) {
        $passed = $false
        $testResultMarkdown = "Guest users own applications or service principals.`n`n%TestResult%"
    }
    else {
        $passed = $true
        $testResultMarkdown = "No guest users own any applications or service principals in the tenant."
    }
    #endregion Assessment Logic

    #region Report Generation
    if (-not $passed) {
        $appPortalLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Owners/appId/{0}/isMSAApp~/false'
        $spPortalLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Owners/objectId/{0}/appId/{1}/preferredSingleSignOnMode~/null/servicePrincipalType/Application/fromNav/'
        $reportSections = [System.Collections.Generic.List[string]]::new()

        if ($hasGuestAppOwners) {
            $appRows = ($guestAppOwners | ForEach-Object {
                $ownerDisplayName = Get-SafeMarkdown $_.displayName
                $ownerUpn = Get-SafeMarkdown $_.userPrincipalName
                $applicationName = Get-SafeMarkdown $_.appDisplayName
                $applicationLink = $appPortalLink -f $_.appId

                "| $ownerDisplayName | $ownerUpn | [$applicationName]($applicationLink) |"
            }) -join "`n"
            $reportSections.Add(@"
### Applications owned by guest users
| User Display Name | User Principal Name | Application |
| :---------------- | :------------------ | :---------- |
$appRows
"@
)
        }

        if ($hasGuestSpOwners) {
            $spRows = ($guestSpOwners | ForEach-Object {
                $ownerDisplayName = Get-SafeMarkdown $_.displayName
                $ownerUpn = Get-SafeMarkdown $_.userPrincipalName
                $servicePrincipalName = Get-SafeMarkdown $_.spDisplayName
                $servicePrincipalLink = $spPortalLink -f $_.spObjectId, $_.spAppId

                "| $ownerDisplayName | $ownerUpn | [$servicePrincipalName]($servicePrincipalLink) |"
            }) -join "`n"
            $reportSections.Add(@"
### Service principals owned by guest users
| User Display Name | User Principal Name | Service Principal |
| :---------------- | :------------------ | :---------------- |
$spRows
"@
)
        }

        $reportTitle = if ($hasGuestAppOwners -and $hasGuestSpOwners) {
            'Guest users own both applications and service principals in your tenant'
        }
        elseif ($hasGuestAppOwners) {
            'Guest users own applications in your tenant'
        }
        else {
            'Guest users own service principals in your tenant'
        }

        $mdInfo = "## $reportTitle`n`n$($reportSections -join "`n")"
        $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    }
    #endregion Report Generation

    $params = @{
        TestId = '21868'
        Title  = "Guests don't own apps in the tenant"
        Status = $passed
        Result = $testResultMarkdown
    }

    Add-ZtTestResultDetail @params
}