Synchronizing 1C accounts using OpenIDM

What is this article about?

In this article we will set up synchronization of 1C and OpenIDM. Consider the case where accounts are created and changed on the OpenIDM side, say by the HR service. Account changes from OpenIDM will be synchronized with 1C. The primary source of user data could be an LDAP directory service, a relational database, or even a CSV file. But for simplicity, we will set up synchronization with OpenIDM accounts.

As part of the article, we will develop a 1C HTTP service for managing accounts, and also configure the OpenIDM adapter to work with this service and set up synchronization.

Setting up 1C

Open 1C Configurator. Add a new HTTP service Users to the 1C configuration. The service will return user accounts to their roles, and will also create, modify and delete accounts.

Set the service's root URL api . Add URL template Users with template /users and template UsersUpdateDelete with template /users/{userId}

Add methods for each template according to the table

Sample

Method

HTTP method

Handler

Users

List of Users

GET

UsersListUsers

Users

CreateUser

POST

UsersCreateUser

UsersUpdateDelete

UpdateUser

PUT

UsersUpdateUpdateUser

UsersUpdateDelete

UpdateUser

DELETE

UsersUpdateDeleteUser

Service module listing:
Функция ПользователиСписокПользователей(Запрос)    
	Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();   
	
	Массив = Новый Массив;
	Для Каждого Пользователь из Пользователи Цикл     
		Массив.Добавить(ПользовательИБВСтруктуру(Пользователь));		
	КонецЦикла;
	
	ЗаписьJSON = Новый ЗаписьJSON;
	ЗаписьJSON.УстановитьСтроку();
	ЗаписатьJSON(ЗаписьJSON, Массив); 
	СтрокаJSON = ЗаписьJSON.Закрыть();
	
	Ответ = ПолучитьHTTPСервисОтвет();
	Ответ.УстановитьТелоИзСтроки(СтрокаJSON);
	Возврат Ответ;
КонецФункции                  

Функция ПользователиСоздатьПользователя(Запрос)       
	ТекстЗапроса = Запрос.ПолучитьТелоКакСтроку();     
	ЧтениеJSON = Новый ЧтениеJSON();
 	ЧтениеJSON.УстановитьСтроку(ТекстЗапроса);
    СтруктураДанных = ПрочитатьJSON(ЧтениеJSON);	
	
	Пользователь = ПользователиИнформационнойБазы.СоздатьПользователя();
	Пользователь = ПользовательИБИзСтруктуры(Пользователь, СтруктураДанных);
	Пользователь.Записать();
	
	ЗаписьJSON = Новый ЗаписьJSON;
	ЗаписьJSON.УстановитьСтроку();  
	Структура = ПользовательИБВСтруктуру(Пользователь);
	ЗаписатьJSON(ЗаписьJSON, Структура); 
	СтрокаJSON = ЗаписьJSON.Закрыть();
	
	Ответ = ПолучитьHTTPСервисОтвет();
	Ответ.УстановитьТелоИзСтроки(СтрокаJSON);

	Возврат Ответ;
КонецФункции

Функция ПользователиОбновитьОбновитьПользователя(Запрос)
	ТекстЗапроса = Запрос.ПолучитьТелоКакСтроку();     
	ЧтениеJSON = Новый ЧтениеJSON();
 	ЧтениеJSON.УстановитьСтроку(ТекстЗапроса);
    СтруктураДанных = ПрочитатьJSON(ЧтениеJSON);
	
	userId = Запрос.ПараметрыURL.Получить("userId");
	
	Идентификатор = Новый УникальныйИдентификатор(userId);
	Пользователь = ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Идентификатор);
	
	Если Пользователь = Неопределено Тогда
		Возврат ПолучитьHTTPСервисОтветНеНайден();		          		
	КонецЕсли;     
	
	Пользователь = ПользовательИБИзСтруктуры(Пользователь, СтруктураДанных);
	Пользователь.Записать();
	
	
	ЗаписьJSON = Новый ЗаписьJSON;
	ЗаписьJSON.УстановитьСтроку();  
	Структура = ПользовательИБВСтруктуру(Пользователь);
	ЗаписатьJSON(ЗаписьJSON, Структура); 
	СтрокаJSON = ЗаписьJSON.Закрыть();
	
	Ответ = ПолучитьHTTPСервисОтвет();
	Ответ.УстановитьТелоИзСтроки(СтрокаJSON);
 
	
	Возврат Ответ;
