Check for ApplicationAccessPolicy

Maybe you are aware that you can scope application registered in Azure AD and configured with OAuth 2.0 application permissions. It is well documented here Limiting application permissions to specific Exchange Online mailboxes – Microsoft Graph | Microsoft Docs and finally also Exchange Web Services (EWS) is supported.

However, I think it is important to perform regular checks in your tenant whether policies exists or not.

What and where to check

The first thing you need to do is to collect all existing serviceprincipals in your tenant.There are several ways of doing this. In the UI you can see permissions with granted admin consent in the permission section of an enterprise application:

On “Permissions” you can see all permissions with type and admin consent was granted

But using the UI is not really efficient. We want to achieve this in a much more elegant way. For this we are using PowerShell and the Microsoft Graph PowerShell SDK as this is the way forward. From this module you will need only one Cmdlet:

Get-MgServicePrincipal (Microsoft.Graph.Applications) | Microsoft Docs

In order to check for ApplicationAccessPolicy in Exchange Online, you will need the following Cmdlet:

Get-ApplicationAccessPolicy (ExchangePowerShell) | Microsoft Docs

The logic would be as follows:

  • get servicePrincipals, which represents Microsoft Graph and Exchange Online:
# retrieve Microsoft Graph and Exchange Online servicePrincipals
$MSGraphEXOSPN = Get-MgServicePrincipal -Filter "(AppId eq '00000002-0000-0ff1-ce00-000000000000') or (AppId eq '00000003-0000-0000-c000-000000000000')"
  • extract AppRoles, which will give you all Ids and Value of permissions supported by ApplicationAccessPolicy
# extract AppRoles
$AppRoles = $MSGraphEXOSPN.AppRoles | Where-Object { $_.Value -match 'Mail\.|MailboxSettings\.|Calendars\.|Contacts\.|full_access_as_app'} | Sort-Object -Property Value -Unique
  • now query all servicePrincipals including the property AppRoleAssignments (as this contains the assigned permissions appRoleAssignment resource type – Microsoft Graph v1.0 | Microsoft Docs)
  • filter all servicePrincipals, which have AppRoles (no need to to check those without any AppRoles)
  • filter existing ones for Exchange Online permissions
  • check if a policy exists for specific AppId

Here is my scripted solution:

function Get-AADServicePrincipalEXOReport
{
    [CmdletBinding()]
    param(
        [System.Management.Automation.SwitchParameter]
        $IncludePolicyCheck
    )
    begin
    {
        # start timer
        $timer = [System.Diagnostics.Stopwatch]::StartNew()
        # initiate collection
        $collection = [System.Collections.ArrayList]@()
        # retrieve Microsoft Graph and Exchange Online servicePrincipals
        $MSGraphEXOSPN = Get-MgServicePrincipal -Filter "(AppId eq '00000002-0000-0ff1-ce00-000000000000') or (AppId eq '00000003-0000-0000-c000-000000000000')"
        # extract AppRoles
        $AppRoles = $MSGraphEXOSPN.AppRoles | Where-Object { $_.Value -match 'Mail\.|MailboxSettings\.|Calendars\.|Contacts\.|full_access_as_app'} | Sort-Object -Property Value -Unique
        # retrieve all servicePrincipals
        $AllSPN = Get-MgServicePrincipal -ExpandProperty AppRoleAssignments -All
        Write-Verbose "Found $($AllSPN.Count) serviceprincipals..."
        # filter for serviceprincipals with AppRoleAssignments
        $SPNwithAppRoles = $AllSPN | Where-Object { -not [System.String]::IsNullOrEmpty($_.AppRoleAssignments)}
        Write-Verbose "Found $($SPNwithAppRoles.Count) serviceprincipals with AppRoleAssignments..."
        Write-Verbose "Retrieved all ServicePrincipals processing time:$($timer.Elapsed.ToString())"
        
        if ($IncludePolicyCheck)
        {
            # retreieve all application access policies from EXO
            $AppPolicies = Get-ApplicationAccessPolicy -ErrorAction SilentlyContinue
        }

        function Test-ServicePrincipal
        {
            [OutputType([System.Boolean])]
            param(
                [Microsoft.Graph.PowerShell.Models.MicrosoftGraphServicePrincipal]
                $ServicePrincipal,

                [Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole[]]
                $AppRoles
            )

            foreach ($AppRoleID in $ServicePrincipal.AppRoleAssignments.AppRoleId)
            {
                if ($AppRoles.Id.Contains($AppRoleID))
                {
                    return $true
                    break
                }
                else
                {
                    return $false
                }
            }
        }

    }

    process
    {
        foreach ($SPN in $SPNwithAppRoles)
        {
            # create object for current serviceprincipal
            $sPNObject = New-Object -TypeName psobject
            # check for relevant permissions
            if (Test-ServicePrincipal -ServicePrincipal $SPN -AppRoles $AppRoles )
            {
                $sPNObject | Add-Member -MemberType NoteProperty -Name AppDisplayName -Value $SPN.AppDisplayName
                $sPNObject | Add-Member -MemberType NoteProperty -Name ObjectId -Value $SPN.Id
                $sPNObject | Add-Member -MemberType NoteProperty -Name AppId -Value $SPN.AppId
                $sPNObject | Add-Member -MemberType NoteProperty -Name AppOwnerOrganizationId -Value $SPN.AppOwnerOrganizationId

                if ($IncludePolicyCheck)
                {
                    if ([System.String]::IsNullOrEmpty($AppPolicies))
                    {
                        $sPNObject | Add-Member -MemberType NoteProperty -Name EXOPolicyExists -Value 'None found!'
                    }
                    else
                    {
                        $sPNObject | Add-Member -MemberType NoteProperty -Name EXOPolicyExists -Value $($AppPolicies.AppId.Contains($SPN.AppId))
                    }
                    # initiate variable
                    [System.String]$Permissions = ''
                    # build permission string from AppRoleId
                    $Permissions = ($SPN.AppRoleAssignments.AppRoleId | ForEach-Object{$perm=$_ ; $AppRoles | Where-Object {$_.Id -eq $perm}}).Value -join '|'
                    $sPNObject | Add-Member -MemberType NoteProperty -Name Permissions -Value $Permissions
                }

                # add to collection for output
                $collection += $sPNObject
            }
        }
    }

    end
    {
        if ([System.String]::IsNullOrEmpty($collection))
        {
            Write-Host 'No enterprise application with EXO related permissions found!'
        }
        else
        {
            $collection
        }
        $timer.Stop()
        Write-Verbose "ScriptRuntime:$($timer.Elapsed.ToString())"
    }
}
The output

For the most reason version of the function or if you need to file an issue, here is the link to my GitHub repository:

IngoGege/Miscellaneous · GitHub

Note: In larger environments you need to run this in PowerShell 7. If you running it on PowerShell 5 it most likely will crash with the exception: StackOverflowException.

This is documented in the GitHub issue here:

Get-MgGroupMember for large Group · Issue #949 · microsoftgraph/msgraph-sdk-powershell · GitHub

However, I truly believe it’s not due to memory limits. I have currently a case open to get this verified. All I know for sure: If I use my own function Get-MSGraphServicePrincipal and perform paging on my own, I don’t run into this issue.

Conclusion

I really recommend checking your tenant for servicePrincipals with such permissions on a regular base. This is one way and I hope this helps you.

Feedback is welcome!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s