PingAM

Common bindings

Each script type exposes a number of bindings, objects that AM injects into the script execution context. The bindings provide a stable way of accessing AM 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.

AM 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.

You can find information about migrating to the enhanced scripting engine in Migrating to next-generation scripts.

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

Access the name of the current cookie.

No

Yes

Make outbound HTTP calls.

Partial 1

Yes

Get a localized message.

No

Partial 3

Write a message to the AM debug log.

Yes

Yes

Identify the current journey and get information about the journey and its configuration.

No

Partial 3

Manage a IDM resource.

No

Yes

Evaluate policies using the policy engine API.

No

Yes

Access the realm to which the user is authenticating.

Yes

Yes

Access the name of the running script.

Partial 1

Yes

Reference secrets and credentials from scripts.

Partial 2

Yes

Reference system properties.

Yes

Yes

Access utility functions such as base64 encoding/decoding, cryptographic operations, and generating random values.

No

Yes

1 Available in OAuth 2.0 script types, Scripted Decision node scripts, and SAML 2.0 scripts.

2 Available in OAuth 2.0 JWT bearer and Scripted Decision node scripts.

3 Available in Configuration Provider node, Scripted Decision node, Device Match node, and custom node 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 AM; for example, utils, you must rename the variable in your scripts before you upgrade.

Retrieve cookie name

The cookieName binding lets you access the name of the cookie as a string. You can use the cookie name to perform session actions such as ending all open sessions for a user.

// add cookie name to shared state, for example: iPlanetDirectoryPro
nodeState.putShared("myCookie", cookieName);

The cookieName binding is available in next-generation scripts only.

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.

Methods

  • Next-generation

  • Legacy

The httpClient binding uses native JavaScript objects to send requests and receive responses, in a similar way to 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 these attributes:

    Field Type

    method

    The HTTP request method. For example, GET, POST, PUT, DELETE, HEAD.

    headers

    The request headers. For example, "Content-Type": "application/json".

    form

    Required for sending a form request.

    The form attribute automatically URL-encodes fields, and 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"
      }
    }

    clientName

    The HTTP client instance required for sending a request using MTLS.

    token

    The token specified as the Authorization header, for example, when sending a synchronous request.

    body

    The content of the request. Not specified for GET, HEAD, or TRACE methods.

  • ResponseScriptWrapper httpClient.send(String uri).get()

    Sends a synchronous GET request with no additional request options.

To access response data

Use the following methods to get response information:

  • 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 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 AM products and services.

The following examples demonstrate different ways to send HTTP client requests:

Example: Send a synchronous request (GET)

The following example uses the httpClient binding in a next-generation script to make a GET request to generate random UUIDs.

var BASE_URL = "http://www.randomnumberapi.com/api/v1.0/uuid";

var COUNT = 5;

var options = {
  method: "GET",
  headers: {
    "Content-Type": "application/json; charset=UTF-8"
  }
};
var requestURL = `${BASE_URL}?count=${COUNT}`;

var response = httpClient.send(requestURL, options).get();

if (response.status === 200) {
  var uuids = JSON.parse(response.text());
  nodeState.putShared("uuids", uuids);
  action.goTo("true");
} else {
  logger.error("Error generating UUIDs: " + response.statusText);
  action.goTo("false");
}

Use a Debug node to verify the UUIDs stored in nodeState. For example:

{
    ...
    "uuids": [
        "d787a51e-7b2a-4eba-9d87-7ec555ec9f32",
        "f561381a-ec03-4b48-8d6d-828c80d43805",
        "6d9ef759-be3d-414d-a942-dce8d3840b59",
        "40737769-0c91-41e9-a0c4-7deab4a15aea",
        "5a40bdb6-62d3-406f-bd23-71be1d5a54f5"
    ]
}

Example: Send a synchronous request (POST)

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

  • Next-generation

  • Legacy

var bearerToken = "Bearer abcd-1234";

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();
}

Example: Send an asynchronous request

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. The send() method then initiates a separate thread to handle the response. Callers are unable to control when the asynchronous call is processed, so won’t be able to use the response as part of authentication processing.

  • 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";
// creates separate thread to handle response
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");
});
// continues processing whilst awaiting response
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();

Example: Send a request using mTLS and set timeouts