КонецФункции

Функция ПользователиОбновитьУдалитьПользователя(Запрос)    
	
	userId = Запрос.ПараметрыURL.Получить("userId");
	
	Идентификатор = Новый УникальныйИдентификатор(userId);
	Пользователь = ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Идентификатор);
	
	Если Пользователь = Неопределено Тогда
		Возврат ПолучитьHTTPСервисОтветНеНайден();		          		
	КонецЕсли;         
	
	Пользователь.Удалить();
	
	Ответ = ПолучитьHTTPСервисОтвет();
	Возврат Ответ;
КонецФункции     
  
Функция ПользовательИБВСтруктуру(Пользователь) 
	Структура = Новый Структура("uid, name, fullName, roles");
	Структура.uid = Строка(Пользователь.УникальныйИдентификатор);  
	Структура.name = Пользователь.Имя;
	Структура.fullName = Пользователь.ПолноеИмя;
	Роли = Новый Массив;
	Для Каждого Роль из Пользователь.Роли Цикл
		Роли.Добавить(Роль.Имя);
	КонецЦикла;
	
	Структура.roles = Роли;
	Возврат Структура;
КонецФункции

Функция ПользовательИБИзСтруктуры(Пользователь, СтруктураДанных)   
	
	Пользователь.АутентификацияСтандартная = Истина;
	Пользователь.Имя = СтруктураДанных.name;
	Пользователь.ПолноеИмя = СтруктураДанных.fullName;
	Пользователь.Пароль =  СтруктураДанных.password;
	Пользователь.Роли.Очистить();
	Для Каждого role из СтруктураДанных.roles Цикл
		Роль = Метаданные.Роли.Найти(role);
		Пользователь.Роли.Добавить(Роль);	
	КонецЦикла;
	Возврат Пользователь;
КонецФункции

Функция ПолучитьHTTPСервисОтвет()  
	Ответ = Новый HTTPСервисОтвет(200);   
	Ответ.Заголовки.Вставить("Content-Type","application/json; charset=utf-8");
	Возврат Ответ;
КонецФункции

Функция ПолучитьHTTPСервисОтветНеНайден()  
	Ответ = Новый HTTPСервисОтвет(404);   
	Ответ.Заголовки.Вставить("Content-Type","application/json; charset=utf-8");
	Возврат Ответ;
КонецФункции

Create a service user for data synchronization and add rights to access the HTTP service, as well as add user management. Let there be a login idm the password will be passw0rd.

Also add the “Manager” role to the metadata and assign the necessary rights to it. We will create a similar role in OpenIDM.

Publish the created HTTP service.

A detailed description of creating and deploying HTTP services is beyond the scope of this article. You can read about them in the articles:

https://infostart.ru/1c/articles/1293341/

https://infostart.ru/1c/articles/842751/

Setting up OpenIDM

Download the OpenIDM distribution from GitHub. Unpack the archive and go to the directory openidm . Launch OpenIDM with the command ./openidm/startup.sh

Add an adapter to the REST service. Place the file provisioner.openicf-scriptedrest.json to a folder conf OpenIDM directory. The content will be something like this:

