Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save adrwh/9dee732630b56312dbe95f2ee5c54505 to your computer and use it in GitHub Desktop.
Save adrwh/9dee732630b56312dbe95f2ee5c54505 to your computer and use it in GitHub Desktop.

Create an Azure Function API to manage Exchange Online (Office 365) mailbox permissions

In this article you will learn how to create an Azure Function API to manage Exchange Online (Office 365) mailbox permissions.

One obvious response at this point is "Why do that? We already have PowerShell and the Exchange Admin portal". Well, Yes, kinda, however both of those options fall short in the following niche scenarios.

  • Imagine you need to scope your administrator access to Exchange based on email domain. You don't want administrators in Country A, administering users in Country B. You can't do this in the Exchange portal or with the ExchangeOnlineManagement module or any out-the-box features.
  • Secondly, you want to be moving towards secured web based connectivity for your tooling and away from the legacy stuff. That's why Microsoft is decommissioning its legacy RPC Session based Exchange modules and replacing them with REST APIs.

So if this sounds like fun or you just like to see cool edge implementations for managing Exchange, read on, it'll give you great ideas of your own if nothing else.

Small Warining! What we're doing here is bypassing the ExchangeOnline PowerShell module and hitting unsupported, beta APIs, so only build production code this way at your own risk.

Let's get started

Head over to Azure Functions and create a new Function App. I recommend using VS Code, installing the Azure Functions tool kit and creating the Function App.

You definitely want to secure your API with OAuth so add Azure AD Authentication. I'm going to skip through the details as Microsoft and others have already documented this here https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad.

This will create an Azure AD app registration in your directory. This is the service principal (the Identity) that your client will authenticate as to successfully call your API.

Now you can create your first API resource, method or function, whatever you like to call it. Create a function, change the method to GET and authentication to anonymous. Yes that's right, the Function auth is anonymous because we're handling authentication at the app layer with OAuth, so rest assured, we're doing things secured.

Now here's some sample code for your first function. It's PowerShell, but you can re-write this is your own preferred language. It takes in a couple request parameters, then authenticates with the Outlook API, then builds a request for the calls the Exchange REST API to check mailbox permissions.

I have tried to be as RESTful as I can with this, using the following design pattern.

GET /mailboxpermissions/{identity}
POST /mailboxpermissions/
PATCH /mailboxpermissions/{identity}
DELETE /mailboxpermissions/{identity}
GET /mailboxfolderpermission/{identity}
POST /mailboxpermissions/
PATCH /mailboxfolderpermission/{identity}
DELETE /mailboxfolderpermission/{identity}

Azure Function sample code

using namespace System.Net

param($Request, $TriggerMetadata)

$actor = $Request.Query.actor
$identity = $Request.Params.identity

if (-not $identity) {
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::BadRequest
    })
}


#region authentication

#  These secrets are stored in the function app env variables and made available at runtime.  This is pretty secure already, but if you're a government agency protecting top secret stuff, you should go a step further and use Azure Key Vault to get/set these values.

$clientId = $ENV:ClientId # The client id for the Az app registraion
$clientSecret = $ENV:ClientSecret # The client secret.  This can be exchanged for a certificate for "extra" security.
$tenantId = $ENV:tenantId # Your tenant directory id.

#  This builds the auth request, used to get an bearer access token from Azure AD.  This is standard OAuth stuff, when talking to Microsoft Graph and other APIs.
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method      = 'POST'
    Uri         = "https://login.microsoftonline.com/$tenantId/oauth2/token"
    Body        = @{
        client_id     = $clientId
        client_secret = $clientSecret
        resource      = 'https://outlook.office365.com'
        grant_type    = 'client_credentials'
    }
}

# Request the token!
$auth = Invoke-RestMethod @PostSplat

# Create header
$Headers = @{
    Authorization      = "$($auth.token_type) $($auth.access_token)"
    Accept             = "application/json"
    "Content-Type"     = "application/json"
    'X-ResponseFormat' = "json"
    'X-AnchorMailbox'  = "UPN:$actor"
}

#endregion

#  Here we build the request body used to invoke the "InvokeCommand" endpoint on the unsupported Beta APIs i was talking about earlier.
try {
    $requestBody = @{
        CmdletInput = @{
            CmdletName = "Get-MailboxPermission"
            Parameters = @{
                Identity = $identity
            }
        }
    } 
    $RequestParams = @{
        Headers     = $Headers
        Method      = "POST"
        Uri         = "https://outlook.office365.com/adminapi/beta/yourdomain.onmicrosoft.com/InvokeCommand"
        ContentType = "application/json"
        Body        = $requestBody | ConvertTo-Json -Depth 5 -Compress
        StatusCodeVariable = "statusCode"
    }
    $response = Invoke-RestMethod @RequestParams

    #  This is Azure Function specific response code, used to build a response from the server/function back to the client.
    $responseBody = @{
        statusCode = $statusCode
        Body = @($response.value | Where-Object User -ne "NT AUTHORITY\SELF" | Select-Object Identity, User, @{Name = "AccessRights"; Expression = { $_.AccessRights -join "," } }, Deny, IsInherited)
    } | ConvertTo-Json -Compress

}
catch {
    Write-Error $_.Exception
    Write-Output "statusCode: [$statusCode], something wrong!"

    $responseBody = @{
        statusCode = $statusCode
        body = $null
    } | ConvertTo-Json -Compress
}


# This is where we send the response back to the caller/client/frontend, whatever you like to call it.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = [HttpStatusCode]::OK
    ContentType = 'application/json'
    Body = $responseBody
})

For this code to authenticate with the Exchange REST APIs we need to create another Azure AD App registration and give it permissions to the API. Go back to Azure Active Directory and create a new App Registration. Copy the Application Id, also known as Client Id, then create and copy an Application Secret. Treat these like a username and passwords, store them securely. Turn them into Env Variables in your function app, so that you can retrienve them in your code (above).

At this point you can hook up your faviroite HTTP client, such as Curl, HTTPie, Postman, etc and test the API.

I have created various test front ends for this API, namely, PowerApps, Teams App (incomplete) and ServiceNow apps, but you can write your own front end as required.

I hope this helps expand your creative thought when it comes to developing applications using Azure Functions, Office365 Exchange Online and other front-ends like PowerApps, Teams and ServiveNow.

Hudds-out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment