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.
Now on to the Publisher Command Transformation Policy set. This has eight policy objects, but four of them are the standard password synchronization policies, that you can read about in these articles, that discuss how a <modify-password> event comes from the shim, and depending on how the Password Management GCVs (Global Configuration Variables) are set is transformed or not. Basically a modify of the eDirectory RSA key pairs (old school password) will remain a <modify-password> event, where if set to change the Universal Password, it will be transformed into a <modify> event, for the attribute nspmDistributionPassword.
The policy objects are:
then the password transform policy objects of:
Well let’s get started.
This policy object has three rules:
- Break if not a User Enhancement
- Set Local Variables
- Enhance Object
Break if not a User Enhancement
First rule limits it to ccSIF-UserEnhancement objects.
Set Local Variables
Like most of the policies in this channel that we have seen some local variables are set up, since they are policy scoped, they get cleared as we move on to the next policy so they need to get reset each time. Driver scoped variables are sometimes helpful for this task, but the problem is that the Subscriber and Publisher channels are independent and run at the same time. Thus it would be possible to get an event in one channel processing at the same time as an event in the other, but if you try to rely on driver scoped variables used in both, then it might get changed midstream as its being used. Now if you limit the variable to be used in one channel only it is less likely to be an issue, but care should still be taken. Nothing like leaving a variable uninitialized for the next event to come through to make for immensely confusing results.
This sets up the attributes:
lv_sifClass like always, with the sifClass value from the operation property set back in the Input transform
lv_CurrentObjectConfig from the GCV stored in a variable, gv_SifConfigFor_$lv_sifClass$
lv_CurrentTime with the current CTIME value
lv_EntryDate with the value of ccSIF-EntryDate
lv_ReferenceAttribute is pulled from the configuration GCV which seems to be the attribute on the object storing the GUID. ccSIF-StudentRecord is reported to be the most common value
lv_EnhancedUserDN is read from the op attr or dest attr depending if it is an add or modify event, from the attribute name stored in lv_ReferenceAttribute
If the EntryDate is available and less than the current time then we process this event.
If the lv_EnhancedUserDN is blank, then the User this references does not exist yet.
Otherwise loop through the Structured GCV nodeset fragment via the XPATH of:
This is loop through the list of EnhancementAttributes stored in the GCV.
Copy the current-node variable into lv_Attr_Name, since there is one more loop and current-node will be used inside there. The inside loop is interesting, as it is over the XPATH of:
This says, from the root of the document (the period) search (the \\)_for any node (the asterisk) with an attr-name XML attribute (The  means a predicate, aka Where) that is the same as the lv_Attr_Name value and then any (the \\) value nodes, and the text() from them.
This basically means get me every attribute value that matches my current attribute name in the event document. This is a very useful XPATH to be able to loop through all the values for your attribute. This would work if you had one <modify-attr> node with one <add-value> and one <value> node under it. Or 5 <value> nodes under it. Or 5 separate <modify-attr> nodes, each with one <add-value> and one <value> node under it. Semantically they are identical, but the XPATH needs to be aware of it. In fact, this is an issue if you manipulate the data, and loop through the values, and do an add destination attribute, you will get one <modify-attr> node for each time through the loop. There is no easy way to make one <modify-attr> node with many <value> nodes two levels down. (You can do it with Append XML node, and build the XML literally by hand, but it is barely ever worth it).
Then for add events, stick in a reference attribute on the User object, pointing at this User Enhancement object. This is useful in general because now you know what is linked to this user. Sometimes a two way link is useful like Group Membership and Member of, but here they only do one.
Finally a driver scoped variable, lv_enhancedClass is set, with the sif.UserObjectClassName GCV value for this object. Should be interesting to see where this is used.
This policy object has the following rules:
- Add operation Data on Enhanced Object
- Set Local Variables
- Check if Rename is Needed
- Rename Object
- Set Object Name on Add
- Clone GroupMemberShip Attributes
- Set GroupOwner if necessary
Add operation Data on Enhanced Object
Well right off the bat we get the answer to my question about if we will use the lv_enahncedClass value. If it has a value, and the operational property eDirClass is not available, then we set the op property to User, and the sifClass op property to the lv_enhancedClass value. Then clear the driver scoped variable by setting it to null.
Set Local Variables
This is much the same as in the previous rule, setting the sifClass local variable, and pulling the structured GCV apart to get just this classes chunk of it.
But this time one extra variable is set, lv_RenameObjectEnabled from the sif.RenameObject GCV inside the structured GCV.
Check if Rename is Needed
For adds on non-ccSIF-UserEnhancement object, or else the case of non-ccSIF-UserEnhancement objects being modified, and the lv_RenameObjectEnabled variable just set is true this rule fires.
For a modify, it loops through the GCV component sif.NamePieceType values, and an attribute of that name is changing, then it sets a variable lv_GenerateName to true.
In the add case it just sets it true anyway.
Basically if any of the attributes that contribute to how an object is named are changing its time to reconsider a name change. Or in the case of an add, we need to generate a name in the first place.
If the lv_GenerateName variable is true, and its a modify, we start working the rename process. (Next rule will handle the add case).
lv_NormalizseName is set to the GCV value for this class of sif.NormalizeIDPiece.
Then we loop through the sif.classNamingConfiguration GCV fragment, and get lv_PieceName out of the sif.NamePieceType GCV.
For text, we get the sif.NamePieceText value and use that as the lv_PieceValue.
Otherwise, we get it from the Operational Attribute named in the lv_PieceName variable. Not sure why, but it is ParseDN’ed down to the leaf most node, if there is a backslash in the value.
If we did not get a value from an attribute, then try again, this time looking at the destination object for the attribute named by the lv_PieceName variable.
Now if after the text or attribute based test we still have no value in lv_PieceValue then rename it to No-Name_ followed by the Association value. But if we did get a value, we get the lv_pieceLeb from the GCV sif.NamePieceLength, lv_PieceDir form the GCV sif.NamePieceOrientation, and then set the lv_ObjectName using an ECMA function called, userIdPartGeneration, passing it the following variables:
This ECMA function is in the sif_ECMA_Scripts object in the driver.
The function takes the following parameters, that it calls, (strPart,intLen,strDir,normalize).
It looks like it replaces a number of characters to cleanse the name using this line of code.
StrPartNorm = new String(StrPart.replace(/\./g,”-”).replace(/\s+/g,”_”).replace(/[^\dA-Z\-\_]/gi,”))
Any periods are replaced with dashes, which is: replace(/\./g,”-”)
Any white spaces are replaced with underscores, which is: replace(/\s+/g,”_”)
Then a fun one, which is in ECMA: replace(/[^\dA-Z\-\_]/gi,”)
If I read that correct, \d means any set of digits, A-Z means the upper case letters. \- and \_ escape the dash and underscore characters. Then the ^ (caret) negates the set, which is defined by the square brackets  thus the fun. So that is anything that is not a digit, upper case letter, space or underscore is removed.
Also, the /g and /gi means replace all (the g, all instances) and the i means case insensitive. Which means that the /gi on that last complex one means that not just A-Z but also a-z which is a subtle twist.
Then I think it substrings it to be sure it is not too long. You could do all this in Policy with Replace All tokens, but this way is just as easy, it is nice having a bunch of options at your finger tips to solve the same problems in different ways.
Next up they use the Unique Name token to make sure the name is not already in use. The Unique Name token is great, and you can read more about it here: Unique Name Token Functionality in IDM 3.5
What I find interesting is that the variable they store the unique name generated by the token into is a nodeset. I am not sure what happens if you call Unique Name in a nodeset context. I suspect this is not important since it should only ever return a single matched value. I.e. A unique value. A nodeset of a single value, when treated as a string, is just the string value. When there is more than one value they get concatenated together.
You can see this most easily with Fax numbers. More correctly the attribute is Facsimile Telephone Number and in eDirectory it uses a syntax unique to Fax numbers. This has three components, faxNumber the actual number, faxBitCount which defaults to 0, and faxParameters. If you just treat this attribute as a string, you actually get the fax number with a zero appended to the end, as really it is a nodeset of a blank faxParameters, a faxNumber value, and a 0. This can be very annoying as the number looks ok in trace, it just has an extra zero on the end. Often fax numbers end in zero so it can be even hard to see happening in trace.
The proper way to handle it would be in the Output transform, to flatten the attribute, with a reformat op attr token, where you use the XPATH of $current-value/component[@name='faxNumber'] to get just the actual number for the fax machine into the operational attribute.
Anyway, I wonder if this is an intentional use of a nodeset variable for some purpose or not. Add to my list of things to try and get the Consensus guys to answer.
Finally a rename using the new name is performed.
Set Object Name on Add
Same basic approach as in the modify case, just for an <add> event. Now in the case of a <modify> you obviously have an object at this point (the association value linked them together earlier just after the Event Transformation) so to process changing naming parameters you want a rename event. For an add event, you really just need to set the name itself, via the destination DN value. This is an XML attribute in the <add> node itself, named dest-dn.
Now this is interesting, that in the Publisher Placement rule a value is set for add events, since the engine will not actually let you continue past the Placement rule if a Destination DN is not generated. You get a No destination DN generated by the placement rule error and the event dies. However, here in the Publisher Command Transformation, we see that they calculate it for and do a similar trick to how the Active Directory driver does it. In the Active Directory driver, if Full Name Mapping is enabled, then the CN in Active Directory will be CN=Geoffrey Carman, ou=Users, dc=acme, dc=com whereas Login Name Mapping would have it as cn=gcarman, ou=Users, dc=acme, dc=com.
To do that, they like here, use the ParseDN functionality that is built into the Source DN and Destination DN tokens, by specifying a length of -2, to chop off the last node from the destination DN, and the append the newly calculated name. This nicely allows you to reuse the values set earlier, and just replace the last bit of the DN.
To be continued in part 7…