PingOne Advanced Identity Cloud

Script bindings

Each script type exposes a number of bindings, objects that PingOne Advanced Identity Cloud injects into the script execution context. The bindings provide a stable way of accessing PingOne Advanced Identity Cloud functionality, without the need to allowlist Java classes. Scripts are provided with all the bindings for their context at the point of execution.

Find information about context-specific bindings in the documentation for each script type.

PingOne Advanced Identity Cloud has introduced a next-generation scripting engine that offers several benefits, including enhanced script bindings.

The availability and usage of bindings depend on the script engine version of the script: legacy or next-generation. Both versions are described in this section.

For information about migrating to the enhanced scripting engine, refer to Migrating to next-generation scripts.

Common bindings

The following bindings are common to many authentication and authorization scripts. Use these bindings to access data and perform script operations such as logging.

Binding

Description

Availability

Legacy

Next-generation

Make outbound HTTP calls.

Partial 1

Yes

Write a message to the PingOne Advanced Identity Cloud debug log.

Yes

Yes

Manage an IDM resource.

No

Yes

Access the realm to which the user is authenticating.

Yes

Yes

Access the name of the running script.

Yes

Yes

Access utility functions such as base64 encoding/decoding and generating random UUIDs.

No

Yes

Reference system properties.

Yes

Yes

1 Available in OAuth 2.0 script types, scripted decision node scripts, and SAML v.2.0 scripts.

Make sure you don’t use the same name for a local variable as that of a common binding in your script. These names are reserved for common bindings only.

If you have already defined a local variable with the same name as one that’s added to common bindings in a more recent version of PingOne Advanced Identity Cloud; for example, utils, you must rename the variable in your scripts.

Access HTTP services

Call HTTP services with the httpClient.send method. HTTP client requests are asynchronous, unless you invoke the get() method on the returned object.

  • Next-generation

  • Legacy

The httpClient binding uses native JavaScript objects, and behaves like the Fetch API.

To invoke an HTTP request:

  • `ResponseScriptWrapper httpClient.send(String uri, Map requestOptions).get()`

    Sends a synchronous request to the specified URI with request options. The requestOptions parameter is a native JavaScript object that supports method, headers, token, and body as request options.

    Use the requestOptions attribute, form, to send a form request. The form attribute automatically url-encodes fields, so you don’t need to specify "Content-Type": "application/x-www-form-urlencoded" as part of the headers.

    For example:

    var requestOptions = {
      method: "POST",
      form: {
        field1: "value1",
        field2: "value2"
      }
    }
  • `ResponseScriptWrapper httpClient.send(String uri).get()`

    Sends a synchronous GET request with no additional request options.

To access response data:

  • `Map response.formData()`

  • `Map response.json()`

  • `String response.text()`

    The following fields provide response status information:

    Field Type

    headers

    Map

    ok

    boolean

    status

    integer

    statusText

    String

    The response is similar to Response object behavior.

To invoke a synchronous HTTP request:

  • `HTTPClientResponse httpClient.send(Request request).get()`

    To access response data:

  • `JSON.parse(response.getEntity().getString())`

HttpClientResponse methods:

  • `Map<String, String> getCookies()`

  • `String getEntity`

  • `Map<String, String> getHeaders()`

  • `String getReasonPhrase()`

  • `Integer getStatusCode()`

  • `Boolean hasCookies`

  • `Boolean hasHeaders`

The following example uses the httpClient binding to send a synchronous authentication request and check for success.

  • Next-generation

  • Legacy

// import a library script that handles authentication
var authLib = require("authLib");
// use the library function to get authentication token
var bearerToken = authLib.generateBearer(nodeState);

var requestOptions = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  token: bearerToken, // Equivalent to Authorization header
  body: {
    username: "bjensen"
  }
}

var requestURL = "https://my.auth.server/authenticate";
var response = httpClient.send(requestURL, requestOptions).get();

if (response.status === 200) {
    action.goTo("true");
} else {
    action.goTo("false");
}
var fr = JavaImporter(org.forgerock.openam.auth.node.api.Action);

var requestURL = "https://my.auth.server/authenticate";
var request = new org.forgerock.http.protocol.Request();
request.setUri(requestURL);
request.setMethod("POST");
request.getHeaders().add("Content-Type", "application/json;");
request.getHeaders().add("Authorization", "Bearer abcd-1234");
request.setEntity(JSON.stringify({"username": "bjensen"}));

var response = httpClient.send(request).get();