Configure the httpclient to use mTLS to exchange data securely when making a request to an external service.

Follow these example steps to send an HTTP request using mTLS:

Configure the HTTP Client service

Complete these steps to configure an instance of the HTTP Client service.

The instance defines settings such as timeout values and the client certificate or truststore secret labels required by the httpclient script binding to make a TLS connection.

You can configure the instance to override default values. For example, to set connection or response timeout values for a request initiated by the HTTP client, enable Use Instance Timeouts in the service instance and set the timeout accordingly.

You can find information about these settings in the Http Client service configuration.

  1. In the AM admin UI, go to Realms > realm name > Services.

  2. Click Add a Service and select Http Client Service from the list of service types.

  3. Enable the service and save your changes.

  4. On the Secondary Configurations tab, click Add a Secondary Configuration.

  5. Provide a name for the HTTP client instance; for example, myHttpClient, and click Create.

  6. Enable the configuration and save your changes.

    Make sure both the service and the secondary configuration are enabled.

  7. On the TLS Configuration tab, enter an identifier to be used in your secret label in the Client Certificate Secret Label Identifier field.

    For example, testCrt creates the dynamic secret label, am.services.httpclient.mtls.clientcert.testCrt.secret.

    To specify a truststore to verify the target server’s certificate, provide a value for Server Trust Certificates Secret Label Identifier.

    This creates the dynamic secret label, am.services.httpclient.mtls.servertrustcerts.identifier.secret.

  8. Optionally, configure connection and response timeout values on the Timeouts tab. Enable Use Instance Timeouts to override the default settings in advanced properties.

  9. Save your changes.

Map a certificate to the secret label

  1. Upload a PKCS#12 certificate (.p12 file) to a directory accessible to your AM deployment. It must contain a private key and a certificate.

    For testing purposes, you can create your own .p12 file using openssl.

    If you have a PEM file containing both the certificate and a private key, you can use openssl to create a PKCS#12 file:

    openssl pkcs12 -export -in myfile.pem -out mycert.p12 -name myAlias

    To check the details of the keystore file, run the following keytool command:

    keytool -list -v -keystore mycert.p12 -storetype pkcs12
    Example output
    Keystore type: PKCS12
    Keystore provider: SUN
    
    Your keystore contains 1 entry
    
    Alias name: myalias
    Creation date: Mar 19, 2025
    Entry type: PrivateKeyEntry
    Certificate chain length: 1
    Certificate[1]:
    Owner: CN=BadSSL Client Certificate, O=BadSSL, L=San Francisco, ST=California, C=US
    Issuer: CN=BadSSL Client Root Certificate Authority, O=BadSSL, L=San Francisco, ST=California, C=US
    Serial number: adb285150f249509
    Valid from: Tue Mar 18 21:00:15 UTC 2025 until: Thu Mar 18 21:00:15 UTC 2027
    Certificate fingerprints:
    SHA1: 0D:51:42:C2:2A:67:8D:14:11:2F:4E:04:C9:75:3F:36:AA:03:5E:AD
    SHA256: 8A:CD:0E:83:1A:1F:6C:5F:0F:A0:50:2D:7C:48:5C:0A:96:84:9A:29:A7:02:54:57:BB:F1:0B:6D:53:89:21:5A
    Signature algorithm name: SHA256withRSA
    Subject Public Key Algorithm: 2048-bit RSA key
    Version: 3
    
    Extensions:
    
    #1: ObjectId: 2.5.29.19 Criticality=false
    BasicConstraints:[
    CA:false
    PathLen: undefined
    ]
    
    #2: ObjectId: 2.5.29.15 Criticality=false
    KeyUsage [
    DigitalSignature
    Non_repudiation
    Key_Encipherment
    ]
    
    #3: ObjectId: 2.16.840.1.113730.1.1 Criticality=false
    NetscapeCertType [
    SSL client
    ]
  2. Create secret label mappings for the keystore and entry passwords in an existing store.

    For example, save the passwords in two files mapped to secrets in a file system secret volume, mystorepass and myentrypass.

  3. Create a keystore in AM with the following settings:

    File

    /path/to/mycert.p12

    Keystore type

    PKCS12

    Store password secret label

    mystorepass

    Entry password secret label

    myentrypass

  4. Map the Http Client service secret label to the keystore. For example:

    Secret Label

    am.services.httpclient.mtls.clientcert.testCrt.secret

    Aliases

    myalias

