Storing Multiple Values in a Single Value in NetIQ Identity Manager

Storing Multiple Values For NetIQ IAM Image

Recently we encountered an organization that had deployed a rather ingenious solution to a seemingly complex problem within their NetIQ IDM solution.

The organization is a large University with over a dozen campuses. Across this broad array of campuses, an individual can have multiple roles as both student and/or staff member at multiple campuses concurrently. As such, there was an internal requirement to track who was assigned what “role” at each campus to ensure that proper entitlements were granted or revoked in the connected systems as necessary based on that person’s involvement with each campus from semester to semester. The number of values to be tracked was equally as significant as the number of campuses involved.

In normal operations, one might find it easiest to just create custom fields/attributes for each campus and each value to be tracked, then create a 1:1 data mapping for each system. However, in this case, that would have required over 130 custom attributes to be created in the authoritative source, and then eventually into their IDM solution. The customer thought better of this approach and instead opted to create a single field in the authoritative source for each value to be tracked (e.g. Are they staff? Are they a prospective student that has applied to the University? Are they an accepted student? Are they a freshman? Are they a prior student that has returned after taking time off? Have they transitioned to Alumni? etc.).

Then, those fields were populated with a bit value that corresponded with the campus, or campuses, such that the person was affiliated within those roles. So the attribute “staff” might hold a value of “1” meaning Campus A, the attribute of “appliedStudent” might hold a value of “4” for Campus C, the attribute of “freshman” might hold a value of “3” for Campus A (1) + Campus B (2), and the attribute of “student” might hold a value of “7” for Campus A (1) + Campus B (2) + Campus C (4). For those fields that the individual was not associated with, the value was simply “0” for nothing.

Note: In bit values, each value is double the previous value, i.e. 0,1, 2, 4, 8, 16, 32, 64, 128, 256, etc., so Campus C would not be “3” since it is the third campus but instead “4” because that is the third bit.

By doing this clever data hack, our customer was now able to track over 130 different statistics using only about a dozen attributes instead of having to create a custom attribute for each permutation needing to be tracked.

Now the challenge was figuring out how to deal with that data inside of their new NetIQ IDM solution so that it could determine who needed what and where. Luckily, even though drivers do not deploy bitwise type logic to deal with such values inherently, ECMAScript was there to save the day.

NetIQ IDM supports custom ECMAScript and JavaScript functions that can be developed, associated with a driver, and called from within standard rule actions, thus allowing the IDM solution to pass the bit value to the script and then have the script return a parsable value that could be converted into an IDM nodeset, that can then be iterated through and processed as necessary.

In IDM, this required three things:

  1. Creating the ECMAScript/JavaScript class that contained the logic to breakdown the bit data.
  2. Linking the ECMAScript/JavaScript class to the driver that needed to call it in the driver configurations.
  3. The action to invoke the class, pass the bit data to it, receive the output, and convert it to an IDM nodeset array.

The ECMAScript would look something like this:

function Groups( value )
{
	var resArray = [];
	var classcode = [ 'Inactive', 'Benefited', 'Employees', 'Faculty', 'Students', 'Admitted', 'Applied', 'Retired', 'Student_Employee', 'SP_Affiliate', 'Affiliate', 'POI_EE', 'POI_FEE', 'OC_Affiliate', 'Students_Credits'  ];
	
	if ( value < 0 ) { return resArray; }
	if (value < Math.pow(2,classcode.length)) {
		for (var x = 0; x < classcode.length; x++ ) {
			if (value & (1<<x)) { resArray.push(classcode[x]) }
		}
	}
	
	return resArray.join('&');
}

And the driver rule action would look something like this:

<do-set-local-variable name="lv_bitData" scope="policy"><br>	<arg-string><br>		<token-op-attr name="Classification"/><br>	</arg-string><br></do-set-local-variable>
<do-set-local-variable name="lv_Flags" scope="policy">
	<arg-node-set>
		<token-split delimiter="&amp;">
			<token-xpath expression="es:Groups( $lv_bitData )"/>
		</token-split>
	</arg-node-set>
</do-set-local-variable>

The driver action calls the script function “Groups” and passes the bit value from the “lv_bitData” local variable that has the bit value stored in the Classification attribute. The script then iterates through the bit values using the Math.pow() method to determine if each bit is “on” or “off” and assigns the corresponding group name to a concatenated string where each value is separated by an ampersand (&). When the calculations are complete, the script sends the concatenated string back to the driver rule where the value is split using the split() verb to split the value at each ampersand into an IDM nodeset array. From there, the driver logic can iterate through the values to take action if so desired.

This process works great for new users or users who went from a 0 to a different value as there was no historical data that needed to be taken into consideration (e.g. access that needs to be revoked, or access that was pre-existing and does not need to be reapplied). However, there was a secondary challenge in the IDM space, the IDM haad to be capable of discerning when new data was added to a bit value or when data was removed from a bit value and to only act on the changes rather than always act on every value every time.

So, now the IDM system had to be able to determine what access the user had previously and compare that with the access the user has now so that permissions could be added and removed as needed while leaving persistent rights alone as not to negatively impact the user’s expected access and capabilities.

Luckily, with the work done above, the job of pulling off this comparative access review is actually rather simple. We already have the script to breakdown the bit values, we only need to map the new and old values to their respective nodesets before performing iterative comparisons, like so:

