Thanks for viewing my blog on Identity Management and Engineering (mostly IDM). Please follow and check out the advertisers.
Search This Blog
Friday, October 27, 2023
SailPoint reverse tokenization challenge
Monday, May 8, 2023
SailPoint UIConfig changes that have helped me search in the Debug pages
Tuesday, January 17, 2023
SailPoint migrating provisioning policies from inline to forms
Saturday, June 25, 2022
SailPoint proper way to construct a rolling log file
Wednesday, March 2, 2022
Programmatic options for Account Aggregation
| Checkbox | Code | Description |
|---|---|---|
| Select Applications to Scan | applications | List of application names, comma separated |
| Optionally select a rule .. | creationRule | Specify rule name |
| Refresh Assigned and Detected Roles | correlateEntitlements | Refresh roles on aggregation ?? |
| Check Active Policies | checkPolicies | Run through policy checking on aggregation |
| Only create links if they.. | correlateOnly | Do not create uncorrelated identities |
| Refresh identity risk scorecards | refreshScorecard | Risk scorecards |
| Maintain identity histories | checkHistory | See history pages |
| Enable delta aggregation | deltaAggregation | If the connector supports this |
| Detect deleted accounts | checkDeleted | Detect deleted accounts - do not use with delta or targeted agg |
| Maximum deleted accounts | checkDeletedThreshold | If more than this, do not delete |
| Refresh assigned scope | correlateScope | Refresh scopes based on attributes |
| Disable auto creation of scopes | noAutoCreateScopes | If enabled do not do this |
| Disable optimization | noOptimizeReaggregation | Process every account - no optimization |
| Promote managed attributes | promoteManagedAttributes | Only use this if there is no group schema |
| Disable auto creation of apps | noAutoCreateApplications | If enabled do not do this |
| Disable marking as needing refresh | noNeedsRefresh | Do not set needsRefresh to true |
| Enable partitioning | enablePartitioning | Enables partitioning if supported |
| Objects per partition | objectsPerPartion | If the connector supports |
| Loss limit | lossLimit | If the connect supports |
| Terminate when maximum | haltOnMaxError | Self explanatory |
| Maximum errors before | maxErrorThreshold | Should specify this |
| Sequential Execution | sequential | Terminate sequence on error |
| Actions to include | logAllowedActions | See options: comma sep list |
- CorrelateManual
- CorrelateMaintain
- CorrelateNewAccount
- CorrelateReassign
- Create
- Ignore
- Remove
Wednesday, September 29, 2021
SailPoint various ways to construct identityName
Construction of identity name
There are various methods for constructing identity name from an authoritative source. Each method has its advantages and disadvantages.
The normal construction of an authoritative source is to have the employee number as the identity attribute and a first-last full name as the display attribute. This is normal and is very helpful in the display of the identity on the main identity warehouse page. However this has a side effect: the identity name of the user is their display name. This is because on account creation, the display name informs the identity name. Hence an issue. Using the employee number for the display attribute has its own redundancy issues and should be discouraged.
Construction of display name
Some sources do not have a display name field, they might have a first name and a last name field. In this case you should construct this field using a customization rule.
- Define the Full Name or Display Name field in the schema
- Write a customization rule that pulls the first and last names and returns the full or display name to the attribute.
- Define the new field as the display attribute.
In order to have the employee number to be used as the identity name instead of the display name, you need to explicitly set the name in the Creation rule of the authoritative source application. This is also where we typically set the initial password for the identity. For instance, if the application's name for employee number is FILENUMBER (this is the value for WorkDay typically) then the code would look like this:
identity.setName(account.getAttribute("FILENUMBER"));
And in fact you could set the identity name to whatever you like here, keeping in mind to make it always unique. For instance you could include the application name in the identity name:
identity.setName(application.getName()+
"-"+account.getAttribute("emplid"));
Or I have also seen:
identity.setName(account.getAttribute(displayName)+
"-"+account.getAttribute("emplid"));
Wednesday, September 22, 2021
SailPoint logging best practice
Logging Best Practices for SailPoint Implementations
SailPoint recommends using log4j for all logging operations. Although some legacy code uses Apache Commons Logging, log4j is the preferred and more efficient approach. Commons Logging ultimately delegates to log4j, creating an unnecessary abstraction layer.
Recommended Logger Construction (log4j)
import org.apache.log4j.Logger;
Logger alog = Logger.getLogger("com.sailpoint.class.module");
Not Recommended (Commons Logging)
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log alog = LogFactory.getLog("com.sailpoint.class.module");
Using Commons Logging works, but it introduces avoidable complexity. Additionally, logger objects defined outside a method cannot mix classes without causing conflicts.
Importance of Proper Logging
Logging is essential in web applications because traditional debugging tools cannot pause execution or inspect runtime variables. Effective logging provides visibility into application behavior and accelerates troubleshooting.
A common anti-pattern is using vague statements such as:
log.error("I am here");
log.error("Now I am here");
This approach is ineffective because:
- Use of an out-of-the-box logger instance
- Incorrect use of the
errorlevel for basic debugging - No indication of program location or context
- No meaningful information in the log output
Best Practices
1. Always Create a Dedicated Logger
Never rely on the default log object. Create your own logger instance. Avoid naming it
log.
2. Use Log Levels Correctly
Understanding log levels is critical:
- TRACE — Highly verbose; use only in tight loops or deep logic analysis
- DEBUG — Primary level for development and troubleshooting
- INFO — Suitable for method-entry statements
- WARN — Use for non-fatal issues that should appear in production logs
- ERROR — Use in catch blocks or severe failures
Configure levels in log4j2.properties (or log4j.properties for older versions).
3. Use Structured Prefixes (“Tags”)
Each log entry should include a standardized prefix identifying the client, module code, and a unique number.
String fileNumber = (String) object.getAttribute("FILENUMBER");
alog.debug("ACM-WDC-001 Entered WorkDay Customization rule for user " + fileNumber);
4. Provide Meaningful Log Content
Avoid empty statements like "I am here". Include relevant data and null-check before printing. This
improves SIEM analysis and accelerates root-cause identification.
Update 2026 — Avoid Logging Wrapper Methods
Some implementations introduce wrapper methods such as:
void logx(Logger xlog, String level, String message) {
if ("debug".equals(level)) xlog.debug(message);
else if ("info".equals(level)) xlog.info(message);
else if ("trace".equals(level)) xlog.trace(message);
else if ("warn".equals(level)) xlog.warn(message);
else if ("error".equals(level)) xlog.error(message);
}
This pattern defeats log4j’s built-in level filtering. For example:
logx(xlogger, "debug", plan.toXml());
Even if the log level is set to WARN, the system still evaluates
plan.toXml(), causing unnecessary overhead.
Correct Approach
if (alog.isDebugEnabled()) {
alog.debug("ACM-WDC-100 The provisioning plan is " + plan.toXml());
}
Use isDebugEnabled() and isTraceEnabled() for DEBUG and
TRACE statements that require object evaluation. These checks are generally not required for
INFO, WARN, or ERROR. They are also not required when the
text being printed does not need evaluation, such as printing Strings or numbers.
Logging Errors
Use WARN when the issue is important enough to appear in production logs but does not stop execution. Inside catch blocks use error. Below is a way to print the class and message for a general exception.
catch (Exception ex) {
alog.error("ACM-WDC-901 Error evaluating expression: " + ex.getClass().getName() + ":" + ex.getMessage());
}
TLDR : Logging Best Practices
- Always use log4j classes and methods.
- Do not name your logger
log. - Define loggers inside method bodies.
-
Enclose the main body of every rule in
{ }to ensure proper garbage collection. Anything defined outside is effectively global; useCustomGlobalfor truly global objects. - Tag each log line with a structured prefix.
- Use
isDebugEnabled() or isTraceEnabled()for DEBUG/TRACE and never wrap log calls in custom logging methods.
Tuesday, January 12, 2021
SailPoint cron settings for different scenarios
Run every 5 minutes:
0 0/5 * 1/1 * ?
Run every 15 minutes, on the 5's (0:05, 0:20, 0:35, 0:50)
0 5/15 * 1/1 * ?
Run every hour at the top of the hour:
0 0 * * * ?
Run every hour at half past:
0 30 * * * ?
Wednesday, December 30, 2020
SailPoint Identity attribute naming recommendations
I have seen some very odd names for Identity attributes.
Just as a refresher, Identity attributes are defined in the ObjectConfig-Identity.xml file.
For example:
<ObjectAttribute displayName="Job Title" editMode="readOnly" name="jobTitle"/>
My example doesn't include any source or target definitions.
If you want the field to be searchable, you have two options. One option is to use one of the extendedNumber values. If you just check the searchable box in the UI, SailPoint will assign the next available extendedNumber value. This option is fraught with dangers. The first danger is that OOTB there are only 10 extended attributes defined in the IdentityExtended.hbm.xml file, so if you exceed 10, you will need to uncomment the 11-20 lines and then create the database table fields. The second danger is that only 5 of the OOTB extended attributes have indexes defined, so any search on those non-indexed attributes will generate a table scan in the database, affecting performance. You should define and create these indexes as soon as possible in your installation process.
The second option is to used named columns. This method is described in the hibernate file and here is where this post is important to apply. My recommendation is to always use strict and concise camelCase for identity attribute names, which go in the ObjectConfig-Identity.xml and in the IdentityExtended.hbm.xml files. Here are some naming schemes that have generated terrible results:
All caps like EMPLID
Trailing caps like personID
Leading caps like ADLoginName
Numbers like AS400Login
Pascal Case such as JobTitle
Repeated caps like autoFRActivate
Long long names like ADLastModifiedDatetime
Using underscores (snake case) like job_title
Database keywords or function names. Here are some I have discovered:
- position
Single lower case values are FINE - emplid, title, these are fine although not very descriptive.
If you want to use "ID" in the description, use "Id" in the name such as personId
Keep it short, keep it simple. Two words is best: jobTitle, departmentName, adLogin, adGuid, empoyeeId, etc. Remember that Oracle 12c only allows a 30 character identifier.
When you deploy the hibernate file and then execute the iiq extendedSchema command, the extendedSchema job takes the camel case and splits it into words, like this:
jobTitle becomes job_title
This is done because database don't normally care about case. For this same reason, always make your indexes to look like the field name, not like the camelCase.
<property name="jobTitle" type="string" length="450"
access="sailpoint.persistence.ExtendedPropertyAccessor"
index="spt_identity_job_title_ci"/>
NOT index="spt_identity_jobTitle_ci"/>
Don't try to create the database scripts on your own, you will likely make a mistake.
End
Wednesday, November 25, 2020
SailPoint why you should NEVER open any OOTB or cloned OOTB workflow in UI
Here's a good suggestion
Unless you authored a workflow in the Business Processes UI in SailPoint, don't open it in the UI. This includes any OOTB workflow - you should NEVER modify any OOTB workflow. It also includes any workflow that you cloned from OOTB.
How do you clone a workflow? How to you clone any replicatable object? Open it in the Debug Page, then change the Name, and then remove the created, id, and (if there) modified tags from the first line (actually line 3). Also then search for and remove any created, id, or modified tags anywhere in the file.
Why don't you want to open an OOTB workflow in the UI? Because the UI will reorganize the workflow in a way you don't want to happen. It will reformat the descriptions, it will add or remove arguments to steps, and you will not be able to make comparisons to the OOTB workflows. It also adds the explicitTransitions tag which is not wanted.
Here's what it does to LCM Provisioning:
Reformats the Descriptions to remove line feeds - they are ugly
In the Initialize step, it adds the following arguments:
- <Arg name="identityRequest">
- <Arg name="asyncCacheRefresh">
It also confusingly shuffles the remaining arguments.
In the Create Ticket step, it adds the following arguments:
- <Arg name="ticketProject">
- <Arg name="ticketPlan">
- <Arg name="dontUpgradePlan"/>
- <Arg name="clearApprovalDecisions"/>
- <Arg name="workItemDescription"/>
- <Arg name="clearApprovalDecisions"/>
- <Arg name="requesterEmailTemplate"/>
- <Arg name="approvalEmailTemplate"/>
- <Arg name="endOnProvisioningForms"/>
- <Arg name="enableRetryRequest"/>
- <Arg name="endOnManualWorkItems"/>
- <Arg name="userEmailTemplate"/>
- <Arg name="batchRequestItemIId"/>
In the Approve and Provision step, it adds the following arguments:
- <Arg name="approvalEmailTemplate"/>
- <Arg name="batchRequestItemId"/>
- <Arg name="clearApprovalDecisions"/>
- <Arg name="enableRetryRequest"/>
- <Arg name="endOnManualWorkItems"/>
- <Arg name="endOnProvisioningForms"/>
- <Arg name="requesterEmailTemplate"/>
- <Arg name="userEmailTemplate"/>
- <Arg name="autoVerifyIdentityRequest"/>
- <Arg name="ticketDataGenerationRule"/>
Monday, November 23, 2020
SailPoint which files should go in your comparison folder
In the ExportXML class you have an option to compare against existing data and create a merge file for certain files. Here are the files you will need to place in there and how to organize them. You want to generate these files from a perfectly stock deployment so that the client's customizations are actually captured in the merge files. If you don't, then the merge file will only contain modifications after the date you generated it.
Create a folder I normally call mine base (or Base for Windows based).
Next create the following folders and put the shown files into those folders:
AuditConfig
AuditConfig-AuditConfig.xml
Configuration
Configuration-ConnectorRegistry.xml
Configuration-IdentityAIConfiguration.xml
Configuration-IdentitySelectorConfiguration.xml
Configuration-SystemConfiguration.xml
ObjectConfig
ObjectConfig-Bundle.xml
ObjectConfig-Identity.xml
ObjectConfig-Link.xml
ObjectConfig-ManagedAttribute.xml
UIConfig
UIConfig-UIConfig.xml
My normal ExportXML output is $Class$-$Name$ that is why they are named that way.
IdentityAI is not found on lower versions of IIQ.
Sunday, November 8, 2020
SailPoint saving AD values as a secondary auth source
This post is primarily so I remember how I do every client's AD. The point of saving AD values as Identity Attributes is twofold: first, to indicate if the user has an AD account (this can literally be done for any target, but AD is the one that almost everyone uses for their primary provisioning target). Second, it allows you the OPTION of saving the values for all time, which would allow you to ensure that no duplicates be created.
I create 5 Identity Attributes:
adLogin (AD Login) which derives from sAMAccountName
adEmailAddress (AD Email Address) which derives from mail
adDistinguishedName (AD Distinguished Name) which derives from distinguishedName
adUserPrincipalName (AD User Principal Name) which derives from userPrincipalName
adObjuctGuid (AD Object Guid) which derives from objectguid
Any or all of these can be backed up with a global rule I normally call IdentityAttribute-PersistOldValue whose source is literally return oldValue;
You have to decide on your own which if any are sortable. Make a value sortable if you plan to do a search on it using any search method.
If you use the global rule then your values will not be removed if the user loses their AD Account. Be careful and aware of this.
Friday, June 5, 2020
Customizing SailPoint Task Definitions - Run with Response
Regarding: Adding responses to a batch process using Run Rule
Creating a TaskDefinition for running a rule is normally performed by the following:
Setup -> Tasks
New Task -> Run Rule
Enter the details such as what you want the rule to be named, description, then the rule to be executed. Save and Run
Customizing SailPoint TaskDefinitions - OOTB method
Regarding: Using a rule to execute batch processing, customization of the inputs
Creating a TaskDefinition for running a rule is normally performed by the following:
Setup -> Tasks
New Task -> Run Rule
Enter the details such as what you want the rule to be named, description, then the rule to be executed. Save and Run
The normal method for making changes to the behavior of the rule is through the Rule Config line. In this, the input data, separated by commas, is entered as:
key,value,key,value,key,value,key,value
Which translates into an input variable called config, which is passed into the rule as a Map. You can verify the value exists by using this:
if(config==void || config==null || config.isEmpty()) {
log message
}
else {
extract the key value pairs out of the map
}
The problem with this method is that it is hard to train people how to use it. See further posts for how to get around this.