Create a script to send the HTTP request

Write a next-generation decision node script to send a request using the HTTP client instance in the request options.

  1. In your script, specify your HTTP client instance as the value for clientName in requestOptions.

    For example:

    var requestOptions = {
      "clientName": "<myhttpclient>"       (1)
    }
    var res = httpClient.send("https://client.badssl.com",
                              requestOptions).get();         (2)
    action.withHeader(`Response code: ${res.status}`);
    
    if (res.status == 200) {
      action.withDescription(res.text());
    } else {
      logger.error(res.text());
      action.goTo("false");
    };
    1 The clientName attribute must reference an enabled instance of the HTTP Client service.
    2 This script uses a test site that checks that the request includes a particular client certificate. Replace this with your mTLS endpoint.
  2. Create a simple journey that includes the Scripted Decision node to test your changes.

  3. Verify that the HTTP request is sent successfully.

    If successful, the example script results in a webpage displaying a response code of 200 on a green background.

Example: Route a request through a proxy

Follow these example steps to send an external HTTP request by connecting to a proxy server:

Configure the HTTP Client service

Configure an instance of the HTTP Client service to override the proxy connection defined in the advanced server properties. This lets you use specific proxy settings per request.

This example configures a proxy connection with authentication, but this is optional.

  1. In the AM admin UI, go to Realms > Realm Name > Services.

  2. Click Add a Service and select Http Client Service from the list of service types.

  3. Enable the service and save your changes.

  4. On the Secondary Configurations tab, click Add a Secondary Configuration.

  5. Provide a name for the HTTP client instance; for example, myHttpClient, and click Create.

  6. Enable the configuration and save your changes.

    Make sure both the service and the secondary configuration are enabled.

  7. On the Proxy Configuration tab, enable Use Instance Proxy, and enter values for the following fields:

    Proxy URI

    The proxy server URI, for example: https://proxyexample.com:8888.

    Proxy Username

    The username to use for proxy authentication, for example: admin.

    Proxy Secret Label Identifier

    The identifier for the secret label mapping to use for proxy authentication, for example: myProxy.

    This creates the secret label am.services.httpclient.proxy.myProxy.secret.

    You can find information about these settings in the Http Client proxy configuration.

  8. Save your changes.

Map a secret to the secret label

To enable proxy authentication, create a secret in a secret store and map it to the dynamic label generated from the proxy configuration.

For example, to map a label to a secret in a file:

  1. Configure a directory as a file system secret store.

  2. Create the required file to store your secret. If you set the proxy secret label identifier to myProxy, name your file am.services.httpclient.proxy.myProxy.secret (when no suffix is required).

  3. Save your password to the file.

Create a script to send the HTTP request

Write a next-generation decision node script to send a request using the HTTP client instance in the request options.

  1. In your script, specify the name of your HTTP client instance as the value for clientName in requestOptions.

    For example:

    var requestOptions = {
      "clientName": "<myHttpClient>"
    }
    var res = httpClient.send("https://example.com",
                              requestOptions).get();
    logger.info(`Status code: ${res.status}`);
    
    if (res.status == 200) {
      action.goTo("true");
    } else {
      logger.error(res.text());
      action.goTo("false");
    };
  2. Create a simple journey that includes the Scripted Decision node to test your changes.

  3. Verify that the HTTP request is sent successfully using the configured proxy connection.

Get localized messages

Use the next-generation locales binding to return the localized version of a string from a translation map.

The binding determines the best locale based on the Accept-Language HTTP header or default system settings, in a similar way to the Message node.

If no match is found, the first entry in the map is returned.

Method

String getLocalizedMessage(Map<String, String> localizations)

Example

The following example sets the greeting property to Hello for a United States English locale. For British English settings, the greeting is set to the first entry in the map, Gutentag, because no match is found.

var languageMap =
{
  'de': 'Gutentag',
  'no': 'Hei',
  'fr': 'Bonjour',
  'en-US': 'Hello'
};

var greeting = locales.getLocalizedMessage(languageMap);

Log script messages

Write messages to AM 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).

You can find information about debug logs in Debug logging.

  • Next-generation

  • Legacy

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

  • Trace

  • Debug

  • Info

  • Warn

  • 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);

