Bridge agent JavaScript Plugins

Developing JavaScript Plugins for Bridge Agent

This documentation details the development of JavaScript plugins for the IBM Security Verify Bridge agent. JavaScript plugins are used to implement customized user authentication and attribute retrieval logic for attribute sources other than the primary LDAP.

High level architecture

909

As of version 1.0.14, the bridge agent supports two different types of attributes sources:

  1. Primary LDAP
  2. Non-primary LDAP

By default, the bridge agent performs user authentication using the primary LDAP. Authentication and attribute retrieval are inbuilt and require no JavaScript plugin.

For advanced use-cases, non-primary LDAP attribute sources may be desired. These allow for customized attribute retrieval and authentication logic to be implemented by means of the JavaScript plugin. The bridge agent supports the following non-primary LDAP attribute sources:

  1. LDAP
  2. Oracle Database
  3. PostgreSQL
  4. IBM Db2

Bindings to these attribute sources are created by means of a JavaScript configuration (jsconfig). The logic for handling attribute retrieval and user authentication must be implemented by means of JavaScript plugin (jsplugin).

This guide details the creation of a jsconfig for configuring a connection to your attribute source and developing a jsplugin for implementing the attribute retrieval and user authentication logic.

Terminology

TermAlternate NamesDefinition
Onprem AgentBridge Agent, ldap agent, agent, identity agentThe agent that runs onprem and interfaces with one or more onprem attribute sources to perform user authentication and attribute retrieval
Bridge Serviceagentbridgesvc, Agent Bridge ServiceISV microservice responsible for handling internal workload requests and the responses received from the onprem agent
Support Serviceagentbridgesupsvc, Agent Bridge Support ServiceISV microservice responsible for managing agent configurations
Data bindingjavascript bindingA connection to an attribute source established via golang utilities and exposed via javascript functions for processing by a javascript plugin
Javascript pluginjspluginA javascript program written by an IBM Security Verify Bridge consumer. It implements logic for attribute retrieval and user authentication on behalf of the onprem agent via various database specific data bindings
Plugin configurationjsconfigConfiguration required by the onprem agent to establish a data binding with an attribute source on behalf of a javascript plugin
Primary LDAPauth LDAPThe primary LDAP directory against which username and password authentication is performed. Can be overriden via authenticationSource property.
Attribute sourcedata source, attribute store, data store, databaseA data source against which authentication can be performed and from which user attributes are retrieved. This can include: LDAP, Db2, PostgreSQL and OracleDB
Authentication sourceauth sourceAn attribute source against which authentication should be performed. Is the primary LDAP by default

Installing the Bridge Agent

Plugins are a new feature of v1.0.14 of the Bridge Agent which is provided as a Docker container. See Installing and configuring the Verify Bridge on Docker.

Important directories

The Bridge Agent will search for configuration files and JavaScript plugins in the two following directories:

DirectoryDescriptionFile Type
/go/src/jsconfigThis directory contains the jsconfig files. These define the binding details and identify the jsplugin. N.B. The bridge can load a maximum of 10 JavaScript plugins. The first 10 configurations in this directory will be loaded in alphabetical order, others will be ignored..json
/go/src/jspluginsThis directory contains the jsplugins, small JavaScript programs that will handle authentication and retrieval of user attributes.js
/certUsed for MTLS with LDAP plugins. The bridge will look here in
/cert/<clientCertLabel>_cert.pem /cert/<clientCertLabel>_key.pem
.pem

Place your configuration and plugin files in their respective directories. Docker bind mounts can be used to simplify this process. Refer to the example docker-compose.yml for using bind mounts

# Example docker-compose.yml
version: "3"

services:
    verify-bridge:
         image: icr.io/isv-saas/verify-bridge:latest


         container_name: bridge_agent
         volumes:
                - ./jsconfig:/go/src/jsconfig:ro
                - ./jsplugins:/go/src/jsplugins:ro
                - ./cert/:/cert:ro

         environment:
                TRACE: "true"
                LICENSE_ACCEPT: "yes"
                TENANT_URI: "https://tenant.verify.ibm.com"
                CLIENT_ID: "12345678-1234-1234-1234-123456789012"
                CLIENT_SECRET: "ABCDEFGHIJ"
          
                
        restart: always

