Hey Folks!!!

Anyone well versed with IDN Rules will know that there has been some historic limitations on what attributes we can and can’t use for uniqueness search or for other such methods. We were generally tied to “Account ID” and “Account Name” attribute in each source which were indexed in the DB which could be used in rules to do so. 

Workaround till date had been to promote these attributes to an identity attribute and index them (which again had limitations of it own in terms on number of attributes and size of attribute).

Now we have a way to do this. Personally been involved with the product team and engineers in getting this shaped and delivered so a bit proud of this work. It is a great first start and will help you in doing so many use cases much simpler now. 

Lets take a use case and a walkthrough on how you can set this up

Use Case – I

We want to generate a new email address which must have a unique prefix (firstname.lastname@) by checking against the “mail”, “userPrincipalName”, “proxyAddresses” attributes across 3 x AD connectors. 

Note: Sources don’t have to be AD explicitly and can be virtually any source (AAD, ServiceNow, Okta, Workday etc) 

Design

Now anyone who has worked with rules before would have known that this use case was not very easy to implement. You would have to promote these attributes, index identity attribute and then use it in the rule. Now we can create searchable attributes in the backend via API and use these in the rule. 

High Level Steps are

  • Identify Source ID and attributes
  • Create Searchable Attributes
  • Do an unoptimised aggregation if source already exists (like production tenant) to populate these searchable attributes.
  • Use new methods in rules to search for uniqueness

Identify Source ID and Attributes

Now we have 3 x AD source in our design. For each of them we need to get their sourceID. You can fetch them with an API call

Where

  • {{api-url}}: This is your tenant URL in form of https://tenantName.api.identitynow.com
  • {{source-id}}: This is the number you see in your browser URL when visiting the source via UI

In the response you will get an externalId with a value like 2c9180867745f3b10177469563be7451d

Gather the externalId for all three sources you want to build it for. 

Create Searchable Attributes

Now when you have all the SourceID’s we need to map and create searchable attributes. We will design 3 attributes (one for each – mail, userPrincipalName, proxyAddresses). It takes care of multivalued attributes as well (like proxyAddresses). The below table explains the design 

Search Attribute NameAccount AttributeSource IDSource Name
allMailAddressesmail2c9180867745f3b10177469563be7451dAD Source 1
2c9180867745f3b10177469563be7451eAD Source 2
2c9180867745f3b10177469563be7451fAD Source 3
allProxyAddressesproxyAddresses2c9180867745f3b10177469563be7451dAD Source 1
2c9180867745f3b10177469563be7451eAD Source 2
2c9180867745f3b10177469563be7451fAD Source 3
allUserPrincipalNamesuserPrincipalName2c9180867745f3b10177469563be7451dAD Source 1
2c9180867745f3b10177469563be7451eAD Source 2
2c9180867745f3b10177469563be7451fAD Source 3

Create each of the attribute via Create Search Attribute Config API

Similarly do it for other 2 attributes as well

Once all are created you can do a GET call to check all attributes

You should see all the above listed. 

Populate Searchable Attributes

Now if these are existing sources with accounts in them, simply do a once off unpotimized aggregation of each source via the following API call. 

Once done for all sources, the search attributes get populated in the backend. Currently you can’t check them with the UI or API calls. Any new accounts which get created after this or come as aggregation (delta or full) will automatically keep updating the search attribute. So once set, you don’t have to do anything. Also new account created is populated immediately. So no need of aggregation of it. So if you are creating multiple users in concurrent – uniqueness check will still capture the previous value calculated – no need to wait for aggregation.

Use New Methods in the Rules

Our IDNRuleUtil guide has been updated with few new methods. Two in particular which use these attributes are 

attrSearchCountAccounts(): This will be helpful to use for uniqueness search

attrSearchGetIdentityName(): This will be helpful in say a correlation rule. 

The link above has a bit more technical in-depth on what parameters are required by these methods and what is the return. But I will show you how to use the attrSerachCountAccounts() method in an example of uniqueness search. 

