Walking through the SIF driver - Part 7

Novell Identity Manager has several drivers for connecting to all sorts of other systems, one of which is the SIF driver, written by Concensus Consulting. SIF is the Schools Interoperability Framework, which is meant to manage the student life cycle, in terms of enrollment and courses they take.

If you can connect this to an Identity Management system, then you can easily manage their lifecycle within your Identity system in terms of accounts at your school.

If you can then use a product like Novell File Management Suite, you can manage the lifecycle of their files, moving them from server to server (or NAS to NAS or directory to directory) as they change school years, and provisioning shared storage for every class, and finally archiving or removing them when they graduate.

If you use a product like ZENworks Configuration Management then you can manage their lifecycle in terms of applications and workstations.

Put it all together and it is quite compelling! But to use the latter two tools, you need to get the student information into your systems, which is where the SIF driver comes in.

I started writing about this in the first article in this series Walking through the SIF driver - Part 1 where I worked through the beginnings of the Subscriber channel policies.

Then in the second article Walking through the SIF driver - Part 2, I worked through the rest of the Sub Event, Sub Command, and the beginning of the Output Transform.

In the third article Walking through the SIF driver - Part 3 I worked through the rest of the output transform and once done, moved on into the Input Transform.

In the fourth article Walking through the SIF driver - Part 4 I worked through the rest of the Input Transform, the Publisher Event Transform, and into the Matching rules.

In the fifth article Walking through the SIF driver - Part 5 I worked through the Creation Policy and Placement Policy Sets.

In the sixth article http://www.novell.com/communities/node/13382/walking-through-sif-driver-part-6 I starting working through some of the Publisher Command Transform policy set.

In this article I will continue on through the Publisher Command Transform policy set.

In the policy object, pub_CommandTransform_Policy, there are two remaining rules to get through.

Clone GroupMemberShip Attributes

Here a test is made for the sif.GroupMembershipAttribute GCV to even get into. If present then there is a for each loop over the values.

If any of those values are changing, using the Operation attribute, with the name coming via variable interpolation of $current-node$, the noun tokens, Operation Attribute and Removed Attribute are used. These are useful, as you can loop over them, and thus for each instance of a value being added (the Operation Attribute noun token) Equivalent to Me and Member are set on the object in the current node pointing at the current object and the Group Membership attribute on this current object gets a member added for the current-node variables value.

This is a little confusing as the first loop is over a GCV of attribute names, and the second loop is over attributes of the current-nodes name changing. But inside that loop, the current-node is the value of that changing attribute, which should be a DN, and therefore is a reference to an object.

The difference between a modify and add event for this is whether the add destination attribute token is adding the value to the current event document (in the <add> event case), or write directly to the data store which is the <modify> case. I find it interesting that in the XML, the way DirXML Script handles denoting these sorts of choices is with the following XML attributes:

when = 'after' for add after this event
when = 'before' for before this current event
direct = 'true' for write direct to data source
nothing special for add to current event

I always wondered why they went with direct='true' instead of maybe when = 'direct' to stay consistent. But regardless it is good to understand the underlying XML.

Much the same is repeated for the loop over any removed attributes. This uses the Removed Attribute noun to provide the nodeset. Same attribute,

Set GroupOwner if necessary

This fires on Group objects, and only if the sif.Object.GroupOwnerAttribute is not set to None. Like many of the configuration values possible in this driver there are many possibilities from none, to a set value, to one from a list. (Very classy by the way!)

In this case, if it is none, clearly we do not need to do this action. Otherwise we get the list from the sif.GroupOwnerAttribute and loop through it.

Much the same as before, for each attribute in that list we do two inner loops. One over Operation Attribute (The values being added) and one over Removed Attribute, the values being removed.

We updated the Owner, Member, Equivalent to Me and Group Membership attributes. It is interesting to note that the reciprocal attribute to Equivalent To Me (Security Equals is not set) but the both sides of the Group Membership and Member pair are set.