provisioner.openicf-scriptedrest.json
{
    "name" : "scriptedrest",
    "connectorRef" : {
        "connectorHostRef" : "#LOCAL",
        "connectorName" : "org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConnector",
        "bundleName" : "org.openidentityplatform.openicf.connectors.groovy-connector",
        "bundleVersion" : "[1.4.0.0,2)"
    },
    "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:8090>",
        "proxyAddress" : null,
        "username" : "idm",
        "password" : {
            "$crypto" : {
                "type" : "x-simple-encryption",
                "value" : {
                    "cipher" : "AES/CBC/PKCS5Padding",
                    "data" : "sLvu2xdRz0eyhjucysxmcA==",
                    "iv" : "+evCKgF0BCaNA4NfA/VY7Q==",
                    "key" : "openidm-sym-default"
                }
            }
        },
        "defaultAuthMethod" : "BASIC_PREEMPTIVE",
        "defaultRequestHeaders" : [
            null
        ],
        "defaultContentType" : "application/json",
        "scriptExtensions" : [
            "groovy"
        ],
        "sourceEncoding" : "UTF-8",
        "customizerScriptFileName" : "CustomizerScript.groovy",
        "createScriptFileName" : "CreateScript.groovy",
        "deleteScriptFileName" : "DeleteScript.groovy",
        "schemaScriptFileName" : "SchemaScript.groovy",
        "searchScriptFileName" : "SearchScript.groovy",
        "updateScriptFileName" : "UpdateScript.groovy",
        "recompileGroovySource" : false,
        "minimumRecompilationInterval" : 100,
        "debug" : false,
        "verbose" : false,
        "warningLevel" : 1,
        "tolerance" : 10,
        "disabledGlobalASTTransformations" : null,
        "targetDirectory" : null,
        "scriptRoots" : [
            "&{launcher.project.location}/tools"
        ]
    },
    "objectTypes" : {
        "account" : {
            "$schema" : "<http://json-schema.org/draft-03/schema>",
            "id" : "__ACCOUNT__",
            "type" : "object",
            "nativeType" : "__ACCOUNT__",
            "properties" : {
                "uid" : {
                    "type" : "string",
                    "nativeName" : "__NAME__",
                    "nativeType" : "string",
                    "flags" : [
                        "NOT_UPDATEABLE",
                        "NOT_CREATEABLE"
                    ]
                },
                "name" : {
                    "type" : "string",
                    "nativeName" : "name",
                    "nativeType" : "string",
                    "required" : true
                },
                "fullName" : {
                    "type" : "string",
                    "nativeName" : "fullName",
                    "nativeType" : "string"
                },
                "roles" : {
                    "type" : "array",
                    "items" : {
                        "type" : "string",
                        "nativeType" : "string"
                    },
                    "nativeName" : "roles",
                    "nativeType" : "string"
                },
                "password" : {
                    "type" : "string",
                    "nativeName" : "password",
                    "nativeType" : "JAVA_TYPE_GUARDEDSTRING",
                    "flags" : [
                        "NOT_READABLE",
                        "NOT_RETURNED_BY_DEFAULT"
                    ]
                }
            }
        }
    }
}

Basic JSON file structure configuration attributes:

  • configurationProperties – parameters for accessing a REST service, in this case, HTTP (REST) ​​1C service.

    • Connection URL

    • Username

    • Password

    • Timeouts operation

  • objectTypes – data structures that will be synchronized. In this case, accounts will be synchronized (ACCOUNT)

  • operationTimeout – timeouts for operations

  • poolConfigOption – connection pool parameters for the REST service

Now we will add scripts with which we will receive and update data from the REST service.

HTTP client configuration

First, let's configure an HTTP client that will access the 1C REST service. Configuration is also carried out using a script in the Groovy language.

Place the script in the tools folder of the OpenIDM directory CustomizerScript.groovy

