Multiple Types of Entitlements - Custom Web Connector Aggregation

Hi ISC Community,

I am new to creating custom SaaS connectors and need some guidance/help please!

Creating a Web Services SaaS Connector

Entitlement Structure:

The target source Entitlement structure is as follows:

The above response is a snip from a ‘GET’ response when aggregating accounts. More details at end of this post

Within “Roles” there is a nested ‘Authorization Zone’, which dictates where the ‘role’ can be performed. In the above example, this user has Team Management but this access is limited to State Headquarters.

(An account can have multiple authorisation zones and roles)

The Challenge

The issue I’m facing is that when I aggregate accounts, the access appears as just “team-management”.

When I need it to be a combination of the two, for example “team-management, State Headquarters” (or something similar):

As you can see, Team-management doesn’t inform us as to which authorisation zone this access is limited to.

Any ideas?

I am struggling to understand how this is possible through the ISC documentation & forums to understand how I can do this within the SaaS connector… Does anyone have any ideas?

Here is an example of the response in more detail:

        {
            "Id": 123,
            "EntityId": 1,
            "Entity": "State Headquarters",
            "CurrentEntityId": 1,
            "Firstname": "Bart",
            "Lastname": "Simpson",
            "Disabled": true,
            "Email": "bart.simpson@test.au",
            "Username": "test12345",
            "PreferredName": "Bart",
            "PersonId": 321,
            "AvailableEntities": [
                {
                    "Id": 1,
                    "Code": "SHQ",
                    "Name": "State Headquarters"
                }
            ],
            "LockedTimestamp": null,
            "TemporaryTimestamp": null,
            "Roles": [
                {
                    "AuthorizationZone": {
                        "Id": 1,
                        "Name": "State Headquarters",
						"Deleted": false
                    },
                    "Id": 3,
                    "Name": "team-management",
                    "Description": "Team Management",
                    "IsSystem": false
                },
                {
                    "AuthorizationZone": {
                        "Id": 1,
                        "Name": "State Headquarters",
						"Deleted": false
                    },
                    "Id": 5,
                    "Name": "opslog-management",
                    "Description": "Ops Log Management",
                    "IsSystem": false
                },

Cheers,

Sean

You will want to use a Web Services After Operation Rule.

Hi @SeanK-W ,

If you are creating a Web Services SaaS connector, you will need to use a customizer to handle the concatenation you are looking for. Looking at your response, I assume that is returned after an account aggregation.

Here is some info on customizers from the Web Services SaaS docs: Customizers

You can also read more about Customizers here: Connectivity Customizers | SailPoint Developer Community

Example 2 in that link is a good start on what you are looking for. That one shows an example of using the .afterStdAccountRead command. You will want to use this (for single account aggregations) and .afterStdAccountList (for full account aggregations). Your customizer(s) should concatenate the intended values together and put them into the intended account attribute (the one marked as an Entitlement).

You will also probably have to do something similar with your entitlement aggregations so the entitlements are “linked” to the account entitlements. I did something similar for a JDBC connector, so I don’t have exactly what you would need for a customizer. But if you are looking to have these entitlements added/removed my ISC, you might have to do a bit more work. Let me know if this is something you need help with. If you are just looking for this data to appear as expected in account aggregations and you aren’t looking to add/remove entitlements, then the first 2 customizer commands I mentioned should suffice.

Thank you,

  • Zach

Web services SaaS connectors don’t use before/after operation rules, they use customizers as @zachm117 mentioned

Great, thank you all - I’ll look into customizers and see what is possible with this :)… I am aware we can’t use rules for SaaS connectors, so was hoping there was something we could piece together via the UI on the connector itself… Sounds like I’ll need to look into a customizer… If I get stuck, might ping you down the line @zachm117 - cheers!

We’ll see re Entitlement Aggregations… I’m hopeful that these seem a bit more straight forward to manage, I’ve set up the connector with multiple entitlement types:

  • Groups (to represent Entitlements on the Accounts)
    • Essentially Role + Auth Zone
  • Authorisation Zone
  • Roles

This way if we ever get to provisioning, I am hopeful we can reference these objects without much complication.

The similar connector I worked on had permissions and permission levels. Something like this:

  • permissionA
    • id = P1
    • name = permissionA
  • permissionB
    • id = P2
    • name = permisssionB
  • permissionC
    • id = P3
    • name = permissionC

Levels:

  • Read
    • id = L1
    • name = Read
  • Update
    • id = L2
    • name = Update
  • Delete
    • id = L3
    • name = Delete

When I aggregated these into entitlements, I combined them into this format:

  • id = permissionId + : + levelId
    • Ex. P1:L1
  • name = permissionName + “ - “ + levelName
    • Ex. permissionA - Read

The combined id value was stored in an account attribute called Permissions so it would show the id for each Permission combination a user had.

It is also the id for the Entitlement so ISC can correctly associate the permissions an account has with the results from an entitlement aggregation. The entitlement name was the concatenated name values.

So I have one Entitlement type that represents any unique combination of permissions and levels. The displayName for the entitlement shows both parts, and the id has both the permissionId and the levelId. When it comes to provisioning (adding or removing), I can separate the entitlementId to have the id for each permission and level to pass in the payload to the source. I did it this way because I wanted to have all of the permission/level combinations and because you couldn’t have one without the other (you could have just a Level, for example). Since an account could have permissionA:Read and permissionA:Delete, I needed to have a unique id for each combination so IDN would know what to add/remove.

Hope this helps, feel free to reach out if you have a question!

  • Zach

Very nice, well done mate, I am sure the company you work for are cheering having that sort of solution in place!

This is very similar to what I’m trying to achieve. Admittedly I’ve never used customizers or the CLI interface much before so I’ll spend some time learning how this works and give it a crack…

Ideally from the account aggregation, I want to do Permission A + Permission B to then map to the schema for the account attribute I set as the multi valued Entitlement so we see something like:

User #12345

Entitlements:

  • Permission A + Perm B
  • Permission A + Perm C
  • Permission A + Perm B
  • Permission G + Perm B
  • Permission H + Perm B

etc…

Would you be comfortable to share any customizer logic that works which could achieve this? Just to help get me started… All good if not!

The alternative is that we have access to the source code for target system, so they might be able to edit the endpoint to return these concat for us. But I’m exploring all options :slight_smile:

I can try to put something together, but I don’t have anything on hand. I’ve done this for JDBC and for VA based Web Services using rules, but I haven’t made a customizer for this yet. Is using a Web Services SaaS connector a hard requirement for you over using a VA-based Web Services connector? I’d still be happy to try and point you in the right direction with a customizer if SaaS is a hard requirement, just might take me some more time.

To try and get you started, I had the Developer Community chatbot throw something together. At first glance, I don’t see any glaring issues. I figured this would be a decent start since I made a few assumptions on what you are looking for and I don’t currently have a good setup to test this myself.

This customizer would run after an entitlement aggregation. Unless you have a better endpoint to get all of the Role:AuthorizationZone combinations, I stuck with getting all of the available combinations returned from the /Users endpoint example you provided earlier. In this case, Account Aggregations and Entitlement Aggregations will make the same API call. If a Role doesn’t have an AuthorizationZone listed, or the AuthorizationZone is marked as deleted (again, pulling these scenarios from the data you provided earlier), It will use “0” for the AuthorizationZone.id.

You would also need to setup these customizers to ensure everything works as expected (from what I assume you expect): Account Aggregation (afterStdAccountList), Single Account Aggregation (afterStdAccountRead), and Account Modify for Add/Remove Entitlements (beforeStdAccountUpdate).

AI Generated Customizer for Entitlement Aggregation

import { ConnectorError, logger, Response, StdEntitlementListOutput } from '@sailpoint/connector-sdk'

export class CustomizerExample {
    /**
     * Process entitlement list after standard aggregation
     * Extracts entitlements from Roles array in user data
     */
    async afterStdEntitlementList(context: Context, output: StdEntitlementListOutput): Promise<StdEntitlementListOutput> {
        logger.info("Starting afterStdEntitlementList customizer")
        
        try {
            // Get the raw data from the standard entitlement list
            // This assumes you're using the same endpoint for both account and entitlement aggregation
            const response: Response = await context.readConfig('webServiceRequest')
            
            if (!response || !response.data) {
                throw new ConnectorError("No response data available")
            }

            // Clear existing entitlements to rebuild from user data
            output.data = []

            // Track unique entitlements to avoid duplicates
            const uniqueEntitlements = new Map<string, any>()

            // Process each user record
            const users = Array.isArray(response.data) ? response.data : [response.data]
            
            for (const user of users) {
                if (!user.Roles || !Array.isArray(user.Roles)) {
                    continue
                }

                // Process each role
                for (const role of user.Roles) {
                    let authZoneId = '0'
                    let authZoneName = 'No Authorization Zone'
                    let authZoneDeleted = false

                    // Check if AuthorizationZone exists and is not deleted
                    if (!role.AuthorizationZone) {
                        logger.warn(`Role ${role.Id} missing AuthorizationZone, using default 0`)
                    } else if (role.AuthorizationZone.Deleted === true) {
                        logger.debug(`Role ${role.Id} has deleted AuthorizationZone, using default 0`)
                        authZoneDeleted = true
                    } else {
                        // Use actual AuthorizationZone data
                        authZoneId = role.AuthorizationZone.Id.toString()
                        authZoneName = role.AuthorizationZone.Name
                    }

                    // Construct entitlement ID: roleId:authZoneId (e.g., "3:1" or "3:0")
                    const entitlementId = `${role.Id}:${authZoneId}`
                    
                    // Construct entitlement name: roleName - authZoneName
                    const entitlementName = `${role.Name} - ${authZoneName}`

                    // Only add if we haven't seen this entitlement before
                    if (!uniqueEntitlements.has(entitlementId)) {
                        const entitlement = {
                            identity: entitlementId,
                            uuid: entitlementId,
                            attributes: {
                                id: entitlementId,
                                name: entitlementName,
                                roleId: role.Id.toString(),
                                roleName: role.Name,
                                roleDescription: role.Description || '',
                                roleIsSystem: role.IsSystem || false,
                                authorizationZoneId: authZoneId,
                                authorizationZoneName: authZoneName,
                                authorizationZoneDeleted: authZoneDeleted
                            }
                        }

                        uniqueEntitlements.set(entitlementId, entitlement)
                        output.data.push(entitlement)
                        
                        logger.debug(`Added entitlement: ${entitlementName} (${entitlementId})`)
                    }
                }
            }

            logger.info(`Processed ${output.data.length} unique entitlements from ${users.length} users`)
            
            return output

        } catch (err: any) {
            const errorMessage = `Error in afterStdEntitlementList: ${err.message}`
            logger.error(errorMessage)
            throw new ConnectorError(errorMessage)
        }
    }
}

This is great, thank you Zach - plenty for me to start with!

Still trying to setup the customiser, need to install npm package which is blocked by security policy, so currently trying to get that sorted out. But once I do, I’ll return here and start chipping away at it.

Yeh we have two separate endpoints, one to collect Roles & one to collect Auth Zones; however currently there is no endpoint that produces every potential combination.

In terms of VA vs SaaS connector, we want to have as little reliance on on-prem resources as possible, so we’re exploring every SaaS option before looking to build a VA connector.

Cheers!