Get journey details

The next-generation journey binding provides information about the current journey (tree).

Call the following methods to access details about the journey and how it’s configured.

Methods

String name()

Returns the name of the current journey. This can be an inner or an outer journey.

String identityResource()

Returns the identity resource of the current tree. For example, managed/user.

boolean innerJourney()

Returns true if the current journey is configured to run as an inner journey only.

boolean mustRun()

Returns true if the current journey is set to always run regardless of whether the user authenticated successfully and a session exists or not.

Example

  • Next-generation

  • Legacy

var currentJourney = journey.name();

logger.info("Identity resource: " + journey.identityResource());

if (journey.innerJourney()){
  logger.info(currentJourney + " is an inner journey.");
}
if(journey.mustRun()){
  logger.info(currentJourney + " is a mustRun journey.");
}

Not available in Legacy bindings

Access IDM scripting functions

The openidm binding lets you manage a IDM resource by calling scripting functions directly from a decision node script.

The openidm binding is only available when AM is integrated with IDM.

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 new user identity as a JSON object (wrapped in a MapScriptWrapper)
var newUser = openidm.create("managed/user", null, {
  "userName": username,
  "mail": "foo@bar.com",
  "givenName": "Foo",
  "sn": "Bar"});

// 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/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/user/" + userID, null, user);

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

// QUERY: returns results array in a map
var queryRes = openidm.query("managed/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/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 secrets and credentials

Use the secrets binding to access secrets configured in AM secret stores.

For example, a script can access credentials or secrets defined in a file system secret volume to make outbound calls to a third-party REST service without hard-coding those credentials in the script.

Only secret labels that begin with the string scripted.node. are accessible to scripts.

Methods

Use the following method to return the value of the specified secret label:

String secrets.getGenericSecret(String secretLabel)

If the secret label is defined at the realm level, its value is returned; otherwise, the script returns the value defined at the global level.

To format the returned secret value, use these supported methods:

getAsBytes()

Retrieve the secret value in byte[] format.

getAsUtf8()

Retrieve the secret value in UTF-8 format.

Examples

The following example scripts show how to get a password from a secret label named scripted.node.example.secret. The scripts use the encoded username (bjensen) and password (passwd) in a basic authentication header to access the http://httpbin.org/basic-auth/{user}/{passwd} service.

  • Next-generation

  • Legacy

// secret [cGFzc3dk] stored in file system secret store
var password = secrets.getGenericSecret("scripted.node.example.secret").getAsUtf8();

var auth = utils.base64.encode("bjensen:" + password);

var requestURL = "http://httpbin.org/basic-auth/bjensen/passwd";

var requestOptions = {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Basic ".concat(auth)
  },
}
var response = httpClient.send(requestURL, requestOptions).get();

if (!response) {
  logger.error("Bad response from " + requestURL);
  action.goTo("false");
} else {
  if (response.status != 200) {
    logger.warn("Authentication not successful. Code: " + response.status);
    action.goTo("false");
  } else {
    logger.debug("Authenticated: " + response.json().authenticated);
    action.goTo("true");
  }
}

To construct the header for basic authorization, make sure you use the concat() function rather than + to append credentials.

To use this sample script, add the following classes to the class allowlist property in the AUTHENTICATION_TREE_DECISION_NODE scripting engine configuration:

  • org.mozilla.javascript.ConsString

  • java.util.Base64

  • java.util.Base64$Encoder

For details, refer to Access Java classes.

var fr = JavaImporter(org.forgerock.openam.auth.node.api.Action);

// secret [cGFzc3dk] stored in file system secret store
var password = secrets.getGenericSecret("scripted.node.example.secret").getAsUtf8();

var auth = java.util.Base64.getEncoder().encodeToString(java.lang.String("bjensen:" + password).getBytes());

var request = new org.forgerock.http.protocol.Request();
request.setMethod("GET");
request.setUri("http://httpbin.org/basic-auth/bjensen/passwd");
request.getHeaders().add("content-type","application/json; charset=utf-8");
request.getHeaders().add("Authorization", "Basic " + auth);

var response = httpClient.send(request).get();
var jsonResult = JSON.parse(response.getEntity().getString());
logger.message("Script result: " + JSON.stringify(jsonResult));

