August 27th, 2018

New Azure AD access reviews feature on the Microsoft Graph

The Azure AD access reviews feature now has an API in the Microsoft Graph beta endpoint.  For the list of API methods, see Azure AD access reviews.  While we are in progress of adding access reviews to Azure AD PowerShell and examples of using access reviews from other development platforms to our documentation, the following instructions may be of interest.

Azure AD access reviews data model

The Azure AD access reviews feature adds the following resource types:

  • accessReview: represents an access review.  This can be a one-time review, a recurring review series, or an instance of a recurring review.
  • businessFlowTemplate: the business flow template determines the type resource on which an access review is to be performed. The identifier of a template, such as to review guest members of a group, is supplied by the caller when creating an access review. (The business flow template objects are read only, they are automatically generated when the global administrator onboards the tenant to use the access reviews feature. No additional business flow templates can be created.)
  • program: represents an Azure AD access review program. A program is a container, holding program controls. A tenant can have one or more programs.  Each control links an access review to a program, to make it easier to locate related access reviews.  Each tenant that has onboarded Azure AD access reviews has one program, `Default program`.  A global administrator can create additional programs, for example to represent compliance initiatives.
  • programControl: represents a control, which links an access review to a particular program.
  • programControlType: the program control type is used when associating a control to a program, to indicate the type of access review the control is for. (The program control type objects are read only, they are automatically generated when the global administrator onboards the tenant to use the access reviews feature.  No additional program control types can be created.)

Understanding authorization requirements

The Azure AD access reviews API performs three checks:

  • First, has the tenant onboarded to the feature – Azure AD access reviews or, in the case of access reviews of Azure AD roles, Azure AD PIM. Both of these features are included in Azure AD Premium P2, and require the administrator to have used the features at least once in order to permit the APIs to be called. If you have not already used Azure AD access reviews, the section “Enable Azure AD access reviews in your tenant” below onboards the Azure AD access reviews feature so you can try out the APIs.
  • Second, does the application have the necessary permissions. The permissions available for these APIs are:
    • Read.All: read access reviews
    • ReadWrite.All: read, create, update and delete access reviews
    • Read.All: read programs and controls
    • ReadWrite.All: read, create, update and delete programs and controls

If you do not already have those permissions on an application, the section “Register an Azure AD application which can call the access reviews Graph API” below creates a new application and assigns it read permissions.  (You can change the scenario to assign it read and write permission).

  • Third, does the user have the necessary permissions.  This is determined by the calling user’s directory role:
Target Resource Desired Operation Required directory role of the user, in addition to the application permission
Access review of an Azure AD role Read Global Administrator, Security Administrator, Security Reader or Privileged Role Administrator
Create, Update or Delete Global Administrator or Privileged Role Administrator
Access review of a group or app Read Global Administrator, Security Administrator, Security Reader or User Administrator
Create, Update or Delete Global Administrator or User Administrator
Programs or controls Read Global Administrator, Security Administrator, Security Reader or User Administrator
Create, Update or Delete Global Administrator or User Administrator

Enable Azure AD access reviews in your tenant

This example assumes you have already onboarded Azure AD access reviews in your tenant directory.  If you have already done so, then skip to the next section “Register an Azure AD application which has permissions to call the access reviews API in Graph”.  Otherwise, continue with these steps to ensure the feature is onboarded so the APIs will return some data.

  1. Log into the Azure portal as a global administrator.
  2. Ensure that your organization has Azure AD Premium P2 or EMS E5 subscription active. If not, click on Try Azure and activate a trial of Enterprise Mobility + Security E5. Otherwise, if your organization has an active subscription, continue at the next step.
  3. Navigate to the Azure AD extension, and click on “Access reviews” on the right hand side under “Other capabilities”.

 

4. If you have not already onboarded Azure AD access reviews in your organization, onboard it now.

5. Click on “Programs” and ensure there is at least one program listed.  At this point you can create additional access reviews if you wish.

Register an Azure AD application which has permissions to call the access reviews API in Graph

The Graph authorization model requires that an application must be consented by a user or administrator prior to accessing an organization’s data.

  1. Log into the Azure portal as a global administrator.
  2. Navigate to the Azure AD extension, and click on “App registrations” in the MANAGE section, to land at the page register apps
  3. Click on “New application registration” button at the top of the page.
  4. Provide a name for the application that is different from any other application in your tenant’s directory (e.g., “graphsample”), change the Application type to Native, and provide the following as the Redirect URI:
