Application Delivery Management
Application Modernization & Connectivity
CyberRes by OpenText
IT Operations Management
In the article: Toolkit Rules in Identity Manager Part 1 I started talking about approaches to writing toolkit rules in Identity Manager to use to test data in the tree, report on it, and possibly clean it up.
The example I started with in the first part of the article was the case where I have some custom attributes, DirectorEID that comes from my Human Resources system, denoting the EmployeeID of the Director for each User.
Of course in eDirectory, I really want to use the Distinguished Name (DN) of the object so that I get all the benefits of using a DN. (If the object is moved or renamed, all is good, the attribute stays up to date.
Whereas if I stored the DN in a string attribute, then I would have to be sure to update it whenever that object moves or is renamed. A great example of this is in an article on Work Orders (Making Sure Your Work Orders Work!), where the suggested solution is build rules to do this checking, but I suggested in the comments it would be much easier to just store the DN in a DN Syntax attribute, which oddly enough is missing in the base schema for a Work Order. I have put in an enhancement request through Bugzilla for the addition of some attribute, say DirXML-nwoTarget that is DN syntax, but in the short term, whenever I personally need to do it, I create an aux class with a custom attribute like acmeWorkOrderTarget for this purpose.)
In my example, the users should all have the DirectorDN attribute populated with the right value, based on the DirectorEID attribute. But like most times, the real world is not that simple, and stuff happens. Therefore, I need to first off confirm everyone has the value set, and report all those who do not. Second if I am ready, then fix those that are wrong.
The basic rule I suggested in part 1, is this (see Part 1 for an explanation of what each step is trying to do:
<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>
Now lets have some fun with tuning this rule.
Triggering the Rule:
Lets talk about how to trigger it. In the example, I suggest watching for an operational attribute SomeTriggerAttribute changing to some value, I chose 42 (Hitchhikers Guide to Galaxy joke of course: http://en.wikipedia.org/wiki/Answer_to_Life,_the_Universe,_and_Everything ) because it is a funny number.
But there are some really cute tricks you can play here. One possibility is to use a Job to trigger the event. In that case, the test would look like:
<conditions>
<and>
<if-operation mode="case" op="equal">trigger</if-operation>
<if-op-property mode="nocase" name="source" op="equal">JobName</if-op-property>
</and>
</conditions>
Thus the test is, operation is trigger (which is what trigger Jobs do), and there is a operation property called source, that is the common name of the Job object. That is a pretty easy way to test it.
There is another approach that makes life a lot easier, using some attribute to trigger it. Initially I used to build the rule, disable the rule that actually does the 'fixing' of the problem, and report on it until I am ready to go. Once I am ready, I edit the rule to enable the fix it actions, and then I fix it. What we can do is actually trigger on the attribute changing to one of two different values. Lets say 42 to report, and 43 to fix the problem. This way, I can see the report, say ok, lets fix it, run the fix it tool, see that it claims to have fixed everything, and then run the report rule again to confirm it really succeeded.
If I wanted to that, the condition block would probably look more like this:
<conditions>
<and>
<if-operation mode="case" op="equal">trigger</if-operation>
<if-op-property mode="nocase" name="source" op="equal">JobName</if-op-property>
</and>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">42</if-op-attr>
</and>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">43</if-op-attr>
</and>
</conditions>
Now, we trigger the rule in three cases.
That was the easy part, now to make it do something different based on what triggered it. What I would do is basically wrap the 'fix it' part in a test for the triggering condition. I would take this action:
<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>
And replace it with this block of code, which only executes if the trigger event was a change of the attribute to 43:
<do-if>
<arg-conditions>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">43</if-op-attr>
</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>
</arg-actions>
<arg-actions/>
</do-if>
You could also extend this by using two jobs, one to report, and one to fix, in the same method. (I will skip that since I like using 42 much better!)
Reporting via Email:
We talked about reporting and fixing the problem, but we really have not delivered any kind of report yet, that was left as an exercise to the reader. Well lets talk about that now. There is a great token, Send Email that we can use, that pretty much covers the whole issue.
I happen to have strong feelings about how this should be used. I think you should avoid hard coding any values into the do-send-email token, so that it is easy to reuse from one rule to another. I think Global Configuration Values (GCV) should be used through out to make this portable and easy to use.
For example, the token requires you to specify a SMTP server. Never hard code a value here, even worse, never use an IP number! This is the kind of stuff that changes all the time at the back end, and who wants to troubleshoot why a report is failing, because the mail guys moved the SMTP server.
I pretty much always add a GCV to the driver set called smtpServer, and specify the IP name of the SMTP server of the local site in it. The field you set the SMTP server name (server), does not take a Token GCV, so you have to use the tilde GCV tilde notation, so it would look like ~smtpServer~ in the server field.
As a consultant, I walk into a site, help them with a project or issue, and then walk away. For the duration of the project, I want to receive these reporting emails. Once I am done with the client, I really am not interested in them any longer. So I always set up a GCV called toErrorEmails, and toCCErrorEmails. I put myself in the toErrorEmails GCV (By the way, to add more than one, use a comma to separate email addresses in the GCV) and the local support guys. Finally, at least on the addressing front, many SMTP servers and mail systems frown upon email without a From: value. Thus we set up a GCV called fromEmail, usually idm@acme.com or whatever the site name is. If there are multiple trees, (say Dev, QA, and Prod), I usually name it idm-dev@acme.com, idm-qa@acme.com, and idm-prod@acme.com so I can easily tell which tree is sending the report.
That would start to look something like this:
<do-send-email server="~smtpServer~" type="text">
<arg-string name="to">
<token-global-variable name="toErrorEmails"/>
</arg-string>
<arg-string name="from">
<token-global-variable name="fromEmail"/>
</arg-string>
<arg-string name="cc">
<token-global-variable name="toCCErrorEmails"/>
</arg-string>
<arg-string name="subject">
<token-text xml:space="preserve">something</token-text>
</arg-string>
<arg-string name="message">
<token-text xml:space="preserve">something</token-text>
</arg-string>
</do-send-email>
We still need to put some useful values in the Subject and Message strings still.
The 'message' is pretty easy, as you will notice we have been building the MESSAGE local variable as we go along containing all the interesting stuff the rule has done so far. At the simplest level, we can make it look like:
<arg-string name="message">
<token-local-variable name="MESSAGE"/>
</arg-string>
We will come back to this one and expand it a lot more later, since we can do some nice cosmetic things, but for now that is quite helpful.
The 'subject' string can be made really useful as well. Lets report the time in nice readable format, add in the driver name (we can use the built in GCV dirxml.auto.driverdn so no hard coding here, it returns the current driver name where the rule is running), so you know with certainty where it is coming from, some descriptive text, and finally, if we have it, the value of the trigger that started this rule off, so we know how it started. We try and show the operation attribute SomeTriggerAttribute, which will be blank if it is not there, and then the operation property, source, in case we started it from a Job. We should only ever trigger it via one of the two methods, so only one value should ever show up in the subject.
<arg-string name="subject">
<token-time format="!LONG.DATETIME" tz="EST5EDT"/>
<token-text xml:space="preserve">: For driver </token-text>
<token-global-variable name="dirxml.auto.driverdn"/>
<token-text xml:space="preserve"> DirectorEID/DN toolkit rule report </token-text>
<token-op-attr name="SomeTriggerAttribute"/>
<token-op-property name="source"/>
</arg-string>
Reporting object counts:
It would be nice to know up at the top of the report message, how many users had the correct values, how many had the wrong values, and perhaps for how many cases we could not find the EID of the director. To get that data we need to use some counters. One thing I have noticed is that there is no integer type for local variables, only string and node set, so it is best to initialize the counter variables up front, so lets do that for DIR-CORRECT-COUNT, DIR-WRONG-COUNT, and DIR-NOTFOUND-COUNT variables even before we do our query for USERS.
That looks like:
<do-set-local-variable name="DIR-CORRECT-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-WRONG-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-NOTFOUND-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
Then inside the loop, in each IF-THEN case, we need to add an appropriate increment to that counter. Here is one example of what the increment would look like:
<do-set-local-variable name="DIR-NOTFOUND-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-NOTFOUND-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
Note in all four cases I use the number() function. In reality it is not needed, usually only needed on subtraction (since the minus sign (-) is a valid value in a variable or function name in XPATH, and if you do not space it correctly, XPATH is not sure. (See this article for more details on XPATH and math XPATH and math). But it does not seem to incur much of a performance hit, and it guarantees no string to number conversion issues.
We will need to include these strings in the message, but we will come back to that in part 3. Stay tuned for the next part, where we start looking at more neat things we can include in the message to be helpful and informative. The rule as it sits now at the end of part 2, is attached as an XML file you can copy and paste into your environment, looks something like this:
<rule>
<description>[CoolSolutions] Toolkit Rule to fix DirectorDN</description>
<conditions>
<and>
<if-operation mode="case" op="equal">trigger</if-operation>
<if-op-property mode="nocase" name="source" op="equal">JobName</if-op-property>
</and>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">42</if-op-attr>
</and>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">43</if-op-attr>
</and>
</conditions>
<actions>
<do-set-local-variable name="DIR-CORRECT-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-WRONG-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-NOTFOUND-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<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-if>
<arg-conditions>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">43</if-op-attr>
</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>
</arg-actions>
<arg-actions/>
</do-if>
<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>
<do-set-local-variable name="DIR-WRONG-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-WRONG-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-set-local-variable name="DIR-CORRECT-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-CORRECT-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</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>
<do-set-local-variable name="DIR-NOTFOUND-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-NOTFOUND-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
</do-if>
</arg-actions>
</do-for-each>
<do-send-email server="~smtpServer~" type="text">
<arg-string name="to">
<token-global-variable name="toErrorEmails"/>
</arg-string>
<arg-string name="from">
<token-global-variable name="fromEmail"/>
</arg-string>
<arg-string name="cc">
<token-global-variable name="toCCErrorEmails"/>
</arg-string>
<arg-string name="subject">
<token-time format="!LONG.DATETIME" tz="EST5EDT"/>
<token-text xml:space="preserve">: For driver </token-text>
<token-global-variable name="dirxml.auto.driverdn"/>
<token-text xml:space="preserve"> DirectorEID/DN toolkit rule report </token-text>
<token-op-attr name="SomeTriggerAttribute"/>
<token-text xml:space="preserve"> </token-text>
<token-op-property name="source"/>
</arg-string>
<arg-string name="message">
<token-local-variable name="MESSAGE"/>
</arg-string>
</do-send-email>
</actions>
</rule>
Also, please note, you will need the following GCV's defined:
<definition display-name="IP name of the SMTP Server (smtpServer)" name="smtpServer" type="string">
<description/>
<value>smtp.acme.com</value>
</definition>
<definition display-name="Email address list, comma seperated to send error emails too. (toErrorEmails)" name="toErrorEmails" type="string">
<description/>
<value>idm-dev-tester@acme.com</value>
</definition>
<definition display-name="Email address list, comma seperated to send error emails too. (toCCErrorEmails)" name="toCCErrorEmails" type="string">
<description/>
<value>idm-dev-tester@acme.com</value>
</definition>
<definition display-name="Email address for the From: field, include tree name (fromEmail)" name="fromEmail" type="string">
<description/>
<value>idm-dev@acme.com</value>
</definition>