if (jsonResult.hasOwnProperty("authenticated")) {
  action = fr.Action.goTo("success").build();
} else {
  action = fr.Action.goTo("failure").build();
}

Reference substituted properties in scripts

The systemEnv binding, available to all script types, includes an instance of the ScriptPropertyResolver class. This interface exposes the following methods:

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

where:

  • propertyName refers to a configuration expression (without the ampersand or braces)

    To reference a substituted property in a script, the property name must include a specific prefix; for example, script.my.variable. This prefix decreases the risk that random property values are resolved in scripts.

    The default prefix for all script types, is script. To change the prefix, go to Configure > Global Services > Scripting > Secondary Configurations > Script Type > Secondary Configurations > Engine Configuration > Property Name Prefix.

    Make sure propertyName is specific enough to distinguish it from other configuration expressions.

  • defaultValue is the default value set for that property in the configuration expression

    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:

The following example assumes that the property name prefix is set to script in the script engine configuration for the script type.

The script is used in a Scripted Decision node to get the values such as the user’s email, hostname, and port. The script also shows type transformation where the type isn’t a string.

  • JavaScript

  • Groovy

var fr = JavaImporter(org.forgerock.openam.auth.node.api.Action);

// Properties should get resolved (set in AM)
var email = systemEnv.getProperty('script.tree.decision.node.email');
var name = systemEnv.getProperty('script.tree.decision.node.hostname', 'defaultHostname');
var port = systemEnv.getProperty('script.tree.decision.node.port', '587', java.lang.Integer);
var double = systemEnv.getProperty('script.tree.decision.node.double', '2.0', java.lang.Double);
var hasPort = systemEnv.getProperty('script.tree.decision.node.hasPort', 'false', java.lang.Boolean);
var map = systemEnv.getProperty('script.tree.decision.node.map', '{"defaultKey":"defaultValue"}', java.util.Map);
var list = systemEnv.getProperty('script.tree.decision.node.list', 'defaultValue', java.util.List);

// Properties should get resolved to their defaults (not set in AM)
var defaultName = systemEnv.getProperty('script.tree.decision.node.hostname.unresolved', 'defaultHostname');
var defaultPort = systemEnv.getProperty('script.tree.decision.node.port.unresolved', '587', java.lang.Integer);
var defaultDouble = systemEnv.getProperty('script.tree.decision.node.double.unresolved', '2.0', java.lang.Double);
var defaultHasPort = systemEnv.getProperty('script.tree.decision.node.hasPort.unresolved', 'false', java.lang.Boolean);
var defaultMap = systemEnv.getProperty('script.tree.decision.node.map.unresolved', '{"defaultKey":"defaultValue"}', java.util.Map);
var defaultList = systemEnv.getProperty('script.tree.decision.node.list.unresolved', 'defaultFirstValue,defaultSecondValue', java.util.List);

// Assert all property values - set the appropriate outcome
if (email === 'test@example.com' && name === 'testHostname' && port === 25 && double === 1.0 && hasPort === true
    && map.get('testKey') == 'testValue' && list == '[testFirstValue, testSecondValue]'
    && defaultName === 'defaultHostname' && defaultPort === 587 && defaultDouble === 2.0 && defaultHasPort === false
    && defaultMap.get('defaultKey') == 'defaultValue' && defaultList == '[defaultFirstValue, defaultSecondValue]') {
  action = fr.Action.goTo("true").build();
} else {
  action = fr.Action.goTo("false").build();
}
// Properties should get resolved (set in AM)
String email = systemEnv.getProperty('script.tree.decision.node.email');
String name = systemEnv.getProperty('script.tree.decision.node.hostname', 'defaultHostname');
Integer port = systemEnv.getProperty('script.tree.decision.node.port', '587', java.lang.Integer);
Double testDouble = systemEnv.getProperty('script.tree.decision.node.double', '2.0', java.lang.Double);
Boolean hasPort = systemEnv.getProperty('script.tree.decision.node.hasPort', 'false', java.lang.Boolean);
Map map = systemEnv.getProperty('script.tree.decision.node.map', '{\"defaultKey\":\"defaultValue\"}', java.util.Map);
List list = systemEnv.getProperty('script.tree.decision.node.list', 'defaultValue', java.util.List);