urn:ietf:wg:oauth:2.0:oob
  1. Click “Create”.
  2. When the application is registered, copy the Application ID value, and save the value for later.
  3. Click on Settings, then click on “Required permissions”.
  4. Click on “Add”. Click on “Select an API”, click on “Microsoft Graph”, and then click “Select”.
  5. Azure AD access reviews uses the following delegated permissions: Read all access reviews that use can access, Manage all access reviews that user can access, Read all programs that user can access, and Manage all programs that user can access. This example application requires only the permissions:Read all access reviews that user can access Read all programs that user can accessPut a checkbox by those two permissions, and click “Select”.

  1. Click “Done”.

(Sample only) Ensure PowerShell and the ADAL libraries are on your computer

The Microsoft Graph requires the application calling it to have an access token.

In this example, the sample code to use the API will leverage the ADAL library which is automatically installed when using Azure AD PowerShell cmdlets.

  1. Ensure that you have PowerShell 3.0 or later, and .NET Framework 4.5 installed on your computer.
  2. Ensure that you have either the Azure AD PowerShell v2 General Availability or Preview modules installed on your computer. If not, more information on how to install them is at https://docs.microsoft.com/en-us/powershell/azure/active-directory/install-adv2?view=azureadps-2.0. Try using Connect-AzureAD to ensure that you can authenticate to Azure AD as a global administrator.

Retrieving the token and calling the API

  1. Create a file named “access-reviews-example1.ps1” whose context in the sample PowerShell from the end of this post.
  2. Start PowerShell.
  3. Change to the directory where the access-reviews-example1.ps1 script is located.
  4. Invoke the script, providing on the command line -User with the User principal name (UPN) of a global administrator, and -ClientId with the application ID value from earlier.For example,
    .\access-reviews-example1.ps1 -User ga@contoso.onmicrosoft.com
    
    -ClientId 280d7b83-8d0a-4ee7-8f1a-064ec36d1fa1
  5. When the script is run for the first time in a PowerShell session, you will be asked to authenticate.  For the purposes of this example, ensure that you sign in as a global administrator.
  6. After authenticating, the first time the script is run for a particular application, you will be prompted to consent the application use of permissions.

  1. Once consented, the script will use the token to call Microsoft Graph and retrieve programs, controls, business flow templates and access reviews, and write a summary of them to the PowerShell window.
  2. Note that the authorization is only applicable to the global administrator who consented to the application, and only for that tenant. If other users in the organization or in other applications also wish to use the application, additional steps are required for admin consent.  See the article how to build for multi-tenant apps for more information on admin consent in applications.

PowerShell sample

The full sample is here:

# Example for using Azure AD access reviews in Microsoft Graph
#
# This material is provided "AS-IS" and has no warranty.
# 
# Last updated August 22, 2018
#
# This example is adapted from the documentation example located at 
# https://docs.microsoft.com/en-us/intune/intune-graph-apis
#
#

Param(
    [Parameter(Mandatory=$true)][string]$User,

    [Parameter(Mandatory=$true)][string]$ClientId
)


# from Intune graph API samples

function Get-GraphExampleAuthToken {
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $User,

        [Parameter(Mandatory = $true)]
        $ClientId,

        [Parameter()]
        $TenantDomain
    )

    $userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User
    $tenant = $userUpn.Host

    if ($TenantDomain -ne $null) {
        $tenant = $TenantDomain
    }

    Write-Verbose "Checking for AzureAD module..."

    $AadModule = Get-Module -Name "AzureAD" -ListAvailable
    if ($AadModule -eq $null) {
        Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable
    }

    if ($AadModule -eq $null) {
        write-host
        write-host "AzureAD Powershell module not installed..." -f Red
        write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
        write-host "Script can't continue..." -f Red
        write-host
        exit
    }

    # Getting path to ActiveDirectory Assemblies
    # If the module count is greater than 1 find the latest version

    if ($AadModule.count -gt 1) {
        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]
        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }
        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    }

    else {
        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    }

    [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
    [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

  
    $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
    $resourceAppIdURI = "https://graph.microsoft.com"

    $authority = "https://login.microsoftonline.com/$Tenant"

    try {
        $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
        # https://msdn.microsoft.com/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
        # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession
        $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
        $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")
        $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $ClientId, $redirectUri, $platformParameters, $userId).Result
        # If the accesstoken is valid then create the authentication header
        if ($authResult.AccessToken) {
            # Creating header for Authorization token
            $authHeader = @{
                'Content-Type' = 'application/json'
                'Authorization' = "Bearer " + $authResult.AccessToken
                'ExpiresOn' = $authResult.ExpiresOn
            }
            return $authHeader
        }
        else {
            Write-Host
            Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red
            Write-Host
            break
        }
    }
    catch {
        write-host $_.Exception.Message -f Red
        write-host $_.Exception.ItemName -f Red
        write-host
        break
    }   
}

