Using Jobs and the Null Driver to Create a Monthly Report

0 Likes

Our client asked us to automatically produce a monthly report which would allow them to see all users and what their last logon time was. This is an audit requirement, and it needs to be done by someone other than the system administrators, making it a perfect candidate for automation.



This solution is an example of how the XML tokens can be used to create new HTML to be inserted into an eMail template. While this is a relatively simple task, it did offer an opportunity to utilize this new, unheralded feature of IDM 3.6.



The components of this solution include:




  • Global Configuration Variables (GCVs)

  • eMail Template

  • Null Driver

  • Subscriber Channel Trigger Job



Null Driver



The logic of this solution is contained within a Null Driver, and all the processing is in a single policy set. The configuration of the solution is done primarily through GCVs.



GCVs

The following GCVs were defined on this driver




  • List of attributes to report on

  • List of attributes in Date Format

  • Group to send reports to

  • Group to cc reports to

  • Group to bc reports to



The lists are defined as lists of strings; the Groups are DN references to a group object. The driver will use these objects to create a list of who to send the eMail to.



<?xml version="1.0" encoding="UTF-8"?><configuration-values>
<definitions>
<definition critical-change="true" display-name="List of Attributes to Report On" name="attr-names" type="list">
<description/>
<value>
<item>company</item>
<item>Description</item>
<item>Full Name</item>
<item>Given Name</item>
<item>Initials</item>
<item>Surname</item>
<item>Group Membership</item>
<item>Internet Email Address</item>
<item>L</item>
<item>Login Disabled</item>
<item>Login Time</item>
<item>manager</item>
<item>OU</item>
<item>Password Expiration Time</item>
<item>Telephone</item>
<item>Title</item>
<item>uniqueID</item>
<item>workforceID</item>
<item>homeDirectory</item>
<item>uidNumber</item>
</value>
</definition>
<definition critical-change="true" display-name="Attributes that are date format" name="Date-Attributes" type="list">
<description/>
<value>
<item>Login Time</item>
<item>Password Expiration Time</item>
</value>
</definition>
<definition critical-change="true" display-name="Group to send reports to" dn-space="dirxml" dn-type="slash" name="to-group" type="dn">
<description/>
<value>Watts\Groups\AUDIT-REPORT-TO</value>
</definition>
<definition critical-change="true" display-name="Group to CC reports to" dn-space="dirxml" dn-type="slash" name="cc-group" type="dn">
<description/>
<value>Watts\Groups\AUDIT-REPORT-CC</value>
</definition>
<definition critical-change="true" display-name="Group to bc reports to" dn-space="dirxml" dn-type="slash" name="bc-group" type="dn">
<description/>
<value>Watts\Groups\AUDIT-REPORT-BC</value>
</definition>
</definitions>
</configuration-values>



Subscriber Event Transform



The entire solution is contained within a single policy set, the subscriber event transform.


