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:
- An OAuth2 client with grant type
Resource Owner Password Credentials
- A user with
openidm-admin
authorisation role.
Then perform an ROPC authorisation flow to obtain a valid token. This: Identity Cloud Postman collection :: ForgeRock Identity Cloud Docs can help you get started.
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 thesystemActions
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?