The bind mount section

- ./jsconfig:/go/src/jsconfig:ro
- ./jsplugins:/go/src/jsplugins:ro
- ./cert/:/cert:ro

Mounts the local directories ./jsconfig, ./jsplugins, ./cert to the relevant locations inside the container. The ro option mounts these directories as read only, this is a good security practice for bind mounts as it ensures that running containers cannot make changes to the host filesystem.

Defining Plugin Configurations

Plugins configurations must be placed in the /go/src/jsconfig directory and must be valid JSON documents. The following properties are available:

Standard Configurations

PropertyDefinition
pluginNameThe name of the plugin. The Bridge Agent will search /go/src/jsplugins/<pluginName>.js for the matching plugin.
pluginTypeThe binding type. Must be one of ldap, db2, postgres or oracle.
executionOrderThe order in which the plugin should be executed relative to other plugins. Executed lowest to highest, i.e. 2 is executed before 3.
hardFailSet to true if you want the bridge to return if the plugin fails to execute or returns anything other than SUCCESS.
isAuthenticationSourceSet to true if this plugin is intended to be the authentication source. I.e. username/password authentication is performed against this attribute source. Setting this to true will automatically cause the plugin to behave as though hardFail were also true. When true, authentication will NOT be performed against the Primary LDAP, however attribute lookup will still be performed. To disable primary LDAP lookup entirely (including attribute lookup) you must set disablePrimaryLdapLookup to true. NB Only ONE (1) plugin can be an authentication source.
disablePrimaryLdapLookupDisables the primary LDAP. This should be set when isAuthenticationSource is true and you wish to also disable attribute lookup from the primary LDAP.
customThis is an optional section and can include custom properties to be passed into the jsplugins at execution time.

Binding Configurations

Binding configurations are used to establish a binding to the attribute source and define the binding's connection details. The column LDAP or DB denotes whether or not the property is expected for either LDAP or DB plugins (db2, postgres, oracle).

PropertyLDAP or DBDefinitionExample
connectionStringDBThe connection string used for establishing a binding to that particular data source. Only used for db2, postgres, and oracle. See connection strings.See connection strings.
maxPoolSizeBOTHMaximum number of connections in connection pool50
minPoolSizeBOTHMinimum number of connections in connection pool10
agedTimeoutBOTHTime in seconds before a connection is discarded60
maxIdleTimeBOTHMaximum amount of time a connection can be idle before it's discarded10
bindDnLDAPThe bind DN used to establish an LDAP binding.cn=admin,dc=ibm,dc=com
bindPasswordLDAPThe bind password used to establish an LDAP bindingpTms!REJECs@hzdNEGZ8
urisLDAPAn array of LDAP server URIs. This is used for failover, if the first server in the array fails the next will be tried, etc. N.B. Protocol is important here. If you wish to use TLS you must use the protocol ldaps://. Other TLS settings will be ignored if this is not setldaps://localhost:636
filterLDAPThe filter to use during LDAP lookup(|(|(objectclass=ePerson)(objectclass=person))(objectclass=User))
userObjectClassesLDAPThe user object classestop,Person,organizationalPerson,inetOrgPerson
selectorLDAPSelectors used during the LDAP lookupobjectClass,cn,sn,givenName,userPassword,streetAddress,seeAlso,mobile
baseDnLDAPThe base DN used during a standard LDAP lookup. Plugin logic can override this if needed.dc=ibm,dc=com
caCertLDAPThe CA cert used during TLS to verify the authenticity of the certificate presented by the LDAP server during TLS-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgA...
insecureSkipVerifyLDAPSkip TLS certificate verification. N.B. should only be used in testing environments and not in productionfalse
tlsMinVersionLDAPSet the minimum TLS version allowed. See minimum tls version771
clientCertLabelLDAPUsed during MTLS. Here you should define a label for the client certificate and key to be presented to the LDAP server. The Bridge Agent will look for this at /cert/<clientCertLabel>_cert.pem /cert/<clientCertLabel>_key.pemldap.client

