tests/Test-Assessment.21868.ps1
<# .SYNOPSIS #> function Test-Assessment-21868 { [CmdletBinding()] param( $Database ) 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" $sqlApp = @' select distinct ON (id) id, appId, displayName from Application order by displayName DESC '@ $sqlSP = @' select distinct ON (id) id, appId, displayName from ServicePrincipal order by displayName DESC '@ $allApp = Invoke-DatabaseQuery -Database $Database -Sql $sqlApp $allSP = Invoke-DatabaseQuery -Database $Database -Sql $sqlSP $queryParameters = '$select=id,displayName,userPrincipalName' # Initialize lists for guest owners only $guestAppOwners = [System.Collections.Generic.List[object]]::new() $guestSpOwners = [System.Collections.Generic.List[object]]::new() # Get all guest users first (more efficient than repeated queries) $sqlGuests = @" SELECT id, userPrincipalName, displayName FROM User WHERE userType = 'Guest' "@ $guestUsers = Invoke-DatabaseQuery -Database $Database -Sql $sqlGuests # Create a HashSet for fast lookups $guestUserIds = [System.Collections.Generic.HashSet[string]]::new() foreach ($guest in $guestUsers) { [void]$guestUserIds.Add($guest.id) } # Filter owners to only include guests foreach ($app in $allApp) { $owners = Invoke-ZtGraphRequest -RelativeUri "applications/$($app.id)/owners/microsoft.graph.user?$queryParameters" -ApiVersion 'v1.0' if ($owners) { foreach ($owner in $owners) { $owner | Add-Member -MemberType NoteProperty -Name 'appDisplayName' -Value $app.displayName -Force -PassThru | Add-Member -MemberType NoteProperty -Name 'appObjectId' -Value $app.id -Force -PassThru | Add-Member -MemberType NoteProperty -Name 'appId' -Value $app.appId -Force if ($guestUserIds.Contains($owner.id)) { $guestAppOwners.Add($owner) } } } } foreach ($sp in $allSP) { $owners = Invoke-ZtGraphRequest -RelativeUri "servicePrincipals/$($sp.id)/owners/microsoft.graph.user?$queryParameters" -ApiVersion 'v1.0' if ($owners) { foreach ($owner in $owners) { $owner | Add-Member -MemberType NoteProperty -Name 'spDisplayName' -Value $sp.displayName -Force -PassThru | Add-Member -MemberType NoteProperty -Name 'spObjectId' -Value $sp.id -Force -PassThru | Add-Member -MemberType NoteProperty -Name 'spAppId' -Value $sp.appId -Force if ($guestUserIds.Contains($owner.id)) { $guestSpOwners.Add($owner) } } } } $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." } # Build the detailed sections of the markdown # Create a here-string with format placeholders {0}, {1}, etc. if ($hasGuestAppOwners -and $hasGuestSpOwners) { $formatTemplate = @" ## Guest users own both applications and service principals in your tenant ### Applications owned by guest users | User Display Name | User Principal Name | Application | | :---------------- | :------------------ | :---------- | {0} ### Service principals owned by guest users | User Display Name | User Principal Name | Service Principal | | :---------------- | :------------------ | :---------------- | {1} "@ $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/' $appTable = ($guestAppOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | [$(Get-SafeMarkdown($_.appDisplayName))]($($appPortalLink -f $($_.appId))) |" }) -join "`n" $spTable = ($guestSpOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | [$(Get-SafeMarkdown($_.spDisplayName))]($($spPortalLink -f $($_.spObjectId), $($_.spAppId))) |" }) -join "`n" $mdInfo = $formatTemplate -f $appTable, $spTable } elseif ($hasGuestAppOwners) { $formatTemplate = @" ## Guest users own applications in your tenant ### Applications owned by guest users | User Display Name | User Principal Name | Application | | :---------------- | :------------------ | :---------- | {0} "@ $appPortalLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Owners/appId/{0}/isMSAApp~/false' $appTable = ($guestAppOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | [$(Get-SafeMarkdown($_.appDisplayName))]($($appPortalLink -f $($_.appId))) |" }) -join "`n" $mdInfo = $formatTemplate -f $appTable } elseif ($hasGuestSpOwners) { $formatTemplate = @" ## Guest users own service principals in your tenant ### Service principals owned by guest users | User Display Name | User Principal Name | Service Principal | | :---------------- | :------------------ | :---------------- | {0} "@ $spPortalLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Owners/objectId/{0}/appId/{1}/preferredSingleSignOnMode~/null/servicePrincipalType/Application/fromNav/' $spTable = ($guestSpOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | [$(Get-SafeMarkdown($_.spDisplayName))]($($spPortalLink -f $($_.spObjectId), $($_.spAppId))) |" }) -join "`n" $mdInfo = $formatTemplate -f $spTable } # Replace the placeholder with the detailed information $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo $params = @{ TestId = '21868' Title = "Guests don't own apps in the tenant" UserImpact = 'Low' Risk = 'Medium' ImplementationCost = 'Medium' AppliesTo = 'Identity' Tag = 'Identity' Status = $passed Result = $testResultMarkdown } Add-ZtTestResultDetail @params } |