AttributeGenerator Rule

Will short type the logic what I have followed here. 

  • Create a list of 3 attributes with sourceID in them. This is required to pass the list to the new method.
  • Get the firstName and lastName from identity attribute, sanitise it and pass it to generateUniqueEmail() method
  • emailSuffix is also brought from identity attribute. This logic is already done via Transforms on what user email domain or suffix is suppose to be
  • In generateUniqueEmail() create a emailPrefix attribute and call isUnique() method.
  • isUnique() is where the magic happens
    • We are using StartsWith option (there is Equals also available). Reason being all the data coming from AD will be in some form of “[email protected], [email protected]”. We want to match “firstname.lastname@” only as we don’t care about exact match or equals match. Remember both are case-insensitive checks.
    • First check with proxyAddressSources in the idn.attrSearchCountAccounts() call
      • Here we have appended SMTP / SIP to do a startsWith check as we know that these are the known values we need to check in proxy address. We don’t have a contains. Also since its case-insensitive we don’t need to worry about camel casing or other such things. Data normally looks like “smtp:[email protected]” , “SIP:[email protected]”.
      • There are other such prefix found if proxyAddress attribute which we didn’t care about check but if your use case does, just add them and search as above.
      • If return == 0 means nothing is found, thus unique and move on to next check
      • Else isUnique == false and will return that value
    • Else check with upnSources source. Same logic as above
    • Else check with mailSources source. Same logic as above
      • If no account found here, isUnique is set to true and thus isUnique() has passed and the new email address is generated
    • If still a value is found, isUnique is set to false and thus isUnique() failed and new iteration starts
  • Max Iteration is set to 99 and then fail

Use Case – II

Rehire an account who comes back after 5 years with the same AD account. 

Design

Now the account could be sitting as an uncorrelated account in IDN and we want to resurrect it. We can only search against the accountID or accountName as discussed previously and say if the correlating factor is employeeID which is not a part of either of these attributes.

Correlation Rule

  • We create an attribute like above called “allADEmployeeNumbers” and populate that with AD – employeeID (or whatever you may have)
  • We do an unoptimised aggregation to populate the search attribute
  • On an aggregation of an authoritative source, we have a correlation rule attached
    • It grabs the “employeeNumber” coming in from the source
    • Calls attrSearchGetIdentityName() to find that employeeNumber in the searchable attribute.
      • If found it returns an IdentityName of the cube and this can be used to correlate
      • If not found it returns an empty returnMap i.e. creates a new identity.
  • During design we took care of the following scenarios
    • It should return null if it found multiple names or no names. 
    • It should return one identityName even if multiple links were found but single identityName (i.e. say if you had multiple employeeNumbers in links but all are attached to cube). Like in a daisy chain scenario.

Possibilites

You can see the power of these new methods and how you can use them. Some of my thoughts here on possibilities. 

  • We can daisy chain them like above to search for multiple attributes in multiple sources as per your pattern.
  • We can use these for more attributes from accounts without the need to identity attribute creation and hitting limits on indexing.
  • Resurrections with attributes not indexed is also now pretty easy. 

Hope this really help you in your future implementations!!!


6 Comments

Nitin · 11/15/2021 at 9:57 PM

Hi,
I am not able to run the Create Search Attribute Config API. I am getting the below error:
{
“detailCode”: “400.0 Bad request syntax”,
“trackingId”: “65f14e196c4e47d7ba6986fe14bb34b0”,
“messages”: [
{
“locale”: “en-US”,
“localeOrigin”: “DEFAULT”,
“text”: “The request could not be parsed.”
}
],
“causes”: []
}

Body of my API call is

{
“name”: “allMailAddresses”,
“displayName”: “All Mail Attributes”,
“applicationAttributes”: [
{
“2c9180877cb94719017cbb93a3572298”: “mail”
}
]
}