Connection strings

Connection strings apply to database attribute sources (db2, oracle and postgres). Connection strings each have their own format and can be used to specify connection details such as TLS, timeouts and other database specific settings. Please review the documentation for your datasource for a full list of connection string options available. The follow are example connection strings:

Oracle Database

oracle://system:[email protected]:1521/XE?CONNECTION TIMEOUT=5

Postgres Database

host=host.docker.internal port=8788 dbname=postgres user=postgres password=postgrespassword connect_timeout=5

Db2 Database

HOSTNAME=host.docker.internal;PORT=50000;UID=db2inst1;PWD=db2_password;DATABASE=usersdb

Minimum TLS Version

The tlsMinVersion property defines the minimum allowed TLS version. This needs to be an integer. The following table is a mapping of these integers to their TLS version.

TLS VersionInteger
v1.0769
v1.1770
v1.2 (Default)771
v1.3772

Use the integer when setting the minimum TLS version, e.g. "tlsMinVersion": 770. Omitting this property or setting a bad value will cause it to default to 771 (TLS version 1.2).

Example LDAP configuration

The following is an example LDAP jsconfig that makes use of MTLS.

{
  "pluginName": "ldap",
  "pluginType": "ldap",
  "executionOrder": 2,
  "hardFail": false,
  "authenticationSource": {
    "isAuthenticationSource": false,
    "disablePrimaryLDAPLookup": false
  },
  "bindingConfig": {
    "bindDn": "cn=admin,dc=ibm,dc=com",
    "bindPassword": "pass",
    "uris": ["ldaps://host.docker.internal:8636", "http://localhost:8389"],
    "maxPoolSize": 50,
    "agedTimeout": 60,
    "connectTimeout": 5,
    "filter": "(|(|(objectclass=ePerson)(objectclass=person))(objectclass=User))",
    "userObjectClasses": "top,Person,organizationalPerson,inetOrgPerson",
    "selector": "objectClass,cn,sn,givenName,userPassword,streetAddress,seeAlso,mobile",
    "baseDn": "dc=ibm,dc=com"
    "tlsConfig": {
      "caCert": "-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIB...\n-----END CERTIFICATE-----\n",
      "insecureSkipVerify": false,
      "tlsMinVersion": 771,
      "clientCertLabel": "ldap.client"
    }
  }
}

Example Database Configuration

The following is an example of a PostgreSQL jsconfig.

{
  "pluginName": "PgPlug",
  "pluginType": "postgres",
  "executionOrder": 1,
  "hardFail": true,
  "authenticationSource": {
    "isAuthenticationSource": true,
    "disablePrimaryLDAPLookup": true
  },
  "bindingConfig": {
    "connectionString": "host=host.docker.internal port=8788 dbname=postgres user=postgres password=postgrespassword connect_timeout=5",
    "maxPoolSize": 50,
    "minPoolSize": 10,
    "agedTimeout": 60,
    "maxIdleTime": 10
  },
  "custom": {
    "table": "users"
  }
}

Developing JavaScript Plugins

JavaScript plugins consist of three important concepts:

  1. Input Objects
  2. Binding Functions
  3. outputData Object

Input objects provide instruction to the plugin, the operational workload to process, the current working object (results of previous operations) and a copy of the plugin configuration. These contain all the data necessary to perform your plugin's processing.

Binding function binding functions are the functions and classes made available for interacting with the data binding. These can be used for performing queries and retrieving attributes from the data source.

outputData Object
The output object is special object that must be created by your script. The output object must conform to a specific structure.

Input Objects

When the script is executed three input objects are passed into script:

Input ObjectDescription
requestParametersThe request received from ISV
workingObjectParameters already retrieved i.e. parameters retrieved from the Primary LDAP or previous plugins in the execution order
pluginConfigThe plugin configuration as exists in the ./jsconfig directory. Plus the addition of the userAttributes object.

N.B these input objects are passed into the plugin script as strings, they must be parsed into JSON before being used:

let rp = JSON.parse(requestParameters);
let wo = JSON.parse(workingObject);
let pc = JSON.parse(pluginConfig);
requestParameters