This driver has no Reciprocal Attribute mappings set, aside from the default, so that means the default pairings should happen. Which is why they can get away with only half of the Equivalent To Me and Security Equals pairing.

In hindsight, I would suggest that adding it now is a wiser choice. If you have ever watched how Reciprocal Attribute mappings are handled in the driver on the fly, you would notice that the engine pauses, sometimes for 1-10 seconds while it does the query it needs to figure it out in the background. Whereas right now, you know both values, no need for any queries. It would save time later in the process to just add a fourth token to handle it explicitly. The functionality this brings to the engine is great, for when you miss one, or forget to handle such a case. But since it can add a serious performance hit, it is better to just do it in policy, as long as the data is already at hand.

That leaves three more custom Policy objects, since the standard password policy objects follow it, and since those have been handled elsewhere I shall skip them.

  • Remaining policy objects

  • pub_Move_Object_Policy

  • pub_PlacementPrep_Policy

Remaining policy objects.

Here we have three rules:

  1. Break in not a modify event

  • Check if Move is Needed

  • Set Object Placement

Break in not a modify event

Usual scoping to just modify events, break for all others.

Check if Move is Needed

As usual the sifClass is read from the operation property, the Structured GCV is picked apart for the instance of this sifClass, and then looped over. If there is such a class, we execute once, if there is none, it will execute zero times.

If the sif.Auto.Move GCV is not true, then move on. Otherwise, loop over the sif.Object.PlacementAttribute GCVs underneath and if the listed attribute is changing, set a local variable lv_MoveRequired to true.

This way you can have zero, one, or many attributes for each sifClass that indicate a move.

Set Object Placement

This rule tests if the lv_MoveRequired potentially just set is true.

If so, the lv_ObjectBaseContainer is picked out of the structured GCV from the sif.BaseContainer GCV.

Then next for each has an interesting XPATH nodeset defined. It is the definition whose name is sif.Object.PlacementAttribute, but then followed by a .. which implies up one node to the parent. I.e. Find the specific node, then look at its parent to loop through.

The lv_CurrentPlacementType is read out of sif.Object.PlacementAttribute and tested basically in a repeat of the Placement policies as discussed in the fifth article: Walking through the SIF driver - Part 5

If it is text, build the DN in a variable lv_ObjectBaseContainer with the base container, plus the text string.

Otherwise look for the named attribute in the current event document. If it is present, then build the DN in a variable lv_ObjectBaseContainer as the base DN plus the value of that attribute.

If that attribute is not found in the current document, look into the destination for the value of that attribute. If still no value is found, move on, nothing to do here.

If a value is found, the lv_ObjectBaseContainer variable gets the base DN plus the value of the attribute (with a slash inserted in between of course, to keep it a valid IDM DN syntax attribute).

Finally, if you get past all that, then do a move object call. It is worth noting that the move destination object token does not take a full DN as the move to path, rather it wants just the OU you want to move the current object (name shall stay the same, else that is really a rename, right?) into. Which is where this mostly differs from the earlier placement rules. There they cared about the full path, including the naming attribute.


This policy has two rules in it.

  • Break if not an add or Move

  • Create Necessary Containment for Objects

Break if not an add or Move

Typical scoping to only handle add and move events. Its interesting that adds are considered here, I suspect it has to do with figuring out if there is a container already waiting to be added into or moved into.

Create Necessary Containment for Objects

As discussed in the previous policy object about Move events, the path specified as the destination DN for a move is the parent OU where the current object is moving into. Whereas on an add event the destination DN contains the object name as well.

Thus there is a if then test, for adds, use the ParseDN functionality that the Destination DN token has to get the Destination DN, but a length of -2, which chops off the lead most node (aka the object name) leaving just the parent container DN.