<do-set-local-variable name="lv_OldVal" scope="policy">
	<arg-string>
		<token-xpath expression='modify-attr[@attr-name="bitDataAttr"]/remove-value/value/text()'/>
	</arg-string>
</do-set-local-variable>
<do-set-local-variable name="lv_NewVal" scope="policy">
	<arg-string>
		<token-xpath expression='modify-attr[@attr-name="bitDataAttr"]/add-value/value/text()'/>
	</arg-string>
</do-set-local-variable>
<do-set-local-variable name="lv_OldFlags" scope="policy">
	<arg-node-set>
		<token-split delimiter="&amp;">
			<token-xpath expression="es:Groups( $lv_OldVal )"/>
		</token-split>
	</arg-node-set>
</do-set-local-variable>
<do-set-local-variable name="lv_NewFlags" scope="policy">
	<arg-node-set>
		<token-split delimiter="&amp;">
			<token-xpath expression="es:undGroups( $lv_NewVal )"/>
		</token-split>
	</arg-node-set>
</do-set-local-variable>

The first action creates a new local variable called “lv_OldVal” that takes the old value for the “bitDataAttr” attribute (“modify-attr[@attr-name=”bitDataAttr”]/remove-value/value/text()”) and the second one creates a local variable called “lv_NewVal” from the new “bitDataAttr” attribute value (“modify-attr[@attr-name=”bitDataAttr”]/add-value/value/text()”).

The next two items do just as before, each calls the “Groups()” script function passing in their respective values, getting back a concatenated string of groups consistent with their bit values, and then splitting the string into an IDM compatible nodeset array at each ampersand (&) character in the returned string.

From here, the solution isn’t really that complex but it is a bit more convoluted. There is no easy method in NetIQ IDM to compare two arrays, outside of creating another script to compare and generate some type of usable output, so using the standard actions in NetIQ IDM we can use nested iterations; meaning that we iterate through all values of one node for each value in the other looking for matches. Any values that do not have matches are either new values or removed values depending on our iteration logic, like so:

<do-for-each>
	<arg-node-set>
		<token-local-variable name="lv_OldFlags"/>
	</arg-node-set>
	<arg-actions>
		<do-set-local-variable name="lv_Remove" scope="policy">
			<arg-string>
				<token-text xml_space="preserve">TRUE</token-text>
			</arg-string>
		</do-set-local-variable>
		<do-set-local-variable name="lv_Cur" scope="policy">
			<arg-string>
				<token-local-variable name="current-node"/>
			</arg-string>
		</do-set-local-variable>
		<do-for-each>
			<arg-node-set>
				<token-local-variable name="lv_NewFlags"/>
			</arg-node-set>
			<arg-actions>
				<do-if>
					<arg-conditions>
						<and>
							<if-local-variable mode="nocase" name="current-node" op="equal">$lv_Cur$</if-local-variable>
						</and>
					</arg-conditions>
					<arg-actions>
						<do-set-local-variable name="lv_Remove" scope="policy">
							<arg-string>
								<token-text xml_space="preserve">FALSE</token-text>
							</arg-string>
						</do-set-local-variable>
					</arg-actions>
					<arg-actions/>
				</do-if>
			</arg-actions>
		</do-for-each>
		<do-if>
			<arg-conditions>
				<and>
					<if-local-variable mode="nocase" name="lv_Remove" op="equal">TRUE</if-local-variable>
				</and>
			</arg-conditions>
			<arg-actions>
				<do-set-local-variable name="lv_Resource" scope="policy">
					<arg-string>
						<token-text xml_space="preserve">cn=UND_</token-text>
						<token-local-variable name="current-node"/>
						<token-local-variable name="lv_ResDN"/>
					</arg-string>
				</do-set-local-variable>
				<do-remove-role id="~service-account-dn~" role-id="$lv_Resource$" time-out="30000" url="~UAProvURL~">
					<arg-password>
						<token-named-password name="UAAdmin"/>
					</arg-password>
				</do-remove-role>
			</arg-actions>
			<arg-actions/>
		</do-if>
	</arg-actions>
</do-for-each>

For each value in the “lv_OldFlags” local variable we create/set a local variable called “lv_Remove” equal to “TRUE” and then create/set another local variable called “lv_Cur” equal to the current value in our “lv_OldFlags” array. Then, we do another for each iterator through the “lv_NewFlags” array values within our “lv_OldFlags” iterator where we compare the current value in the “lv_NewFlags” iterator against our “lv_Cur” value. If the two values match, we set the local variable “lv_Remove” to “FALSE” and either way, continue iterating through the “lv_NewFlags” values until all values have been looked at against the “lv_Cur” value. Once that iteration is complete, the rule looks at the “lv_Remove” variable value and if the value is “TRUE”, the driver removes IDM role associated with “lv_Cur” value. Then, we go back to the top and repeat those steps for the next value in the “lv_OldFlags” nodeset. Rinse and repeat until all values in the “lv_OldFlags” nodeset have been processed and when it is done, any values removed from the bit data will have resulted in the associated roles being removed from the user in IDM.

To check for new values and add new roles, simply apply the same approach but switch the iterators for “lv_OldFlags” and “lv_NewFlags” and call the add role function for those values accordingly.

Admittedly, another approach would be to create a script function that does this array comparison for you and returns a string value that only contains the removed or added values that could then be parsed more simply in driver logic. Ultimately, that comparison will still need to happen somewhere — if you need it to happen in IDM, this is a great way to do it.