CustomizerScript.groovy
import groovyx.net.http.RESTClient
import groovyx.net.http.StringHashMap
import org.apache.http.HttpHost
import org.apache.http.auth.AuthScope
import org.apache.http.auth.UsernamePasswordCredentials
import org.apache.http.client.ClientProtocolException
import org.apache.http.client.CredentialsProvider
import org.apache.http.client.HttpClient
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.protocol.HttpClientContext
import org.apache.http.conn.routing.HttpRoute
import org.apache.http.impl.auth.BasicScheme
import org.apache.http.impl.client.BasicAuthCache
import org.apache.http.impl.client.BasicCookieStore
import org.apache.http.impl.client.BasicCredentialsProvider
import org.apache.http.impl.client.HttpClientBuilder
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager
import org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConfiguration
import org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConfiguration.AuthMethod
import org.identityconnectors.common.security.GuardedString

// must import groovyx.net.http.HTTPBuilder.RequestConfigDelegate
import groovyx.net.http.HTTPBuilder.RequestConfigDelegate

/**
 * A customizer script defines the custom closures to interact with the default implementation and customize it.
 */
customize {
    init { HttpClientBuilder builder ->

        //SETUP: org.apache.http
        def c = delegate as ScriptedRESTConfiguration

        def httpHost = new HttpHost(c.serviceAddress?.host, c.serviceAddress?.port, c.serviceAddress?.scheme);

        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // Increase max total connection to 200
        cm.setMaxTotal(200);
        // Increase default max connection per route to 20
        cm.setDefaultMaxPerRoute(20);
        // Increase max connections for httpHost to 50
        cm.setMaxPerRoute(new HttpRoute(httpHost), 50);

        builder.setConnectionManager(cm)

        // configure timeout on the entire client
        RequestConfig requestConfig = RequestConfig.custom().build();
        builder.setDefaultRequestConfig(requestConfig)



        if (c.proxyAddress != null) {
            builder.setProxy(new HttpHost(c.proxyAddress?.host, c.proxyAddress?.port, c.proxyAddress?.scheme));
        }



        switch (ScriptedRESTConfiguration.AuthMethod.valueOf(c.defaultAuthMethod)) {
            case ScriptedRESTConfiguration.AuthMethod.BASIC_PREEMPTIVE:
            case ScriptedRESTConfiguration.AuthMethod.BASIC:
                // It's part of the http client spec to request the resource anonymously
                // first and respond to the 401 with the Authorization header.
                final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

                c.password.access(
                        {
                            credentialsProvider.setCredentials(new AuthScope(httpHost.getHostName(), httpHost.getPort()),
                                    new UsernamePasswordCredentials(c.username, new String(it)));
                        } as GuardedString.Accessor
                );

                builder.setDefaultCredentialsProvider(credentialsProvider);
                break;
            case ScriptedRESTConfiguration.AuthMethod.NONE:
                break;
            default:
                throw new IllegalArgumentException();
        }

        c.propertyBag.put(HttpClientContext.COOKIE_STORE, new BasicCookieStore());
    }

    /**
     * This Closure can customize the httpClient and the returning object is injected into the Script Binding.
     */
    decorate { HttpClient httpClient ->

        //SETUP: groovyx.net.http

        def c = delegate as ScriptedRESTConfiguration

        def authCache = null
        if (AuthMethod.valueOf(c.defaultAuthMethod).equals(AuthMethod.BASIC_PREEMPTIVE)){
            authCache = new BasicAuthCache();
            authCache.put(new HttpHost(c.serviceAddress?.host, c.serviceAddress?.port, c.serviceAddress?.scheme), new BasicScheme());

        }

        def cookieStore = c.propertyBag.get(HttpClientContext.COOKIE_STORE)

        RESTClient restClient = new InnerRESTClient(c.serviceAddress, c.defaultContentType, authCache, cookieStore)

        Map<Object, Object> defaultRequestHeaders = new StringHashMap<Object>();
        if (null != c.defaultRequestHeaders) {
            c.defaultRequestHeaders.each {
                if (null != it) {                
                    def kv = it.split('=')
                    assert kv.size() == 2
                    defaultRequestHeaders.put(kv[0], kv[1])
                }
            }
        }

        restClient.setClient(httpClient);
        restClient.setHeaders(defaultRequestHeaders)

        // Return with the decorated instance
        return restClient
    }

}

