Thursday, May 26, 2011

ITIM Custom Participants Explained

One of IBM Tivoli Identity Manager's strengths is in its workflow engine. Visually defining workflows by adding actions, scripts and approval "nodes" can actually be fun and the visual results can often be a thing of beauty.

That said, the visual beauty can often be regarded as ugly compared to the elegance and simplicity within our scripting!

An approval node, for example, allows a workflow designer to select an approver as a particular ITIM user; a group of users associated with an ITIM role; or users with a specific relationship to the entity being operated on (such as a supervisor or service owner). But this can be extended using "Custom Participants".


Custom participants allows us to make use of the power of scripting. Instead of defining the participant for an approval (for example), we could use scripting to find an approver based on more obscure relationships.

All we need to do within our scripting is return an array of DNs representing each user who will act as an approver for the activity.


Why would we want to do this? Let's walk through an example scenario:

Big Corp has lots of departments filled with lots of people who are very busy indeed. Supervisors have been identified but are too busy to deal with approval requests coming from ITIM because they operate in a highly volatile world when it comes to access rights. Big Corp has therefore decided that a role called "Departmental Approver" will be created and each department can assign multiple people to the role.

Big Corp, however, has insisted that users' access requests will only be approved by those Departmental Approver who exist within the same department as the requestor.

Native ITIM wizards allow you to select the "Departmental Approver" role as the participant of an approval activity but the link to the department that the approver is in won't exist! Using this mechanism, ALL departmental approver across the entire organisation would be used as an approver. This is where our Custom Participant script can come into play and provide us with a means of selecting Departmental Approver who exist in the same department as the requestor.

The solution to the problem would look a little like this:

// Let's search for the role first - we need its DN
var roleSearch = new RoleSearch();
var roleResult = roleSearch.searchByName("Departmental Approver");

if (roleResult.length < 1) {
    // This is a disaster - the role doesn't exist
    // You should handle this by whatever means suits you
} else {
    supervisordn = roleResult[0].dn;

    // Let's search for Departmental Approvers within our department
    var myFilter = "(&(erparent=" + container.get().dn + ")(erroles=" + supervisordn + "))";
    var personSearch = new PersonSearch();
    var personResult = personSearch.searchByFilter("person", myFilter, 2);

    // For each user found, let's add them to an array
    var myParticipants = new Array();
    for (i=0; i < personResult.length; i++) {
        myParicipants[i] = new Participant(ParticipantType.USER, personResult[i].dn);
    return myParticipants;

Of course, we should handle the situation whereby no approvers were found! Hopefully, however, there is enough information above to help you build a robust custom participant solution.

Sunday, May 08, 2011

Tivoli Directory Integrator Web Services

Tivoli Directory Integrator has supported web services for quite some time but the AxisEasyInvokeSoapWebServiceFunctionComponent sounds like it should be a straightforward drag, drop and point at a WSDL in order to enable TDI to call a web service without any knowledge of how web services work.

Of course, the reality is quite different and the word Easy in the middle of that component string is a tad misleading.

Let's construct a TDI web service client to retrieve a stock quote. Those nice people at have made a stockquote service available at

Complex Types
Invoking a web service normally means that some data needs to be provided to the service and the service will respond with some data. The data involved is normally wrapped up in what is called a complex type. In other words, a data object which can have one or more data elements.

Supplying complex types to a Function Component is straightforward, but not in the traditional way of building an input or output map the way we do for most other components.

Thankfully, TDI comes with a "Complex Types Generator". Drop a Complex Types Generator function component into your assembly line, point it at the WSDL, provide a JAR file to collect the necessary Java code that will construct the Complex Types as such:

Click on "Generate Complex Types" and you should get the following result:

The JAR file should now be copied to your {TDI_HOME}\jars directory. I create a WSDL directory under 3rdparty for such JAR files - you might like to do something similar.

To make use of the JAR file, TDI should be restarted!

We can disable the Complex Types Generator component as it won't be required at runtime. Now, we can make use of our JAR file and call the web service.

I use the excellent Java Decompiler to look inside the JAR file which helps me determine the full names of the Complex Types and the methods I can use on them:

Now, with a JAR file and the knowledge of what's inside the JAR file, we can build a web service call. For ease of understanding, I'm going to create three components/connectors:

Component/Connector 1: Script
First, we create a script component which will build our complex type. The code is:

var GQ = NET.webserviceX.www.GetQuote();
work.setAttribute("GetQuote", GQ);

What this is doing is creating a complex type called GetQuote containing a Symbol attribute with a value of MSFT.

Component/Connector 2: AxisEasyInvokeSoapWebServiceFunctionComponent
Now we can make the call to the service with our next connector. Pointing the AxisEasyInvoke.... component at WSDL we can select the GetQuote operation by clicking on Operations. The parameter to be supplied to the component (Operation Parameters) will be GetQuote and we specify our input and output complex types in the Advanced Pane as NET.webserviceX.www.GetQuote and NET.webserviceX.www.GetQuoteResponse as such:

Our Output Map should map the GetQuote work object as such:

Our Input Map should map the supplied Return object as such:

Component/Connector 3: The Result
Finally, we are going to insert a script to decrypt the information retrieved from the AxisEasyInvoke.... component:

var myReturn = work.getAttribute("return").getValue(0);
task.logmsg("INFO", myReturn.getGetQuoteResult().toString());

And when we run the Assembly Line, this is what we should get:

23:19:40,789 INFO  - CTGDIS087I Iterating.
23:19:40,790 INFO  - CTGDIS086I No iterator in AssemblyLine, will run single pass only.
23:19:40,790 INFO  - CTGDIS092I Using runtime provided entry as working entry (first pass only).
23:19:41,242 INFO  - [AxisEasyInvokeSoapWebServiceFunctionComponent] CTGDIZ601I Web service called successfully.
23:19:41,246 INFO - <StockQuotes>;<Stock>;<Symbol>;MSFT</Symbol>;<Last>;25.87</Last>;<Date>;5/6/2011</Date>;<Time>;4:00pm</Time>;<Change>;+0.08</Change>;<Open>;26.01</Open>;<High>;26.22</High>;<Low>;25.75</Low>;<Volume>;55993640</Volume>;<MktCap>;218.2B</MktCap>;<PreviousClose>;25.79</PreviousClose>;<PercentageChange>;+0.31%</PercentageChange>;<AnnRange>;22.73 - 29.73</AnnRange>;<Earns>;2.517</Earns>;<P-E>;10.25</P-E>;<Name>;Microsoft Corpora</Name>;</Stock>;</StockQuotes>;
23:19:41,247 INFO  - CTGDIS088I Finished iterating.
23:19:41,247 INFO  - CTGDIS100I Printing the Connector statistics.
23:19:41,248 INFO  -  [BuildComplexType] Calls: 1
23:19:41,249 INFO  -  [AxisEasyInvokeSoapWebServiceFunctionComponent] CallReply:1
23:19:41,250 INFO  -  [DecodeReturn] Calls: 1
23:19:41,250 INFO  - CTGDIS104I Total: CallReply:1.
23:19:41,251 INFO  - CTGDIS101I Finished printing the Connector statistics.
23:19:41,252 INFO  - CTGDIS080I Terminated successfully (0 errors).

I leave the parsing of the result to you and wish you all the best with your future Web Services' adventures.