ManagerCorrelation Rule Not Falling Back to Secondary Source

I am implementing a ManagerCorrelation rule in SailPoint ISC to correlate maanger from 2 different source systems.

  1. Read maanger employeeID from HR source (Current Manager)
  2. If HR deosnot contains the value, read the manager from Secondary Source (Onboarding - Manager ID)
  3. REturn the correct manager ID for identity correlation.

The HR part works correctly, but when HR doesn’t have data, the rule doesn’t fall back to Secondary Source.

Below is the Rule:

import sailpoint.object.Identity;
import sailpoint.object.Link;

Map returnMap = new HashMap();

// HR Attribute
String hrMgrAttr = "Current Manager";

// Cornerstone Attribute
String ssMgrAttr = "Onboarding - Manager ID";

// Values
String hrMgr = link.getAttribute(hrMgrAttr);
log.debug("HR Manager ID extracted: " + hrMgr);
String ssMgr = null;

// Extract manager ID from secondary source
Identity id = link.getIdentity();
for (Link l : id.getLinks()) {
    if (l.getApplicationName() != null &&
        "Secondary-Source".equalsIgnoreCase(l.getApplicationName())) {
        ssMgr = l.getAttribute(ssMgrAttr);
    }
}

// Final managerId
String finalMgrId = null;

if (hrMgr != null && hrMgr.trim().length() > 0) {
    finalMgrId = hrMgr;
} else if (ssMgr != null && ssMgr.trim().length() > 0) {
    finalMgrId = ssMgr;
}

if (finalMgrId == null) {
    return null;
}

returnMap.put("identityAttributeName", "uid");
returnMap.put("identityAttributeValue", finalMgrId);
return returnMap;

I have attached the manager correlation rule only to HR source and executing HR aggregations and observed below behaviors.

Observed Behavior:

HR value exists & Secondary Source value exists → manager mapped successfully using HR data in user’s identity profile.

HR value exists & Secondary Source value doesn’t exists → manager mapped successfully using HR data in user’s identity profile.

HR value doesn’t exists (= null) & Secondary Source value exists - > no manager mapping in user’s identity profile.

Any guidance or recommended patterns for correlating manager from multiple sources would be appreciated.

@thiruppathit

you can try the below code it check the correlation from multiple sources


import sailpoint.object.Link;
import sailpoint.object.QueryOptions;
import sailpoint.object.Filter;
import sailpoint.object.Identity;
import java.util.Map;
import java.util.HashMap;
  
  
log.error("Rule Name: HR Manager Correlation Rule - Message = Creating list of applications");
//Creating a list of all the HR applications to search for.
List applications = new ArrayList();
applications.add("HR - ONE");
applications.add("HR - TWO");



/**
	Gets the manager identity link from HR system. "Reporting Officer ID" attribute of the subordinate HR record will be matched against "Employee Number" / account name record of the manager HR record.
	
	@param employeeNumber employeeNumber of the manager.
	@return manager identity if available.Else null value will be returned.
*/
Identity getManagerIdentity(String employeeNumber){
	log.error("Entering the method - getManagerIdentity - employeeNumber = "+ employeeNumber );
	//QueryOptions and filters are built for querying the account in HR sources.
	QueryOptions qo = new QueryOptions();
	qo.addFilter(Filter.eq("nativeIdentity", employeeNumber));
	qo.addFilter(Filter.in("application.cloudDisplayName", applications));
	//Getting links based on the filters built. This should match with only 1 account as per the customer data / design.
	List links = context.getObjects(Link.class, qo);
	//No links found or duplicates found.
	if( links.size() != 1 ){
		//Manager can't be set in this case. Hence, return null.
		log.error("Exiting the method  - getManagerIdentity - identity = " + links.size());
		return null;
	}
	else{
		//returns identity of matching HR record.
		Identity identity = links.get(0).getIdentity();
		log.error("Exiting the method  - getManagerIdentity - identity = " + identity.getName());
		return identity;
	}
	// safety null return 
    log.error("Exiting the method  - getManagerIdentity - identity = null");
	return null;
}

  
//Find reporting ID number based on RO ID.
String managerEmployeeNumber = null;
if( link != null)
{	
	managerEmployeeNumber = link.getAttribute( "Reporting Officer ID" );
  log.error("Rule Name: HR Manager Correlation Rule - Message = Link not null and RO ID is " + managerEmployeeNumber);
}
else{
	log.error("Rule Name: HR Manager Correlation Rule - Message = Link is null");  
}

