Next-generation scripts
The next-generation scripting engine offers the following benefits:
- Stability
-
-
A stable set of enhanced bindings, available to journey decision node scripts, that reduces the need to allowlist Java classes to access common functionality.
-
- Ease of use
-
-
Simplify your scripts with fewer imports and more intuitive return types that require less code.
-
Easier debugging through clear log messages and a simple logging interface based on SLF4J.
-
Making requests to other APIs from within scripts is easier with a more intuitive HTTP client.
-
- Reduced complexity
-
-
Simplify and modularize your scripts with library scripts by reusing commonly used code snippets as CommonJS modules.
Reference library scripts from a journey decision node script.
-
Access identity management information seamlessly through the
openidm
binding.
-
Migrate to next-generation scripts
Different bindings are available to the journey decision node script depending on the scripting engine version; legacy or next-generation.
The next-generation engine can’t use legacy scripts. If your Scripted Decision node uses legacy scripts, you must convert them to use updated bindings to take advantage of the benefits of the next-generation scripting engine. Where possible, you should migrate legacy scripts to take advantage of next-generation stability. |
You can’t change the script engine version after you have created a script.
To migrate existing scripts, create a new script and convert your legacy code:
-
Create a journey decision node script and select Next Generation on the Choose Script Engine page.
-
Copy and paste the legacy version of your script into the JavaScript field.
-
Review any Java classes that you needed to allowlist to use in your legacy script.
You can’t add Java classes to the next-generation allowlist.
Instead, check if any next-generation bindings provide similar functionality, or reimplement the class as a library script. Library scripts let you add third-party code as reusable JavaScript modules that can be referenced from other scripts.
If this isn’t possible, you can request the functionality to be included as a supported script binding in a future release.
-
Review the changes in the following table and update the bindings according to the examples in the links provided.
Binding Next-generation change Example action
New.
Use static method
goTo()
to set the script outcome.To send callbacks, instead of calling
Action.send()
, use the newcallbacksBuilder
functionality.callbacksBuilder
New.
Instead of creating a
Callback
object and invokingAction.send()
, add callbacks using static methods on thecallbacksBuilder
object; for examplenameCallback
andpasswordCallback
. These callbacks are automatically sent when the script completes.httpClient
Uses native JavaScript objects, similar to the Fetch API.
idRepository
Class changed from
ScriptIdentityRepository
toScriptedIdentityRepository
.Use
getIdentity()
method in addition to methods to get or set attributes.You must now explicitly call
store()
to persist changes to attribute values.jwtAssertion
New.
Generate JWT assertions in scripts.
jwtValuation
New.
Validate JWT assertions in scripts.
logger
Logger is now based on
org.slf4j.Logger
, instead ofcom.sun.identity.shared.debug.Debug
.nodeState
The
sharedState
andtransientState
bindings are no longer supported.openidm
New.
Use this binding to access the
openidm
scripting functions supported in IDM.
action
var fr = JavaImporter(
org.forgerock.openam.auth.node.api.Action);
// Journey continues along the "false" path
action = fr.Action.goTo("false").build(); 1
// Journey continues along the "false" path
action.goTo("false"); 1
1 No need to import the Action
class to access the goTo
method. Instead, call the goTo
method directly on the action
binding.
callbacksBuilder
var fr = JavaImporter( 1
org.forgerock.openam.auth.node.api.Action,
javax.security.auth.callback.NameCallback,
javax.security.auth.callback.PasswordCallback,
java.lang.String
);
if (callbacks.isEmpty()) {
// Request callbacks
action = fr.Action.send( 2
new fr.NameCallback("User Name"),
new fr.PasswordCallback("Password", false)).build();
} else {
// Callbacks returned with credentials
var username =
fr.String(callbacks.get(0).getName());
var password =
fr.String(callbacks.get(1).getPassword());
sharedState.put("username", username);
if (password === null || !password) {
action = fr.Action.goTo("false").build();
} else { 3
transientState.put("password", password);
action = fr.Action.goTo("true").build(); 4
}
}
if (callbacks.isEmpty()) { 1
// Request callbacks
callbacksBuilder.nameCallback(
"User Name", "User Name");
callbacksBuilder.passwordCallback(
"Password", false);
} else {
// Callbacks returned with credentials
var username =
callbacks.getNameCallbacks().get(0);
var password =
callbacks.getPasswordCallbacks().get(0);
nodeState.putShared("username", username);
if (password === null || !password) {
action.goTo("false"); 2
} else {
nodeState.putTransient("password", 3
password);
action.goTo("true"); 4
}
}
1 Use the callbacksBuilder
object instead of importing Callback
classes.
2 No need to explicitly call send()
. The script sends every callback added to the callbacksBuilder
when it completes.
3 Use nodeState.putShared()
instead of sharedState.put()
and nodeState.putTransient()
instead of transientState.put()
.
4 No need to set the outcome, because action.goTo()
was invoked.
httpClient
Call HTTP services with the httpClient.send
method. HTTP client requests are asynchronous,
unless the get()
method is invoked on the returned object.
For more information, refer to Access HTTP services.
var fr = JavaImporter(
org.forgerock.openam.auth.node.api.Action);
var requestURL =
"https://example.com/authenticate";
var request = new
org.forgerock.http.protocol.Request();
request.setUri(requestURL); 1
request.setMethod("POST");
request.getHeaders().add("Content-Type",
"application/json;");
request.getHeaders().add("Authorization",
"Bearer abcd-1234"); 2
request.setEntity(JSON.stringify(
{"username": "demo"}));
var response =
httpClient.send(request).get(); 3
var responseCode =
response.getStatus().getCode(); 4
if (responseCode === 200) {
action = fr.Action.goTo("true").build();
} else {
action = fr.Action.goTo("false").build();
}
// import an external library to get token
var authLib = require('authLib');
var bearerToken =
authLib.generateBearer(nodeState);
var options = { 1
method: "POST",
headers: {
"Content-Type": "application/json"
},
token: bearerToken, 2
body: {
username: "demo"
}
}
var requestURL =
"https://example.com/authenticate";
var response = httpClient.send(
requestURL, options).get(); 3
if (response.status === 200) { 4
action.goTo("true");
} else {
action.goTo("false");
}
1 Set the request options as a native JavaScript object, instead of setting parameters on a Request object.
2 Use Library scripts to reuse common pieces of code; for example, to get an authentication token.
3 Call httpClient.send
with the request URL and options as separate arguments, instead of a Request object.
4 Access response data directly using the methods and properties of the returned response
object.
idRepository
var uuid = "3f5ab61c-1587-44b3-b7d4-675e5159fcca";
var mail = idRepository.getAttribute(
uuid, "mail"); 1 2
idRepository.setAttribute(username, "mail",
["new@example.com"]); 3
var uuid = "3f5ab61c-1587-44b3-b7d4-675e5159fcca";
var identity =
idRepository.getIdentity(uuid); 1
var mail =
identity.getAttributeValues("mail"); 2
// Does NOT automatically persist data
identity.setAttribute("mail",
["new@example.com"]); 3
// persists data (throws an exception if setAttribute failed)
try {
identity.store(); 4
} catch(e) {
logger.error("Unable to persist attribute. " + e);
}
1 The idRepository
object is no longer used to get attribute values. Instead, use the getIdentity()
method of the new org.forgerock.openam.scripting.api.identity.ScriptIdentityRepository
interface to get the identity object.
2 Use the identity
object, instead of idRepository
, to get or set attribute values.
3 Adding or setting attributes on the identity
object does not persist data.
4 You must explicitly persist changes by calling the store
method.
For more information about the idRepository
binding, refer to Access profile data.
logger
The com.sun.identity.shared.debug.Debug
logger class is deprecated and replaced by org.forgerock.openam.scripting.logging.ScriptedLoggerWrapper
.
ScriptedLoggerWrapper
provides a subset of the methods offered by SLF4J.
For more information, refer to Log script messages.
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);
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);
nodeState
// var username = sharedState.get("username");
1
var username =
nodeState.get("username").asString(); 2
var attributes = 3
nodeState.get("objectAttributes").asMap();
var username = nodeState.get("username"); 2
var attributes =
nodeState.getObject("objectAttributes"); 3
1 Deprecated sharedState
and transientState
bindings are no longer available. Use nodeState.get()
instead. To store state values, use nodeState.putShared()
or nodeState.putTransient()
instead of sharedState.put()
and transientState.put()
.
2 No need to call methods such as asString()
or asMap()
.
3 New getObject()
method to retrieve a map with values stored across different states.
The map is immutable.
To get the UUID from |
For more information about the nodeState
binding, refer to Access shared state data.
openidm
The new openidm
binding lets you manage an IDM resource by calling
scripting functions directly from a decision node script.
The following CRUDPAQ functions are supported:
-
create
-
read
-
update
-
delete
-
patch
-
action
-
query
The following example shows the extensive code required in a legacy script
to query the existence of a user by their email address in IDM,
compared to the ease of using the openidm
binding.
For further examples of how to use the openidm
binding in your next-generation scripts,
refer to Access IDM scripting functions.
For details of other supported functions, refer to Scripting functions.
The |
function lookupUser (email) {
try {
var idmUserEndpoint =
+ '/openidm/managed/alpha_user?
_queryFilter=userName+eq+%22'
+ email + '%22';
var request = new
org.forgerock.http.protocol.Request();
var accessToken =
transientState.get("idmAccessToken"); 1
request.setMethod('GET');
request.setUri(idmUserEndpoint); 1
request.getHeaders().add('Authorization',
'Bearer ' + accessToken);
request.getHeaders().add('Content-Type',
'application/json');
request.getHeaders().add('Accept-API-Version',
'resource=1.0');
var httpResponse =
httpClient.send(request).get(); 1
var responseCode =
httpResponse.getStatus().getCode();
if (responseCode === 200) {
var response = JSON.parse(
httpResponse.getEntity().getString());
if (response && response.result &&
response.result.length > 0) {
// User found
return {
success: true,
user: response.result[0]};
} else {
// User NOT found
return { success: true, user: null };
}
} else {
return {
success: false,
error: 'Error looking up user: ' + responseCode
};
}
} catch (e) {
return {
success: false,
error: 'Error querying user: ' + e.toString()
};
}
}
openidm.query("managed/user", { 1
"_queryFilter":`/userName eq '${email}'`
}
);
1 Replace code that gets an idmAccessToken
and uses the HTTP client
object to invoke a request on an /openidm/*
endpoint,
with the direct use of the openidm
binding.