SailPoint IdentityIQ: The Build Map Rule Revisited

(Not covered at the time this article was written is… The following information pretty much covers the JDBC Build Map rule and the SAP Build Map rule as well. There are very few differences.)

Well, I’m behind on posting again. Apologies to those following here who I know were looking forward to this particular post which I promised in person to a number of you.

Build Map Rules in Aggregations

The Build Map Rule… Just what is a “BuildMap rule” exactly? Maybe you’ve used or even written one, but you admit you still really don’t understand what it’s actually doing or how it really works in the case of account aggregations. I actually get that kind of comment all the time, so don’t feel bad. Let’s crack ‘er open and see if we can crystalize the concept of how this actually works. Once the concept is crystal clear, you’ll know exactly when to use it, and your usage of it will be that much more sophisticated and precise.

Hang On… What Is A Map, First Of All?!

Before we get into what a Build Map rule is, we first need to cover the concept of a “map” to begin. Again, this is a comment I often get as I am on site implementing SailPoint IdentityIQ for the first time in enterprises — “what is a map?”

SailPoint IdentityIQ is built using JEE technology. Therefore, it draws from many paradigms within that reference technology platform. A Map object in Java, or just a “map,” is essentially an indexed name/value pair system. Focusing on strings as the map implementation (it’s possible to have other map types in Java, but we’ll forgo that discussion here), a very stripped-down version of a map is something like you might find in a configuration or initializer file of some sort:

name=Chris Olive
address=123 Somewhere St.
city=St. Paul
state=MN
zip=55102

This is also known as a key/value pairing because the name on the left-hand side can only occur once. If you are familiar with other programming languages, a Java Map is roughly equivalent to what is called a hash in Perl and Ruby, a dictionary in the older Microsoft development parlances (VBScript, etc.), or a dictionary in Javascript (though popularization of Javascript and it’s object orient model extends this scheme into JSON objects, which again we will forgo delving into in depth in this discussion.)

Here are the equivalent “maps” in some of the languages I’ve mentioned above. If you are familiar with all or any of these, then you know what a Java Map (object) is:

Perl:

my $map = {
   name    => 'Chris Olive',
   address => '123 Somewhere St.',
   city    => 'St. Paul',
   state   => 'MN',
   zip     => '55102'
};

Ruby:

map = {
   :name    => 'Chris Olive', \
   :address => '123 Somewhere St.', \
   :city    => 'St. Paul', \
   :state   => 'MN', \
   :zip     => '55102' \
}

Javascript/JSON:

map = {
   "name"    : "Chris Olive",
   "address" : "123 Somewhere St.",
   "city"    : "St. Paul",
   "state"   : "MN",
   "zip"     : "55102"
};

Java (BeanShell):

// Unfortunately, Java doesn't offer a shortcut way of initializing
// a HashMap. I'll just not comment on that here. :-)
//
// Since Java 5, real Java wants these sorts of things "typed" as
// well.  We'll forgo that and do this BeanShell style as per IIQ.
// BeanShell doesn't require type syntax.

import java.utils.HashMap; // Not required in BeanShell
   :
   :
HashMap map = new HashMap();
map.add( "name", "Chris Olive" );
map.add( "address", "123 Somewhere St." );
map.add( "city", "St. Paul" );
map.add( "state", "MN" );
map.add( "zip", "55102" );

Now, that last example looks somewhat familiar if you’ve done any writing (or plagiarizing :-)) of SailPoint IdentityIQ Build Map rules already. (Funny how in literary circles, plagiarism is very much frowned upon, whereas in IT, it’s very much encouraged, isn’t it?! :-))

So while we’re here, let me just say that the variable name “map” carries no special significance. People tend to name their variables in simple scenarios according to what they are and the variable name could just as easily have been “foo” or “frank” — it’s doesn’t matter (other than when you program that way, things get a little unclear fairly quickly.)

So this would do just as well:

HashMap me = new HashMap();
me.add( "name", "Chris Olive" );
me.add( "address", "123 Somewhere St." );
me.add( "city", "St. Paul" );
me.add( "state", "MN" );
me.add( "zip", "55102" );

IdentityIQ Uses Maps EVERYWHERE

So now that you (hopefully) know what a “map” is, then maybe at least the name has suddenly taken on more significance. “Build Map” means… a Java Map object instance (or just a map) is going to be built. “Why” will be explained in just a moment.