This is the request payload at retrieved from ISV. It has the following properties:
The properties of importance are:

PropertyDescription
addressedToThe name of the module, this will always be ldapauth
enqueuedTimeThe time the operation was enqueued
operationThe operation to be performed by the plugin. Can be either password-verify or password-change
parametersA parameter set corresponding to the specific operation
password-verify example
{
  "addressedTo": "ldapauth",
  "enqueuedTime": "2023-10-15 05:15:36.769",
  "id": "jobUuId",
  "operation": "password-verify",
  "parameters": { 
       "password": "Passw0rd", 
       "username": "Scott" 
   }
}
password-change example
{
  "addressedTo": "ldapauth",
  "enqueuedTime": "2023-10-15 05:15:36.769",
  "id": "jobUuId",
  "operation": "password-change",
  "parameters": { 
       "oldpassword": "OldPassw0rd", 
       "newpassword": "NewPassw0rd", 
       "username": "Scott" 
   }
}

N.B You should only process password-change for your authentication source.

workingObject

The workingObject contains attributes that have already been retrieved from previous lookups in the lookup chain. It has the following structure:

PropertyDescription
groupsAn array of the ISV groups to which the user should belong
userAn object whose child objects correspond to the returned attributes for the user
workingObject Example
{
  "groups": [],
  "user": {
    "dn": ["uid=scott,ou=Verify,dc=ibm,dc=com"],
    "mail": ["[email protected]"],
    "mobile": ["+61 2345 6789"],
    "sn": ["Administrator"],
    "uid": ["scott"]
  }
}

N.B. The value for the user attribute must be enclosed inside an array as in the above example.

pluginConfig

The pluginConfig object simply contains an exact copy of the plugin configuration for the plugin that is executing plus the userAttributes.

The userAttributes are the attributes that this plugin will be asked to retrieve. These are injected into the configuration when the script is called.

pluginConfig Example
// pluginConfig value that will be passed to the PgPlug.js plugin
{
  "pluginName": "PgPlug",
  "pluginType": "postgres",
  "executionOrder": 1,
  "hardFail": true,
  "authenticationSource": {
    "isAuthenticationSource": true,
    "disablePrimaryLDAPLookup": true
  },
  "bindingConfig": {
    "connectionString": "host=host.docker.internal port=8788 dbname=postgres user=postgres password=postgrespassword connect_timeout=5",
    "maxPoolSize": 50,
    "minPoolSize": 10,
    "agedTimeout": 60,
    "maxIdleTime": 10
  },
  "userAttributes": [ // These attributes sourced from ISV
       "phone",
       "address",
       "sn" 
  ]
  "custom": {
    "table": "users"
  }
}
pluginConfig userAttributes

userAttributes is the string array representation of the user attributes requested by ISV.

let pc = JSON.parse(pluginConfig);
let requestedAttributes = pc.userAttributes; // Array of attributes requested for this user

Binding functions

Binding functions are functions/classes supplied to your script that enable you to interface with your attribute source via the established connection binding.

There are different bindings functions for databases and ldap:

Logger

A logger binding function is provided. To log, add the following to the top of your Plugin script

importClass(logger);

The logger class provides the following logging functions:

FunctionDescription
trace(message)Log with level trace
debug(message)Log with level debug
info(message)Log with level info
warn(message)Log with level warn
error(message)Log with level error
Logger Example
importClass(logger); // Import logger at the top of your plugin script

// Log with logging level info
logger.info(`Plugin script logging with level info`)
Logging level

Logging level is for your reference and will appear in the bridge agent's standard output.

time="2023-10-24T11:15:47Z" level=<log level> msg="log message" func=pkg.binding.logger.log

Database binding functions

To access the database binding functions place the following import at the top of your plugin script.

// Place this at the top of your Plugin.js file
importClass(database);

This will import the javascript object database which has the following functions defined on it:

database.query()

database.query() accepts

database.query(serverName, query); 

Parameters:

  • serverName (String): Specifies the internal server name of the database against which the query will be performed. This can be set to the value serverName which is a preset variable containing the correct value.
  • query (Object): An object of the form:
{
       "sql": "query string", 
       "args": [
              ["argument 1", "argument 2"]
       ]
}

