Using the Remote Connector Server to access on premises APIs from Identity Cloud

By Patrick Diligent and Konstantin Lapine

Introduction

The Remote Connector Server (RCS) is an essential component to synchronise resources between on-premises resources and Identity Cloud. In order to allow synchronisation through the organisation firewall, RCS is configured in client mode, that is, it establishes a web socket connection to Identity Cloud using an OAuth2 authorisation flow.

We are going to use that critical feature - reaching through the firewall - to access internal APIs from Identity Cloud. In this recipe, we are going to use these two main ingredients:

  • The Groovy Scripted REST connector.
  • The Script On Resource operation.

It is assumed that you have already installed the RCS on your local computer. You can refer to the documentation to install the RCS on premises at Sync identities :: ForgeRock Identity Cloud Docs.

The mock server

I have been using Create your first mock API with Mockoon for this demonstration, but feel free to use whatever you have at hand.

Here is a screen shots of the route configuration:

This defines the route POST /internal with returns the request body in the response.

Configuring the connector

Under the RCS install directory path/to/openicf/connectors install the Scripted Rest Connector jar (you can download it from Downloads - BackStage).

Create a tools/api folder.

In an IDM 7.2 package, locate the samples/scripted-rest-with-dj/tools folder, copy CustomizerScript.groovy to /path/to/openicf/tools/api/.

Create an empty script /path/to/openicf/tools/api/SchemaScript.groovy, the IDM provisioning service needs it, however, in our case there is no schema to define.

Finally create a ScriptOnResource.groovy file with the following content:

import org.slf4j.LoggerFactory
import groovyx.net.http.RESTClient
import groovy.json.JsonBuilder
import static groovyx.net.http.ContentType.JSON
import static groovyx.net.http.Method.POST
import org.apache.http.client.HttpClient

def connection = customizedConnection as RESTClient

content = scriptArguments
builder = new JsonBuilder(content)

return connection.request(POST, JSON) { req ->
    uri.path = "/internal"
    body = builder.toString() 

    response.success = { resp, json ->
        return json
    }
}

Next step is to provide the provisioner configuration. We are going to use IDM REST to create it. In order to invoke IDM’s config endpoint in the Identity Cloud, first an access token should be obtained. For this configure the following:

Let’s push the configuration to Identity Cloud:

curl  --request PUT 'https://<tenant-host>/openidm/config/provisioner.openicf/internal' \
--header 'Content-Type: application/json' \
--header 'Accept-API-Version: resource=1.0' \
--header 'Authorization: Bearer sXuu......Ydor5o' \
--data-raw '{
    "connectorRef": {
        "connectorHostRef": "onprem",
        "displayName": "Scripted REST Connector",
        "bundleVersion": "1.5.20.7",
        "systemType": "provisioner.openicf",
        "bundleName": "org.forgerock.openicf.connectors.scriptedrest-connector",
        "connectorName": "org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConnector"
    },
    "poolConfigOption" : {
        "maxObjects" : 10,
        "maxIdle" : 10,
        "maxWait" : 150000,
        "minEvictableIdleTimeMillis" : 120000,
        "minIdle" : 1
    },
    "operationTimeout" : {
        "CREATE" : -1,
        "UPDATE" : -1,
        "DELETE" : -1,
        "TEST" : -1,
        "SCRIPT_ON_CONNECTOR" : -1,
        "SCRIPT_ON_RESOURCE" : -1,
        "GET" : -1,
        "RESOLVEUSERNAME" : -1,
        "AUTHENTICATE" : -1,
        "SEARCH" : -1,
        "VALIDATE" : -1,
        "SYNC" : -1,
        "SCHEMA" : -1
    },
    "resultsHandlerConfig" : {
        "enableNormalizingResultsHandler" : true,
        "enableFilteredResultsHandler" : true,
        "enableCaseInsensitiveFilter" : false,
        "enableAttributesToGetSearchResultsHandler" : true
    },
    "configurationProperties" : {
        "serviceAddress" : "http://localhost:3001",
        "defaultAuthMethod" : "NONE",
        "defaultRequestHeaders" : [
            null
        ],
        "defaultContentType" : "application/json",
        "scriptExtensions" : [
            "groovy"
        ],
        "sourceEncoding" : "UTF-8",
        "customizerScriptFileName" : "CustomizerScript.groovy",
        "scriptOnResourceScriptFileName" : "ScriptOnResource.groovy",
        "schemaScriptFileName" : "SchemaScript.groovy",
        "scriptBaseClass" : null,
        "recompileGroovySource" : false,
        "minimumRecompilationInterval" : 100,
        "debug" : false,
        "verbose" : false,
        "warningLevel" : 1,
        "tolerance" : 10,
        "disabledGlobalASTTransformations" : null,
        "targetDirectory" : null,
        "scriptRoots": ["tools/api"]
    },
    "systemActions" : [
        {
            "scriptId" : "API",
            "actions" : [
                {
                    "systemType" : ".*ScriptedRESTConnector",
                    "actionType" : "groovy",
                    "actionSource" : "someCustomValue = '\''random'\''"
                }
            ]
        }
    ]
}'

Invoke the internal API thru IDM’s system endpoint

curl  --request POST 'https://<tenant-url>/openidm/system/internal?_action=script&scriptExecuteMode=resource&scriptId=API' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer h4qY...O1k2qc' \
--data-raw '
{
    "action" : "send-sms",
    "phone": "0456789345",
    "code" : "123456",
    "ttl" : "2min"
}
'
{
    "actions": [
        {
            "result": {
                "code": "123456",
                "phone": "0456789345",
                "action": "send-sms",
                "ttl": "2min"
            }
        }
    ]
}

Since the mock api server is configured to return the request body in the response, we are getting it back here in the “actions” array.

How does this work?

The configuration main pieces are :

  • The systemActions block
  • The ScriptOnResourceScriptFileName property

A script can be invoked in two ways:

  • Using a script on connector operation
  • Using a script on resource operation

The script on resource operation just requires the systemActions block, which contains an array of actions, each of them with an id scriptId, the type of connector it applies to (.*ScriptedRESTConnector) and a path to the script (actionFile) or inline text (actionSource). actionFile here refers to a file local to IDM s(in Identity Cloud), however, we can’t do this here, unless providing it inline as actionSource. Since this script can be quite elaborated, it is rather preferable to store the script at the RCS side, that’s why here using a script on resource operation is preferable. We define the script to run (stored at the RCS server) with the scriptOnResourceScriptFileName configuration property.actionSource then specifies code text that is transmitted to the RCS server and passed to the script scope as scriptText - however it is up to the script to act on it.

In this demonstration, we do not evaluate the scriptText, but if you ever need to, this is how this is done:

new GroovyShell(binding).evaluate(scriptText)

If that statement is the last in the script, the result of the evaluation becomes the response to the operation.

Finally, an important piece is the scriptArguments object available in the script scope. This variable contains the body of the POST request sent to the IDM system endpoint.

In order to invoke a script on resource operation, we send a POST request to /system/internal?_action=script&scriptExecuteMode=resource&scriptId=API :

  • scriptExecuteMode=resource instruct to invoke the script on resource operation (default is on connector)
  • scriptId refers to the action defined in the systemActions array. You can use this to partition your API, or adding further control to the result

Conclusion

With this architecture in place, it is now possible to invoke an internal API in the premises network - things like leveraging organisation services such as Mobile services. Just write a Scripted Decision node that invokes the IDM system endpoint to integrate the internal API with journeys with a high level of security. Neat, right?

5 Likes