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.

Wednesday 4 May 2016

OpenIDM: Sequential Number Generator

The problem 

Working on a Proof of Concept recently the customer wanted numbers to be allocated to new users in sequence. They wanted the sequence to start at 300000000 and increase by 1 each time a new user was created. This was to 'guarantee' uniqueness of the number allocated to each user.

The solution

I decided to make use of the generic repository object capability of OpenIDM. This is a little known feature but something I've blogged about in the past (See: http://yaunap.blogspot.co.uk/2015/09/data-driven-preferences-list-in-openidm.html) But just storing a number wasn't enough...I needed some logic to increment the number and store the result, as well as an API in order to 'GetNextId' when a new user was created.
So there are several parts to this:
  1. Repository Object 
  2. Script to read stored number, increment it, store it and return it 
  3. Custom endpoint in OpenIDM to expose the script to clients 

Repository object 

Creating the repository object is straightforward. I called my object 'counter'. It will have a single field/value called 'lastid' seeded with 300000000 (note limitation below):
curl --header "X-OpenIDM-Username: openidm-admin" --header "X-OpenIDM-Password: openidm-admin"  --header "Content-Type: application/json" --data '{"lastid":"300000000" }' --request POST http://localhost:8080/openidm/repo/counter?_action=create

Script

The script is also fairly simple:
(function getNextId () {
  //read last number from custom repo object, add 1, store it back, return it!
  var  lastid = (openidm.read('repo/counter/lastid'));
  var nextid = Number(lastid['lastid']) + 1;
  lastid['lastid'] = nextid;
  openidm.update('repo/counter/lastid',null,lastid);
  return {nextid:nextid.toString()};
}());
This needs to be saved to a new .js file in the 'script' directory under the OpenIDM deployment directory. e.g. openidm/script/getNextId.js

Custom Endpoint

The custom endpoint is a simple definition (creating custom endpoints is well documented in the Integrators Guide):
{
    "type" : "text/javascript",
    "file" : "script/getNextId.js"
} 

 This needs to be saved to a .json file in the 'conf' directory. The naming convention is important. It must start with 'endpoint-'. Anything after the '-' will be the name of API you can access over HTTP. Therefore a file called:
openidm/conf/endpoint-getnextid.json
will be addressable thus:
curl -H "X-OpenIDM-Username: openidm-admin" -H "X-OpenIDM-Password: openidm-admin" 'http://localhost:8080/openidm/endpoint/getnextid'


Limitation: 

The endpoint only handles javascript numbers with values less than 2147483648 If you want larger numbers (i.e. starting at 3000000000) then the logic will need to support some way of handling the larger number.

Counter Reset:

To reset the counter, use:
curl --header "X-OpenIDM-Username: openidm-admin" --header "X-OpenIDM-Password: openidm-admin"  --header "Content-Type: application/json" --data '{"lastid":"300000000" }' --header "If-Match: *" --request PUT http://localhost:8080/openidm/repo/counter/lastid