Writing scripts

Running custom JavaScript scripts is the core functionality of the Onegini Extension Engine. This topic guide will discuss how to implement and configure such scripts.

Overview

Each script is executed using Oracle Nashorn. By default, access to Java classes is disabled. See Configuring access to Java classes for more information.

Script format

A script should consist of an execute function, along with any number of helper functions. The execute function must meet the following requirements:

  • Name: execute
  • Arguments:
    1. requestPayload: String with the payload
    2. userIdentifier: Unique identifier for the user on the device (String, can be null)
    3. registrationData: Registration data that was returned in a previous execution (String, can be null)
  • Returns: An object with the following properties:
    • status: Integer that indicates whether the operation was successful (required)
    • responsePayload: String with a payload that will be interpreted by the caller (optional)
    • registrationData: String with registration data that should be stored by the caller for future requests (optional)
    • user: Map of data containing user information

The execute function is responsible for:

  • Parsing the requestPayload
  • Performing the communication with the API of the authenticator
  • Interpreting the response of that API as successful or otherwise
  • Converting the response from the API into a responsePayload string that is understood by the caller
  • Returning a status that is understood by the caller to indicate whether the execution was successful

Accessing configuration properties

The Onegini Extension Engine includes the option of configuring properties in its database. This has several benefits. For one, sensitive values like credentials used to communicate with an authenticator API are stored encrypted. It also makes it easier to update these values without changing the scripts, for example when moving from the test to the production environment.

These properties are available to the JS functions through the configuration object, and can be accessed using several methods on this object.

Accessing a single property:

var serverUrl = configuration.get('authenticators_myApi_serverUrl');

Accessing all properties:

var configMap = configuration.getAll();
var serverUrl = configMap['authenticators_myApi_serverUrl'];

In this case, authenticators_myApi_serverUrl is the unique identifier of the config property. The last method is available in order to minimize the number of database queries.

Logging

Console logging is available to the JavaScript functions through the global LOG object. This object is an instance of the SLF4J Logger class. It can be used in JavaScript in the same way as in Java classes.

LOG.debug("Test value {}", value);
LOG.error("Could not reach the server", exception);

Using Cache

Cache is available within a script if you need to store data that will be utilized at a future script execution. A global CACHE object is available to store and fetch data via a key/value pair

CACHE.store("sampleKey", "sampleValue");
CACHE.store("sampleKey", "sampleValue", 100); //sets TTL to 100 seconds for this key.
var value = CACHE.fetch("sampleKey");
CACHE.delete("sampleKey");

Using RestTemplate

The SpringFramework RestTemplate class is also exposed by default to make REST calls easier. A global REST_TEMPLATE object is available to make REST calls.

//GET
var xml = REST_TEMPLATE.getForEntity("http://example.com", java.lang.String.class).getBody();

//POST
var headers = new org.springframework.http.HttpHeaders();
headers.add("headerName", "headerValue");
var requestMap = new org.springframework.util.LinkedMultiValueMap();
requestMap.add("paramName", "paramValue");
var entity = new org.springframework.http.HttpEntity(requestMap, headers);
var response = REST_TEMPLATE.postForEntity("http://example.com", entity, java.lang.String.class);

These are just a few sample usages. Any other usages you can normally do with a restTemplate Java object is also possible.

Example script

function execute(requestPayload, userIdentifier, registrationData) {
  var requestImage = getImage(requestPayload);
  var registrationImage = getImage(registrationData);
  var verificationResult = verifyUser(userIdentifier, requestImage, registrationImage);

  var status = verificationSucceeded(verificationResult) ? 2000: 4000;

  return {
    status: status,
    responsePayload: resultToString(verificationResult),
    registrationData: getRegistrationData(userIdentifier, registrationImage, requestImage),
    user: {
      id: "user123"
    }
  };
}

function getImage(imageData) {
  LOG.debug('Stub to extract the image.');
  var body = JSON.parse(imageData);
  return body['image'];
}

function verifyUser(userIdentifier, requestImage, registrationImage) {
  var responseInputStream = postImages(userIdentifier, requestImage, registrationImage); 
  return readResponse(responseInputStream);
}

function postImages(userIdentifier, requestImage, registrationImage) {
  var serverUrl = configuration.get('authenticators_myApi_serverUrl');

  LOG.debug('TODO: Create HTTP request to verify the image for user {} to server {}', userIdentifier, serverUrl);

  // Stubbing a response
  var response;
  if (requestImage === registrationImage) {
    response = "success!";
  } else {
    response = "failure!";
  }
  return new java.io.ByteArrayInputStream(response.getBytes());
}

function readResponse(responseInputStream) {
  var sb = new java.lang.StringBuilder();
  var inputStreamReader = new java.io.InputStreamReader(responseInputStream);
  var reader = new java.io.BufferedReader(inputStreamReader);
  var inputLine;
  while ((inputLine = reader.readLine()) !== null) {
    sb.append(inputLine);
  }
  reader.close();
  inputStreamReader.close();
  return sb.toString();
}

function verificationSucceeded(verificationResult) {
  LOG.debug('Stub to interpret the verification result'); 
  return verificationResult === 'success!';
}

function resultToString(verificationResult) {
  LOG.debug('Stub to return the result as String');
  return JSON.stringify({result: verificationResult});
}

function getRegistrationData(userIdentifier, image, requestImage) {
  LOG.debug("An example of registration data.");
  return JSON.stringify({
    userIdentifier: userIdentifier,
    image: image,
    latestImage: requestImage
    });
}

General Troubleshooting

If you receive an error like this:

java.lang.ClassCastException: Cannot cast jdk.nashorn.internal.runtime.NativeJavaPackage to java.lang.Class

You are likely attempting to use a class within your script that you have not exposed properly via configuration. See configure access to Java classes for more information.