var responseCode = response.getStatus().getCode();
if (responseCode === 200) {
    action = fr.Action.goTo("true").build();
} else {
    action = fr.Action.goTo("false").build();
}

The httpclient binding also supports asynchronous requests so that you can perform non-blocking operations, such as recording logging output after the script has completed.

To make an asynchronous request, use the same method signatures to send the request but without calling get() on the returned object:

  • Next-generation

  • Legacy

public Promise<ResponseScriptWrapper, HttpClientScriptException> send(String uri)
public Promise<ResponseScriptWrapper, HttpClientScriptException> send(String uri, Map<String, Object> requestOptions)
public Promise<Response, NeverThrowsException> send(Request request)

For example:

  • Next-generation

  • Legacy

var requestURL = "https://my.auth.server/audit";
var response = httpClient.send(requestURL).then((response) => {
  if (!response) {
    logger.error("Bad response from " + requestURL);
    return;
  }
  if (response.status != 200) {
    logger.error("Unexpected response: " + response.statusText);
    return;
  }
  logger.debug("Returned from async request");
});
action.goTo("true");
var fr = JavaImporter(
    org.forgerock.http.protocol.Request,
    org.forgerock.http.protocol.Response,
    org.forgerock.openam.auth.node.api.Action);

var request = new fr.Request();
request.setUri("https://my.auth.server/audit");
request.setMethod("GET");

var response = httpClient.send(request).then((response) => {
  if (!response) {
    logger.error("Bad response from " + requestURL);
    return;
  }
  var status = response.getStatus().getCode();

  if (status != 200) {
    logger.error("Unexpected response: " + response.getEntity().getString());
    return;
  }
  logger.message("Returned from async request");
});

action = fr.Action.goTo("true").build();

The httpClient script binding automatically adds the current transaction ID as an HTTP header, X-ForgeRock-TransactionId. This lets you correlate caller and receiver logs when you use httpClient from your script to make requests to other Ping Identity products and services.

Log script messages

Write messages to Advanced Identity Cloud debug logs by using the logger object.

Scripts that create debug messages have their own logger which is created after the script has executed at least once.