The arguments will be substituted for parameter markers.

The following is an example of a complete call to database.query:

// Db2 style substitution using the `?` symbol
result = database.query(serverName, {
       "sql": "SELECT * FROM USERS WHERE ID = ? AND PASSWORD = ?",
       "args": [
              ["scott", "Password"]
       ]
}); 

The ? symbol above is a parameter marker is an substituted for the values in the args property.

N.B. the different database types use different parameter markers:

Parameter markers
DatabaseParameter Marker
Db2?
Oracle Database:1
PostgreSQL$1

The following is the same example but using PostgreSQL style parameter markers:

// Postgres style substitution using the `$1` symbol
result = database.query(serverName, {
       "sql": "SELECT * FROM USERS WHERE ID = $1 AND PASSWORD = $1",
       "args": [
              ["scott", "Password"]
       ]
}); 

Result Object

When the query returns it will return one of the following:

  1. Result Object
  2. Error Object
Result Object
PropertyDescription
rowsAffectedNumber of rows changed by operation
rowsAn array of Object. Each object represents a row returned by the query. Each property of the object corresponds to a column and its value.
{
  "rowsAffected": 0, // Rows changed
  "rows": [          // Array of objects representing each row. Each property represents a column and its value
    {
      "CREATED_ON": "2023-11-02T00:00:00Z",
      "EMAIL": "[email protected]",
      "ISLOCKED": false,
      "LAST_LOGIN": "2023-10-24T00:00:00Z",
      "PASSWORD": "scottpass",
      "PGATTR": "scottpsec1234",
      "USERNAME": "Scott",
      "USER_ID": 2
    }
  ]
}

When no rows are returned, null will be returned for rows.

Example:

{"rowsAffected":0,"rows":null}
Error Object

An error object is returned when database.query fails.

PropertyDescription
errorThe error string

Error result object example:

{ "error": "the error string" }

An error object will be returned for situations in which the query failed to execute properly.

database.execute()

Executes a parameterized SQL with optional arguments and returns the number of rows affected. This function works similarly to the database.query function but is used for INSERT, UPDATE, DELETE, DROP.

result = database.execute(serverName, query)

See database query parameters for a description of these parameters.

See result object for a description of the result and error objects.

Example
result = database.execute(serverName,
  {
    "sql": "DELETE FROM TEST_DB_BINDING WHERE ID = ?",
    "args": [
      ["test_key001"],
      ["test_key002"]
    ]
  }
);

Like with database.query, substitutions can be used.

database.ping()

The ping function is a simple query to check if the database is alive.

result = database.ping(serverName);

A healthy result will return the following:

{"rowsAffected":0,"rows":null}

Otherwise an error object will be returned.

LDAP binding functions

To access the LDAP binding functions place the following import at the top of your Plugin script

importClass(ldapUserLookupHelper);

These imports expose the following classes:

ClassOverview
UserLookupHelperProvides functions for the lookup of a user.
UserRepresents a user and provides functions for user operations
UserLookupHelper object reference

Initialize UserLookupHelper

// Initialize new user lookup helper
let pc = JSON.parse(pluginConfig);
let configName = `${pc.pluginName}_config`
let userLookupHelper = new UserLookupHelper(configName);

The configName parameter is a string that must be supplied in the form <pluginName>_config. The above code sample extracts the pluginName from the pluginConfig object and formats it appropriately.

UserLookupHelper methods
MethodReturn TypeDescription
isReady()boolean or Errortrue when ready. Error object on error.
createUser(userName, dn, password, firstName, LastName)UserCreates an LDAP user. Returns a User object.
getUser(userName)UserReturns a User object with the corresponding User object.
getUserByNativeId(nativeId)UserReturns a User object. nativeId represents a full dn (distinguished name).
deleteUser(userName)boolean, nullDeletes user with username userName. Returns true if deleted successfully. May return null on error.

All of the above lookups (excluding createUser and getUserByNativeId) will be performed using the LDAP lookup configuration defined in the LDAP binding configuration (userIdentifier, baseDn, etc).