//Creating an empty return map
Map returnMap = new HashMap();

//No manager correlation if there is no manager employee number found.
if(managerEmployeeNumber == null)
{
	return returnMap;
}
else{  // Retrieve manager identity based on the manager employee number.
	Identity managerIdentity = getManagerIdentity(managerEmployeeNumber);
  
	if(managerIdentity == null){
		log.error("Rule Name: HR Manager Correlation Rule - Message = Manager Identity is null");  
		return returnMap;// No manager correlation manager is not found.
	}
	else{ //Correlation based on uid attribute (SailPoint user name attribute from Identity profile) in case manager identity is found.
		log.error("Rule Name: HR Manager Correlation Rule - Message = Manager Identity = " + managerIdentity.getName());
		String uid = managerIdentity.getStringAttribute("uid");
    
		returnMap.put("identityAttributeName", "uid");
		returnMap.put("identityAttributeValue", uid);
		log.error("Rule Name: HR Manager Correlation Rule - Message = Manager UID = " + uid);
		return returnMap;
	}
}

  return returnMap;

Thanks @schattopadhy for sharing the sample rule.

Getting rule validation failure when try with the shared code. Rule validator throws errors stating ‘getObjects’ are not allowed. please refer the below screeshot.

How did you manage to get this deployed into your tenant?

try with “Secondary-Source [source]” here

@thiruppathit this rule is running but might be some classes might not be used any more.you check once with sailpoint

Sure will check and let you know. Is there any specific reason you recommend to add [source] with the source name to compare?

sure. Thanks @schattopadhy

This is the format to be used in Cloud rules when you are referring to a source by it’s name

SailPoint does not allow context object to be used in cloud rules anymore

@colin_mckibben do you have any suggestions?

I tried with the suggested format , but no luck.

if you can have an identity attribute, you can map it in you correlation directly with below transform :

“name”: “ManagerFromMultiSources”,
“type”: “static”,
“attributes”: {
“value”: “#set($primaryLinks = $identity.getLinksByAppIdOrName(‘123456789123456789’, null)) #foreach($pl in $primaryLinks) #if($pl.getAttribute(‘mgrID’)) $pl.getAttribute(‘mgrID’)#if($foreach.hasNext), #end #else #set($secondaryLinks = $identity.getLinksByAppIdOrName(‘987654321123456789’, null)) #foreach($sl in $secondaryLinks) #if($sl.getAttribute(‘ssMgrID’) ) $sl.getAttribute(‘ssMgrID’)#if($foreach.hasNext), #end #end #end #end #end
},
“internal”: false
}

Hi @lampard08,

Thank you so much for your response. I tried with the shared transform but it always set manager correlation only from HR source. If manager details doesn’t exists in HR , it doesn’t fall back to Secondary Source. Below is my sample Transform, Could you please correct me what exactly the problem is with my transform,

{
  "name": "ManagerFromMultiSources",
  "type": "static",
  "attributes": {
    "value": "#if( $identity.getLinksByAppIdOrName('01be94060209437xxxxxxx', null)[0].getAttribute('Current Manager') != '') $identity.getLinksByAppIdOrName('01be940602094371bbcxxxxxxxx', null)[0].getAttribute('Current Manager') #elseif( $identity.getLinksByAppIdOrName('6d1fd0d00bce4298851f216a60b06c8a', null)[0].getAttribute('Onboarding - Onboarding User Future Manager ID') != '') $identity.getLinksByAppIdOrName('6d1fd0d00bce4298851f216a60b06c8a', null)[0].getAttribute('Onboarding - Onboarding User Future Manager ID') #elseif #set( $result = '' ) $result #end"
  },
  "internal": false
}