Logger names use the format: scripts.<context>.<script UUID>.(<script name>); for example, `scripts.OIDC_CLAIMS.36863ffb-40ec-48b9-94b1-9a99f71cc3b5.(OIDC Claims Script).

For information about debug logs, refer to Get audit and debug logs.

  • Next-generation

  • Legacy

The ScriptedLoggerWrapper is based on the SLF4J logging framework. You can log messages at the following levels:

  • Trace

  • Debug (default level for development tenant environments)

  • Info

  • Warn (default level for staging and production environments)

  • Error

var traceEnabled = logger.isTraceEnabled();
logger.trace("Trace with arg {}", arg);
var debugEnabled = logger.isDebugEnabled();
logger.debug("Debug with arg {}", arg);
var infoEnabled = logger.isInfoEnabled();
logger.info("Info with arg {}", arg);
var warnEnabled = logger.isWarnEnabled();
logger.warn("Warn with arg {}", arg);
var errorEnabled = logger.isErrorEnabled();
logger.error("Error with arg {}", arg);

The Debug logger lets you log messages at the following levels:

  • Message

  • Warning

  • Error

var messageEnabled = logger.messageEnabled();
logger.message("Message with arg {}", arg);
var warnEnabled = logger.warningEnabled();
logger.warning("Warn with arg {}", arg);
var errorEnabled = logger.errorEnabled();
logger.error("Error with arg {}", arg);

Access IDM scripting functions

The openidm binding lets you manage an IDM resource by calling scripting functions directly from a next-generation script.

The following CRUDPAQ functions are supported:

  • create

  • read

  • update

  • delete

  • patch

  • action

  • query

For more information, refer to Scripting functions.

The openidm binding provides administrative access to IDM functions. Use it with caution to prevent the exposure of sensitive data.

The following example illustrates how to create a user, update their details, send an email, and finally delete the user:

  • Next-generation

  • Legacy

var username = "bjensen";

// CREATE: returns the user identity as a JSON object (wrapped in a MapScriptWrapper)
var newUser = openidm.create("managed/alpha_user", null, {
  "userName": username,
  "mail": "bjensen@example.com",
  "givenName": "Barbara",
  "sn": "Jensen"});

// Access the fields directly, for example: ._id, .sn, .city, .country
var userID = newUser._id;

// READ: returns entire identity as a JSON object
var user = openidm.read("managed/alpha_user/" + userID);

// Debug to output all fields
logger.debug("user: " + JSON.stringify(user));

// UPDATE: replaces entire identity with specified object
// Returns the updated identity as a JSON object
user.description = 'New description';
var updatedUser = openidm.update("managed/alpha_user/" + userID, null, user);

// PATCH: selectively modify object, returns entire identity
var patchedUser = openidm.patch("managed/alpha_user/" + userID, null, [{
        "operation":"replace",
        "field":"/mail",
        "value":"new@example.com"
}]);

// QUERY: returns results array in a map
var queryRes = openidm.query("managed/alpha_user",
    {"_queryFilter":`/userName eq '${username}'`},["*", "_id"]);

// Debug query result count and the requested properties
logger.debug("Query result count: " + queryRes.resultCount);
logger.debug("Queried user: " + queryRes.result[0].givenName);

// ACTION: send email using the action function
var actionRes = openidm.action("external/email", "send", {
    "from": "admin@example.com",
    "to": patchedUser.mail,
    "subject": "Example email",
    "body": "This is an example"
});
// Example response if not null: {"status":"OK","message":"Email sent"}
logger.debug("Status: " + actionRes.status + " : " + actionRes.message);

// DELETE: returns deleted object if successful, throws exception if not
openidm.delete('managed/alpha_user/'+ userID, null);

action.goTo("true");

Not available in Legacy bindings

Output realm name

The realm binding lets you access the name of the realm to which the user is authenticating as a string.

For example, authenticating to the alpha realm returns a string value of /alpha.

  • Next-generation

  • Legacy

// log current realm
logger.debug("User authentication realm: " + realm);
// log current realm
logger.message("User authentication realm: " + realm);

Output script name

Use the scriptName binding to get the name of the running script as a string.

  • Next-generation

  • Legacy

// log current script name
logger.debug("Running script: " + scriptName);

// or use a library script to log script name
var mylib = require('loggingLibrary');
mylib.debug(logger, scriptName);
// log current script name
logger.message("Running script: " + scriptName);

Access utility functions

Use the utils binding to base64 encode or decode text and generate random UUIDs.

  • Next-generation

  • Legacy

// generate a pseudorandom UUID (version 4)
var uuid = utils.crypto.randomUUID();
logger.debug("UUID: " + uuid); //eef5b4e1-ae86-4c0a-9160-5afee2b5e791

// encode a string
var encoded = utils.base64.encode("exampletext")
logger.debug("Encoded text: " + encoded); //ZXhhbXBsZXRleHQ=

var decoded = utils.base64.decode(encoded);
logger.debug("Decoded text: " + decoded);

// encode a URL
var encodedURL = utils.base64url.encode("http://exampletext=")
logger.debug("Encoded URL: " + encodedURL); //aHR0cDovL2V4YW1wbGV0ZXh0PQ

var decodedURL = utils.base64url.decode(encodedURL);
logger.debug("Decoded URL: " + decodedURL);

Not available in Legacy bindings

Reference ESVs in scripts

The systemEnv binding, available to all script types, provides the following methods shown with their Java signatures:

String getProperty(String propertyName);
String getProperty(String propertyName, String defaultValue);
<T> T getProperty(String propertyName, String defaultValue, Class<T> returnType);

where:

  • propertyName refers to an ESV. For details, refer to ESVs.

    The propertyName always starts with esv.; for example, esv.my.variable.

    Make sure the propertyName is specific enough to distinguish it from all other ESVs defined.

  • defaultValue is a default value to use when no ESV matches propertyName.

    The defaultValue must not be null.

  • returnType is one of the following fully-qualified Java class names:

    • java.lang.Boolean

    • java.lang.Double

    • java.lang.Integer

    • java.lang.String

    • java.util.List

    • java.util.Map

The getProperty(String propertyName) method returns null when the propertyName is not valid.

For example:

var myProperty = systemEnv.getProperty('esv.my.variable');
var myDefault = systemEnv.getProperty('esv.nonexisting.variable', 'defaultValue');
var myDouble = systemEnv.getProperty('esv.double.variable', '0.5', java.lang.Double);
var myBool = systemEnv.getProperty('esv.bool.variable', false, java.lang.Boolean);
var myInt = systemEnv.getProperty('esv.int.variable', 34, java.lang.Integer);
var map = systemEnv.getProperty('esv.map.variable', '{"defaultKey":"defaultValue"}', java.util.Map);