// Properties should get resolved to their defaults (not set in AM)
String defaultName = systemEnv.getProperty('script.tree.decision.node.hostname.unresolved', 'defaultHostname');
Integer defaultPort = systemEnv.getProperty('script.tree.decision.node.port.unresolved', '587', java.lang.Integer);
Double defaultDouble = systemEnv.getProperty('script.tree.decision.node.double.unresolved', '2.0', java.lang.Double);
Boolean defaultHasPort = systemEnv.getProperty('script.tree.decision.node.hasPort.unresolved', 'false', java.lang.Boolean);
Map defaultMap = systemEnv.getProperty('script.tree.decision.node.map.unresolved', '{\"defaultKey\":\"defaultValue\"}', java.util.Map);
List defaultList = systemEnv.getProperty('script.tree.decision.node.list.unresolved', 'defaultFirstValue,defaultSecondValue', java.util.List);

// Assert all property values - set the appropriate outcome
if (email.equals('test@example.com') && name.equals('testHostname') && port == 25 && testDouble == 1.0d && hasPort == true
    && defaultName.equals('defaultHostname') && defaultPort == 587 && defaultDouble == 2.0d && defaultHasPort == false
    && map.get('testKey').equals('testValue')
    && list.get(0).equals('testFirstValue') && list.get(1).equals('testSecondValue')
    && defaultMap.get('defaultKey').equals('defaultValue')
    && defaultList.get(0).equals('defaultFirstValue') && defaultList.get(1).equals('defaultSecondValue')) {

    outcome = 'true';
} else {
    outcome = 'false';
}

Access utility functions

Use the next-generation utils binding to perform functions such as encoding/decoding and encryption/decryption, type conversion, and cryptographic operations.

The utils binding isn’t available in legacy scripts.
Base64 encode and decode
String base64.encode(String toEncode)

Encodes the specified text using base64.

String base64.encode(byte[] toEncode)

Encodes the specified bytes using base64.

String base64.decode(String toDecode)

Decodes the specified text using base64.

byte[] base64.decodeToBytes(String toDecode)

Decodes the specified text using base64 and returns the result as an array of bytes.

Example
var txt = "exampletext";
var bytes = utils.types.stringToBytes(txt);

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

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

var encodedBytes = utils.base64.encode(bytes);
logger.debug("Encoded bytes: " + encodedBytes);

var decodedBytes = utils.base64.decode(encodedBytes);
logger.debug("Decoded bytes: " + decodedBytes);
Base64Url encode and decode
String base64url.encode(String toEncode)

Encodes the specified text using base64url.

String base64url.encode(byte[] toEncode)

Encodes the specified bytes using base64url.

String base64url.decode(String toDecode)

Decodes the specified text using base64url.

byte[] base64url.decodeToBytes(String toDecode)

Decodes the specified text using base64url and returns the result as an array of bytes.

Example
var url = "http://exampletext=";
var bytesUrl = utils.types.stringToBytes(url);

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

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

var encodedBytesUrl = utils.base64url.encode(bytesUrl);
logger.debug("Encoded bytes: " + encodedBytesUrl);

var decodedBytesUrl = utils.base64url.decode(encodedBytesUrl);
logger.debug("Decoded bytes: " + decodedBytesUrl);
Generate random values
String crypto.randomUUID()

Returns a type 4 pseudo-random generated UUID.

<JavaScript array> crypto.getRandomValues(<JavaScript array> array)

Returns the specified array filled with the same number of generated random numbers.

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

// generate an array of 5 random values
var array = [0,0,0,0,0];
utils.crypto.getRandomValues(array);
array.forEach(function(element){
  logger.debug("Random value: " + element);
});
Convert types
String types.bytesToString(byte[] toConvert)

Converts a byte array to a string.

byte[] types.stringToBytes(String toConvert)

Converts a string to a byte array.

Example
var dataBytes = utils.types.stringToBytes("data");
var dataString = utils.types.bytesToString(dataBytes);
Generate keys
Object crypto.subtle.generateKey(String algorithm)

Generates a key using the specified algorithm and default values.

Object crypto.subtle.generateKey(Map<String, Object> algorithm)

Generates a key using the parameters provided, depending on the algorithm specified.

Parameters
Option Algorithm Description

name

All