getUserByNativeId uses a a full DN to look for the user and therefore bypasses userIdentifier.

User object reference

The user object represents a user. It provides methods for user operations such as password authentication and attribute retrieval.

Initialization

This object should not be initialized manually, rather it should be returned via a UserLookupHelper function.

let ulh = new UserLookupHelper(configName);
user = ulh.getUser(`Scott`) // returned user is a User object for the user with id `Scott`
User methods
MethodReturn TypesDescription
hasError()booleanReturns true if the user object has an error. This error can be set during user lookup function (getUser, getUserByNativeId, createUser). N.B. You should check this value before calling any other method on a User object.
getError()stringReturns the value of the Error object (i.e. the error message)
authenticate(password)bool, nullReturns true when password string is correct for user.
getId()string, nullReturns the userId/username of the user.
getNativeId()string, nullReturn full distinguished name (dn) of the user.
getAttributeNames()ArrayReturns array of the attribute names for this user. Note: this does not return the values of these attributes.
getAttribute(attrName)string, nullReturns the value for the attribute attrName. null if no corresponding attribute found. Note: if the attribute attrName returns an array, this function will return the first element of that array. If you wish to return this array use the getAttributes method.
getAttributes(attrName)Array, nullReturns an array of values corresponding to the attribute name attrName. If the value of attrName isn't an array it will be returned as a single member array containing that value.
attributeExists(attrName)booleanReturns true if the user attribute attrName exists.

outputData Object reference

Your plugin script must end by defining an object called outputData. The outputData object represents a response payload to be returned to ISV. It has the following properties:

PropertyDefinition
status.resultThe result code
status.messageAn optional status message
parameters.groupsAn array of ISV group objects to which a user should belong
userAn object whose child objects represent the parameters returned from this operation. Note: the value of each attribute must be an array.
Example
// Define this object at the end of your plugin script
outputData = {
  status: {
    result: "Success",
    message: "",
  },
  parameters: {
    groups: [
      {
        name: "AGroup1",
        id: "AGroup1Id1",
      },
      {
        name: "AGroup2",
        id: "AGroup2Id2",
      },
    ],
    user: {
      attribute1: ["value for attribute 1"],
      attribute2: ["value for attribute 2"],
    },
  },
};
Group Object
PropertyDescription
nameThe displayName of the ISV group
idThe id of the ISV group

You can obtain group ID's via the /v2.0/Groups ISV endpoint.

Example:

curl --request GET \
  --url https://tenant.verify.ibm.com/v2.0/Groups \
  --header 'authorization: Bearer <token>'
// Response from /v2.0/Groups
{
  "displayName": "developer",
  "meta": {
    "created": "2023-03-15T04:52:32Z",
    "lastModified": "2023-03-15T05:06:57Z"
  },
  "id": "60500081MT",
  "urn:ietf:params:scim:schemas:extension:ibm:2.0:Group": {
    "totalMembers": 1,
    "groupType": "reserved",
    "description": ""
  }
}

Setting group objects will JIT provision the returned user into the relevant ISV group.

Quick Start

This section contains minimal example plugins scripts. To use these examples you must first define a plugin configuration to point to the script and correctly establish the binding.

LDAP Minimal Scaffold

The following scaffold is an incomplete LDAP plugin. Logic for authentication and attribute retrieval can be added in the TODO section.

// LDAP minimal scaffold

// Import binding functions
importClass(logger);
importClass(ldapUserLookupHelper);

// Parse Input Objects
let rp = JSON.parse(requestParameters);
let wo = JSON.parse(workingObject);
let pc = JSON.parse(pluginConfig);

// Initialize the UserLookupHelper
let configName = `${pc.pluginName}_config`;
let userLookupHelper = new UserLookupHelper(configName);

// Define the output object to be returned
let output = {
  status: {
    result: "",
    message: "",
  },
  parameters: {
    groups: [],
    user: {},
  },
};

// Inserting main logic inside a function allows for `return` to be used in order to break execution
let process = () => {
  // TODO: Insert plugin logic here
};
process();

// End script by defining the outputData object
let outputData = output;

LDAP Simple Lookup Example