The solution operates as follows:




  1. The list of attributes which are of SYN_TIME is read in.

  • A node-set variable is created which contains the root of a document fragment which will be inserted into the eMail template. This document fragment starts with the element "use-html". This is a special element which instructs the eMail template that what follows is to be inserted in HTML in the document.

    It should be noted that support for this began with IDM 3.6 and later.

  • The next several instructions create a table, and the header row. The first column will contain the distinguished name of the object, and the remaining columns are the attributes. Since eDirectory’s NDAP names for attributes are relatively friendly, we will use them as is. However we could add a mapping table to make them more user friendly in the future.

  • The next for-each loop is the core of the solution; it iterates over the result of a query. The query requests each user object but no attributes; although we could have requested all attributes with all users the size of the query was a concern.

    1. The next IF statement is used to present virtual greenbar paper, for those of you nostalgic for mainframe chain printers. It alternates between two styles, greenbar and whitebar, to present the rows in alternating colors for clarity.

  • The next several statements create the row and set it’s style.

  • A query for the particular user is performed to get the column values. In this case there might have been a way to leverage the list in the GCV by iterating over the list and individually reading in each attribute. However this would have made the report considerably slower to process, so I choose to do some small amount of redundant administration as a trade off.

  • The next loop iterates through the list of attribute names

    1. Using an XPATH expression we get the list of values for the attribute

  • We then iterate through the list

    1. Before adding each item to the list, we add a line break element (<br/>)

    • We add the element

    • There is one more nested loop here, which checks to see if the attribute is on the list of time attributes, if so it transforms the date from CTIME to a formatted string.
    • We insert the data into the table

    • We remove the first <br/> to clean up the appearance of the table.

    • We then read in the name of the group which contains the To: list for the emails. We iterate through this list and read the Internet Email Address for each member, creating a comma separated string.

    • This is repeated for the Cc: and Bcc: lists

    • We call the eMail template, inserting our new HTML. The styles and other text are embedded in the template.



    <?xml version="1.0" encoding="UTF-8"?><policy>
    <rule>
    <description>[CIS] Report</description>
    <conditions>
    <and>
    <if-operation mode="case" op="equal">trigger</if-operation>
    </and>
    </conditions>
    <actions>
    <do-set-local-variable name="TYPES" scope="policy">
    <arg-string>
    <token-global-variable name="Date-Attributes"/>
    </arg-string>
    </do-set-local-variable>
    <do-set-local-variable name="DOC" scope="policy">
    <arg-node-set>
    <token-xml-parse>
    <token-text xml:space="preserve"><use-html/></token-text>
    </token-xml-parse>
    </arg-node-set>
    </do-set-local-variable>
    <do-append-xml-element expression="$DOC/use-html" name="table"/>
    <do-append-xml-element expression="$DOC//table[last()]" name="tr"/>
    <do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
    <do-set-xml-attr expression="$DOC//table/tr[last()]" name="class">
    <arg-string>
    <token-text xml:space="preserve">underline</token-text>
    </arg-string>
    </do-set-xml-attr>
    <do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
    <arg-string>
    <token-text xml:space="preserve">Distinguished Name</token-text>
    </arg-string>
    </do-append-xml-text>
    <do-for-each>
    <arg-node-set>
    <token-global-variable name="attr-names"/>
    </arg-node-set>
    <arg-actions>
    <do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
    <do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
    <arg-string>
    <token-local-variable name="current-node"/>
    </arg-string>
    </do-append-xml-text>
    </arg-actions>
    </do-for-each>
    <do-for-each>
    <arg-node-set>
    <token-query class-name="User" datastore="src">
    <arg-dn>
    <token-src-dn/>
    </arg-dn>
    <arg-string>
    <token-text xml:space="preserve">Object Class</token-text>
    </arg-string>
    </token-query>
    </arg-node-set>
    <arg-actions>
    <do-if>
    <arg-conditions>
    <and>
    <if-local-variable mode="nocase" name="BAR" op="equal">greenbar</if-local-variable>
    </and>
    </arg-conditions>
    <arg-actions>
    <do-set-local-variable name="BAR" scope="policy">
    <arg-string>
    <token-text xml:space="preserve">whitebar</token-text>
    </arg-string>
    </do-set-local-variable>
    </arg-actions>
    <arg-actions>
    <do-set-local-variable name="BAR" scope="policy">
    <arg-string>
    <token-text xml:space="preserve">greenbar</token-text>
    </arg-string>
    </do-set-local-variable>
    </arg-actions>
    </do-if>
    <do-append-xml-element expression="$DOC//table[last()]" name="tr"/>
    <do-set-xml-attr expression="$DOC//table/tr[last()]" name="class">
    <arg-string>
    <token-local-variable name="BAR"/>
    </arg-string>
    </do-set-xml-attr>
    <do-set-local-variable name="RECORD" scope="policy">
    <arg-node-set>
    <token-query class-name="User" datastore="src" scope="entry">
    <arg-dn>
    <token-xpath expression="$current-node/@src-dn"/>
    </arg-dn>
    <arg-string>
    <token-text xml:space="preserve">company</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Description</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Full Name</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Given Name</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Initials</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Surname</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Group Membership</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Internet Email Address</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">L</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Login Disabled</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Login Time</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">manager</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">OU</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Password Expiration Time</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Telephone</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">Title</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">uniqueID</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">workforceID</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">homeDirectory</token-text>
    </arg-string>
    <arg-string>
    <token-text xml:space="preserve">uidNumber</token-text>
    </arg-string>
    </token-query>
    </arg-node-set>
    </do-set-local-variable>
    <do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
    <do-if>
    <arg-conditions>
    <and>
    <if-xpath op="true">$RECORD/attr[@attr-name="Login Disabled"]/value/text()='true'</if-xpath>
    </and>
    </arg-conditions>
    <arg-actions>
    <do-append-xml-element expression="$DOC//table/tr[last()]/td[last()]" name="strike"/>
    <do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]/strike">
    <arg-string>
    <token-xpath expression="$current-node/@src-dn"/>
    </arg-string>
    </do-append-xml-text>
    </arg-actions>
    <arg-actions>
    <do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
    <arg-string>
    <token-xpath expression="$current-node/@src-dn"/>
    </arg-string>
    </do-append-xml-text>
    </arg-actions>
    </do-if>
    <do-for-each>
    <arg-node-set>
    <token-global-variable name="attr-names"/>
    </arg-node-set>
    <arg-actions>
    <do-set-local-variable name="ATTR-NAME" scope="policy">
    <arg-string>
    <token-local-variable name="current-node"/>
    </arg-string>
    </do-set-local-variable>
    <do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
    <do-for-each>
    <arg-node-set>
    <token-xpath expression="$RECORD/attr[@attr-name=$ATTR-NAME]/value"/>
    </arg-node-set>
    <arg-actions>
    <do-set-local-variable name="VALUE" scope="policy">
    <arg-string>
    <token-xpath expression="$current-node/text()"/>
    </arg-string>
    </do-set-local-variable>
    <do-if>
    <arg-conditions>
    <and>
    <if-xpath op="true">contains($TYPES,$ATTR-NAME)</if-xpath>
    </and>
    </arg-conditions>
    <arg-actions>
    <do-set-local-variable name="VALUE" scope="policy">
    <arg-string>
    <token-convert-time dest-format="!LONG.DATETIME" dest-lang="en-US" dest-tz="US/Eastern" src-format="!CTIME" src-tz="UTC">
    <token-local-variable name="VALUE"/>
    </token-convert-time>
    </arg-string>
    </do-set-local-variable>
    </arg-actions>
    <arg-actions/>
    </do-if>
    <do-append-xml-element expression="$DOC//table/tr[last()]/td[last()]" name="br"/>
    <do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
    <arg-string>
    <token-local-variable name="VALUE"/>
    </arg-string>
    </do-append-xml-text>
    </arg-actions>
    </do-for-each>
    <do-strip-xpath expression="$DOC//table/tr[last()]/td[last()]/br[1]"/>
    </arg-actions>
    </do-for-each>
    </arg-actions>
    </do-for-each>
    <do-for-each>
    <arg-node-set>
    <token-src-attr name="Member">
    <arg-dn>
    <token-global-variable name="to-group"/>
    </arg-dn>
    </token-src-attr>
    </arg-node-set>
    <arg-actions>
    <do-set-local-variable name="current-email" scope="policy">
    <arg-string>
    <token-src-attr name="Internet EMail Address">
    <arg-dn>
    <token-local-variable name="current-node"/>
    </arg-dn>
    </token-src-attr>
    </arg-string>
    </do-set-local-variable>
    <do-if>
    <arg-conditions>
    <and>
    <if-local-variable mode="nocase" name="current-email" op="not-equal"/>
    </and>
    </arg-conditions>
    <arg-actions>
    <do-set-local-variable name="TO-EMAIL" scope="policy">
    <arg-string>
    <token-local-variable name="TO-EMAIL"/>
    <token-text xml:space="preserve">,</token-text>
    <token-local-variable name="current-email"/>
    </arg-string>
    </do-set-local-variable>
    </arg-actions>
    <arg-actions/>
    </do-if>
    </arg-actions>
    </do-for-each>
    <do-for-each>
    <arg-node-set>
    <token-src-attr name="Member">
    <arg-dn>
    <token-global-variable name="cc-group"/>
    </arg-dn>
    </token-src-attr>
    </arg-node-set>
    <arg-actions>
    <do-set-local-variable name="current-email" scope="policy">
    <arg-string>
    <token-src-attr name="Internet EMail Address">
    <arg-dn>
    <token-local-variable name="current-node"/>
    </arg-dn>
    </token-src-attr>
    </arg-string>
    </do-set-local-variable>
    <do-if>
    <arg-conditions>
    <and>
    <if-local-variable mode="nocase" name="current-email" op="not-equal"/>
    </and>
    </arg-conditions>
    <arg-actions>
    <do-set-local-variable name="CC-EMAIL" scope="policy">
    <arg-string>
    <token-local-variable name="CC-EMAIL"/>
    <token-text xml:space="preserve">,</token-text>
    <token-local-variable name="current-email"/>
    </arg-string>
    </do-set-local-variable>
    </arg-actions>
    <arg-actions/>
    </do-if>
    </arg-actions>
    </do-for-each>
    <do-for-each>
    <arg-node-set>
    <token-src-attr name="Member">
    <arg-dn>
    <token-global-variable name="bc-group"/>
    </arg-dn>
    </token-src-attr>
    </arg-node-set>
    <arg-actions>
    <do-set-local-variable name="current-email" scope="policy">
    <arg-string>
    <token-src-attr name="Internet EMail Address">
    <arg-dn>
    <token-local-variable name="current-node"/>
    </arg-dn>
    </token-src-attr>
    </arg-string>
    </do-set-local-variable>
    <do-if>
    <arg-conditions>
    <and>
    <if-local-variable mode="nocase" name="current-email" op="not-equal"/>
    </and>
    </arg-conditions>
    <arg-actions>
    <do-set-local-variable name="BC-EMAIL" scope="policy">
    <arg-string>
    <token-local-variable name="BC-EMAIL"/>
    <token-text xml:space="preserve">,</token-text>
    <token-local-variable name="current-email"/>
    </arg-string>
    </do-set-local-variable>
    </arg-actions>
    <arg-actions/>
    </do-if>
    </arg-actions>
    </do-for-each>
    <do-send-email-from-template notification-dn="Security\Default Notification Collection" template-dn="Security\Default Notification Collection\[CIS] User Report">
    <arg-string name="to">
    <token-substring start="1">
    <token-local-variable name="TO-EMAIL"/>
    </token-substring>
    </arg-string>
    <arg-string name="WHEN">
    <token-time format="!LONG.DATETIME" lang="en-US" tz="US/Eastern"/>
    </arg-string>
    <arg-string name="TABLE">
    <token-xml-serialize>
    <token-local-variable name="DOC"/>
    </token-xml-serialize>
    </arg-string>
    <arg-string name="cc">
    <token-substring start="1">
    <token-local-variable name="CC-EMAIL"/>
    </token-substring>
    </arg-string>
    <arg-string name="bcc">
    <token-substring start="1">
    <token-local-variable name="BC-EMAIL"/>
    </token-substring>
    </arg-string>
    </do-send-email-from-template>
    </actions>
    </rule>
    </policy>



    eMail Template



    The eMail template is very simple; it contains just variables which represent when the report was run as well as one to insert the HTML. It also contains style definitions which could be used to provide a nicer output, although in this case we just use them to simulate greenbar paper.




    <template name="[CIS] User Report" subject="User Report: $WHEN$">
    <data>
    <html>
    <form:tokendescriptions>
    <form:tokendescription description="" itemname="WHEN"/>
    <form:tokendescription description="" itemname="TABLE"/>
    </form:tokendescriptions>

    <head>
    <title>User Report for $WHEN$</title>

    <style>
    <! body { fontfamily: Trebuchet MS } >
    </style>

    <style type="text/css">

    tr.greenbar td {
    backgroundcolor: #EBFFEB; color: black;}
    tr.whitebar td {
    backgroundcolor: #FFFFFF; color: black;}
    tr.underline td {
    textdecoration: underline; }
    td.strikethrough td {
    textdecoration: linethrough;}
    </style>
    </head>
    <body BGCOLOR="#FFFFFF">
    <H1>User Report</H1>
    $TABLE$
    </body>
    </html>
    </data>
    </template>



    Subscriber Channel Trigger Job





    Create a new Subscriber Channel Trigger on the Jobs tab of the driver in iManager.





    The resulting job needs to be assigned to the appropriate server.





    The subscriber channel trigger job can execute as often as you wish, we selected the first of every month at 1:00 am for the job to execute.





    Select the root container where you wish this report to be effective.

    Important: Select the job to apply only to the container. The policies will query for subordinate objects. Selecting a subtree could result in many separate very short reports being mailed rather than one large one.





    Make certain that "Submit a trigger document for objects without a driver association" is set to true. Otherwise this will never execute since no objects have an association to this null driver.



    Security



    You must also grant the job object trustee rights to the driver object. This can be done in iManager.





    Select the View Objects view from the top button bar of iManager, and browse down the tree to your new Null Driver object. Check the driver object and from the Actions menu select Modify Trustees.





    Click the Add Trustee button.





    Browse to the new Job object you just created






    Click on Assigned Rights





    Under "All Attribute Rights" click "Write" and click Done to save.


    Note: You can assign a single additional right to write to "DirXML-AccessSubmitCommand" instead of modifying all attributes but this is fewer clicks.



    You can now test the job by returning to the job, selecting it and clicking "Run Now".



    Conclusion


    This is a simple example of what can be done with an IDM policy to generate an automated report. It should be noted that this report will take a long time to run; our testing showed that it was processing approximately one object every three seconds. On our 4200 object tree, this is a 3½ hour report time. It also does use significant processing power to run.



    This does not obviate the need for auditing tools such as those provided by Novell Access Goverance solution; in fact what it does is point up that need. The use of the IDM solution to audit itself is clever but really violates the principals of auditing; you should always audit with a different tool than you are administering with.


    However, this does provide a starting point for auditing when you need a simple list generated monthly to review.

    Comment List
    Related
    Recommended