Otherwise its a move event, in which case as discussed we know we have the parent OU as the destination DN. There just get it via the XPATH. Which is interesting, since they could have used the simple Destination DN token, but I see from the XPATH that they use ./parent/@src-dn. This is because a move event looks a little different then an add. In an add event, the dest-dn XML attribute is in the <add> node. Whereas in a move event, there is a <parent> node under the <move> node, and the <parent> node is what contains the dest-dn XML attribute.

I guess that means the Destination DN token is not smart enough to catch that difference. Would be nice if it could though. Keep it nice, simple and consistent.

Now they use a very clever approach to proving the entire DN path is available! Often you see people hard code a check for the parent, then the grand parent, and maybe the great grand parent node. But what if you might have indeterminate depth of nodes? Some crazy school decides that 22 layers deep is optimal. Well as a driver guy, you really should support that. This approach handles it quite nicely and is quite clever.

So the lv_destFinalContainer has the OU that the object is being created or moved into.

Now ParseDN that value, for a length of 1, that is, the root most node, into a variable lv_DNtoCheck.

Now loop via a While construct, while lv_DestFinalContainer is not equal to $lv_DNtoCheck.

Get it? I initially thought they would just decrement the lv_DestFinalContainer variable, but again they were clever! Instead, they build up lv_DNtoCheck in each loop, since really you need to start making containers from the root down. If you are missing three levels of containers, it does not work to make the lowest levels and then higher ups.

So how did they build UP the lv_DNtoCheck variable? Well they call a ECMA function, stringCompare() where the parameters are the full DN, and then the partial DN. This removes the partial DN from the full DN value.

This seems odd, as it takes O\OU\OU2\OU3\OU4 as the full DN and O\OU\OU2 as the partial DN, and returns:


But then they did something clever again, they ParseDN that value, saying start at 0, and go for length of 1, which is OU3. So their partial O\OU\OU2 picks up the \OU3 and is now O\OU\OU2\OU3 which is the one longer they wanted.

Very clever. I suppose they could have used a counter variable but it looks like the length and start values of the ParseDN token do not accept variables there. (I wonder if it would work, now that I think about it).

Keep looping as lv_DNtoCheck gets longer and longer and closer to the full DN path.

Once it matches, you know you have handled the entire DN path.

Inside the loop, do the typical way of getting an object and its DN via a Query. You query for it, into a nodeset variable. In this case an entry query for the value we just built up into the lv_DNtoCheck variable.

If you get a result, then $lv_DNQuery will have a src-dn XML attribute.

If there isn't one, then we need to make the object so do an add destination object, with the DN of the missing container, then set the OU attribute with the object name (ParseDN of start at -1, length of 1). Technically the OU attribute is the naming attribute for an OU object so I am not sure this is entirely necessary, like specifying the CN of an object named CN=.

The next four rules are the standard Password management policies, which you can read about in this pair of articles:

I won't bother rehashing them here, they are basically the same in every driver (The Notes driver has a subtle twist on the Subscriber channel).

That basically wraps up this walk through. If I have to take away one lesson from this, it would be that Structured GCV's are just the bees knees! Wild how much they use them in this driver, it is very cool! From a configuration perspective it allows for many classes to have a stack of settings per class. In an easy fashion to manage.

A little confusing to wrap your head around, but very neat to see in action.

I hope you have enjoyed this series and learned something from it. I know I have. If you are ever looking at a new driver, I would suggest you consider writing it up, sort of in this fashion, as I would love to have others contribute more such articles. I am tracking all this type that I know, dissecting drivers, on a Wiki page:

Even if you want to just dissect one policy or rule that is of interest please do, and let me know so I can link to it in the Wiki.

Next drivers for me to look at are the SAP drivers, in the Consulting Editions. Those should be an absolute ton of fun, they are so complicated and cool! Tons to learn!.
function StringCompare(fullDN,partDN)
strPartDN = new String(partDN)
strFullDN = new String(fullDN)
strReturnValue = new String(strFullDN.substring(strPartDN.length))

return strReturnValue.valueOf()


How To-Best Practice
Comment List