RequestConfigDelegate blank = null;

class InnerRESTClient extends RESTClient {

    def authCache = null;
    def cookieStore = null;

    InnerRESTClient(Object defaultURI, Object defaultContentType, authCache, cookieStore) throws URISyntaxException {
        super(defaultURI, defaultContentType)
        this.authCache = authCache
        this.cookieStore = cookieStore
    }

    @Override
    protected Object doRequest(
            final RequestConfigDelegate delegate) throws ClientProtocolException, IOException {

        // Add AuthCache to the execution context
        if (null != authCache) {
            //do Preemptive Auth
            delegate.getContext().setAttribute(HttpClientContext.AUTH_CACHE, authCache);
        }
        // Add AuthCache to the execution context
        if (null != cookieStore) {
            //do Preemptive Auth
            delegate.getContext().setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);
        }
        return super.doRequest(delegate)
    }
}

Data Search

Place the script in the tools folder of the OpenIDM directory SearchScript.groovy with the following content.

SearchScript.groovy
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.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 static groovyx.net.http.Method.GET

// 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")

def queryFilter="true"
if (filter != null) {
    queryFilter = filter.accept(CRESTFilterVisitor.VISITOR, [
            translateName: { String name ->
                if (AttributeUtil.namesEqual(name, Uid.NAME)) {
                    return "uid"
                } else if (AttributeUtil.namesEqual(name, "name")) {
                    return "name"
                } else if (AttributeUtil.namesEqual(name, "fullName")) {
                    return "fullName"
                } else {
                    throw new IllegalArgumentException("Unknown field name: ${name}");
                }
            },
            convertValue : { Attribute value ->
                if (AttributeUtil.namesEqual(value.name, "members")) {
                    return value.value
                } else {
                    return AttributeUtil.getStringValue(value)
                }
            }] as VisitorParameter).toString();
}
switch (objectClass) {
    case ObjectClass.ACCOUNT:
        def searchResult = connection.request(GET) { req ->
            uri.path="/api/users"
            uri.query = [
                    _queryFilter: queryFilter
            ]

            response.success = { resp, json ->
                json.each() { value ->
                    resultHandler {
                        uid value.uid
                        id value.uid
                        attribute 'name', value?.name
                        attribute 'fullName', value?.fullName
                        attribute 'roles', value?.roles
                    }
                }
                json
            }
        }

        return new SearchResult()

    case ObjectClass.GROUP:
        throw new IllegalArgumentException("Group sync is not supported");
}

The script converts sends a GET request to the 1C HTTP service /api/users with parameter _queryFilter. Receives data, converts each record into a structure, which is then read by the OpenIDM connector.

Creating Accounts

Place the script in the tools folder of the OpenIDM directory CreateScript.groovy with the following content.

CreateScript.groovy
import groovy.json.JsonBuilder
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.common.security.SecurityUtil
import org.identityconnectors.framework.common.objects.Attribute
import org.identityconnectors.framework.common.objects.AttributesAccessor
import org.identityconnectors.framework.common.objects.ObjectClass
import org.identityconnectors.framework.common.objects.OperationOptions
import org.identityconnectors.framework.common.objects.Uid

import static groovyx.net.http.ContentType.JSON
import static groovyx.net.http.Method.PUT

def operation = operation as OperationType
def updateAttributes = new AttributesAccessor(attributes as Set<Attribute>)
def configuration = configuration as ScriptedRESTConfiguration
def httpClient = connection as HttpClient
def connection = customizedConnection as RESTClient
def name = id as String
def log = log as Log
def objectClass = objectClass as ObjectClass
def options = options as OperationOptions
def uid = uid as Uid

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

