tests/Test-Assessment.51013.ps1
|
<#
.SYNOPSIS App Protection Policies block managed-app access when user re-authentication fails (disabled Entra ID accounts, blocked sign-ins, expired tokens) .DESCRIPTION Checks whether at least one assigned App Protection Policy on both iOS/iPadOS and Android enforces a block or wipe action when the managed app cannot re-authenticate the user. Per-platform scope is determined first by counting enrolled devices; a platform with zero enrolled devices is reported as Skipped rather than a false Fail. .NOTES Test ID: 51013 Category: Devices Pillar: Devices Required API: Microsoft Graph v1.0, beta Q1: deviceManagement/managedDevices - enrolled device counts per platform (v1.0) Q2: deviceAppManagement/iosManagedAppProtections (id, displayName, appActionIfUnableToAuthenticateUser, isAssigned) (beta) Q3: deviceAppManagement/androidManagedAppProtections (id, displayName, appActionIfUnableToAuthenticateUser, isAssigned) (beta) #> function Test-Assessment-51013 { [ZtTest( Category = 'Devices', ImplementationCost = 'Low', CompatibleLicense = ('INTUNE_A'), Pillar = 'Devices', RiskLevel = 'High', SfiPillar = 'Protect identities and secrets', TenantType = ('Workforce'), TestId = 51013, Title = 'App Protection Policies block managed-app access when user re-authentication fails (disabled Entra ID accounts, blocked sign-ins, expired tokens)', UserImpact = 'Low' )] [CmdletBinding()] param() #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Checking App Protection Policies for authentication failure enforcement' # Q1: Count enrolled iOS / iPadOS and Android devices to determine per-platform scope. # ConsistencyLevel defaults to eventual in Invoke-ZtGraphRequest; $count=true requires it. Write-ZtProgress -Activity $activity -Status 'Counting enrolled iOS / iPadOS devices' $iosDeviceCount = 0 $androidDeviceCount = 0 try { $iosRaw = Invoke-ZtGraphRequest -RelativeUri 'deviceManagement/managedDevices' -Filter "operatingSystem eq 'iOS' or operatingSystem eq 'iPadOS'" -Select 'id' -Top 1 -QueryParameters @{ '$count' = 'true' } -DisablePaging -ApiVersion v1.0 -ErrorAction Stop $iosDeviceCount = $iosRaw.'@odata.count' Write-ZtProgress -Activity $activity -Status 'Counting enrolled Android devices' $androidRaw = Invoke-ZtGraphRequest -RelativeUri 'deviceManagement/managedDevices' -Filter "operatingSystem eq 'Android'" -Select 'id' -Top 1 -QueryParameters @{ '$count' = 'true' } -DisablePaging -ApiVersion v1.0 -ErrorAction Stop $androidDeviceCount = $androidRaw.'@odata.count' } catch { $statusCode = Get-ZtHttpStatusCode -ErrorRecord $_ if ($statusCode -in @(401, 403)) { Write-PSFMessage "Error counting enrolled devices: HTTP $statusCode - insufficient permissions to read managedDevices." -Tag Test -Level Warning } else { Write-PSFMessage "Error counting enrolled devices: $($_.Exception.Message)" -Tag Test -Level Warning } $params = @{ TestId = '51013' Title = 'App Protection Policies block managed-app access when user re-authentication fails (disabled Entra ID accounts, blocked sign-ins, expired tokens)' Status = $false Result = '⚠️ The Intune App Protection Policies API returned an authorization (401/403) or transient (5xx) error, so coverage could not be determined. Re-run after verifying caller permissions - Global Reader at tenant scope.' CustomStatus = 'Investigate' } Add-ZtTestResultDetail @params return } # Determine per-platform scope. $iosInScope = $iosDeviceCount -gt 0 $androidInScope = $androidDeviceCount -gt 0 # If neither platform has enrolled devices this check is not applicable. if (-not $iosInScope -and -not $androidInScope) { Write-PSFMessage 'No iOS / iPadOS or Android devices enrolled - skipping App Protection Policy auth-failure check.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotApplicable return } # Q2 / Q3: Retrieve App Protection Policies for both platforms. Write-ZtProgress -Activity $activity -Status 'Getting iOS app protection policies' $iosPolicies = @() $androidPolicies = @() try { $iosPolicies = @(Invoke-ZtGraphRequest -RelativeUri 'deviceAppManagement/iosManagedAppProtections' -Select 'id,displayName,appActionIfUnableToAuthenticateUser,isAssigned' -ApiVersion beta -ErrorAction Stop) Write-ZtProgress -Activity $activity -Status 'Getting Android app protection policies' $androidPolicies = @(Invoke-ZtGraphRequest -RelativeUri 'deviceAppManagement/androidManagedAppProtections' -Select 'id,displayName,appActionIfUnableToAuthenticateUser,isAssigned' -ApiVersion beta -ErrorAction Stop) } catch { $statusCode = Get-ZtHttpStatusCode -ErrorRecord $_ if ($statusCode -in @(401, 403)) { Write-PSFMessage "Error querying App Protection Policies: HTTP $statusCode - insufficient permissions to read App Protection Policies." -Tag Test -Level Warning } else { Write-PSFMessage "Error querying App Protection Policies: $($_.Exception.Message)" -Tag Test -Level Warning } $params = @{ TestId = '51013' Title = 'App Protection Policies block managed-app access when user re-authentication fails (disabled Entra ID accounts, blocked sign-ins, expired tokens)' Status = $false Result = '⚠️ The Intune App Protection Policies API returned an authorization (401/403) or transient (5xx) error, so coverage could not be determined. Re-run after verifying caller permissions - Global Reader at tenant scope.' CustomStatus = 'Investigate' } Add-ZtTestResultDetail @params return } #endregion Data Collection #region Assessment Logic # Only block or wipe constitute hard enforcement; warn, blockWhenSettingIsSupported, and null do not. $passingActions = @('block', 'wipe') $iosQualifyCount = ($iosPolicies | Where-Object { $_.appActionIfUnableToAuthenticateUser -in $passingActions -and $_.isAssigned -eq $true }).Count $androidQualifyCount = ($androidPolicies | Where-Object { $_.appActionIfUnableToAuthenticateUser -in $passingActions -and $_.isAssigned -eq $true }).Count # A platform that is out of scope (no enrolled devices) is treated as passing and does not fail the overall check. $iosPass = -not $iosInScope -or ($iosQualifyCount -gt 0) $androidPass = -not $androidInScope -or ($androidQualifyCount -gt 0) $passed = $iosPass -and $androidPass if ($passed) { $testResultMarkdown = "✅ For every in-scope mobile platform with enrolled devices, an assigned App Protection Policy blocks or wipes managed-app access when the managed app cannot re-authenticate the user (disabled account, blocked sign-in, revoked token).`n`n%TestResult%" } else { $testResultMarkdown = "❌ One or more in-scope mobile platforms (iOS / iPadOS, Android) has no assigned App Protection Policy that blocks or wipes managed-app access on authentication failure - disabled accounts and revoked tokens retain a multi-hour window of access to corporate data on personally owned phones.`n`n%TestResult%" } #endregion Assessment Logic #region Report Generation $portalLink = 'https://intune.microsoft.com/#view/Microsoft_Intune_DeviceSettings/AppsMenu/~/protection' # Overall verdict line (rendered above the two platform sections). $overallVerdict = if ($passed) { 'Overall: Pass' } else { 'Overall: Fail' } $mdSections = "`n**$overallVerdict**`n" foreach ($platformEntry in @( [pscustomobject]@{ Label = 'iOS / iPadOS'; Policies = $iosPolicies; OdataType = 'iosManagedAppProtection'; InScope = $iosInScope; DeviceCount = $iosDeviceCount; QualifyCount = $iosQualifyCount; Pass = $iosPass }, [pscustomobject]@{ Label = 'Android'; Policies = $androidPolicies; OdataType = 'androidManagedAppProtection'; InScope = $androidInScope; DeviceCount = $androidDeviceCount; QualifyCount = $androidQualifyCount; Pass = $androidPass } )) { $platformLabel = $platformEntry.Label $platformPolicies = @($platformEntry.Policies) $totalCount = $platformPolicies.Count if (-not $platformEntry.InScope) { # Platform has no enrolled devices - render a Skipped stub (no table). $mdSections += @" ### $platformLabel App Protection Policies - Auth Failure Action **Status: Skipped** - No $platformLabel devices enrolled in this tenant. "@ continue } $platformVerdict = if ($platformEntry.Pass) { 'Pass' } else { 'Fail' } $platformHeader = "**Status: $platformVerdict** - $($platformEntry.DeviceCount) $platformLabel devices enrolled; $($platformEntry.QualifyCount) of $totalCount policies qualify." $tableRows = '' # Cap output at 10 rows per platform; append a summary row when the list is longer (per spec). $displayedPolicies = if ($totalCount -gt 10) { $platformPolicies[0..9] } else { $platformPolicies } foreach ($policy in $displayedPolicies) { $policyName = Get-SafeMarkdown -Text $policy.displayName $encodedName = [Uri]::EscapeDataString($policy.displayName) $policyLink = 'https://intune.microsoft.com/#view/Microsoft_Intune/PolicyInstanceMenuBlade/~/0/policyId/{0}/policyOdataType/%23microsoft.graph.{1}/policyName/{2}' -f $policy.id, $platformEntry.OdataType, $encodedName # Map null/empty action to "not configured"; otherwise keep the enum value as-is. $actionLabel = if ([string]::IsNullOrEmpty($policy.appActionIfUnableToAuthenticateUser)) { 'not configured' } else { $policy.appActionIfUnableToAuthenticateUser } $assignedLabel = if ($policy.isAssigned -eq $true) { '✅ Yes' } else { '❌ No' } $rowPassed = ($policy.appActionIfUnableToAuthenticateUser -in $passingActions) -and ($policy.isAssigned -eq $true) $statusLabel = if ($rowPassed) { '✅ Pass' } else { '❌ Fail' } $tableRows += "| [$policyName]($policyLink) | $actionLabel | $assignedLabel | $statusLabel |`n" } if ($totalCount -gt 10) { $tableRows += "| ... and $($totalCount - 10) more policies | | | |`n" } $formatTemplate = @' ### {0} App Protection Policies - Auth Failure Action {1} | Policy Name | Action on Auth Failure | Assigned | Status | | :---------- | :--------------------- | :------- | :----- | {2} '@ $mdSections += $formatTemplate -f $platformLabel, $platformHeader, $tableRows } $formatOuter = @' ## [App Protection Policies - Unable to Authenticate Action]({0}) {1} '@ $mdInfo = $formatOuter -f $portalLink, $mdSections $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation $params = @{ TestId = '51013' Title = 'App Protection Policies block managed-app access when user re-authentication fails (disabled Entra ID accounts, blocked sign-ins, expired tokens)' Status = $passed Result = $testResultMarkdown } Add-ZtTestResultDetail @params } |