The main thing to emphasize here is… SailPoint IdentityIQ uses maps literally EVERYWHERE. So just get used to it. And that being said, I can’t think of a concept in SailPoint IdentityIQ that you need to make sure is rock solid any more than the concept of a map. Again, SailPoint IdentityIQ uses them literally EVERYWHERE.

And knowing that will explain a LOT of stuff in SailPoint IdentityIQ. Some of you have worked with SailPoint support and for whatever reason, to provide you with additional functionality you may need, they’ve asked you to go into an application definition or a workflow definition or a task definition and “add an entry key in the Map section.” There you have it. You’re enhancing or augmenting a map that SailPoint IdentityIQ uses internally in a number of ways. We’ll discuss specifically here only of how they work in aggregation.

But to illustrate, go to the debug pages of your SailPoint IdentityIQ application, look up your applications, select an application, and check out its XML. Here’s an example of an application I have in my SailPoint IdentityIQ sandbox right now:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Application PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<application connector="sailpoint.connector.LDAPConnector" created="1334382161949" featuresString="AUTHENTICATE, MANAGER_LOOKUP, SEARCH" id="4028804536a318c70136af5ff01d0452" modified="1334383014187" name="LDAP" profileClass="" type="LDAP">
  <attributes>
    <map>
      <entry key="acctAggregationEnd">
        <value>
          <date>1334383014185</date>
        </value>
      </entry>
      <entry key="acctAggregationStart">
        <value>
          <date>1334382993306</date>
        </value>
      </entry>
      <entry key="authSearchAttributes">
        <value>
          <list>
            <string>dn</string>
            <string>cn</string>
            <string>uid</string>
            <string>mail</string>
          </list>
        </value>
      </entry>
      <entry key="authorizationType" value="simple"/>
      <entry key="compositeDefinition"/>
      <entry key="group.filterString"/>
      <entry key="group.groupMemberSearchDN"/>
      <entry key="group.iterateSearchFilter"/>
      <entry key="group.searchDN"/>
      <entry key="group.searchScope" value="BASE"/>
      <entry key="groupMemberAttribute" value="uniqueMember"/>
      <entry key="host" value="localhost"/>
      <entry key="pageSize" value="1000"/>
      <entry key="password" value="1:FcS4cFH4mEHLER7KR2iTYw=="/>
      <entry key="port" value="9389"/>
      <entry key="searchDN" value="ou=People,dc=qident, dc=com"/>
      <entry key="searchScope" value="SUBTREE"/>
      <entry key="templateApplication" value="LDAP Template"/>
      <entry key="useSSL">
        <value>
          <boolean></boolean>
        </value>
      </entry>
      <entry key="user" value="cn=Directory Manager"/>
    </map>
  </attributes>
  <description></description>
  <owner>
    <reference class="sailpoint.object.Identity" id="4028803f322346b501322346d05e00b1" name="spadmin"/>
  </owner>
  <schemas>
    <schema created="1334382161950" displayAttribute="cn" id="4028804536a318c70136af5ff01e0453" identityAttribute="dn" instanceAttribute="" modified="1334382928391" nativeObjectType="inetOrgPerson" objectType="account">
      <attributedefinition name="businessCategory" remediationModificationType="None" type="string">
        <description>business category</description>
      </attributedefinition>
   :
   :
   </schema>
  </schemas>
</application>

I’ve shortened this XML representation somewhat, but notice at the top the <map> section? Look familiar? Yes, it has all the application attributes you used to define the application in SailPoint IdentityIQ‘s GUI in a map, represented in XML format. Each entry has a key and value. Sometimes you’ll see a <List> as an entry value. This means that entry key holds the XML representation of a Java List object. We’ll get to those in another blog posting where we cover some advanced Build Map concepts. For now, just understand that maps are used everywhere. Go ahead and on your own, poke around in the debug pages and pull up other object types and you’ll see maps are absolutely pervasive in SailPoint IdentityIQ.

So suddenly, maybe this is starting to make a little (a lot?!) more sense in the broader picture of SailPoint IdentityIQ, yes?! But what specifically are maps used for in aggregation? Glad you asked. We’re getting closer to the answer.

How IdentityIQ Aggregation Works

Now that we know what a map actually is and the fact that SailPoint IdentityIQ relies heavily upon them, we want to explore specifically:

(1) How maps are used in aggregations,
(2) What is a Build Map rule, and…
(3) Why would I want to use one?

Let’s talk about those in that order.

How Maps Are Used In Aggregations?