switch (objectClass) {
    case ObjectClass.ACCOUNT:
        def builder = new JsonBuilder()
        builder {}

        builder.content["name"] =  updateAttributes.findString("name")
        builder.content["fullName"] =  updateAttributes.findString("fullName")
        builder.content["roles"] =  updateAttributes.findList("roles")

        if (updateAttributes.hasAttribute("password") && updateAttributes.findGuardedString("password") != null) {
            def pass = SecurityUtil.decrypt(updateAttributes.findGuardedString("password"))
            builder.content["password"] = pass
        }

        return connection.request(PUT, JSON) { req ->
            uri.path = "/api/users/${uid.uidValue}"
            body = builder.toString()
            headers.'If-None-Match' = "*"

            response.success = { resp, json ->
                new Uid(json.uid)
            }
        }

    case ObjectClass.GROUP:
        throw new IllegalArgumentException("Group sync is not supported");
}
return uid

The script sends a POST request with a JSON structure of account properties. The 1C HTTP service creates an account and returns the user ID.

Updating accounts

Place the script in the tools folder of the OpenIDM directory UpdateScript.groovy

UpdateScript.groovy
import groovy.json.JsonBuilder
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.common.security.SecurityUtil
import org.identityconnectors.framework.common.objects.Attribute
import org.identityconnectors.framework.common.objects.AttributesAccessor
import org.identityconnectors.framework.common.objects.ObjectClass
import org.identityconnectors.framework.common.objects.OperationOptions
import org.identityconnectors.framework.common.objects.Uid

import static groovyx.net.http.ContentType.JSON
import static groovyx.net.http.Method.PUT

def operation = operation as OperationType
def updateAttributes = new AttributesAccessor(attributes as Set<Attribute>)
def configuration = configuration as ScriptedRESTConfiguration
def httpClient = connection as HttpClient
def connection = customizedConnection as RESTClient
def name = id as String
def log = log as Log
def objectClass = objectClass as ObjectClass
def options = options as OperationOptions
def uid = uid as Uid

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

switch (objectClass) {
    case ObjectClass.ACCOUNT:
        def builder = new JsonBuilder()
        builder {}

        builder.content["name"] =  updateAttributes.findString("name")
        builder.content["fullName"] =  updateAttributes.findString("fullName")
        builder.content["roles"] =  updateAttributes.findList("roles")

        if (updateAttributes.hasAttribute("password") && updateAttributes.findGuardedString("password") != null) {
            def pass = SecurityUtil.decrypt(updateAttributes.findGuardedString("password"))
            builder.content["password"] = pass
        }

        return connection.request(PUT, JSON) { req ->
            uri.path = "/api/users/${uid.uidValue}"
            body = builder.toString()
            headers.'If-None-Match' = "*"

            response.success = { resp, json ->
                new Uid(json.uid)
            }
        }

    case ObjectClass.GROUP:
        throw new IllegalArgumentException("Group sync is not supported");
}
return uid

The script is almost identical CreateScript.groovy except that it sends a PUT request to the endpoint specifying the user account ID.

Deleting an account

Removal script DeleteScript.groovy much shorter than other scripts

DeleteScript.groovy
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.ObjectClass
import org.identityconnectors.framework.common.objects.OperationOptions
import org.identityconnectors.framework.common.objects.Uid

def operation = operation as OperationType
def configuration = configuration as ScriptedRESTConfiguration
def httpClient = connection as HttpClient
def connection = customizedConnection as RESTClient
def log = log as Log
def objectClass = objectClass as ObjectClass
def options = options as OperationOptions
def uid = uid as Uid

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

switch (objectClass) {
    case ObjectClass.ACCOUNT:
        connection.delete(path: '/api/users/' + uid.uidValue);
        break
    case ObjectClass.GROUP:
        throw new IllegalArgumentException("Group sync is not supported");
}

And, as can be seen from the code, it sends a DELETE request to the REST endpoint indicating the account ID.

Setting up synchronization

