Collectors/ConsentGrants.ps1

@{
    Name           = 'ConsentGrants'
    FileName       = 'consentGrants'
    ApiVersion     = 'v1.0'
    RequiredScopes = @('Application.Read.All', 'Directory.Read.All')
    Description    = 'Actually granted Graph application roles (including third-party apps) and delegated OAuth2 consent grants.'
    Collect        = {
        # Well-known Microsoft Graph application role ids
        $roleNames = @{
            '19dbc75e-c2e2-444c-a770-ec69d8559fc7' = 'Directory.ReadWrite.All'
            '7ab1d382-f21e-4acd-a863-ba3e13f7da61' = 'Directory.Read.All'
            '1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9' = 'Application.ReadWrite.All'
            '9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8' = 'RoleManagement.ReadWrite.Directory'
            '06b708a9-e830-4db3-a914-8e69da51d44f' = 'AppRoleAssignment.ReadWrite.All'
            '741f803b-c850-494e-b5df-cde7c675a1ca' = 'User.ReadWrite.All'
            'df021288-bdef-4463-88db-98f22de89214' = 'User.Read.All'
            'e2a3a72e-5f79-4c64-b1b1-878b674786c9' = 'Mail.ReadWrite'
            'b633e1c5-b582-4048-a93e-9f11b44c7e96' = 'Mail.Send'
            '810c84a8-4a9e-49e6-bf7d-12d183f40d01' = 'Mail.Read'
            '75359482-378d-4052-8f01-80520e7db3cd' = 'Files.ReadWrite.All'
            '01d4889c-1287-42c6-ac1f-5d1e02578ef6' = 'Files.Read.All'
            '62a82d76-70ea-41e2-9197-370581804d09' = 'Group.ReadWrite.All'
            '5b567255-7703-4780-807c-7be8301ae99b' = 'Group.Read.All'
            '9492366f-7969-46a4-8d15-ed1a20078fff' = 'Sites.ReadWrite.All'
            '332a536c-c7ef-4017-ab91-336970924f0d' = 'Sites.Read.All'
        }
        $highPrivilegeIds = @(
            '19dbc75e-c2e2-444c-a770-ec69d8559fc7'  # Directory.ReadWrite.All
            '1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9'  # Application.ReadWrite.All
            '9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8'  # RoleManagement.ReadWrite.Directory
            '06b708a9-e830-4db3-a914-8e69da51d44f'  # AppRoleAssignment.ReadWrite.All
            '741f803b-c850-494e-b5df-cde7c675a1ca'  # User.ReadWrite.All
            'e2a3a72e-5f79-4c64-b1b1-878b674786c9'  # Mail.ReadWrite
            'b633e1c5-b582-4048-a93e-9f11b44c7e96'  # Mail.Send
            '75359482-378d-4052-8f01-80520e7db3cd'  # Files.ReadWrite.All
            '62a82d76-70ea-41e2-9197-370581804d09'  # Group.ReadWrite.All
            '9492366f-7969-46a4-8d15-ed1a20078fff'  # Sites.ReadWrite.All
        )

        $graphFilter = [uri]::EscapeDataString("appId eq '00000003-0000-0000-c000-000000000000'")
        $graphServicePrincipal = @(Invoke-TLGraphRequest -Uri ('/v1.0/servicePrincipals?$filter=' + $graphFilter + '&$select=id,appId,displayName') -All) |
            Select-Object -First 1

        $appRoleGrants = @()
        if ($graphServicePrincipal) {
            $appRoleGrants = @(Invoke-TLGraphRequest -Uri ('/v1.0/servicePrincipals/' + $graphServicePrincipal.id + '/appRoleAssignedTo?$top=999') -All)
        }
        $highPrivilegeCount = 0
        foreach ($grant in $appRoleGrants) {
            $roleId = [string]$grant.appRoleId
            $permission = $(if ($roleNames.ContainsKey($roleId)) { $roleNames[$roleId] } else { $roleId })
            $isHighPrivilege = [bool]($highPrivilegeIds -contains $roleId)
            if ($isHighPrivilege) { $highPrivilegeCount++ }
            Add-Member -InputObject $grant -NotePropertyName '_tlPermission' -NotePropertyValue $permission -Force
            Add-Member -InputObject $grant -NotePropertyName '_tlIsHighPrivilege' -NotePropertyValue $isHighPrivilege -Force
        }

        $delegatedGrants = @(Invoke-TLGraphRequest -Uri '/v1.0/oauth2PermissionGrants?$top=999' -All)

        # Resolve client service principals to names AND owner tenant, so
        # Microsoft first-party apps (normal platform behavior) can be told
        # apart from third-party consent-grant risk.
        $microsoftOwnerIds = @(
            'f8cdef31-a31e-4b4a-93e4-5f571e91255a'  # Microsoft first-party services
            '72f988bf-86f1-41af-91ab-2d7cd011db47'  # Microsoft corporate tenant
        )
        $clientIds = @($delegatedGrants | ForEach-Object { [string]$_.clientId } | Where-Object { $_ } | Sort-Object -Unique)
        $servicePrincipalMap = @{}
        for ($offset = 0; $offset -lt $clientIds.Count; $offset += 1000) {
            $chunk = @($clientIds[$offset..([Math]::Min($offset + 999, $clientIds.Count - 1))])
            $found = @()
            try {
                $found = @(Invoke-TLGraphRequest -Method POST -Uri '/v1.0/directoryObjects/getByIds' -Body @{ ids = @($chunk); types = @('servicePrincipal') } -All)
            }
            catch {
                Write-Verbose ("Service principal lookup for delegated grants failed: {0}" -f $_.Exception.Message)
            }
            foreach ($servicePrincipal in $found) { $servicePrincipalMap[[string]$servicePrincipal.id] = $servicePrincipal }
        }
        foreach ($grant in $delegatedGrants) {
            $clientName = [string]$grant.clientId
            $ownerId = $null
            $servicePrincipal = $null
            if ($servicePrincipalMap.ContainsKey([string]$grant.clientId)) { $servicePrincipal = $servicePrincipalMap[[string]$grant.clientId] }
            if ($servicePrincipal) {
                if ($servicePrincipal.PSObject.Properties['displayName'] -and $servicePrincipal.displayName) { $clientName = [string]$servicePrincipal.displayName }
                if ($servicePrincipal.PSObject.Properties['appOwnerOrganizationId']) { $ownerId = [string]$servicePrincipal.appOwnerOrganizationId }
            }
            Add-Member -InputObject $grant -NotePropertyName '_tlClientName' -NotePropertyValue $clientName -Force
            Add-Member -InputObject $grant -NotePropertyName '_tlAppOwnerOrganizationId' -NotePropertyValue $ownerId -Force
            Add-Member -InputObject $grant -NotePropertyName '_tlIsMicrosoftApp' -NotePropertyValue ([bool]($ownerId -and ($microsoftOwnerIds -contains $ownerId))) -Force
        }

        @{
            graphServicePrincipalId = $(if ($graphServicePrincipal) { [string]$graphServicePrincipal.id } else { $null })
            summary                 = [ordered]@{
                appRoleGrantCount       = @($appRoleGrants).Count
                highPrivilegeGrantCount = $highPrivilegeCount
                delegatedGrantCount     = @($delegatedGrants).Count
            }
            appRoleGrants           = $appRoleGrants
            delegatedGrants         = $delegatedGrants
        }
    }
}