Defining Workflows
Workflows provide the flexibility of the TrustBuilder platform. A workflow is made up of a number of activities that are linked together by connections. A workflow is executed from its initial state to its final state. The route through the workflow is determined by returned values from scripts. So a return value matches a connection value/label, that determines the route and executes the next activity.
Workflow Elements
Connections → A Connection is a link between two other elements. It marks the route that the workflow can take. Each connection can be given a Label and a Value (by right-clicking on the connection). The Label is for display purposes, whereas the Value has to correspond to an element's output value, for this connection to be followed. As best practice, define the same 'value' for value and label. If no value is specified, the workflow takes the label into consideration.
Conditions → A condition evaluates one or more input parameters, and determines the outcome of a decision as to which step to execute next. A condition will always return a single value, which will determine the route to follow. This value is marked on the exiting connection. Connections marked "otherwise" are followed when no other connection can be followed.
Scripts → A piece of JavaScript code which is executed. This can execute anything. Each activity in a workflow (adapter, component, condition, sub-workflow, script) can have a script related to it to process, for instance, a response from an adapter. Say we have a JDBC adapter that does a query the response from the query is read in a script and processed possibly then set to data or a template.
Templates → Templates are used within scripts to return, for instance, an html page or a JSON message to the client. In that sense, they differ from Templates within the scope of TrustBuilder. Which are used as pages used by the gateway/orchestrator for rendering, for example: password/login pages, IDP selection for a specific SP, etc.
Components → Components are pre-configured complete configurations. They can contain adapters, services, workflows, scripts and a configuration of their own. The scripts within a component are, however, not configurable. A Component activity is created within a workflow and is called as a step in the workflow. So a component might do a task such as validate a certificate against certificates within a certificate store, for instance. All the logic and scripting will be contained within the component, but it can be configured for URLs, ports and such. The workflow will call the component and continue with its response.
Services → Services are a way to interact with 3rd party code from within a workflow run by the TrustBuilder Core. A service is a wrapper around a java library, that is then exposed to scripts within the workflow. This way the java code can be called directly from the scripts. There are also several packaged services within TrustBuilder that expose Java methods; Eg. date and time, or encryption.
Adapters → An adapter is used by the TrustBuilder Core within a workflow to connect to any resource. This could be a call to an LDAP server or a call to a database to get details of a user for instance. Within the adapter such things as connection URL/port or datasource name or JDBC URL or database username password are configured. An Adapter Activity in a workflow is a refence within a workflow to a pre-configured adapter.
Usage in TB.Connect
Workflows can be created to serve several purposes. Below we have listed the different ways workflows can be used within TB.Connect.
Derived Attributes → Applies a workflow to derive a new User Attribute from other attributes. Derived attribute workflows are executed when the user session changes. A simple example is to derive the Full Name from the First Name and the Surname.
Internal IDP → Applies a workflow to authenticate and/or authorize a user. For example, a company has several Active Directories (AD). For any given user, they don't know which AD to look into. The workflow will look in each AD separately, and may even store which AD the user can be found in as an attribute. The workflow can reuse this attribute to make future look-ups more efficient.
HTTP Request Handling → A stand-alone workflow can handle any kind of HTTP request, execute logic and make a response. These workflows are accessible by the path
/idhub/tb/{workflowname}
Session Workflow → Go to Settings > Authentication > General > Session Workflow ID. The session hook is called by TB.Connect when a session is created or destroyed. This allows you to create custom logic whenever one of the actions above occurs. An example when this is used is to destroy an ISAM session. The workflow adds additional headers in the response.
Derived Attributes workflow
In the definition of a derived attribute, the administrator specifies the workflow that will produce the value of the attribute for the user logging in. One workflow can provide the value for multiple derived attributes. The input parameters of derived attribute workflows are "credential", "sp_request" (if it's relevant), "idp_request" (if it's relevant) and "headers".
The response of the workflow is expected to be a simple value response containing the following structure:
<subtype_name>: {
<attr_name>: ["value1", ... ,"value3"],
...
<attr_name>: ["value1", ... ,"value3"]
}
...
<subtype_name>: {
<attr_name>: ["value1", ... ,"value3"],
...
<attr_name>: ["value1", ... ,"value3"]
}
As an example, the following workflow script produces a value for the attribute Full Name by concatenating the First Name and Last Name attribute values of the principal.
function getFullNameValues(workItem){
var fn = workItem.input.value.attributes.common.first_name[0];
var ln = workItem.input.value.attributes.common.last_name[0];
var full_name = fn + " " + ln;
var full_name_str = JSON.stringify(full_name).replace(/\"/g, "");
workItem.output = tb.simpleResponse({
common: {
full_name: [full_name_str]
}
});
}
The workflow function is given an object as parameter, called workItem in this example, that contains the principal object (accessed via workItem.input.value) and where the output of the workflow must also be stored, in workItem.output. As shown in the example above, the output object can be created using tb.simpleResponse and passing as parameter a JavaScript object with the correct structure.
Finally, since the returned values have to be in JSON format, the full_name property is converted into a JSON String by the JSON.stringify method and the backward slashes \ created by the conversion are removed.
Session Hook workflow
In general authentication settings you can configure a session hook workflow id. This workflow will be executed whenever a session is updated. The workflow gets a SimpleValueRequest as input that contains 5 parameters.
type: This is a string that has a value "login" if the session is updated and a value "logout" if the session is removed.
response_headers: contains the response headers.
credential: contains the session.
request_headers: contains the request headers.
request_cookies: contains the request cookies.
The response from the session hook workflow is expected to be an object containing one property "headers", which is a map of headers. These headers will be added to the response headers.
Provisioning workflow
For every IDP, it is possible to configure a provisioning workflow. This workflow gets executed when a request from that IDP is handled by TB.Connect. The response of the workflow can contain attribute definitions and accompanying values, which are added to the user's session.
The input parameters of a provisioning workflow are "credential", "sp_request" (if it's relevant), "idp_request" and "headers".
The response of the workflow is expected to be a simple value response containing the following structure:
<subtype_name>: {
<attr_name>: ["value1", ... ,"value3"],
...
<attr_name>: ["value1", ... ,"value3"]
}
...
<subtype_name>: {
<attr_name>: ["value1", ... ,"value3"],
...
<attr_name>: ["value1", ... ,"value3"]
}
Token Exchange Policy workflow
Implementation
This workflow is an extension on the Token Exchange grant (OAuth) in TrustBuilder.
Because the basic process only checks the validity of the tokens and IDP's, it will always return an access token under the Token Exchange grant. However, it may be requested to add additional business logic to whether or not an access token should be granted for the specified audience and/or scopes.
Therefore it's possible to extend the Token Exchange process by providing tailor-made decision policies.
The workflow output will be a simple 'allow' or 'deny.'
Example
Below is a simple example, to demonstrate the return value.
function checkPolicy (workitem) {
workitem.output=tb.simpleResponse({
allow: false
});
}
As of TrustBuilder 10.3.0, you can also specify a custom error message. This will be returned in the "error_description" field as per the OAuth spec (https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 )
function checkPolicy (workitem) {
workitem.output=tb.simpleResponse({
allow: false,
error: "only administrators are allowed to do this"
});
}
Update user attributes
This feature is available as of TrustBuilder 9.5.9. In the policy workflow you can also update user attributes, which can then be passed along in the generated token. This can especially be useful if the backend application needs a custom identifier of the user or when application specific roles have to be communicated.
function checkPolicy (workitem) {
workitem.output=tb.simpleResponse({
allow: true,
common : {
applicationRoles: ["read", "write"],
customIdentifier: ["john.doe"]
}
});
}
Cookie handling in workflows
This is an example of how you can set cookies in a workflow:
var cookies = {
"TbSession": { // name of the cookie
value: 'someIdentifier', // content of the cookie
maxage: '300', // expiration time in seconds
path: "/", // define a (sub) path to optionally limit the scope of the cookie
httponly: true, // do not expose the cookie to javascript; defaults to false
secure: true // only send the cookie via https; defaults to false
},
"RememberMe": {
value: '1',
maxage: '300',
path: "/"
},
};
var header{
"Content-type": "text/html",
'X-Application': 'Trustbuilder'
};
tb.generateResponse('my doc',headers,cookies,200);
Encryption Services
Argon 2
be.securit.trustbuilder.service.EncryptionService
argon2Hash()
generates an Argon2 hash using the ARGON2id type.
parameters:
password : String : the password to hash
base64EncodedSalt : String : the salt to use, base 64 encoded to a String
parallelism : int : the number of threads to use
resultLength : int : the desired tagLength, this is the desired number of bytes in the hash. Note that is not equal to the length of this function's result
memorySizeInKb : int : amount of memory (in kilobytes) to use
iterations : int : number of iterations to perform
return value:
An encoded hash. This encoded hash contains all parameters used to calculate the hash, so it alone suffices to verify a password.
argon2Verify()
Verifies a given password against a argon2 encoded hash string
parameters:
password : String : the password to verify
encodedHash : String : the encoded Argon hash string
return value:
boolean : true if the password was successfully verified, false otherwise
Example
function argon2Hash (workitem){
var service = tb.getService('Argon2');
var pwd = 'passw';
var b64 = tb.base64Encode('some random string', true);
var parallelism = 4;
var resultLength = 64;
var memorySizeInKb = 4096;
var iterations = 10;
var hash = service.argon2Hash(pwd, b64,parallelism,resultLength,memorySizeInKb,iterations);
tb.log(hash);
}
function argon2Verify (workitem){
var service = tb.getService('Argon2');
var pwd = 'passw';
var hash = '';
var result = service.argon2Verify(pwd, hash);
tb.log(result?'success':'fail');
}
PBKDF2
2 methods were added for the PBKDF2 password hashing - pbkdf2Hash(password, base64EncodedSalt, iterations, keyLength, algorithm) password :
the password string to hash.
base64EncodedSalt : a regular base64 encoded String representing the chosen salt (The standard recommends a salt length of at least 64 bits. The US National Institute of Standards and Technology recommends a salt length of 128 bits.)
iterations : number of iterations for the PBKDF2 algorithm
keyLength : desired length of the resulting key in bits
algorithm : one of the available PBKDF2 algorithm variations, mainly specifying the hash algorithm to use.
At the time of writing the available ones are :
PBKDF2WithHmacSHA1
PBKDF2WithHmacSHA224
PBKDF2WithHmacSHA256
PBKDF2WithHmacSHA384
PBKDF2WithHmacSHA512
The function returns a regular base64 encoded String representing the resulting hash bits. - pbkdf2Verify(password, hash, base64EncodedSalt, iterations, keyLength, algorithm) password : the password string to verify.
hash : a regular base64 encoded String representing the hash to verify against
base64EncodedSalt : a regular base64 encoded String representing the chosen salt (The standard recommends a salt length of at least 64 bits. The US National Institute of Standards and Technology recommends a salt length of 128 bits.)
iterations : number of iterations for the PBKDF2 algorithm
keyLength : desired length of the resulting key in bits
algorithm : one of the available PBKDF2 algorithm variations, mainly specifying the hash algorithm to use. The function returns true if the password is verified against the hash, and false otherwise
LTPA2 Token
Method: newLpta2Encoder
This API Service can be used to create LTPA2 tokens.
Parameters
privateKey : PrivateKey object : the private key to use, to sign the tokens.
secretKeyString : String : Base64 encoded shared symmetric key (AES) to use to encrypt the token.
Return Value
An LptaTokenEncoder object. Object that can be used to encode/decode and verify the signature of LTPA tokens, using the configured keys.
Method: decodeLTPAToken
decodeLTPAToken : method which decodes an encoded ltpa token string.
parameters
token : String : the encoded ltpa token String
return value :
LtpaToken object
methods:
getExpire : long : expiry time in seconds since epoch
getHost : String
getLtpaVersion : LtpaVersion enum object
getNamingProvider : String
getPlainUserMetadata : String : the plain unencoded token String
getPort : int
getServerName : String
getType : String
getUser : String
getAuthenticationMethod : String
toString : String : the encoded token string
Method: encodeLTPAToken
parameters:
ltpaToken : LtpaToken object : to create an unsigned token, see ltpa2TokenBuilder() method
return value :
String : encoded ltpa token string
Method: ltpa2TokenBuilder
return value : Token Builder object
usage example :
var token = builder.setUser("user\\:defaultWIMFileBasedRealm/uid=wpsadmin,o=defaultWIMFileBasedRealm")
.setAuthenticationMethod("authMethod")
.setExpire(1535545274)
.setHost("host")
.setNamingProvider("namingProvider")
.setPort(8484)
.setServerName("serverName")
.setType("type")
.build();