Private/EntraMonitor/Detections/Test-EntraAppPermissionGrant.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Test-EntraAppPermissionGrant {
    [CmdletBinding()]
    param(
        [hashtable[]]$AuditEvents = @()
    )

    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    # Activities related to OAuth2 permission grants
    $permActivities = @(
        'Add delegated permission grant'
        'Add app role assignment grant to user'
        'Add app role assignment to service principal'
        'Consent to application'
        'Add OAuth2PermissionGrant'
        'Update OAuth2PermissionGrant'
        'Add application permission grant'
    )

    # High-privilege permission scopes to flag
    $highPrivScopes = @(
        'Mail.ReadWrite'
        'Mail.Send'
        'Files.ReadWrite.All'
        'Directory.ReadWrite.All'
        'RoleManagement.ReadWrite.Directory'
        'AppRoleAssignment.ReadWrite.All'
        'User.ReadWrite.All'
        'Group.ReadWrite.All'
        'Application.ReadWrite.All'
        'Sites.ReadWrite.All'
        'MailboxSettings.ReadWrite'
        'full_access_as_app'
    )

    foreach ($event in $AuditEvents) {
        $activity = $event.ActivityDisplayName
        $isPermGrant = $false

        foreach ($pa in $permActivities) {
            if ($activity -match [regex]::Escape($pa)) {
                $isPermGrant = $true
                break
            }
        }

        if (-not $isPermGrant) { continue }

        # Extract app and permission details
        $appName = ''
        $scopes = ''
        $consentType = ''
        foreach ($resource in $event.TargetResources) {
            if ($resource.DisplayName) { $appName = $resource.DisplayName }
            foreach ($prop in $resource.ModifiedProperties) {
                if ($prop.DisplayName -eq 'Scope' -or $prop.DisplayName -eq 'DelegatedPermissionGrant.Scope') {
                    $scopes = $prop.NewValue -replace '"', ''
                }
                if ($prop.DisplayName -eq 'ConsentType') {
                    $consentType = $prop.NewValue -replace '"', ''
                }
            }
        }

        # Check if any high-privilege scopes are granted
        $isHighPrivilege = $false
        if ($scopes) {
            $grantedScopes = $scopes -split '\s+'
            foreach ($scope in $grantedScopes) {
                foreach ($hps in $highPrivScopes) {
                    if ($scope -match [regex]::Escape($hps)) {
                        $isHighPrivilege = $true
                        break
                    }
                }
                if ($isHighPrivilege) { break }
            }
        }
        # Admin consent is always high privilege
        if ($consentType -eq 'AllPrincipals') { $isHighPrivilege = $true }

        $initiator = $event.InitiatedBy.UserPrincipalName
        if (-not $initiator) { $initiator = $event.InitiatedBy.AppDisplayName }

        $results.Add([PSCustomObject]@{
            Timestamp       = $event.Timestamp
            Activity        = $activity
            Result          = $event.Result
            InitiatedBy     = $initiator
            AppName         = $appName
            Scopes          = $scopes
            ConsentType     = $consentType
            IsHighPrivilege = $isHighPrivilege
            Category        = $event.Category
            CorrelationId   = $event.CorrelationId
        })
    }

    return @($results)
}