Cybersecurity
DevOps Cloud (ADM)
IT Operations Cloud
There are a lot of interesting new features in the new Novell Identity Manager 4 release. I have discussed some of them in some of these articles:
One of the major new features on the engine side of the product is Packages. Packages are the just the bees knees! There is so much interesting to say and do with them, that it took 9 articles to work through as much as I could find. Take a look at these articles for more information on Packages:
One of the new features is the ability for a Package to include pretty much any arbitrary object you might need in eDirectory. For example, at a client recently we added some monitoring code to watch and make sure we could both read and write to Active Directory through the driver. There was a bug in the Remote Loader and Active Directory driver when running on Windows 2008 R2 server. (You need the IDM 3.6.1 engineIR2a patch, which has a Remote Loader patch, and the latest ADDriver.dll file, (currently 3.5.13) for it work properly on the R2 server). We could not get a good monitoring approach via external tools, to notice the absence of changes, instead we wrote some code that writes and reads a date stamp from Active Directory, and then stores both values on some object we made. It would be nice top package that all up, to reuse at other locations. But to do it, we need an object created under the Driver Set container object of a particular object class, so the monitoring code will work. (Need somewhere to store the timestamp values).
Well Packages include all the usual suspects (except Keyser Söze) in terms of objects you can include, but what about some other object you might wish to include in a package? Well it turns out one of the current limitations is that Schema attributes and classes cannot be included in a Package. That would be nice, but stay tuned for a later release for that to get added.
Well to handle the arbitrary object type, they included the notion of a ds-object. You create a DirXML-Resource object, and add it to the package. The DirXML-ContentType attribute of the DirXML-Resource object indicates which type of user interface Designer should show, to render the XML that is stored in the XmlData attribute.
The available DirXML-ContentType values are:
application/vnd.novell.dirxml.mapping-table xml;charset=UTF-8
text/ecmascript;charset=UTF-8
text/vnd.novell.idm.entitlementConfiguration xml
text/xml
text/ text
application/vnd.novell.dirxml.sso-application xml
application/vnd.novell.dirxml.sso-repository xml
application/vnd.novell.dirxml.pkg-prompt xml
application/vnd.novell.dirxml.filter-ext xml
application/vnd.novell.dirxml.ds-object xml
You are probably familiar with most of those content types. ECMA Script resource objects were introduced around Identity Manager 3.5 along with Mapping Table objects. Entitlement Configuration objects are needed to store the information about the Entitlement to Resource mappings needed for the Role Based Provisioning Module 3.7 (you can read more about that here: Converting Entitlements to Resources, more details).
Package Prompts are new in Identity Manager 4.0 obviously, and the SSO stuff is a long story in and of themselves.
Anyway, the last in my list is the ds-object. The problem is that the User Interface in Designer for the ds-object DirXML-ContentType is basically an empty XML editor. Oh lovely. So how do you copy an object out of the directory and into such an object. That would be useful, wouldn't it? Alas that too is a missing piece of functionality in the current release.
I got asked by a client, so what is the format of ds-object's? Well darned if I know, the docs are basically nonexistent at this stage, so I opened up Designer, imported an Active Directory driver with all the available Packages, and then exported it to an iManager configuration file. Opened it up in a text editor and looked for <ds-object> and look what I found:
<ds-object checksum="576812079" ds-object-class="DirXML-Job" ds-object-name="PasswordExpireJob" package-id="NTYAT7QU_201011110814280953" pkg-assoc-id="A06B4WBC_201011241333470067">
<ds-attributes>
<ds-attribute ds-attr-name="DirXML-TraceLevel">
<ds-value><![CDATA[0]]></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-TraceSizeLimit">
<ds-value><![CDATA[0]]></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="XmlData">
<ds-value base64-encoded="true"><![CDATA[SomeB64data]]></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-EmailTemplates">
<ds-value>
<typed-name-interval><![CDATA[0]]></typed-name-interval>
<typed-name-level><![CDATA[0]]></typed-name-level>
<typed-name-object><![CDATA[Password Reset Fail.Default Notification Collection.Security]]></typed-name-object>
</ds-value>
</ds-attribute>
<job-email-server-query job-name="PasswordExpireJob.Driver Set.null"/>
<ds-attribute ds-attr-name="DirXML-pkgAssociationId">
<ds-value><![CDATA[A06B4WBC_201011241333470067]]></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-pkgGUID">
<ds-value><![CDATA[NTYAT7QU_201011110814280953]]></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-pkgChecksum">
<ds-value><![CDATA[576812079]]></ds-value>
</ds-attribute>
<job-servers-query job-display-name="Password Expiration Notification" job-name="PasswordExpireJob.Driver Set.null"/>
</ds-attributes>
</ds-object>
This is the definition for a Job object, that checks Password expirations. Now it turns out that there is a specific eDirectory object class for DirXML-Job objects (I.e. It is not usually a DirXML-Resource object, since I did not list it above in the possible DirXML-ContentType values), but there is no specific object in Packages to handle it, so it falls into the ds-object catch all. Useful catch all that it is. Useful to me, since this nicely demonstrates most of the syntax for the ds-objects.
Looking at that XML you can see how to express pretty much everything you need about an object. Specifically it also includes a structured attribute, since I never in a million years would have guessed how they handle the component values in a ds-object. I mean, seriously? Prepend typed- to the component name? Oh well, I guess we need one DTD to rule them all? (Drop the XML in Mount Doom?)
I assume that the XML attributes, package-id, pkg-assoc-id and checksum get added by Designer, since those are not the usual hand generated values you might otherwise expect.
The one feature that is missing is what a multi valued attribute would look like. I assume it would be a second <ds-value> node, but who knows with this DTD.
For fun I thought, this is XML, and when all you have is a hammer, everything looks like a nail. I happen to hate XSLT, so this seemed like a job for DirXML-Script. I mean, how hard could it be? I noticed when doing a SOAP driver that talks to Salesforce,com that is pretty easy to transform one XML dialect to another via the use of DirXML Script. In fact it is pretty quick too. Quicker than my experience with something similar in XSLT.
If you would like to read more about how to transform SOAP XML into XDS, you can read this series of articles:
Now its not clear what event exactly you would use to generate <ds-object> XML in a normal driver, and I concede this is a contrived use case, mostly because I am lazy and recoding it by hand is a pain. Of course this is probably the right place to use XSLT to do this, but I like DirXML Script darn it, so I shall. I was thinking that a Loopback or Null driver (Careful, if you have only the Bundle Edition license for IDM, then even one of those drivers will change the license to require a regular IDM license, in which case, you do it in an eDirectory driver since that is still free) with this rule in the Subscriber Command Transform, would be a good way to use this rule.
Basically edit the filter for whatever class you want, add all the attributes you need. In iManager, migrate the object for the Loopback driver, go look at trace to get the output <ds-object> information. Of course, you could email it to yourself. You could add it as a DirXML-Data attribute on the object, since it is already intended to hold XML text in that attribute.
The good news is that with Simulator in Designer, you could very easily paste in an <add> event for some object and it will convert it for you in the resulting trace. This way it all stays within Designer.
Basically I did this to see if I could, and how hard it would be, and it is of reasonably low utility, but you never know when it will help someone, so why not share it.
The code is really straightforward, so lets look at it, and walk through what is happening.
<rule>
<description>[CIS] Convert <add> event to <ds-object> event.</description>
<comment xml:space="preserve">Should be fun!
Take an <add> event that looks something like:
<nds dtdversion="3.5" ndsversion="8.x">
<source>
<product version="3.6.10.4747">DirXML</product>
<contact>Novell, Inc.</contact>
</source>
<input>
<add class-name="contact" dest-dn="cn=RSMITH7,ou=Contacts,dc=acme,dc=local" event-id="acmedidv3#20101230223222#3#1" qualified-src-dn="O=idv\OU=Users\CN=RSMITH7" src-dn="\acmeIDV\idv\Users\RSMITH7" src-entry-id="214293">
<add-attr attr-name="displayName">
<value timestamp="1256696225#32" type="string">Ryan Smith</value>
</add-attr>
<add-attr attr-name="givenName">
<value timestamp="1256696220#26" type="string">Ryan</value>
</add-attr>
<add-attr attr-name="dirxml-uACAccountDisable">
<value timestamp="1293747133#12" type="state">false</value>
</add-attr>
<add-attr attr-name="sn">
<value timestamp="1256696220#27" type="string">Smith</value>
</add-attr>
<add-attr attr-name="telephoneNumber">
<value timestamp="1256696220#16" type="teleNumber">NA</value>
</add-attr>
<add-attr attr-name="title">
<value timestamp="1256696220#24" type="string">User Administrator</value>
</add-attr>
<add-attr attr-name="cn">
<value>RSMITH7</value>
</add-attr>
<add-attr attr-name="mail">
<value type="string">RSMITH7@acmemail.com</value>
</add-attr>
<add-attr attr-name="mailNickname">
<value type="string">RSMITH7</value>
</add-attr>
<add-attr attr-name="proxyAddresses">
<value type="string">SMTP:RSMITH7@acmemail.com</value>
</add-attr>
<add-attr attr-name="targetAddress">
<value type="string">SMTP:RSMITH7@acmemail.com</value>
</add-attr>
</add>
</input>
</nds>
And make it look like this <ds-object> event, with the open and close square brackets replaced by dash (-) symbols, since while Designer lets you paste it, it cannot save the XML with that enclosed. Looks like an encoding error, Bugzilla bug entered.
<ds-object checksum="576812079" ds-object-class="DirXML-Job" ds-object-name="PasswordExpireJob" package-id="NTYAT7QU_201011110814280953" pkg-assoc-id="A06B4WBC_201011241333470067">
<ds-attributes>
<ds-attribute ds-attr-name="DirXML-TraceLevel">
<ds-value><!-CDATA-0--></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-TraceSizeLimit">
<ds-value><!-CDATA-0--></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="XmlData">
<ds-value base64-encoded="true"><!-CDATA-SomeB64data--></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-EmailTemplates">
<ds-value>
<typed-name-interval><!-CDATA-0--></typed-name-interval>
<typed-name-level><!-CDATA-0--></typed-name-level>
<typed-name-object><!-CDATA-Password Reset Fail.Default Notification Collection.Security--></typed-name-object>
</ds-value>
</ds-attribute>
<job-email-server-query job-name="PasswordExpireJob.Driver Set.null"/>
<ds-attribute ds-attr-name="DirXML-pkgAssociationId">
<ds-value><!-CDATA-A06B4WBC_201011241333470067--></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-pkgGUID">
<ds-value><!-CDATA-NTYAT7QU_201011110814280953--></ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="DirXML-pkgChecksum">
<ds-value><!-CDATA-576812079--></ds-value>
</ds-attribute>
<job-servers-query job-display-name="Password Expiration Notification" job-name="PasswordExpireJob.Driver Set.null"/>
</ds-attributes>
</ds-object>
</comment>
<comment name="author" xml:space="preserve">Geoffrey Carman</comment>
<comment name="version" xml:space="preserve">1</comment>
<comment name="lastchanged" xml:space="preserve">Dec 31, 2010</comment>
<conditions>
<and/>
</conditions>
<actions>
<do-trace-message disabled="true" level="0">
<arg-string>
<token-text xml:space="preserve">Had to replace ![CDATA[something]] with !-CDATA-something-- else it cannot be contained in the comment field. </token-text>
</arg-string>
</do-trace-message>
<do-set-local-variable name="payload" scope="policy">
<arg-node-set>
<token-xml-parse>
<token-text xml:space="preserve"><ds-object/></token-text>
</token-xml-parse>
</arg-node-set>
</do-set-local-variable>
<do-set-xml-attr expression="$payload/ds-object" name="ds-object-class">
<arg-string>
<token-xpath expression="@class-name"/>
</arg-string>
</do-set-xml-attr>
<do-set-xml-attr expression="$payload/ds-object" name="ds-object-name">
<arg-string>
<token-parse-dn dest-dn-format="slash" length="1" src-dn-format="slash" start="-1">
<token-xpath expression="@src-dn"/>
</token-parse-dn>
</arg-string>
</do-set-xml-attr>
<do-append-xml-element expression="$payload/ds-object" name="ds-attributes"/>
<do-for-each>
<arg-node-set>
<token-xpath expression="add-attr"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="CURR-ATTR" scope="policy">
<arg-string>
<token-xpath expression="$current-node/@attr-name"/>
</arg-string>
</do-set-local-variable>
<do-append-xml-element expression="$payload/ds-object/ds-attributes" name="ds-attribute"/>
<do-set-xml-attr expression="$payload/ds-object/ds-attributes/ds-attribute[last()]" name="ds-attr-name">
<arg-string>
<token-local-variable name="CURR-ATTR"/>
</arg-string>
</do-set-xml-attr>
<do-append-xml-element expression="$payload/ds-object/ds-attributes/ds-attribute[@ds-attr-name=$CURR-ATTR]" name="ds-value"/>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$current-node/value[@type='structured']</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-for-each>
<arg-node-set>
<token-xpath expression="$current-node/value"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="component-name" scope="policy">
<arg-string>
<token-text xml:space="preserve">typed-name-</token-text>
<token-xpath expression="$current-node/@name"/>
</arg-string>
</do-set-local-variable>
<do-append-xml-element expression="$payload/ds-object/ds-attributes/ds-attribute[@ds-attr-name=$CURR-ATTR]/ds-value" name="$component-name$"/>
<do-append-xml-text disabled="true" expression="$payload/ds-object/ds-attributes/ds-attribute[@ds-attr-name=$CURR-ATTR]/ds-value/$component-name">
<arg-string>
<token-text xml:space="preserve">![CDATA[</token-text>
<token-xpath expression="$current-node/component/text()"/>
<token-text xml:space="preserve">]]</token-text>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-for-each>
</arg-actions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$current-node/value[@type='octet']</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-xml-attr expression="$payload/ds-object/ds-attributes/ds-attribute[@ds-attr-name=$CURR-ATTR]/ds-value" name="base64-encoded">
<arg-string>
<token-text xml:space="preserve">true</token-text>
</arg-string>
</do-set-xml-attr>
</arg-actions>
<arg-actions/>
</do-if>
<do-append-xml-text expression="$payload/ds-object/ds-attributes/ds-attribute[@ds-attr-name=$CURR-ATTR]/ds-value">
<arg-string>
<token-text xml:space="preserve">![CDATA[</token-text>
<token-xpath expression="$current-node/value"/>
<token-text xml:space="preserve">]]</token-text>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-if>
</arg-actions>
</do-for-each>
<do-trace-message>
<arg-string>
<token-xml-serialize>
<token-local-variable name="payload"/>
</token-xml-serialize>
</arg-string>
</do-trace-message>
</actions>
</rule>
As you can see, I like to paste in the source and output documents into the comments. If you think about this, it is a HUGE time saver if you ever have to come back and work on the rule later. I learned this when I worked on the Salesforce.com driver. I would spend all this time debugging a rule, using the source document in Simulator, wrap it up, till some edge case meant I had to reopen it again. But then I otherwise would have to go find the document I needed to pass into it, to work on the rule. This way I always had a copy handy, and I could modify it as needed to include each test case I needed to fully exercise the code.
First off, we set up a nodeset variable, payload, that we use the XML Parse token to set the parent node, <ds-object>. Since it is XML Parse, the text string has to be valid XML, so we actually need the string <ds-object/> as we need that closed tag.
Then we set the ds-object-class XML attribute based on the @class-name. I decided not to use Source Name for ds-object-name, since I was not really sure how I was going to pass the event in, and instead used ParseDN on the -dn XML attribute, however in hindsight, I am not sure it would matter either way.
The Package related attributes like checksum, package-id and pkg-assoc-id I assume that when you actually add this to a package, Designer will manage for you, so no need to set them here.
Add a <ds-attributes> XML node and we reference our variable, payload and the XPATH path inside that variable to append the XML element with $payload/ds-object which is really the key to how this all works. Since we can use these tokens on the operational document in the flow, but also on a local variable that is a node set, then all the work this rule does becomes possible.
Initially I wanted to handle <modify> and <instance> events as well, but as I worked through it, it became a smidgen too difficult, and I could not quite get the XPATH I needed inside some of the loops perfect, so I simplified to just handle the <add> case which was much easier. This is because the nodes look different in each of those three events, and handling all three got into some really long XPATH that was just not worth the effort of handling. This is why Reformat Operation Attribute is such a powerful token. It handles all three cases. It always looks like it would be easy to write your own, but you are very often wrong, and it is easier to use the built in one, since it is so much better in handling all three cases.
Once it is locked down to a single case, just looping through the <add-attr> nodes is easy, get the @attr-name into a variable, since we will have an inner loop that will take over $current-node with its local current-node.
Stick in a <ds-attribute> node, and an XML attribute ds-attr-name. Then we have to handle at least three different cases. Strings are easy, not much to do, and they are the else case. Structured attributes are trickier, but still not that hard, they are the THEN case of the IF test. Base 64 encoded attributes, as a minor tweak on string attributes, just need to add an XML attribute to the <ds-value> node that base64-encoded = true to support those.
The structured attribute case is slightly trickier, as they chose to name the nodes typed-name- then the component name. (I hope they kept the current component names, without a DTD to look at, I am just assuming). Also, in the structured case, there is by definition more than one value, so we need an inner loop.
Use XPATH to notice the source attribute is @type = 'structured' and then loop through the <component> nodes adding each one as needed, with typed-name- prepending the for the <component> node. I.e. For Path syntax (one of my personal favorites!) the names would seem likely to be typed-name-nameSpace, typed-name-volume, and typed-name-path if the pattern follows.
Finally trace out the payload variable, having first XML serialized it from DOM to strings, and then you can copy and paste the output to where you need it.
Easy peasy eh?
What needs to be handled still, and will a slightly painful change is multi valued attributes. I need to see what the second attribute value will look like, and probably bury everything in one more loop, since a single valued attribute would loop once, and a multivalued attribute would loop as many times as needed to cover all the values. But since I am not sure what the output should look like, I did not add support for it.
If you happen to know, let me know, and I will try and update it.