Pass Non-Provisioned Attributes from Before Provision rule to Connector Rules

Introduction

A common use case when developing for provisioning is the need to provide information to a connector rule that is not available on the virtual appliance (VA). This can occur because you need identity information to perform additional logic, or because a request payload requires information that is not actively changing in the account.

The first case can often occur in Active Directory and Azure Native Rules, where a developer must take additional actions based on Identity attributes that are not part of the provisioning plan. They may not be stored in the target application at all.
The second case is a common occurrence for applications such as WebServices and JDBC applications which will often require all the account information be passed in with each query or request. This information generally already exists on the target account, and could include attributes like first name, last name, employeeId, etc.

This presents a problem. While the Before Provisioning Rule can retrieve these values from the Identity and add them to the provisioning plan as AttributeRequests, IDN will filter these AttributeRequests from the provisioning plan before it reaches the Connector Rule if their values are already detected on the target account.

Both cases are easily solved using AccountRequest Arguments.

This blog assumes that you have an understanding of connector rules and how to deploy them. For more information about rules, please read the rules documentation. You can also check out the rule development kit (RDK), which is a tool that helps with the building and testing of rules.

AccountRequest Arguments

Every AccountRequest has a set of arguments in the form of a sailpoint.object.Atributes object. This object extends java.util.HashMap, and can be accessed through as follows:

// setting the value for attribute named 'foo'
Attributes attributes = accountRequest.getArguments()
if (null == attributes) attributes = new Attributes();
attributes.putClean("foo","value of foo");
accountRequest.setArguments(attributes);

// alternate approach
Object value = accountRequest.addArgument("foo","value of foo");

---
// retrieving the value for attribute named 'foo'
Attributes attributes = accountRequest.getArguments()
Object value = attributes.get("foo");

// alternate approach
Object value = accountRequest.getArgument("foo");

These arguments will not be filtered from the provisioning plan, and can be used to store additional attributes and information from the Before Provisioning Rule to any one of the connector rules (Webservices Before Operation Rule, JDBC Provision rule, etc) which may not have access to the context.

Scenario 1: Active Directory

Example Scenario

The customer has two new hire scenarios: accounts created before hire date and accounts created after hire date. The AD After Create rule must also create an exchange account, and hide it from the GAL for prehire, but not posthire users.

Solution

For this solution, we will pass the lifecycle state of the user as an argument that is not provisioned to Active Directory

BeforeProvision Rule

note this code below uses IDN Java objects in the BeforePovision rule, but can easily be modified to fit IIQ available objects.

If (null != plan){
	Identity identity = plan.getIdentity();
	String lcs = identity.getAttribute(“cloudLifecycleState”);
	List accountRequests = plan.getAccountRequests();
	if (null != accountRequests) {
		for (AccountRequest acctReq : accountRequests) {		
	        //set the attributes on the Account Request with the updated values
			acctReq.addArgument("lcs", lcs);
		}
	}
}

AfterCreate Powershell

Add-type -path utils.dll;
# Create SailPoint objects        
$sReader = New-Object System.IO.StringReader([System.String]$env:Request);
$xmlReader = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($sReader));
$requestObject = New-Object Sailpoint.Utils.objects.AccountRequest($xmlReader);

# retrieve argument from AccountRequest 
# treat the $requestObject.Attributes object as a Map, 
# and retrieve the attributes by the key
$lcs = $requestObject.Attributes.lcs

if($lcs -eq 'active'){
	# create mailbox
} elseif ($lcs -eq 'prehire'){
	#create mailbox and hide from GAL
}

Scenario 2: JDBC

Example Scenario:

JDBC Provisioning rule uses stored procedure that requires firstName, lastName, and manager for every update

Solution

For this solution, we will set arguments individually

BeforeProvision Rule

if (null != plan) {
	Identity identity = plan.getIdentity();
	//Get required attributes from identity that may not be part of the plan
	String firstName = identity.getFirstname();
	String lastName = identity.getLastname();
	String managerName = null;
	if (null != identity.getManager()) {
		managerName = identity.getManager().getDisplayName();
	}
	
	//check account/attribute requests. If values are present in provisioning plan, use those values to prevent potential conflicting information
	List accountRequests = plan.getAccountRequests();
	if (null != accountRequests) {
		for (AccountRequest acctReq : accountRequests) {
			List attributeRequests = acctReq.getAttributeRequests();
			if (null != attributeRequests) {
				for (AttributeRequest req :attributeRequests) {
					if (null != req.getName() && req.getName().equals("firstName")) {
						firstName = req.getValue();
					} else if (null != req.getName() && req.getName().equals("lastName")) {
						lastName = req.getValue();
					} else if (null != req.getName() && req.getName().equals("managerName")){
						managerName = req.getValue();
					}
				}
			}
			
            /*
            * set the attributes on the Account Request 
            * with the updated values
            */
			acctReq.addArgument("firstName", firstName);
			acctReq.addArgument("lastName", lastName);
			acctReq.addArgument("managerName", managerName);
			
		}
	}
}

