Tuesday 10 May 2016

OpenIDM: Allocate from a Pool

The problem

During a recent Proof of Concept we had a need to allocate 'serial numbers' of hardware tokens to users as the user was created.  The serial number would come from a 'pool' of available tokens and should be allocated automatically on user creation.  It should also be possible to remove serial numbers from users which would be returned to the pool.

The solution

We decided to create a custom managed object in OpenIDM to hold the 'pool' of token serial numbers.  We could then leverage the new (in v4) relationship model to allocate serial numbers to users.
So we needed a few bits to this jigsaw:

  1. Define the custom managed object
  2. Populate the new object with the available serial numbers
  3. Modify the user creation logic to retrieve an available serial number and associate it with the user

Define the custom managed object

We were using FrejaId tokens, so we defined a 'managed object' like this:
SerialNo is just defined as a string and uses the OOTB box policy validation capability to ensure uniqueness (take a look at the 'managed user' object and its 'userName' property to see how validation policies can be applied to properties).  Also, it is defined as 'Viewable' and 'Searchable'.

AssignedUser is defined as a 'reverse relationship' which matches up with a property on the managed user object called 'AssignedSerialNo'.
We therefore also have to modify the 'managed user' object to include the 'AssignedSerialNo' property which is defined as a reverse relationship to the 'AssignedUser' property on on the FrejaToken object.

Populate the token object with SerialNos

There are several ways you could achieve this depending on the source of the information.  I just had a list of serial numbers from the 3rd party hardware vendor.  The easiest way for me was to create a command line script that iterated through the list and called the OpenIDM REST API for the new managed object.  So I created a file called 'tokens.csv' with each SerialNo on a new line.  There was no need for a header to be included.  The script then used this file as the source of input to a REST call to populate the object.  This was a bash script saved in 'tokenimport.sh' and required the CSV file as an input parameter when it runs e.g.:
#!/bin/bash
# tokenimport.sh
#  import the specified token serial no to OpenIDM
E_NOARGS=85
if [ -z "$1" ]   # Exit if no argument given.
then
  echo "Usage: `basename $0` file"
  exit $E_NOARGS
fi
cat "$1" | while read line;
           do {
             curl --header 'X-OpenIDM-Username: openidm-admin' --header 'X-OpenIDM-Password: openidm-admin'  --header 'Content-Type: application/json' --header 'If-None-Match: *' --data '{"SerialNo":"'"$line"'"}' --request PUT http://localhost:8080/openidm/managed/FrejaToken/"$line"
           }
           done
exit 0

You can run this multiple times.  The 'unique' policy validation constraint on the managed object 'SerialNo' property will stop duplicates being saved.  Also note I'm using a 'PUT' request and specifying the _id of the item being created.  (The _id being the same as the 'SerialNo').
Note also that I'm using the default OpenIDM-Admin username and password, and running this script on the same machine as OpenIDM.

Modify user creation logic

As you may be aware OpenIDM provides 'hooks' in many places to allow custom logic to be triggered at various points.  One such place is when a specific instance of a managed object is created.  In fact, the out-of-the-box definition for a managed user object includes an 'onCreate' script.  This script is called onCreate-user-set-default-fields.js  The script exists in openidm/bin/defaults/script/ui.  You should never ever modify this script...but you can make a copy of it and modify the copy!  So take a copy and place it in:
openidm/script/ui
(you may need to create the 'ui' directory)
Then add this to the bottom of the file:
// allocate first available freja token
var availableTokens = openidm.query('managed/FrejaToken', {_queryFilter:'true'}, ['*', 'AssignedUser']).result.filter(function (token) { return !(token.AssignedUser)});
if ((availableTokens) && (availableTokens.length > 0)) {
  object.AssignedSerialNo = {"_ref":"managed/FrejaToken/" + availableTokens[0]._id};
}

The first line queries all tokens, and filters out the tokens that are already assigned.  The availableTokens array therefore contains all available tokens.
We then check that there are actually tokens in the array, and then get the first one, saving it as a 'relationship' to the 'AssignedSerialNo' property of the managed user object that we created earlier.

Note that as this is defined as a 'reverse property' you would also see that the 'AssignedUser' property of the FrejaToken is now automagically populated.  Which also means that the next time this query is run then this token will already be 'allocated' and therefore not exist in the availableTokens array.

Deallocation

Because of the 'reverse property' relationship, deallocation is straightforward.  You can remove the link between the user and SerialNo using REST calls or the User Interface.  This means the token becomes available again.  Also, if you delete a user then the relationship is removed, returning the assigned SerialNo to the pool.

No comments:

Post a Comment