Required. The name of the algorithm. Possible values: AES, RSA, HMAC, ECDSA

length

AES

Optional. Default: 256.

modulusLength

RSA

Optional. Default: 2048.

namedCurve

ECDSA

Optional. Possible values: P-256 (default), P-384, P-521.

hash

HMAC

Optional. Possible values: SHA-1, SHA-256 (default), SHA-384, SHA-512.

Example
var aesKey = utils.crypto.subtle.generateKey("AES");

// Optionally specify 'length' (default 256)
var aesKeyCustom = utils.crypto.subtle.generateKey(
  {
    "name": "AES", length: 256
  }
);

var rsaKey = utils.crypto.subtle.generateKey("RSA");

// Optionally specify 'modulusLength' (default 2048)
var rsaKeyCustom = utils.crypto.subtle.generateKey(
  {
    "name": "RSA", modulusLength: 4096
  }
);

var hmacKey = utils.crypto.subtle.generateKey("HMAC");

// Optionally specify 'hash' (default 'SHA-256')
var hmacKeyCustom = utils.crypto.subtle.generateKey(
  {
    "name": "HMAC", "hash": "SHA-384"
  }
);

var ecdsaKey = utils.crypto.subtle.generateKey("ECDSA");

// Optionally specify 'namedCurve' (default P-256)
var ecdsaKeyCustom = utils.crypto.subtle.generateKey(
  {
    "name": "ECDSA", namedCurve: "P-384"
  }
);

logger.debug("AES key: " + aesKey.length);
logger.debug("HMAC key: " + hmacKey.length);
logger.debug("ECDSA key: " + ecdsaKey.publicKey.length + " : " + ecdsaKey.privateKey.length);
logger.debug("RSA keys: " + rsaKey.publicKey.length + " : " + rsaKey.privateKey.length);
Derive keys
byte[] crypto.subtle.deriveKey(String algorithm, byte[] baseKey, Number derivedKeyLength)

Derives a key to the specified length, using the algorithm details and the base key provided.

This method supports the PBKDF2 key derivation function, which enhances key security by repeatedly applying a cryptographic function to a base key and salt. The more iterations, the more secure the key becomes.

Example
// get salt as byte[] - for example, from nodeState or API
var salt = utils.types.stringToBytes("random-salt");

// get key as byte[] - for example, from nodeState or secret store
var baseKey = secrets.getGenericSecret("scripted.node.example.secret").getAsBytes();

// length in octets, for example 128, 256, 512
var derivedKeyLength = 256;

// derivation algorithm must be PBKDF2
// hash can be SHA-1, SHA-256, SHA-384, SHA-512
var algorithmParams = {
"name": "PBKDF2",
"hash": "SHA-256",
"salt": salt,
"iterations": 10
}

var key = utils.crypto.subtle.deriveKey(algorithmParams, baseKey, derivedKeyLength);

// verify that length is 32 (256 bits)
logger.debug("Derived key length: " + key.length);
Encrypt and decrypt
byte[] crypto.subtle.encrypt(String algorithm, byte[] key, byte[] data)

Encrypts the data using the specified key and algorithm (AES or RSA).

byte[] crypto.subtle.decrypt(String algorithm, byte[] key, byte[] data)

Decrypts the data using the specified key and algorithm (AES or RSA).

Example
var data = utils.types.stringToBytes("data");

var aesKey = utils.crypto.subtle.generateKey("AES");
var rsaKey = utils.crypto.subtle.generateKey("RSA");

var encryptedAes = utils.crypto.subtle.encrypt("AES", aesKey, data);
var decryptedAes = utils.crypto.subtle.decrypt("AES", aesKey, encryptedAes);

var encryptedRsa = utils.crypto.subtle.encrypt("RSA", rsaKey.publicKey, data);
var decryptedRsa = utils.crypto.subtle.decrypt("RSA", rsaKey.privateKey, encryptedRsa);

logger.debug("decryptedAes: " + decryptedAes + " decryptedRsa: " + decryptedRsa);
Compute digest (hash) values
String crypto.subtle.digest(String algorithm, byte[] data)

Returns the digest of the data using the specified algorithm. The algorithm must be one of SHA-1, SHA-256, SHA-384, SHA-512.

Example
var data = utils.types.stringToBytes("data");
var digest = utils.crypto.subtle.digest("SHA-256", data);