Scenario 3: WebServices

Example Scenario

Websevices Add and Remove Entitlement payloads function as a ‘Post’ operation, and require payload to include final list of entitlements on the updated account (after add/remove takes place).

Solution

For this Solution, we will use retrieve and set arguments via the Attributes object. note: The objects availble to the Before Provision rule are different between IDN and IIQ. The code below represents IDN implementation. For IIQ, update the code accordingly

Before Provision Rule

if (null != plan) {
List accountRequests = plan.getAccountRequests();
	if (null != accountRequests) {
		for (AccountRequest acctReq : accountRequests) {
			if(AccountRequest.Operation.Modify.equals(acctReq.getOp())) {
                // Get currently assigned entitlements 
                String nativeIdentity = acctReq.getNativeIdentity();
                Account account = idn.getAccountByNativeIdentity(application.getName(), nativeIdentity);
				Map accountAttributes = account.getAttributes();
				List currentRoles = new ArrayList();
				if(null != accountAttributes.get("role")) {
					//add all roles on the account to the currentRoles list
					currentRoles.addAll(getValues(accountAttributes.get("role")));
				}
	            //Get current attribute requests
				List attributeRequests = acctReq.getAttributeRequests("role");
				for (AttributeRequest attributeRequest : attributeRequests) {
					if (attributeRequest.getOperation().equals(ProvisioningPlan.Operation.Add)){
						//if operation is 'add' add role(s) to currentRoles
						attributeRequestsToRemove.add(attributeRequest);
					} else if (attributeRequest.getOperation().equals(ProvisioningPlan.Operation.Remove)){
						//if operation is 'remove' remove role(s) to currentRoles
						currentRoles.removeAll(getValues(attributeRequest.getValue()));
					}
				}
				
                /*
                * Get arguments from Account Request to make sure we 
                * do not overwrite existing arguments
                */
				Attributes attributes = acctReq.getArguments();
				if (null == attribtues) {
					attributes = new Attributes();
				}
				
                /**
                * Add ALL roles as a list to the Attributes (arguments)
                */
				attributes.putClean("currentRoles",currentRoles);
				acctReq.setArguments(attributes);	
			}
		}
	}
}

Before Operation Rule

Then we just have to retrieve the list of roles from the AccountRequest.Attributes

List currentRoles = null;
if (null != provisioningPlan) {
	List accountRequests = provisioningPlan.getAccountRequests();
	if (null != accountRequests) { 
		for (AccountRequest acctReq : accountRequests) {
			Attributes attributes = acctReq.getArguments();
			if (null != attributes) {
				currentRoles = attributes.get("currentRoles");
			}
		}
	}
    // update and format the requestEndPoint with the required data here
}

Conclusion

This approach will allow you to pass specific attribute information, along with metadata you may need for logic in your connector rule (a tag to determine if this operation represents specific type of user, for example).

This is not limited to IdentityNow. IdentityIQ faces the same issue. While an IIQ developer has access to the context, and can work around these limitations, it is worth considering a future where the product and code may be migrated to an IDN instance, where that is not a viable option.

6 Likes

This is a great post and way better than the recommendation I received from ES to use a non-published V3 endpoint to exclude attributes from provisioning you want to pass along to “after” rules. Your solution is particularly useful if you want to push all identity attributes without having to get your before provisioning rule published every time you add a new identity attribute. One could just replace your “lcs” attribute example with a map of attributes. Thanks!

I case anyone is looking for the standard way, here is the endpoint that allows you to exclude particular accountAttributes you added in a before provisioning rule from being included in provisioning, but still accessible in after-provisioning rules.

PATCH /v3/sources/{id}
Content-Type: application/json-patch+json
[
    {
        "op": "add",
        "path": "/connectorAttributes/excludeAttributesFromProvisioning",
        "value": [
            "yourAttributeHere"
        ]
    }
]
1 Like

Additionally, if using the Services Standard Before Provisioning Rule, you can use the ‘AddArgument’ method to update the Attributes argument:
ex. you want to include ‘mail’ from the Identity email attribute:

{
	"Action": "AddArgument",
	"Attribute": "mail",
	"Value": "#{identity.email}"
}
1 Like