To simplify things quite a bit, we’ll focus on a flat file or DelimitedFile type of aggregation. A JDBC aggregation will be very similar. Now I personally don’t have access to the SailPoint IdentityIQ source code, so I can’t say definitely what all the stages are in an application aggregation, but they roughly parallel these stages in processing:

Connect -> Read/Create Record -> Create Attribute Map -> Merge (if necessary) -> Create Resource Object -> …

We’ll stop there for now as we’ve described enough to discuss what we want here for now.

So SailPoint IdentityIQ first connects to the data source. Then it reads a record from that source and stores it in a record object. (Or a ResultSet object for a JDBC connector.) Then it creates an attribute map internally. In this area of processing, if we spiked out that a merge needs to take place, this is the area when this processing happens. Then SailPoint IdentityIQ creates a ResourceObject.

(Further processing takes place from here where other hooks like a BuildMap are provided, such as a MergeMap Rule, a Customization Rule, a Correlation Rule, etc. We’ll save those for another time. But maybe in discussing in depth the Build Map rule processing, you’ll see these new hooks in the processing flow in a new light. On its own, SailPoint IdentityIQ will do this processing by itself automagically. These are hooks to allow you to customize each stage in the aggregation process.)

What Is A Build Map Rule?

What a Build Map rule does, essentially is, wave down SailPoint IdentityIQ during this processing and say, “Hey! When you get to the stage where you build the internal map, I want control of building it!” SailPoint IdentityIQ, if it sees you have a Build Map rule defined, hands complete control at this stage of processing over to you, and says “Okay… You asked for it. Do whatever you want. I’ll pass you some parameters that should be helpful (including the record or ResultSet object for JDBC), and you go for it! But I expect a Java Map as a return object when you are done!

That last point is very important. But so is the overall concept: A Build Map rule is a hook into the aggregation process which provides you, the developer, complete control. If you want to spend time computing Pi to seven sig figs, you can do that. Just when you are done, SailPoint IdentityIQ expects you to provide it a Java Map object as the return.

Here is the kicker — the return map doesn’t even have to have anything to do with the data you are reading. Now why you would want to do something like that, I don’t know. But again, the point I’m making is… SailPoint IdentityIQ is going to completely relinquish building the application attribute map to your control and trust you to return a Java Map object. That’s it. It doesn’t even care what’s IN the map (strictly speaking — it should match the schema you’ve assigned to the application, but it doesn’t even have to do that). No integrity checking (against the schema, for instance) is done. SailPoint IdentityIQ is trusting you completely.

Don’t you feel important now?! 🙂

Why Would I Want To Use A Build Map Rule?

Common scenarios for writing a Build Map rule are things like:

(1) Needing to add or build an attribute from other application attributes. You have an attribute you require for loading into SailPoint IdentityIQ but the application data you are consuming doesn’t provide this in the form you require. Instead, you will need to build it from existing attributes in the application feed.

(2) Transforming data. There are other places where this can be done, including writing a Customization Rule or creating a Map To ResourceObject rule. The Build Map rule is a little easier place to do data transformations in my opinion.

Sample Use Cases

Let’s lay out some sample use cases, a sample Build Map rule that addresses these use cases, and then we’ll wrap this discussion up.

(1) I once worked for a company we’ll call “Foo.” Foo used to use “foo.com” as its domain name, but after going through a major acquisition, began calling itself “The Big City Foo” and started using “bcf.com” as its domain name. Later, it shed the “The Big City Foo” designation and went back to “Foo” and “foo.com.” (It was a smart move, by the way. :-))

So for a number of years, email formats became “colive@bcf.com” and then needed to move back to “colive@foo.com.” It was a big company (and still is), so it was quite a change that took a long time. And in keeping with our use case, as far as I know, Foo still hasn’t eliminated all occurrences of “bcf.com” in the enterprise. So we’ll simulate that change in our sample Build Map rule below for an application that is still handing out “bcf.com” domains for email addresses.

(2) We want to set employee status — “Employee” or “Contractor” depending on the feed. (That is, this one Build Map rule will be used in two connectors.)

(3) Our feed provides a “fullname” which is in the form of “FirstName.LastName”. We want to use the “fullname” provided as a username attribute we will create as a “virtual attribute” in the rule (and have defined previously in our application account schema) and transform the “fullname” to “FirstName (space) LastName”.

Here’s our Build Map rule:

// Imports.

import sailpoint.object.Schema;
import sailpoint.connector.Connector;
import sailpoint.connector.DelimitedFileConnector;

// Build the initial map from the record read using a Class call
// to accomplish this.