logger.debug("Digest length: " + digest.length);
Sign and verify
byte[] sign(String algorithm, byte[] key, byte[] data)

Signs the data using the specified algorithm and key.

byte[] sign(Map<String, Object> algorithmOptions, byte[] key, byte[] data)

Signs the data using the specified algorithm options and key.

boolean verify(String algorithm, byte[] key, byte[] data, byte[] signature)

Verifies the signature of the data using the specified algorithm and key.

boolean verify(Map<String, Object> algorithmOptions, byte[] key, byte[] data, byte[] signature)

Verifies the signature of the data using the specified key and map of parameters.

Parameters
Option Algorithm Description

name

All

Required. The name of the algorithm. Possible values: RSA, HMAC, ECDSA.

hash

HMAC, ECDSA

Optional. Possible values: SHA-1 (HMAC only), SHA-256 (default), SHA-384, SHA-512 (1).

(1) The namedCurve length must match the hash length for EDCSA keys. For example, P-256 and SHA-256, or P-521 and SHA-512.

Example
var data = utils.types.stringToBytes("data");
var rsaKey = utils.crypto.subtle.generateKey("RSA");
var hmacKey = utils.crypto.subtle.generateKey("HMAC");
var ecdsaKey = utils.crypto.subtle.generateKey("ECDSA");

var signRsa = utils.crypto.subtle.sign("RSA", rsaKey.privateKey, data);
var verifyRsa = utils.crypto.subtle.verify("RSA", rsaKey.publicKey, data, signRsa);


var hmacOpts = {
  "name": "HMAC",
  "hash": "SHA-512"
}
var signHmac = utils.crypto.subtle.sign(hmacOpts, hmacKey, data);
var verifyHmac = utils.crypto.subtle.verify(hmacOpts, hmacKey, data, signHmac);

var ecdsaOpts = {
  "name": "ECDSA",
  "hash": "SHA-256"
}

var signEcdsa = utils.crypto.subtle.sign(ecdsaOpts, ecdsaKey.privateKey, data);
var verifyEcdsa = utils.crypto.subtle.verify(ecdsaOpts, ecdsaKey.publicKey, data, signEcdsa);

logger.debug("RSA key verified: " + verifyRsa);
logger.debug("HMAC key verified: " + verifyHmac);
logger.debug("ECDSA key verified: " + verifyEcdsa);

Evaluate policies

The next-generation policy binding lets you access the policy engine API and evaluate policies from within scripts.

Methods

List<Map<String, Object>> policy.evaluate(subject, application, resources, environment)

Use the evaluate() method to request policy decisions for specific resources.

Parameters

The following parameters are required:

Parameter Type Description

subject

Map<String, Object>

The subject making the request, specified as an ssoToken, a jwt, or a claims value.

application

String

The name of the policy set.

resources

List<String>

The resources to request decisions for.

environment

Map<String, List<String>>

Specify environment conditions as a map of keys to lists of values, or {} to indicate none.

Returns

The method returns evaluation decisions as a list of maps containing the following fields:

Field Description

resource

The requested resource.

actions

A map of actions and corresponding boolean values. For example:

"actions":{
    "POST":false,
    "PATCH":false,
    "GET":false,
    "DELETE":false,
    "OPTIONS":true,
    "HEAD":false,
    "PUT":false
}

attributes

A map of attribute names to their values if attributes exist for the policy.

advices

A map of advice names to their values if advice exists for the policy.

Learn how the evaluate method works and its parameters in Request policy decisions for a specific resource. The policy binding works in a similar way to this REST API call.

Example

The following example script requests a policy decision for a URL resource.

var subject = {
    ssoToken: requestCookies[cookieName]
}

var application = "testPolicySet";

var resources = ["http://example.com:80/test"];

var environment = {
    "myField": ["myValue"]
}

var evaluationResult;
try {
    // policy.evaluate() returns List<Map<String, Object>>
    var results = policy.evaluate(subject, application, resources, environment);
    evaluationResult = results[0];
} catch(e) {
    logger.error(`Policy Evaluation Failed: ${e.message}`);
}

if (evaluationResult && evaluationResult.actions['GET'] === true) {
    action.goTo("authorized")
} else {
    action.goTo("unauthorized")
}