Search code examples
groovyopenidm

Openidm - Access source or target values in connector groovy script


I am currently building my own Openidm connector for provisioning an external system using the Groovy framework connector based on the ScriptedRest2DJ sample.

I am implementing the searchScript.groovy file in order to search resources (users) on the target system and I want to pass the current User UID of the source system in my request.

Here is the SearchScript.groovy source code :

import groovyx.net.http.RESTClient
import org.apache.http.client.HttpClient
import org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConfiguration
import org.forgerock.openicf.misc.scriptedcommon.OperationType
import org.identityconnectors.common.logging.Log
import org.identityconnectors.framework.common.objects.Attribute
import org.identityconnectors.framework.common.objects.AttributeUtil
import org.identityconnectors.framework.common.objects.Name
import org.identityconnectors.framework.common.objects.ObjectClass
import org.identityconnectors.framework.common.objects.OperationOptions
import org.identityconnectors.framework.common.objects.SearchResult
import org.identityconnectors.framework.common.objects.Uid
import org.identityconnectors.framework.common.objects.filter.Filter
import org.identityconnectors.framework.common.objects.AttributesAccessor

import static groovyx.net.http.Method.GET
import static groovyx.net.http.ContentType.JSON;

// imports used for CREST based REST APIs
import org.forgerock.openicf.misc.crest.CRESTFilterVisitor
import org.forgerock.openicf.misc.crest.VisitorParameter

def operation = operation as OperationType
def configuration = configuration as ScriptedRESTConfiguration
def httpClient = connection as HttpClient
def connection = customizedConnection as RESTClient
def filter = filter as Filter
def log = log as Log
def objectClass = objectClass as ObjectClass
def options = options as OperationOptions
def resultHandler = handler

log.info("Entering " + operation + " Script")

switch (objectClass) {
    case ObjectClass.ACCOUNT:
        // Search for a specific user in Alfresco
        // http://docs.alfresco.com/community/references/RESTful-PersonPersonGet.html
        def searchResult = connection.request(GET, JSON) { req ->
            uri.path = 'people'

            headers.Accept = 'application/json'

            response.success = { resp, json ->
                json.people.each() { value ->
                    resultHandler {
                        uid value.userName
                        id value.userName
                        attribute 'email', value?.email
                        attribute 'lastName', value?.lastName
                        attribute 'userName', value?.userName
                        attribute 'firstName', value?.firstName
                        attribute 'enabled', value?.enabled
                        //attribute ('groups', *(value?.groups))
                    }
                }
                json
            }
        }

        return new SearchResult(null, -1) // no remaining results
}

How could you access to the source value in the script ? I have test Uid, Id, Name, ... without success.

Thanks for the help


Solution

  • For your SearchScript to do more than simply return all users, you need to make use of the "filter" object. You have it declared in your script, but you aren't using it - this is the object that has all of the details the connector needs to know what sort of "Search" this script should be doing on the backend (via REST in this case). Filter objects are essentially tree structures, and their expected use is based on the "Visitor Pattern."

    The scriptedrest2dj sample that you are basing your code on shows the use of the CRESTFilterVisitor, which is a special one that is particularly helpful for working with OpenDJ (the target of that sample). For a more general-purpose visitor implementation, I suggest looking at sample3's SearchScript.groovy. Sample3 is an example of working with a SQL backend, but the visitor shown there can be used to generate any type of query string (such as one you might pass to your REST service).

    From sample3's filter code, this returns a regular map of values representing the filter tree:

    def query = filter.accept(MapFilterVisitor.INSTANCE, null)
    

    Here is the structure of this map for simple values (eg. field eq "value") :

    [ 
        "operation": "EQUALS|CONTAINS|STARTSWITH|ENDSWITH",
        "not": "true|false",
        "left": "fieldName",
        "right": "value"
    ]
    

    "left" and "right" here can be thought of as sides of an equation. In the case of : 'Id eq "bob"' left will be "Id" and right will be "bob". "operation" would be "EQUALS".

    And here is the structure for more complex expressions (eg. simpleExpr AND simpleExpr):

    [ 
        "operation": "AND|OR",
        "left": "simpleExpression|complexExpression",
        "right": "simpleExpression|complexExpression"
    ]
    

    As you can see here, you can have arbitrarily-complex boolean expressions represented within this map structure. Depending on how much complexity you care to support in your particular case, you can choose to support a fully-recursive expression builder (such as is shown in sample3) or you can simply support the core cases.

    Here are some examples of the sorts of operation your script should be able to handle:

    GET /openidm/system/scriptedrest/account?_queryId=query-all-ids

    • this is basically what you have now. When doing this operation, the "filter" object will be null and so you don't need to filter anything at all

    GET /openidm/system/scriptedrest/account/bob

    • this is a "read" operation, but it is also implemented via the SearchScript. "Read" is exactly the same thing as a simple filter for a specific __UID__ value. The quick and dirty way to accomplish this would be to convert your filter object into a map (like I described) and then assume the map is only going to contain a filter searching for a specific __UID__, like so:

      def uid = query.get('right')

    Then you can pass this into your REST call (probably by appending it to the uri.path variable).

    Ideally, your search script should be able to handle more complex filters, like so:

    GET /openidm/system/scriptedrest/account?_queryFilter=/email eq "[email protected]"

    • this is a more generic search for records with particular values. Your REST backend will have to support such things, and your filter query builder will have to be able to walk through the tree in order to build up the appropriate query string. Depending on what you need to do with this connector, it may not be necessary.