# ISC Extended Workflow Helper

**URL:** <https://developer.sailpoint.com/discuss/t/isc-extended-workflow-helper/182916>\
**Category:** Community Tools\
**Created:** [September 25, 2025, 9:55pm UTC](https://developer.sailpoint.com/discuss/t/isc-extended-workflow-helper/182916 "2025-09-25T21:55:40Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![neeraj99](https://sea1.discourse-cdn.com/sailpoint/discuss/user_avatar/developer.sailpoint.com/neeraj99/32/14001_2.png) [@neeraj99](https://developer.sailpoint.com/discuss/u/neeraj99)\
**Post date:** [September 25, 2025, 9:55pm UTC](https://developer.sailpoint.com/discuss/t/isc-extended-workflow-helper/182916/1 "2025-09-25T21:55:40Z")

</div>

![isc-extended-workflow-helper](https://global.discourse-cdn.com/sailpoint/original/3X/8/2/824cdccb67107e91a0f115210aebbaa4cb9ce3f1.jpeg)

  

| | | |
| --- | --- | --- |
| 🗒 | **Description** | **isc-extended-workflow-helper** extends SailPoint Identity Security workflows by providing an HTTP API to run Java/BeanShell code. It enables workflows to execute custom logic, integrate with external systems, or perform advanced transformations that go beyond its out-of-the-box capabilities. |
| ⚖ | **Legal Agreement** | **By using this CoLab item, you are agreeing to SailPoint’s [Terms of Service](https://developer.sailpoint.com/discuss/tos) for our developer community and open-source CoLab.** |
| 🛠 | **Repository Link** | [GitHub - sailpoint-oss/colab-isc-extended-workflow-helper · GitHub](https://github.com/sailpoint-oss/colab-isc-extended-workflow-helper) |
| 🏥 | **Supported by** | Community Developed |

# Overview

The project focuses on extending the capabilities of SailPoint Identity Security workflows by integrating with them as an API. It allows to execute the arbitrary Java/Beanshell code using the provided endpoints. The result of the Beanshell code can be collected in any data type depending on the use case.

# Requirements

- Java JDK 17
- Maven 3.8+

# Guide

## Installing Locally

Install Dependencies  
`mvn clean install -Dmaven.test.skip=true`

Start the Server with API Calling Support **Enabled** :  
`mvn spring-boot:run -DskipTests -Dspring-boot.run.jvmArguments="--add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED"`

Start the Server with API Calling Support **Disabled** :  
`mvn exec:java`

## Available Endpoints

- /api/scripts
- /api/escape
- /api/upload-jars

## Returning data

### 1. Type: String (`""`)

```json

  {
    "language": "beanshell",
    "script": "return \"Hello from BeanShell!\";"
  }

```

**Expected Output:**

```json
"data": "Hello from BeanShell!"

```

* * *

### 2. Type: List (`[]`)

```json

  {
    "language": "beanshell",
    "script": "import java.util.*; return Arrays.asList(\"apple\", \"banana\", \"cherry\");"
  }

```

**Expected Output:**

```json
"data": ["apple", "banana", "cherry"]

```

* * *

### 3. Type: Map (`{}`)

```json

  {
    "language": "beanshell",
    "script": "import java.util.*; Map<String,Object> map=new LinkedHashMap<>(); map.put(\"key\",\"value\"); map.put(\"status\",\"ok\"); return map;"
  }

```

**Expected Output:**

```json
"data": {
    "key": "value",
    "status": "ok"
}

```

* * *

### 4. Printed Output Only

```json

  {
    "language": "beanshell",
    "script": "System.out.println(\"Hello World\"); System.out.println(\"Generated Password: 12345\");"
  }

```

**Expected Output:**

```json
"data": {
    "output": "Hello World\nGenerated Password: 12345"
}

```

* * *

### 5 Mixed Return and Print

```json

  {
    "language": "beanshell",
    "script": "System.out.println(\"This will not be included\"); return \"Final Result\";"
  }

```

**Expected Output:**

```json
"data": "Final Result"

```

## Use case

### Create an account using unique incremental ID\*\*

To create an account on delimited file source with increment unique identifier starting from a sequence.

- Currently there exists no OOTB math operations to perform such kind of data transformation in workflows.

Example:

```API
Endpoint: /api/scripts
Method: POST
Body: {
    "language": "beanshell",
    "script": "String startingSeq = \"{{$.defineVariable.startingSeq}}\";\r\nString totalAccountsInSource = \"{{$.defineVariable.count}}\";\r\nreturn String.valueOf(Integer.parseInt(startingSeq)+Integer.parseInt(totalAccountsInSource));"
}

```

Response:

```json
{
  "body": {
    "data": "2816",
    "error": null,
    "language": "beanshell",
    "status": 200,
    "timestamp": "2025-09-18T21:38:17.727516684Z"
  },
  "headers": {
    "Content-Type": [
      "application/json"
    ]
  },
  "responseTime": "0.139118 seconds",
  "statusCode": 200
}

```

The data which has unique incrementor number, can now be used to create an account.

### Handle the Arrays

##### Payload:

```json
{
    "accountRequests": [
        {
            "provisioningTarget": "Active Directory",
            "ticketId": "123xxx2131",
            "attributeRequests": [
                {
                    "operation": "Add",
                    "attributeName": "memberOf",
                    "attributeValue": "CN=sp,DC=sp,DC=example"
                },
                {
                    "operation": "Add",
                    "attributeName": "mail",
                    "attributeValue": "sp@sp.example"
                },
                {
                    "operation": "Add",
                    "attributeName": "sAMAccountName",
                    "attributeValue": "sp.sp.example"
                }
            ]
        }
    ]
}

```

In order to get the sAMAccountName from Active Directory one might use the JSON Path Expression for the below listed payload:

`{{$.accountRequests[?(@.provisioningTarget=='Active Directory')].attributeRequests[?(@.attributeName=='sAMAccountName')].attributeValue}}`

The response of the JSONPath results in an Array.

```json
[
  "sp.sp.example"
]

```

This cannot be stored in “Define Variable”.

But the same when passed through “Escape API” Endpoint would return a string.

```API
Endpoint: /api/escape
Method: POST
Body: {{$.trigger.accountRequests[?(@.provisioningTarget=='Active Directory')].attributeRequests[?(@.attributeName=='sAMAccountName')].attributeValue}}

```

Response:

```json
{
  "body": {
    "escaped": "[\"test1234\"]",
    "length": 12
  },
  "headers": {
    "Content-Type": [
      "application/json"
    ]
  },
  "responseTime": "0.177894 seconds",
  "statusCode": 200
}

```

The same can be stored in the DefineVariable Operator and processed further for any usecase.

## Escape Data/Code

The endpoint is responsible for escaping any data to be usable in `/api/scripts`.

#### 1. API Endpoint

```http
POST /api/escape
Content-Type: text/plain

```

#### 2. Request Format

```java
import java.net.*;
import java.io.*;

URL url = new URL("https://jsonplaceholder.typicode.com/posts/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
    content.append(inputLine);
}
in.close();
conn.disconnect();
return content.toString();

```

#### 3. Response Format

```json

  {
    "escaped": "<escaped Java Code>",
    "length": <int length>
  }

```

* * *

## Upload Jars

#### 1. API Endpoint

```http
POST /api/upload-jar
Content-Type: multipart/form-data

```

* * *

#### 2. Request Format

Send a JAR file using `multipart/form-data`.

```http
POST /api/upload-jar
Content-Type: multipart/form-data
Body: form-data
      file = /select/file/location.jar

```

**cURL Example:**

```bash
curl -X POST "{{baseUrl}}/api/upload-jar" -H "Content-Type: multipart/form-data" -F "file=@com.google.gson_2.10.1.jar"

```

#### 3. Response Format

Generic structure of API response:

```json
{
  "status": "<uploaded|already_exists|error>",
  "path": "Absolute file path where the JAR was stored",
  "timestamp": "YYYY-MM-DDTHH:MM:SS.sssZ",
  "error": null
}

```

#### 4. Example Usage in BeanShell Scripts

Once a JAR is uploaded, it can be dynamically loaded in BeanShell scripts using:

```java
addClassPath("/app/uploaded-jars/com.google.gson_2.10.1.jar"); //add the path that was returned by Upload JAR API
//add another JAR Location
//add another JAR Location

import com.google.gson.Gson; //Normal, library import statements for the jar that was uploaded
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

gson = new Gson();

// Create array ["foo", "bar"]
JsonArray arr = new JsonArray();
arr.add(new JsonPrimitive("foo"));
arr.add(new JsonPrimitive("bar"));

// Create object { "foo":true, "bar":true, "foo-bar":false }
JsonObject orgs = new JsonObject();
orgs.add("foo", new JsonPrimitive(true));
orgs.add("bar", new JsonPrimitive(true));
orgs.add("foo-bar", new JsonPrimitive(false));

// Wrap into parent object
JsonObject root = new JsonObject();
root.add("array", arr);
root.add("orgs", orgs);

// Return as Map so Spring serializes it as JSON object
return gson.fromJson(root, java.util.Map.class);

```

**Expected Output:**

```json
{
    "timestamp": "2025-09-19T13:15:29.938366600Z",
    "data": {
        "array": [
            "foo",
            "bar"
        ],
        "orgs": {
            "foo": true,
            "bar": true,
            "foo-bar": false
        }
    },
    "status": 200,
    "error": null,
    "language": "beanshell"
}

```

## Calling APIs Inside Java Code

#### 1. API Endpoint

```http
POST /api/scripts
Content-Type: application/json

```

#### 2. Request Format

> **Note:** Must be escaped before sending to `/api/scripts`.

```java
import java.net.*;
import java.io.*;

URL url = new URL("https://jsonplaceholder.typicode.com/posts/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
    content.append(inputLine);
}
in.close();
conn.disconnect();
return content.toString();

```

#### 3. Response Format

```json

  {
    "timestamp": "YYYY-MM-DDTHH:MM:SS.sssZ",
    "data": <Returned data>,
    "status": 200,
    "error": null,
    "language": "beanshell"
  }

```

#### 4. Error Handling

```json

  {
    "language": "beanshell",
    "script": "int x = \"wrong\";"
  }

```

**Expected Output:**

```json
"timestamp": "",
"data": null,
"status": 500,
"error": "Encountered \"=\" at line 1..."

```
