Managing Azure AD Connected Organizations through Graph

Summer is soon finished, and my blogging will restart. This time, I am checking out the newly documented endpoint for managing connected organizations, used by Azure AD Entitlement Management for having different workflows depending on the relationship to the external organization. This could be:

  • Having an external or internal sponsor approve requests, such as an account manager being able to approve access to their customers
  • Having an external sponsor reviewing who of their users should still have access to your systems, through the access review feature
  • Having certain access packages only being visible to your connected organizations

I will not be covering how to get yourself an access token, as I already have a billion blog posts doing this. Based on this documention, you will need to use user credentials and have the scope EntitlementManagement.ReadWrite.All enabled, in order to create connected organizations.

Let’s start by getting our connected organizations:

GET https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/connectedOrganizations/
{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#identityGovernance/entitlementManagement/connectedOrganizations",
    "value": [
        {
            "id": "80c39fd6-ab8c-4cd5-8fcd-883cac6faf17",
            "displayName": "TietoEVRY",
            "description": "TietoEVRY",
            "createdBy": "admin@M365x895981.onmicrosoft.com",
            "createdDateTime": "2020-07-05T12:11:00.067Z",
            "modifiedBy": "admin@M365x895981.onmicrosoft.com",
            "modifiedDateTime": "2020-07-05T12:11:00.067Z",
            "identitySources": [
                {
                    "@odata.type": "#microsoft.graph.azureActiveDirectoryTenant",
                    "tenantId": "cbede638-a3d9-459f-8f4e-24ced73b4e5e",
                    "displayName": "TietoEVRY"
                }
            ]
        }
    ]
}

This is pretty selfexplanatory, the displayName is the name of the connected organization (you can put anything here), the id is an arbitrary guid for this entry (not the tenant id) and identitySources is an array that always contains ONE item of type microsoft.graph.azureActiveDirectoryTenant, also with the same displayName, but this time the tenantId of the external tenant. No relationship to a domain like you see when creating a new connected organization using the Azure portal.

But what about the external and internal sponsors? Well, looking in the documention, we see there are two relationships “internalSponsors” and “externalSponsors”. Usually you can use $expand on these, in order to get them (or at least the 20 first entries), but that is not currently implemented on this endpoint (which is weird, as entitlement management APIs are extensively using $expand), and you will need to GET the /internalSponsors and /externalSponsors for each organization using the ID of the connected organization like this:

GET https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/connectedOrganizations/80c39fd6-ab8c-4cd5-8fcd-883cac6faf17/externalSponsors
{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#directoryObjects",
    "@odata.count": 2,
    "value": [
        {
            "@odata.type": "#microsoft.graph.user",
            "id": "bd663709-2dfd-4ffc-b5a1-0878b952c542",
            "displayName": "Lynne Robbins",
            "mail": "LynneR@M365x895981.OnMicrosoft.com",
            "userPrincipalName": "LynneR@M365x895981.OnMicrosoft.com"
        },
        {
            "@odata.type": "#microsoft.graph.user",
            "id": "975329ba-ff91-4219-b3a6-85ea49d340c4",
            "displayName": "Lidia Holloway",
            "mail": "LidiaH@M365x895981.OnMicrosoft.com",
            "userPrincipalName": "LidiaH@M365x895981.OnMicrosoft.com"
        }
    ]
}

Using PowerShell, you can do like this to get a full report of all internal and external sponsors:

Github url for script

$accessToken = "eyJ0eXAiOiJKV1Q---snip---IQm1wJvegpe4_skQ"

$restParams = @{Headers = @{Authorization = "Bearer $accessToken"}}
$endpoint = "https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/connectedOrganizations/"

# Get all connected organizations
$connectedOrganizations = Invoke-RestMethod $endpoint @restParams

# Get internal and external sponsors of each connected organization and add ass properties to the existing objects
$connectedOrganizations.value | ForEach-Object {
    Add-Member -InputObject $_ -MemberType NoteProperty -Name "externalSponsors" -Value (Invoke-RestMethod ("{0}/{1}/externalSponsors" -f $endpoint, $_.id) @restParams | Select-Object -ExpandProperty Value)
    Add-Member -InputObject $_ -MemberType NoteProperty -Name "internalSponsors" -Value (Invoke-RestMethod ("{0}/{1}/internalSponsors" -f $endpoint, $_.id) @restParams | Select-Object -ExpandProperty Value)
}

# List as table
$list = $connectedOrganizations.value | Foreach {
    $org = $_
    if($org.internalSponsors) {
        $org.internalSponsors | ForEach-Object {
            [PSCustomObject] @{
                Type = "InternalSponsor"
                TenantId = $org.identitySources[0].tenantId
                ObjectId = $_.id
                DisplayName = $_.displayName
                userPrincipalName = $_.userPrincipalName
                mail = $_.mail
            }
        }
    }
    if($org.externalSponsors) {
        $org.externalSponsors | ForEach-Object {
            [PSCustomObject] @{
                Type = "ExternalSponsor"
                TenantId = $org.identitySources[0].tenantId
                ObjectId = $_.id
                DisplayName = $_.displayName
                userPrincipalName = $_.userPrincipalName
                mail = $_.mail
            }
        }
    }
}

$list | ft 

Let’s try creating a new connected organization using the Graph.

POST https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/connectedOrganizations/
{
    "displayName": "Microsoft",
    "description": "Microsoft",
    "identitySources": [
        {
            "@odata.type": "#microsoft.graph.azureActiveDirectoryTenant",
            "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "displayName": "Microsoft"
        }
    ],
    "internalSponsors": [
        {
            "@odata.type": "#microsoft.graph.user",
            "id": "bd663709-2dfd-4ffc-b5a1-0878b952c542"
        }
    ],
    "externalSponsors": [
        {
            "@odata.type": "#microsoft.graph.user",
            "id": "975329ba-ff91-4219-b3a6-85ea49d340c4"
        }
    ]
}

Worked fine, but the sponsors did not work this way (Oh btw, this is the way i write blog posts if you are new to this site. Try something, failing, and trying something else).

Let us try to POST to the externalUsers endpoint then, to see what happens?

POST https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/connectedOrganizations/6cc55d36-045c-4a58-b58c-028f93d5e15d/externalSponsors
{
    "id": "975329ba-ff91-4219-b3a6-85ea49d340c4"
}

Failed. Also tried patch with a full array – also failed. There is no documentation on this, and reverse engineering what happens in the Azure portal does not help at all, as the calls there are very different. So, I guess we cannot handle the sponsors at this time. I will create a new post when that feature is available. 🙂

Update about sponsor management

I was in touch with Mr. Mark Wahl, a Microsoft Program Manager, and was pointed in the correct direction. What we need to do us to use the $ref endpoint of externalSponsors and internalSponsors, in order to manage the sponsors.

POST https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/connectedOrganizations/6cc55d36-045c-4a58-b58c-028f93d5e15d/externalSponsors/$ref
{
    "@odata.id":"https://graph.microsoft.com/beta/directoryObjects/975329ba-ff91-4219-b3a6-85ea49d340c4"
}

But this results in an internal server error with the following response:

{
    "error": {
        "code": "RelationalConstraintExeption",
        "message": "A SQL relational constraint is violated.",
        "innerError": {
            "date": "2020-07-30T20:21:41",
            "request-id": "cbfc6223-fe00-4556-a592-fb3f65394f98"
        }
    }
}

Reported this error back to Microsoft for follow up. Stay tuned! 🙂

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 )

Google photo

You are commenting using your Google 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