This article will show how to use the recently released feature on creating indexed attributes which can be referenced in rules to do uniqueness searches and generate attributes like sAMAccountName / email / userPrincipalName etc.
It allows you to search accounts across sources to determine if a specific attribute value is already in use in those sources and help generate a new unique value.
Lets take a use case and a walkthrough on how you can set this up
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)
High Level Steps are
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
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.
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 | 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 in the response.
Now if these are existing sources with accounts in them, simply do a once off unoptimized 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
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.
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!");
}
switch ( iteration ) {
case 0:
String emailPrefix = fName + "." + lName;
break;
default:
String emailPrefix = fName + "." + lName + String.valueOf(iteration)
break;
}
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, 0) + emailSuffix;
}
}
return generatedUniqueEmail;
Rehire an account who comes back after 5 years with the same AD account.
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 historically and say if the correlating factor is employeeID which is not a part of either of these attributes then we couldn't do it.
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;
Hello,
I think something is missing on the increment part.
With this, the increment is never used :
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);
}
}
Maybe you should update with adding a switch between :
throw new Exception("emailPrefix counter limit 99!");
}
and
if (isUnique(emailPrefix)) {
Which give you someting like that :
throw new Exception("emailPrefix counter limit 99!");
}
switch ( iteration ) {
case 0:
String emailPrefix = fName + "." + lName;
break;
default:
String emailPrefix = fName + "." + lName + String.valueOf(iteration)
break;
}
if (isUnique(emailPrefix)) {
Please check and update or tell me if i'm wrong.
Best regards.
@pedrolit0 I feel like you are right. But I am saying just by looking at the code and not really running it. The iteration needs to run with an append of the new iteration count.
@pedrolit0 you are right. I had removed some specific logic from code to use as example here which caused the issue. Updated with your code. Thanks for pointing it out.
@piyush_khandelwal I just need to generate a unique id between 2000 and 5000, I was hoping there will be an attributGenerator OOB for that. If you know of one, can you please help.
Hello,
Is this possible to use on a WebServiceBeforeOperationRule?
I would like to create an email for the user in the target and validate that it is unique.
I'm getting:
Instead of
POST {{api-url}}/cc/api/source/loadAccounts/{{source-id}}
use https://developer.sailpoint.com/docs/api/beta/import-accounts/
@piyush_khandelwal can attrSearchCountAccounts handle proxyaddresses if you don't put them in a searchable attribute?