btw , I have attached this transform to maanger (Manager Name) attribute.

manager attribtue has below reference,

HR System (source) → Current Manager (Attribute) → Transform (ManagerFromMultiSources)

Note: Also i noticed if i use firstValid , it fetches the maangerID properly but the manager correlation doesn’t occurs.

Sample Transform with firstValid:

{
        "id": "a7ad9018-3864-4a95-8843-c3cc6cd941bd",
        "name": "ManagerFromMultiSources30",
        "type": "firstValid",
        "attributes": {
            "values": [
                {
                    "type": "static",
                    "attributes": {
                        "value": "#set($primaryLinks = $identity.getLinksByAppIdOrName('01be940602094371bbcb4a33816ba19f', null)) #foreach($pl in $primaryLinks) #if($pl && $pl.getAttribute('Current Manager')) $pl.getAttribute('Current Manager') #end #end"
                    }
                },
                {
                    "type": "static",
                    "attributes": {
                        "value": "#set($secondaryLinks = $identity.getLinksByAppIdOrName('6d1fd0d00bce4298851f216a60b06c8a', null)) #foreach($sl in $secondaryLinks) #if($sl && $sl.getAttribute('Onboarding - Onboarding User Future Manager ID')) $sl.getAttribute('Onboarding - Onboarding User Future Manager ID') #end #end"
                    }
                },
                null
            ],
            "ignoreErrors": true
        },
        "internal": false
    }

I just come across this KB article from SP stating we can’t use Transform to correlate manager from multiple source accounts. Reference : Support Articles - Transform does not pull correct manager with multiple authoritative source accounts - Customer Support

Is it working in your Tenant?

try this and let me know ….

{
“name”: “ManagerFromMultiSources”,
“type”: “static”,
“attributes”: {
“value”: “#if( $identity.getLinksByAppIdOrName(‘01be94060209437xxxxxxx’, null)[0].getAttribute(‘Current Manager’) && !$identity.getLinksByAppIdOrName(‘01be94060209437xxxxxxx’, null)[0].getAttribute(‘Current Manager’).empty ) $identity.getLinksByAppIdOrName(‘01be940602094371bbcxxxxxxxx’, null)[0].getAttribute(‘Current Manager’) #elseif( $identity.getLinksByAppIdOrName(‘6d1fd0d00bce4298851f216a60b06c8a’, null)[0].getAttribute(‘Onboarding - Onboarding User Future Manager ID’)) $identity.getLinksByAppIdOrName(‘6d1fd0d00bce4298851f216a60b06c8a’, null)[0].getAttribute(‘Onboarding - Onboarding User Future Manager ID’) #elseif #set( $result = ‘’ ) $result #end
},
“internal”: false
}

you are right. I tried this in my tenant. while i was able to get the data from both sources, the manager name was not updated. Let me see why your rule is not working.

here is one issue i found.

in this piece:

for (Link l : id.getLinks()) {
if (l.getApplicationName() != null &&
“Secondary-Source”.equalsIgnoreCase(l.getApplicationName())) {
ssMgr = l.getAttribute(ssMgrAttr);
}
}

id.getLinks() will return a list of links but you are assigning it to single link object which will fail and are never going to get the send source data.

try below :

Identity id = link.getIdentity();
List<Link> links = id.getLinksByAppIdOrName('12345678900987654321', null);
if (links != null && !links.isEmpty()){
for (Link l : links) {
    
        ssMgr = l.getAttribute(ssMgrAttr);
}

Thanks a lot Chelsea. This rule is working fine.

Rule along with the Transform is the way to achieve the manager correlation from multiple source systems.