Add to file sync.json to array mappings synchronization object managedUser_systemScriptedrestAccount:

sync.json
{
    "mappings" : [
        {
            "target" : "system/scriptedrest/account",
            "source" : "managed/user",
            "name" : "managedUser_systemScriptedrestAccount",
            "properties" : [
                {
                    "target" : "name",
                    "source" : "userName"
                },
                {
                    "target" : "fullName",
                    "transform" : {
                        "type" : "text/javascript",
                        "globals" : { },
                        "source" : "source.givenName + ' ' + source.sn"
                    },
                    "source" : ""
                },
                {
                    "target" : "roles",
                    "transform" : {
                        "type" : "text/javascript",
                        "globals" : { },
                        "source" : "var i=0;\nvar res = []\nfor (i = 0; i < source.effectiveRoles.length; i++) {\n  var roleId = source.effectiveRoles[i][\"_ref\"];\n  if (roleId != null && roleId.indexOf(\"/\") != -1) {\n    var roleInfo = openidm.read(roleId);\n    logger.warn(\"Role Info : {}\",roleInfo);\t\n    res.push(roleInfo.name);\n  }\n  \n}\nres"
                    },
                    "source" : ""
                },
                {
                    "target" : "password",
                    "source" : "password"
                }
            ],
            "policies" : [
                {
                    "action" : "EXCEPTION",
                    "situation" : "AMBIGUOUS"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "SOURCE_MISSING"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "MISSING"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "FOUND_ALREADY_LINKED"
                },
                {
                    "action" : "DELETE",
                    "situation" : "UNQUALIFIED"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "UNASSIGNED"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "LINK_ONLY"
                },
                {
                    "action" : "IGNORE",
                    "situation" : "TARGET_IGNORED"
                },
                {
                    "action" : "IGNORE",
                    "situation" : "SOURCE_IGNORED"
                },
                {
                    "action" : "IGNORE",
                    "situation" : "ALL_GONE"
                },
                {
                    "action" : "UPDATE",
                    "situation" : "CONFIRMED"
                },
                {
                    "action" : "UPDATE",
                    "situation" : "FOUND"
                },
                {
                    "action" : "CREATE",
                    "situation" : "ABSENT"
                }
            ]
        }
    ]
}

Checking the adapter

Login to the OpenIDM admin console by url http://localhost:8080/admin with login openidm-admin and password openidm-admin. In the console, go to Manage → User. In the list that opens, create a new user and fill in the attributes Username, First Name, Last Name

On the bookmark Password set the default password for the user and click the button Save .

Open the user’s card again, and on the Provisioning Roles tab, add the “Manager” role to him. If the role does not already exist, add it by clicking on the “Create New Role” link

Click the button Add

Now let's synchronize the data.

Go to the Configure → Mappings section and for managedUser_systemScriptedrestAccount click the button Reconcile .

A new user must be created on the 1C side.

Check for its presence by calling the OpenIDM API:

curl -X GET --location "<http://localhost:8080/openidm/system/scriptedrest/account?_queryFilter=true>" \\ 
    -H "X-OpenIDM-Username: openidm-admin" \\
    -H "X-OpenIDM-Password: openidm-admin"
    
{
   "result":[
   ...
      {
         "_id":"3ea8017a-0b26-4311-abef-cb147f748059",
         "roles":[
            "Менеджер"
         ],
         "name":"петр",
         "uid":"3ea8017a-0b26-4311-abef-cb147f748059",
         "fullName":"Петр Петров"
      },
   ...
   ],
   "resultCount":3,
   "pagedResultsCookie":null,
   "totalPagedResultsPolicy":"NONE",
   "totalPagedResults":-1,
   "remainingPagedResults":-1
}

Open the 1C Configurator application and check that a new user has been created.

Change the user data (for example, the Last Name attribute) in OpenIDM and restart the synchronization process. You will see that the account with this ID has been changed in 1C.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *