Walking through the SIF driver - Part 2

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.

Now to continue on, through the rest of the subscriber channel.

Process Check User Enhancements:

This next rule is for when the triggers source name is Check for User Enhancements. The current time is taken, and a query for ccSIF-UserEnhancement objects found beneath the current driver DN in eDirectory, whose ccSIF-FutureEnhancement value is true, is generated with * for Read Attributes which means all attributes.

Then the driver loops through the results of that query. For each returned value, check to see if the ccSIF-EntryDate is in the past, by the test of:
if XPATH is NOT true $current-node//attr[@attr-name="ccSIF-EntryDate"]/value/text() >$lv_CurrentTime

In the current-node (each object found will be one instance, and thus each will go through this loop), look for any (the //) attr node, whose XML attribute attr-name is ccSIF-EntryDate, then compare the value to the current time with a greater than test. So negated greater than is less than. Not sure why they did it this way instead of less than, but I am sure there is an interesting story behind it.

Now if this is a case of a future enhancement that is now in the past (aka a future dated event come due) set some variables.

The first holds the nodeset of this event. The second gets the class from a similar XPATH to above but for attr[@attr-name="ccSIF-class"]. Then they do something cutesy. They have a driver scoped variable set up elsewhere (we will get to it in the Input Transform) that basically is the contents of the structured GCV for this object, and they do it via variable interpolation even in the variable name itself! How neat! Thus the variable name: gv_SifConfigFor_$lv_CurrentFutureEnhancmentSIFClass$ itself uses a variable to define itself on the fly. Someone was being very clever! I like it!

Anyway, inside this variable, which being globally defined in the Input transform as part of the driver startup is a fair bit of information that is extracted in this rule. The sif.UserObjectReferenceAttribute value is stored in a variable lv_CurrentFutureEnhancementUserToEnhanceAttribute and then immediately used to get the attribute of that name into the lv_CurrentFutureUserToEnhance variable, which appears to be the DN of the User that is to be enhanced.

Now it loops through that part of the global variable just set, looking at a list GCV in there called sif.EnhancementAttributes, and for each of those values, checking to see if our current ccSIF-FutureEnhancement object has an attribute of that name.

If it does, then we add a source attribute to the user previously decided to be enhanced, with the nodeset that was stored for the attribute in the ccSIF-FutureEnhancement object. Thus they store the future event on an object, and read back the changes needed to do it.

Next they add a ccSIF-UserEnhancementRef to the detected users, with the current future enhancement objects DN to track who added what to the User.

The class of that user is grabbed from the user into a variable, (ccSIF-Class is not User or Group, rather it is something like StaffPersonal, StudentPersonal for Users, SchoolInfo, SchoolCourseInfo, RoomInfo, SectionInfo for Groups, and StudentSchoolEnrollment, and StudentSectionEnrollment for ccSIF-UserEnhancement) and then used to get the list of attributes from the Structured GCV related to that class for the next loop.

Within that loop, if there is an attribute value in the stored ccSIF-FutureEnhancement object of the sif.GroupMembership list, then they add some attributes. First a Group Membership on the user to the Group DN retrieved from the ccSIF-FutureEnhancement object, then adding the Security Equals attribute the same way, and finally the converse for the Member attribute on the Group to list this user on that group. The usual dance that needs to be done with eDirectory groups. Depending on how and where you do this, if it will go through the Notify filter, after the Command Transform, then the reciprocal attribute mappings will do the work for you, but in this case, writing back to source, in the Subscriber channel it needs to be managed manually.

Finally we clear the ccSIF-FutureEnhancement on the ccSIF-UserEnhancement object that we started with. When we started it was true, to imply there is a pending future event. Now that we have processed it, we clear it to stop it being processed again.

Next we have a test if the class is User. This seemed odd and innocuous, and the reason is clear once you think about it. Remember our current event is a <trigger> which could be on a single user, or on a container. Thus the current events object class is either User or something else. If it is User, then we only care about a single User to work with and thus the lv_UserObjectQuery gets the single user by its association. Reading back ccSIF-EntryDate, ccSIF-ExitDate, and ccSIF-Class from the source (eDirectory).

If it is not a user, say it is a container, then we look for all Users in the tree for the same attributes. That seems a bit odd, and you would think it would make sense to have this one scoped, but I guess not.

Now we have a variable with either one object or a long list of all users, and we loop through it.

Get the current class from the ccSIF-Class attribute returned in the event doc, using the XPATH of $current-node//attr[@attr-name=ccSIF-Class"]/value/text() and then using the Global variables defined in the Input transform, get the chunk of the structured GCV for this type of ccSIF-Class object.

If the ccSIF-ExitDate has a value, and its less than the current time we started with, (using CTIME, a count of seconds, so less or greater than tests work), then we get the src-dn from the current-node so we can identify the subject user object. Then read back from the source any ccSIF-UserEnhancement object whose ccSIF-StudentRecord is the current users DN, reading back all attributes.

Then we loop through any of these such objects, getting the ccSIF-ExitDate and ccSIF-EntryDate into variables.

If we have an Exit Date, and it is not greater than the current time (i.e. in the past, so looks expired) then one more check for sanity that ExitDate > EntryDate or EntryDate is greater than current time. I.e. The data is not goofy, (like the exit date is before the entry date). Then this is object is expired otherwise, it is non-expired. Store this in a nodeset GCV and build it as a list we can later loop through.

Now they do something very cool, and I have never tried! They for each over the original query that returned either one or many users, with the XPATH defining the nodeset as:
That is, in the Query result doc they stored, find me the ones whose event node (the period) meets the criteria, that it contains the expired list in the src-dn XML attribute.

This is cool because it is taking a nodeset of values and comparing it for matches really to another list, and only pulling out the matches. That is, the lv_EnhancementQuery has a bunch of <instance src-dn="Something"> nodes. The lv_expired variable has a list of DN's. This XPATH test finds all the <instance> nodes that match a value in the list of lv_expired. Cool! I had just this week been wondering how this might be done!

Now inside this loop, get the ccSIF-Class, copy the detected expired users info so we can use it in a for-each loop in a moment (since that resets the value of current-node, we need to store it before we go into it). then get the part of the structured GCV via the globally scoped variable set in the Input Transform, and loop through its list of sif.EnhancementAttributes. I.e. The ones that will need to be processed on an expired user.

So inside the loop. get the attribute name, and then evaluate this funky XPATH test, if its not true:

Look back at the query for all the ccSIF-UserEnhancements, and find the <attr> node whose attr-name XML attribute is the one we just picked up inside our loop, and then make sure its src-dn is in the non expired list. I.e. Double check that although there may be one expired ccSIF-UserEnhancement for this attribute with the user, that there is no ccSIF-UserEnhancement for the same attribute that is NOT expired. Very classy!

If there is no other unexpired same attribute in the list of enhancements for this user, then we remove the source attribute from the user DN we picked up earlier for the attribute we are working on, with the value that the ccSIF-UserEnhancement has as its payload.

Then we remove the Group membership attributes as the opposite (remove vs add) that we performed a few steps back of adding Group Membership and Security Equals to the User, pointing at the specific group, and on the group removing the Member attribute value for this user.

Finally we delete the ccSIF-UserEnhancement objects that have expired and been processed.

Wow! That was an interesting rule.

Veto Trigger Events:

This is common when using jobs, to veto any <trigger> events that get any further, so as to prevent them from being submitted to the shim which will not know how to process them.

Subscriber Command Transform:

There are three rule objects in here, but they are the basic rules that most drivers include, that try to convert a modify of nspmDistributionPassword in eDirectory into a <modify-password> event in the connected system. The converse happens in the Publisher command transforms, and you can read in much more gory detail how these work in the following articles:

However since the User object is set in the filter no Subscriber ignore, I doubt that passwords will work to flow back to SIF even if the shim supported it. These may just be superfluous rules, but I am not certain.

Output Transform:

There is a single policy object in the Output transform, output_Query_transform, that has 5 rules:

  1. Break if not a Query

  • Clean all eDir Values From Query

  • Veto if sifClass not available and association not present or sifGuid not available

  • Change Query to query specific Class found in ccSIF-Class Attribute

  • Change Query to query specific association found in ccSIF-GUID Attribute

Break if not a Query

This rule just makes all other event types break to save processing time.

Clean all eDir Values From Query

The previous rule locked us down to only <query> docs getting this far, so no conditions necessary, and then strip by XPATH a couple of query specific things that the shim does not need.

First off is the class-name XML attribute and secondly is the <search-class> node, which a query doc will contain both types of, both referencing the same class.

Veto if sifClass not available and association not present or sifGuid not available

This rule is trying to make sure that the data the shim needs to process a query is present. That is:

  • sifClass should be present

  • association should have a value

  • Key attribute should be available.

If all three are missing, the veto the event with a trace message in the log, since the shim would not be able to do much with it.

Well that's about it for this segment, more to come as I work through the rest of the driver.


How To-Best Practice
Comment List