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 operationspoolConfigOption
– 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.