// simpleLdapLookup.js - Simple 

// Import binding functions
importClass(logger);
importClass(ldapUserLookupHelper);

// Parse Input Objects
let rp = JSON.parse(requestParameters);
let wo = JSON.parse(workingObject);
let pc = JSON.parse(pluginConfig);

// Initialize the UserLookupHelper
let configName = `${pc.pluginName}_config`;
let userLookupHelper = new UserLookupHelper(configName);

// Define the output object to be returned
let output = {
  status: {
    result: "",
    message: "",
  },
  parameters: {
    groups: [],
    user: {},
  },
};

// Inserting main logic inside a function allows for `return` to be used in order to break execution
let process = () => {
  // Get the requested user
  let userName = rp.parameters.username;

  // Get the requested attributes
  let requestedAttributes = pc.userAttributes;
  if (!Array.isArray(requestedAttributes)) {
    requestedAttributes = Array.from([requestedAttributes]);
  }

  // Get the user
  let user = userLookupHelper.getUser(userName);

  // Check if user has error
  if (user.hasError()) {
    // Get the error object
    let errorString = user.getError();

    // Log error
    logger.info(`Unable to get user: ${errorString}`);

    // Update the output object with the error
    output.status.result = "ERROR";
    output.status.message = errorString;

    // Return will break execution and cause the output object to return with the above error
    return;
  }

  // Get the requested attributes
  requestedAttributes.forEach((attrName) => {
    // Retrieve this attribute from the user
    output.parameters.user[attrName] = user.getAttribute(attrName);
  });

  // Set the status to successful
  output.status.result = "SUCCESS";
};
process();

// End script by defining the outputData object
let outputData = output;

LDAP Simple Authentication Example

This example adds authentication to the LDAP Simple Lookup Example.

Make sure to identify this plugin as an authentication source in the plugin configuration:

// simpleLdapAuth_config.json
"authenticationSource": {
  "isAuthenticationSource": true,
  "disablePrimaryLDAPLookup": true
},
// simpleLdapAuth.js

// Import binding functions
importClass(logger);
importClass(ldapUserLookupHelper);

// Parse Input Objects
let rp = JSON.parse(requestParameters);
let wo = JSON.parse(workingObject);
let pc = JSON.parse(pluginConfig);

// Initialize the UserLookupHelper
let configName = `${pc.pluginName}_config`;
let userLookupHelper = new UserLookupHelper(configName);

// Define the output object to be returned
let output = {
  status: {
    result: "",
    message: "",
  },
  parameters: {
    groups: [],
    user: {},
  },
};

// Inserting main logic inside a function allows for `return` to be used in order to break execution
let process = () => {
  // Get the requested user
  let userName = rp.parameters.username;
  let password = rp.parameters.password;

  // Get the requested attributes
  let requestedAttributes = pc.userAttributes;
  if (!Array.isArray(requestedAttributes)) {
    requestedAttributes = Array.from([requestedAttributes]);
  }

  // Get the user
  let user = userLookupHelper.getUser(userName);

  // Check if user has error
  if (user.hasError()) {
    // Get the error object
    let errorString = user.getError();

    // Log error
    logger.info(`Unable to get user: ${errorString}`);

    // Update the output object with the error
    output.status.result = "ERROR";
    output.status.message = errorString;

    // Return will break execution and cause the output object to return with the above error
    return;
  }

  // Perform authentication
  if (user.authenticate(password) !== true) {
    output.status.result = "PASSWORD_NOT_VALID";
    output.status.message = "Invalid user password";
    return;
  }

  // Get the requested attributes
  requestedAttributes.forEach((attrName) => {
    // Retrieve this attribute from the user
    output.parameters.user[attrName] = user.getAttribute(attrName);
  });

  // Set the status to successful
  output.status.result = "SUCCESS";
};
process();

// End script by defining the outputData object
let outputData = output;

Notice that this example only differs from the simple LDAP lookup example in that it adds authentication:

// Perform authentication
if (user.authenticate(password) !== true) {
  output.status.result = "PASSWORD_NOT_VALID";
  output.status.message = "Invalid user password";
  return;
}