# start of access review specific example

function Get-GraphExampleProgramControls($authHeaders,$programId)
{
    $uri1 = "https://graph.microsoft.com/beta/programs('" + $programId + "')/controls"
    Write-Host "GET $uri1"

    $resp1 = Invoke-WebRequest -UseBasicParsing -headers $authHeaders -Uri $uri1 -Method Get
    $val1 = ConvertFrom-Json $resp1.Content

   
    foreach ($c in $val1.Value) {
        $cid = $c.controlId
        $displayname = '"' + $c.displayName + '"'
        Write-Host "control $cid $displayname"
    }

}

function Get-GraphExamplePrograms($authHeaders) {
    $uri1 = "https://graph.microsoft.com/beta/programs"
    Write-Host "GET $uri1"

    $resp1 = Invoke-WebRequest -UseBasicParsing -headers $authHeaders -Uri $uri1 -Method Get
    $val1 = ConvertFrom-Json $resp1.Content

   
    foreach ($program in $val1.Value) {
        $id = $program.id
        $displayname = '"' + $program.displayName + '"'
        Write-Host "program $id $displayName"

        Get-GraphExampleProgramControls $authHeaders $id
        Write-Host ""
    }

}

function Get-GraphExampleAccessReviewDecisions($authHeaders,$arid)
{
    $uri1 = 'https://graph.microsoft.com/beta/accessReviews(' + "'" + $arid  + "')/decisions"
    Write-Host "GET $uri1"
    $resp1 = Invoke-WebRequest -UseBasicParsing -headers $authHeaders -Uri $uri1 -Method Get
    $val1 = ConvertFrom-Json $resp1.Content

    foreach ($ard in $val1.Value) {
        $rr = $ard.reviewResult
        $upn = $ard.userPrincipalName

        Write-Host "access review decision $upn $rr"
    }
    Write-Host ""
}

function Get-GraphExampleAccessReviewInstances($authHeaders,$arid)
{
    $uri1 = 'https://graph.microsoft.com/beta/accessReviews(' + "'" + $arid  + "')/instances"
    Write-Host "GET $uri1"
    $resp1 = Invoke-WebRequest -UseBasicParsing -headers $authHeaders -Uri $uri1 -Method Get
    $val1 = ConvertFrom-Json $resp1.Content

    foreach ($ard in $val1.Value) {
        $iid = $ard.id
        $start = $ard.startDateTime
        $end = $ard.endDateTime
        $status = $ard.status

        Write-Host "access review instance $start $end $status"
        if ($status -ne "NotStarted") {
            Get-GraphExampleAccessReviewDecisions $authHeaders $iid
        }
    }
    Write-Host ""
}


function Get-GraphExampleAccessReviews($authHeaders,$bftid)
{
    $uri1 = 'https://graph.microsoft.com/beta/accessReviews?$filter=businessFlowTemplateId%20eq%20' + "'" + $bftid  + "'"
    Write-Host "GET $uri1"
    $resp1 = Invoke-WebRequest -UseBasicParsing -headers $authHeaders -Uri $uri1 -Method Get
    $val1 = ConvertFrom-Json $resp1.Content

    foreach ($ar in $val1.Value) {
        $id = $ar.id
        $displayname = '"' + $ar.displayName + '"'
        $startDateTime = $ar.startDateTime
        $status = $ar.status

        Write-Host "access review $id $displayName $startDateTime $status"

        Get-GraphExampleAccessReviewDecisions $authHeaders $id

        Get-GraphExampleAccessReviewInstances $authHeaders $id
    }
   
    

}

function Get-GraphExampleBusinessFlowTemplates($authHeaders) {
    $uri1 = "https://graph.microsoft.com/beta/businessFlowTemplates"
    Write-Host "GET $uri1"
    $resp1 = Invoke-WebRequest -UseBasicParsing -headers $authHeaders -Uri $uri1 -Method Get
    $val1 = ConvertFrom-Json $resp1.Content

   
    foreach ($bft in $val1.Value) {
        $id = $bft.id
        Write-Host "business flow template $id"

        Get-GraphExampleAccessReviews $authHeaders $id
        Write-Host ""

    }
}

$authHeaders = Get-GraphExampleAuthToken -User $User -ClientId $ClientId


Get-GraphExamplePrograms $authHeaders

Get-GraphExampleBusinessFlowTemplates $authHeaders