HashMap map = DelimitedFileConnector.defaultBuildMap( cols, record );

// Make sure we ONLY apply our logic to account aggregations.

if (schema.getObjectType().compareTo( Connector.TYPE_ACCOUNT ) == 0) {

   // Fix userName versus fullName issue.

   String fullName = map.get( "fullName" );
   map.put( "userName", fullName );
   fullName = fullName.replace( ".", " " );
   map.put( "fullName", fullName );
   
   // Transform the email.

   String email = map.get( "email" );
   String newEmail = email.replace( "bcf.com", "foo.com" );
   map.put( "email", newEmail.toLowerCase() );

   // Update the status to Employee or Contractor based on the
   // name of the application.

   String appName = application.getName();
   if (appName.indexOf( "Employees" ) > 0) {
      map.put( "status", "Employee" );
   } else {
      map.put( "status", "Contractor" );
   }

}

// Return the map.  For group aggregations, the default map falls through.
// For account aggregations, our new map will be returned.

return map;

Breaking It Down

Whew, well thanks for hanging with me this far! Let’s break this down just a little bit farther, and we’ll have our entire Build Map Rule Revisition completed. 🙂 I get a number of questions on “why” Build Map rules are written roughly as above, so let me cover the common questions:

Look at this line:

if (schema.getObjectType().compareTo( Connector.TYPE_ACCOUNT ) == 0) {

What’s going on here? We need to understand this line in order to understand why we imported the libraries we imported in the “Imports” commented section. In short, this line says “If this is an account aggregation…” Why do we do this?

If you go back to most application definitions, you’ll notice you can often create an account schema and a group schema. These schemas are aggregated separately as separate SailPoint IdentityIQ tasks. But note! In the application definition, there is only ONE place for a Build Map rule. That means account aggregations and group aggregations share the same Build Map rule! This could be a problem!

This is why you will often see experienced implementers implement the logic above.

More specifically, the schema object instance for the application being processed is provided to the Build Map rule. We use schema and call a method on that instance to get its object type, which just returns a literal “ACCOUNT” or “GROUP”. The Connector class literal “TYPE_ACCOUNT” equates to “ACCOUNT”, so this code simply checks to see if… this is an account aggregation.

And yes, you guessed it — if you needed to write a Build Map rule for a group aggregation instead, you would check for Connector.TYPE_GROUP. Very good.

Next, let’s look at this line:

HashMap map = DelimitedFileConnector.defaultBuildMap( cols, record );

What’s going on here? Well, remember… We have complete control over building the map we are returning to SailPoint IdentityIQ when we are done. 99.999% of the time, we only want to manipulate a map SailPoint IdentityIQ would have built on its own. But when we are given control of the Build Map rule… NOTHING exists. No default map built from the record read… NOTHING.

The call above you will see experienced implementers use to create a default map to work with. The DelimitedFileConnector class has a method called defaultBuildMap() which takes a column and record object as its parameters. These parameters are provided to you by SailPoint IdentityIQ — all you have to do is make the call. You will see this (and a very similar class call, JDBCConnector.buildMapFromResultSet( result );, for JDBC connections) used often by experienced SailPoint IdentityIQ implementers.

The rest of the code, which I will let you read and which should now (hopefully!) make more sense, is just manipulating the map we created with the DelimitedFileConnector.defaultBuildMap() call, using logic that adheres to our use case requirements.

That’s All Folks!

So there you have it… The Build Map Rule Revisited. Explained. Demystified. Hopefully… 🙂

Cheers from The First State, Delaware!!

Chris Olive

Chris Olive is a seasoned and passionate cybersecurity strategist, evangelist, consultant, trusted advisor, and hands-on technologist with over two decades of cybersecurity consulting experience in the US/UK governments, the Fortune 500, and large international companies all over the world. Chris has primary expertise in Identity Access Management and Identity Governance & Administration along with professional experience and expertise in Ethic Hacking & Penetration Testing, Secure Development, and Data Security & Encryption. Chris is a frequent writer, speaker, and evangelist on a range of cybersecurity topics. Chris is currently a Senior National Security Advisor & Architect for CDW -- a worldwide leader and innovator in solutioning, architecting, and delivering secure information technology solutions on-prem, in the cloud, multi-cloud, hybrid, or co-hosted leveraging the world's largest, best, and most trusted brands.

View all posts by Chris Olive →

One thought on “SailPoint IdentityIQ: The Build Map Rule Revisited

Comments are closed.