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
|
GET {{api-url}}/cc/api/source/get/{{source-id}} |
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 Name | Account Attribute | Source ID | Source Name |
allMailAddresses | mail | 2c9180867745f3b10177469563be7451d | AD Source 1 |
| | 2c9180867745f3b10177469563be7451e | AD Source 2 |
| | 2c9180867745f3b10177469563be7451f | AD Source 3 |
allProxyAddresses | proxyAddresses | 2c9180867745f3b10177469563be7451d | AD Source 1 |
| | 2c9180867745f3b10177469563be7451e | AD Source 2 |
| | 2c9180867745f3b10177469563be7451f | AD Source 3 |
allUserPrincipalNames | userPrincipalName | 2c9180867745f3b10177469563be7451d | AD Source 1 |
| | 2c9180867745f3b10177469563be7451e | AD Source 2 |
| | 2c9180867745f3b10177469563be7451f | AD Source 3 |
Create each of the attribute via Create Search Attribute Config API
|
POST {{api-url}}/beta/accounts/search-attribute-config/ BODY { "name": "allMailAddresses", "displayName": "All Mail Attributes", "applicationAttributes": { "2c9180867745f3b10177469563be7451d": "mail", "2c9180867745f3b10177469563be7451e": "mail", "2c9180867745f3b10177469563be7451f": "mail" } } |
Similarly do it for other 2 attributes as well
|
POST {{api-url}}/beta/accounts/search-attribute-config/ BODY { "name": "allProxyAddresses", "displayName": "All Proxy Addresses Attributes", "applicationAttributes": { "2c9180867745f3b10177469563be7451d": "proxyAddresses", "2c9180867745f3b10177469563be7451e": "proxyAddresses", "2c9180867745f3b10177469563be7451f": "proxyAddresses" } } |
|
POST {{api-url}}/beta/accounts/search-attribute-config/ BODY { "name": "allUserPrincipalNames", "displayName": "All Mail Attributes", "applicationAttributes": { "2c9180867745f3b10177469563be7451d": "userPrincipalName", "2c9180867745f3b10177469563be7451e": "userPrincipalName", "2c9180867745f3b10177469563be7451f": "userPrincipalName" } } |
Once all are created you can do a GET call to check all attributes
|
GET {{api-url}}/beta/accounts/search-attribute-config/ |
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.
|
POST {{api-url}}/cc/api/source/loadAccounts/{{source-id}} BODY form-data KEY: disableOptimization VALUE: true |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
import sailpoint.object.Identity; import org.apache.commons.lang.StringUtils; List proxyAddressSources = new ArrayList(Arrays.asList(new String[] { "2c9180867745f3b10177469563be7451d", "2c9180867745f3b10177469563be7451e", "2c9180867745f3b10177469563be7451f" })); List upnSources = new ArrayList(Arrays.asList(new String[] { "2c9180867745f3b10177469563be7451d", "2c9180867745f3b10177469563be7451e", "2c9180867745f3b10177469563be7451f" })); List mailSources = new ArrayList(Arrays.asList(new String[] { "2c9180867745f3b10177469563be7451d", "2c9180867745f3b10177469563be7451e", "2c9180867745f3b10177469563be7451f" })); public String generateUniqueEmail(String fName, String lName, int iteration) throws Exception { if (iteration > 99) { throw new Exception("emailPrefix counter limit 99!"); } String emailPrefix = fName + "." + lName; if (isUnique(emailPrefix)) { return emailPrefix; } else { return generateUniqueEmail(fName, lName, iteration + 1); } } public boolean isUnique(String emailPrefix) { String startWithOp = "StartsWith"; boolean isUnique = true; List searchValues = new ArrayList(Arrays.asList(new String[] { "smtp:" + emailPrefix + "@", "sip:" + emailPrefix + "@" })); // check proxy addresses if (idn.attrSearchCountAccounts(proxyAddressSources, "allProxyAddresses", startWithOp, searchValues) == 0) { // check UPNs searchValues = new ArrayList(Arrays.asList(new String[] { emailPrefix + "@" })); if (idn.attrSearchCountAccounts(upnSources, "allUserPrincipalNames", startWithOp, searchValues) == 0) { // check mails if (idn.attrSearchCountAccounts(mailSources, "allMailAddresses", startWithOp, searchValues) > 0) { isUnique = false; } } else { isUnique = false; } } else { isUnique = false; } return isUnique; } String generatedUniqueEmail = null; if (identity != null) { String emailSuffix = StringUtils.trimToNull(identity.getAttribute("emailSuffix")); String fname = StringUtils.trimToNull(identity.getAttribute("firstname")); String lname = StringUtils.trimToNull(identity.getAttribute("lastname")); if (fname != null && lname != null && emailSuffix != null) { fname = fname.replaceAll("[^a-zA-Z0-9]", ""); fname = fname.toLowerCase(); lname = lname.replaceAll("[^a-zA-Z0-9]", ""); lname = lname.toLowerCase(); generatedUniqueEmail = generateUniqueEmail(fname, lname, 1) + emailSuffix; } } return generatedUniqueEmail; |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
import sailpoint.object.*; import java.util.*; import org.apache.commons.lang.StringUtils; List adSource = new ArrayList(Arrays.asList(new String[] { "2c9180867745f3b10177469563be7451d" })); String employeeNumberSearchAttribute = "allADEmployeeNumbers"; Map returnMap = new HashMap(); String employeeNumber = StringUtils.trimToNull(account.getStringAttribute("employeeNumber")); if (employeeNumber != null) { String identityName = null; List searchValues = new ArrayList(Arrays.asList(new String[] { employeeNumber })); identityName = idn.attrSearchGetIdentityName(adSource, employeeNumberSearchAttribute, "Equals", searchValues); if (identityName != null) { returnMap.put("identityAttributeName", "name"); returnMap.put("identityAttributeValue", identityName); } else {} } return returnMap; |
- 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!!!
Like this:
Like Loading...