Can you help what could be the issue.

    Piyush Khandelwal · 11/15/2021 at 10:18 PM

    @Nitin looks like API call has changed.. No []

    Please try

    {
    “name”: “allMailAddresses”,
    “displayName”: “All Mail Attributes”,
    “applicationAttributes”: {
    “2c9180877cb94719017cbb93a3572298”: “mail”
    }
    }

      Nitin · 11/16/2021 at 12:39 AM

      Thank you very much Piyush, this worked.
      From where I can get the updated API, it seems Sailpoint documentation still show this old content in body.

        Piyush Khandelwal · 11/16/2021 at 6:30 AM

        Bug is filed for it and also the guide above has been updated to reflect it.

          Nitin · 11/22/2021 at 7:13 PM

          HI Piyush,

          Could you please help me in knowing how can we put logger statements in IDNow rules to check the values and which logs we need to see the values.

          Thanks
          Nitin

Dipen · 03/18/2022 at 1:20 AM

I wrote this rule to generate a unique sAMAccountName for our privileged sources. We have 5 domains that manage privileged accounts. The idea here is “if an account is created for a user in any one of the domains, it returns the same value for account creation on other domain. Also, the rule takes the first 3 and last 3 initials to generate sAMAccountName. So. if rakshy-y exists in source A, then for someone with the similar name it should generate rakshy1-y in source B.

import sailpoint.object.Identity;
import org.apache.commons.lang.StringUtils;

int maxIteration = 100;
int counter = 1;

List sAMAccountSources = new ArrayList(Arrays.asList(new String[] {
“2c9180857de34736017dfcc56e426c60”,
“2c9180867acb0c34017aea244a2c6a99”,
“2c9180877de347a8017dfcc448f46aee”,
“2c9180877de347a8017dfcc634506b0b”,
“2c9180897de347a2017dfcc70dc65698”
}));

public String generateUniquePrivilegedSamaccountName(String fName, String lName, int iteration) {
if (iteration == 0){
iteration = 1;
}
switch ( iteration )
{
case 1:
privilegedsAMAccountName = fName.substring(0,3) + lName.substring(0,3) + “-y”;
if(privilegedsAMAccountName.length() > 20){
privilegedsAMAccountName = privilegedsAMAccountName.substring(0,20);
}
break;
default:
privilegedsAMAccountName = fName.substring(0,3) + lName.substring(0,3) + counter + “-y” ;
if(privilegedsAMAccountName.length() > 20){
privilegedsAMAccountName = privilegedsAMAccountName.substring(0,20);
}
counter++;
break;
}

if (isUnique(privilegedsAMAccountName)) {
return privilegedsAMAccountName;
} else {
return generateUniquePrivilegedSamaccountName(fName, lName, iteration + 1);
}
}

public boolean isUnique(String privilegedsAMAccountName) {

String startWithOp = “Equals”;
boolean accountNotFound = true;
List searchValues = new ArrayList(Arrays.asList(new String[] {
privilegedsAMAccountName
}));

if (idn.attrSearchCountAccounts(sAMAccountSources, “sAMAccountName”, startWithOp, searchValues) == 0) {
searchValues = new ArrayList(Arrays.asList(new String[] {privilegedsAMAccountName}));
} else {
accountNotFound = false;
}
return accountNotFound;
}

String generatedUniquePrivilegedSamaccountName = null;

if (identity != null) {

String fName = StringUtils.trimToNull(identity.getAttribute(“firstname”));
String lName = StringUtils.trimToNull(identity.getAttribute(“lastname”));

if (fName != null && lName != null) {

fName = fName.replaceAll(“[^a-zA-Z0-9]”, “”);
fName = fName.toLowerCase();

lName = lName.replaceAll(“[^a-zA-Z0-9]”, “”);
lName = lName.toLowerCase();

generatedUniquePrivilegedSamaccountName = generateUniquePrivilegedSamaccountName(fName, lName, 1) ;
}
}

return generatedUniquePrivilegedSamaccountName;

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.