Toolkit Rules in Identity Manager:
One of the key strengths of Novell Identity Manager is that it is an event driven identity management system. What that means, is that when an event happens in the Identity Vault, it triggers the Identity Manager engine right away.
If the connected system supports events eventing as they happen, then the driver usually supports it as well. An example would the JDBC driver, when implemented in Triggered (as opposed to Triggerless mode) mode, where every event hits a trigger which sends it on immediately. Other drivers like the Active Directory driver, poll the Active Directory change log on a regular interval. (Technically this is different than dredging the entire system for changes, which is sort of what the Triggerless JDBC implementation does. In that case, the JDBC driver builds a table with the time stamps of all the columns and rows, and queries for the timestamp to compare on each cycle, then it knows what changed in the interval. You would not expect this to perform anywhere near as well as it does, but prepare to be surprised!).
Thus most rules written for Identity Manager is usually focused on an event based model. This approach requires a specific mindset.
There is however an entirely different set of tasks that might need a different mindset.
There is an entire class of rules you can build in Identity Manager 3.5x and higher, because of the amazingly useful tokens, If-then, and Query. This class would be things that look at all the data in the tree and assess it for correctness or consistency.
The simplest example might be, are all the uniqueID values in the tree, truly unique? For LDAP applications that are authenticating against eDirectory, the vast majority do not search for users with CN=BSmith, rather they often search for uid=BSmith (where uid is the LDAP name of the eDirectory attribute Unique ID). By LDAP’s definition, unique ID (uid) must be unique, thus if the search returns two values, the correct response is to error, not to choose to use the first or second value. By definition, if Unique ID returns more than one value it is not unique, which is therefore an error condition.
How would you approach this problem? I have not looked, but this is an excellent task to use Analyzer for Identity Manager, but that product (a spin off of Designer, running in an Eclipse environment) has gone commercial, and you may not want to pay for that product for a once in a while query.
I find this a very useful rule to run when I start working at a client site. Its not that I do not trust the environment, its just that the system has grown and experienced weird conditions over its many years of life, and stuff happens. Thus I like to trust, but verify.
There are a number of issues that come up when you start working with this class of rules. The most surprising one is how slow things can get. My first attempt at this rule on a lab system, with 10,000 user objects took almost two hours. When I was done tuning it, I got it down to about 2 minutes. I was pretty happy with that improvement. The good news is that as long as you have a server with a replica of the objects on it, you can run the rule there. I called my driver Blocking Actions, since it was doing stupid things that would block the queue on a normal driver.
My favorite most basic toolkit rule is to show the Java heap memory values, you can see some discussion on doing that in the article: Reading and Displaying the Value of Java Heap in Identity Manager Rules
I include this in every project now, because it quickly answers the question, how much RAM is in use and available.
Another kind of toolkit rule I had to write was one to fix a mistake we made in the original implementation of a driver, which we realized later was a mistake. Our authoritative source for users, and their managers stored the reference to the manager as an Employee ID value. But we really need a DN reference to the manager object in eDirectory. (For a variety of reasons we did not use manager and directReports, we used custom attributes).
As I was fixing this mistaken implementation, I ended up writing the rule twice. Once in an event driven approach, for incoming users and changes to existing users, to store the EmployeeID of the manager from the HR system, find the DN of that object, and store that as well. Then I wrote a second version that would go through every object in the tree, and read the managers Employee ID, find the DN, check if the user has that value set, and if not, fix it.
I like leaving the fixer up rule behind, even if I am only using it once or twice, since running it, and finding no users that need fixing is itself a useful thing to know.
All this would probably be possible with a lot of work in Identity Manager 3.0.x which lacks the Query and If-then token. Alas, it would be WAY too hard. I would not even bother in fact. But once you get up to the Identity Manager 3.5 range, it is way easier! (See this article for more on the difference between the two versions: Working in IDM 3.0.1 after using 3.5.1 ).
One interesting side affect of building rules like this, is that I started noting the free Java heap memory at the start of the rule, and then again at the end of it. The query of all the users into a single node set is a huge gobbler of memory, and most everything else pales in comparison. I also can tell how many nodes are in the node set. This was a fun excuse to try and estimate how much memory a node set takes up. Any guess? (Not fair if you read my articles on the topic:
Reading and Displaying the Value of Java Heap in Identity Manager Rules
http://www.novell.com/communities/node/6085/more-thoughts-size-node-set-identity-manager ) It depends, but basically I would estimate 10K a returned node if there is an attribute and a value.
There are a couple of tricks worth knowing before you start this kind of rule. One is how Identity Manager caches queried attributes. Basically if you use Source Attribute, Destination Attribute, or a Query token, then the results are cached for the duration of the Policy object. The Query token will always ask again, but the Source Attribute and Destination Attribute tokens will use the cache first, if the value is there. (Read more about this in the articles:
The different attribute options in Identity Manager
More thoughts on Source/Destination/Operation attribute tokens in Identity Manager ). This is really important to think about from a performance perspective. Each time you query for something that has to process, while pretty darn quick, takes measurable time.
Reading the value out of the cache in memory is way faster! Thus, when you start off with your first query make sure you ask for any attribute that you will need, to pre-seed the cache. Then if you need to get it for a user, you can just ask for it as normal, but rather than actually querying, it will get the value from the cache. That first query is usually pretty quick, (I am finding on modern hardware, 10K objects, with three or four attributes takes about 10-15 seconds), so there does not seem to be much of a penalty for getting everything at once in one operation.
Next comes the fun step of doing the magic. This one depends on what your toolkit rule is doing. Lets talk about the fixing the manager attribute.
First lets trigger it. There are a number of approaches, but in this case, pick some attribute in the filter, and watch for it changing to 42, since that is the Answer to the ultimate question of Life, the Universe, and everything!
In that case, we query all active Users, for the Employee ID, DirectorEID (Employee ID of the users manager), DirectorDN (DN of the users manager) and store it in a node set local variable called USERS.
Now we have a for-each loop, that looks at each <instance> node that is in our query node set, so the node set for the for-each loop is Local Variable of USERS. Inside the loop, we do the test that determines if we hit an error condition or a correctly configured user. In this example, what I did was look at the DirectorEID value, (use XPATH of $current-node/attr[@attr-name=DirectorEID]/value/text() to get it out of the USERS node set we are looping through ) then query for the user who had that value in EmployeeID into a node set local variable called DIR-DN, and then use XPATH of $DIR-DN/@src-dn to get the DN out of node set that was returned and store it in a string variable, DIR-DN. Now I know the DN that SHOULD be in the DirectorDN attribute value, so now compare with the value in the query node set document. Use an XPATH test of something like is the XPATH $current-node/attr[@attr-name=DirectorDN]/value/text()=$DIR-DN false, if so, set the source attribute to the value of DIR-DN. Probably store the action you took in a long string variable, so that you can email the report to yourself when done.
You can extend this model to do whatever you need, just change the test conditions and whatnot to match your needs. Here is the way I whipped up the rule quickly in Designer, (I had to redo it since my usual toolkit rules have way too many dependencies on Global Configuration Values I use everywhere, and cleaning them up would take longer than re-writing it from scratch). You can see how easy it is to approach this.
<rule> <description>[CoolSolutions] Toolkit Rule to fix DirectorDN</description> <conditions> <and> <if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">42</if-op-attr> </and> </conditions> <actions> <do-set-local-variable name="USERS" scope="policy"> <arg-node-set> <token-query class-name="User" datastore="src"> <arg-string> <token-text xml:space="preserve">DirectorEID</token-text> </arg-string> <arg-string> <token-text xml:space="preserve">DirectorDN</token-text> </arg-string> <arg-string> <token-text xml:space="preserve">EmployeeID</token-text> </arg-string> </token-query> </arg-node-set> </do-set-local-variable> <do-for-each> <arg-node-set> <token-local-variable name="USERS"/> </arg-node-set> <arg-actions> <do-set-local-variable name="CURR-USER" scope="policy"> <arg-string> <token-xpath expression="$current-node/@src-dn"/> </arg-string> </do-set-local-variable> <do-set-local-variable name="DIR-DN" scope="policy"> <arg-node-set> <token-query class-name="User" datastore="src"> <arg-match-attr name="EmployeeID"> <arg-value type="string"> <token-xpath expression="$current-node/attr[@attr-name=DirectorEID]/value/text()"/> </arg-value> </arg-match-attr> </token-query> </arg-node-set> </do-set-local-variable> <do-set-local-variable name="DIR-DN" scope="policy"> <arg-string> <token-xpath expression="$DIR-DN/@src-dn"/> </arg-string> </do-set-local-variable> <do-if> <arg-conditions> <and> <if-local-variable mode="nocase" name="DIR-DN" op="not-equal"/> </and> </arg-conditions> <arg-actions> <do-if> <arg-conditions> <and> <if-xpath op="not-true">$current-node/attr[@name=DirectorDN]/value/text()=$DIR-DN</if-xpath> </and> </arg-conditions> <arg-actions> <do-set-src-attr-value name="DirectorDN"> <arg-dn> <token-local-variable name="CURR-USER"/> </arg-dn> <arg-value> <token-local-variable name="DIR-DN"/> </arg-value> </do-set-src-attr-value> <do-set-local-variable name="MESSAGE" scope="policy"> <arg-string> <token-local-variable name="MESSAGE"/> <token-text xml:space="preserve"> </token-text> <token-text xml:space="preserve">User </token-text> <token-local-variable name="CURR-USER"/> <token-text xml:space="preserve"> had a DirectorDN of </token-text> <token-xpath expression="$current-node/attr[@attr-name=DirectorDN]/value/text()"/> <token-text xml:space="preserve"> and we changed it to: </token-text> <token-local-variable name="DIR-DN"/> </arg-string> </do-set-local-variable> </arg-actions> <arg-actions/> </do-if> </arg-actions> <arg-actions> <do-set-local-variable name="MESSAGE" scope="policy"> <arg-string> <token-local-variable name="MESSAGE"/> <token-text xml:space="preserve"> </token-text> <token-text xml:space="preserve">User had DirectorEID of </token-text> <token-xpath expression='$current-node/attr[@attr-name="DirectorEID"]/value/text()'/> <token-text xml:space="preserve"> but we could not find a user with that EID in the system.</token-text> </arg-string> </do-set-local-variable> </arg-actions> </do-if> </arg-actions> </do-for-each> </actions> </rule>
That was the basic outline. Now the fun begins, since that is a big brute force approach and there are a number of minor touches that can make things run much better!
First off, whenever you are about to use add source attribute, add destination attribute, set source attribute, or set destination attribute actions for an attribute that is of DN syntax, always make sure there is a value in what you are about to set. If you try to set DirectorDN to the local variable DIR-DN and it is empty, this will through a -613 error, syntax violation. That is because DN syntax attributes are not nullable (they cannot exist empty), and require a valid object reference in the attribute. Thus you can see I put in an IF-THEN test for if DIR-DN is not equal to null. As long as it has a value, I am ok with trying to set it. Bothering to validate if the value it has is real is probably not worth the effort, but validating that it at least has SOME value definitely is! This is a big deal, because otherwise you get all sorts of errors and you do not know if things are really working or not.
Secondly, I like using a string variable called MESSAGE, and adding a line for each thing the loop did. If it worked, record it. If it failed because DIR-DN was null, tell me that. If it failed because the current DN value was already set, I might be interested in knowing, but in this case, I only want to know what it had to fix, not what was already working. Of course, if I wanted to report on who was set and who was not, I might take this opportunity to add the information to the MESSAGE variable. Customize as you see fit! What you do with the MESSAGE variable is up to you. You could send it in an email to a holding account so you can track these things. You could send it as the data string in an Audit event to send to Sentinel, NSure Audit, or the new Identity Audit that was just released (http://download.novell.com/Download?buildid=2Jpzs5LliIw~ looks like it is a stripped down version of Sentinel without the super cool stuff, to replace NSure Audit).
There are a number more things to do about tuning, so stay tuned for part 2 where I discuss things you can do to make this run faster. (Quick hint, turn off DStrace while it runs!!! You will be surprised at the time difference).