Although the SCIM (System for Cross-domain Identity Management) standard has been around since 2011 it has seen little adoption among software vendors. Driving forces behind it can be considered Google, Salesforce.com, Sailpoint and Ping Identity and the idea behind the standard is to simplify exchange of user identity information between IT systems using identities - Identity Silos if you will.
The SCIM standard can be viewed as a successor to SPML (Service Provisioning Markup Language) developed by OASIS with the similar goal but based on an XML framework. With the emerging RESTful world to counter the heavy-weight and cumbersome XML based standards - SCIM was born. SCIM offers a light-weight schema that is JSON and REST-based with a concrete data model but as for any standard - it doesn't become a standard unless it is widely adopted, and here is where SCIM currently is struggling.
The current release of the SCIM standard is SCIM 2.0 and released as IETF RFC 7643 and IETF RFC 7644 with a complementing use-case document as IETF RFC 7642, but most implementations are still SCIM 1.1.
So what is it that SCIM typically can do? Mainly two things; CRUD operation on users and groups. For SCIM to work, the application needs to provide a SCIM compliant service and the integration needs to offer a SCIM compliant connector.
As of "ForgeRock Identity Management 5.5" (Referred to as IDM in this article), a SCIM 1.1 compliant connector will now be part of the product allowing IDM to interact with SCIM complaint services such as Google and WSO2 Identity Server to create, update, delete users and groups. However, since the connector is based on the OpenICF 1.4 framework the connector is backward compatible with previous versions of ForgeRock Identity Management implementing the OpenICF 1.4 framework.
This article will offer a step by step guide how to set up a SCIM compliant test environment based on WSO2 Identity Server, how to configure the SCIM connector in IDM making it ready to provision a set of users to WSO2 Identity Server.
Installation of WSO2 Identity Server is easy and the archive downloaded from WSO2s website with the binary bits is self-contained with everything required to install and run.
Make sure the $JAVA_HOME variable is set appropriately prior to invoking the wso2server.sh script. Possibly some manipulations are required in the startup script to reflect your particular environment.
The following steps are done to install WSO2 Identity Server:
2. Run the wso2server.sh on *NIX or wso2server.bat file on Windows, in the /bin directory
WSO2 Identity Server integrates WSO2s Charon, which is a fully Open Source implementation of the SCIM protocol and provides the necessary serverside components to facilitate a SCIM server exposing SCIM endpoints which expose Users and Groups resources in a RESTful way.
The next step once WSO2 Identity Server is up and running is to create a new service provider followed by configuring SCIM and OAuth.
For the inbound provisioning, define PRIMARY as the user/group store for SCIM and be ready to configure the OAuth part.
Configure OAuth and make sure to save the client secret and key (SCIM connector uses client credential grant type).
Once the service provider is configured accordingly, the SCIM service is ready to be tested with IDM and the SCIM connector.
Unless running ForgeRock Identity Management 5.5 the actual connector needs to be deployed in the IDM install directory. Copy the connector jar file to $OPENIDM/connectors. The connector file is available via Backstage for customers with an active subscription and is not posted here for licensing purposes.
The connector also requires a configuration file that needs to be deployed made available to IDM. Copy the provisioner.openicf-scim.json to $OPENIDM/conf.
Provisioner.openicf-scim.json
{
"name" : "scim"
"enabled" : true,
"connectorRef" : {
"bundleName" : "org.forgerock.openicf.connectors.scim-connector",
"connectorName" : "org.forgerock.openicf.connectors.scim.ScimConnector",
"connectorHostRef" : "#LOCAL",
"bundleVersion" : "1.4.0.0-SNAPSHOT"
},
"poolConfigOption" : {
"maxObjects" : 10,
"maxIdle" : 10,
"maxWait" : 150000,
"minEvictableIdleTimeMillis" : 120000,
"minIdle" : 1
},
"resultsHandlerConfig" : {
"enableNormalizingResultsHandler" : true,
"enableFilteredResultsHandler" : false,
"enableCaseInsensitiveFilter" : false,
"enableAttributesToGetSearchResultsHandler" : false
},
"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
},
"configurationProperties" : {
"SCIMVersion" : 1,
"authenticationMethod" : "BASIC",
"user" : "admin",
"password" : "admin",
"clientId" : "FsvGkUww7vHYYgRh_Hd071iQQroa",
"clientSecret" : null,
"acceptSelfSignedCertificates" : true,
"disableHostNameVerifier" : true,
"maximumConnections" : 10,
"httpProxyHost" : null,
"httpProxyPort" : null
},
"objectTypes" : {
"group" : {
"id" : "__GROUP__",
"type" : "object",
"nativeType" : "__GROUP__",
"properties" : {
"displayName" : {
"type" : "string",
"required" : true,
"nativeName" : "displayName",
"nativeType" : "string"
},
"meta" : {
"type" : "object",
"nativeName" : "meta",
"nativeType" : "object",
"flags" : [
"NOT_CREATABLE",
"NOT_UPDATEABLE"
]
},
"members" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "members",
"nativeType" : "object"
},
"__NAME__" : {
"type" : "string",
"required" : true,
"nativeName" : "__NAME__",
"nativeType" : "string"
},
"externalId" : {
"type" : "string",
"nativeName" : "externalId",
"nativeType" : "string"
}
}
},
"user" : {
"id" : "__ACCOUNT__",
"type" : "object",
"nativeType" : "__ACCOUNT__",
"properties" : {
"phoneNumbers" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "phoneNumbers",
"nativeType" : "object"
},
"emails" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "emails",
"nativeType" : "object"
},
"__NAME__" : {
"type" : "string",
"required" : true,
"nativeName" : "__NAME__",
"nativeType" : "string"
},
"active" : {
"type" : "boolean",
"nativeName" : "active",
"nativeType" : "boolean"
},
"groups" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "groups",
"nativeType" : "object",
"flags" : [
"NOT_CREATABLE",
"NOT_UPDATEABLE"
]
},
"costCenter" : {
"type" : "string",
"nativeName" : "costCenter",
"nativeType" : "string"
},
"division" : {
"type" : "string",
"nativeName" : "division",
"nativeType" : "string"
},
"employeeNumber" : {
"type" : "string",
"nativeName" : "employeeNumber",
"nativeType" : "string"
},
"organization" : {
"type" : "string",
"nativeName" : "organization",
"nativeType" : "string"
},
"profileUrl" : {
"type" : "string",
"nativeName" : "profileUrl",
"nativeType" : "string"
},
"userType" : {
"type" : "string",
"nativeName" : "userType",
"nativeType" : "string"
},
"x509Certificates" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "x509Certificates",
"nativeType" : "object"
},
"manager" : {
"type" : "object",
"nativeName" : "manager",
"nativeType" : "object"
},
"userName" : {
"type" : "string",
"required" : true,
"nativeName" : "userName",
"nativeType" : "string"
},
"timezone" : {
"type" : "string",
"nativeName" : "timezone",
"nativeType" : "string"
},
"photos" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "photos",
"nativeType" : "object"
},
"displayName" : {
"type" : "string",
"nativeName" : "displayName",
"nativeType" : "string"
},
"password" : {
"type" : "string",
"nativeName" : "__PASSWORD__",
"nativeType" : "JAVA_TYPE_GUARDEDSTRING",
"flags" : [
"NOT_READABLE",
"NOT_RETURNED_BY_DEFAULT"
]
},
"meta" : {
"type" : "object",
"nativeName" : "meta",
"nativeType" : "object",
"flags" : [
"NOT_CREATABLE",
"NOT_UPDATEABLE"
]
},
"entitlements" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "entitlements",
"nativeType" : "object"
},
"department" : {
"type" : "string",
"nativeName" : "department",
"nativeType" : "string"
},
"ims" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "ims",
"nativeType" : "object"
},
"name" : {
"type" : "object",
"nativeName" : "name",
"nativeType" : "object"
},
"nickName" : {
"type" : "string",
"nativeName" : "nickName",
"nativeType" : "string"
},
"locale" : {
"type" : "string",
"nativeName" : "locale",
"nativeType" : "string"
},
"roles" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "roles",
"nativeType" : "object"
},
"preferredLanguage" : {
"type" : "string",
"nativeName" : "preferredLanguage",
"nativeType" : "string"
},
"addresses" : {
"type" : "array",
"items" : {
"type" : "object",
"nativeType" : "object"
},
"nativeName" : "addresses",
"nativeType" : "object"
},
"title" : {
"type" : "string",
"nativeName" : "title",
"nativeType" : "string"
},
"externalId" : {
"type" : "string",
"nativeName" : "externalId",
"nativeType" : "string"
}
}
}
},
"operationOptions" : {
"CREATE" : {
"objectFeatures" : {
"__GROUP__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : { }
}
},
"__ACCOUNT__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : { }
}
}
}
},
"UPDATE" : {
"objectFeatures" : {
"__GROUP__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : { }
}
},
"__ACCOUNT__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : { }
}
}
}
},
"DELETE" : {
"objectFeatures" : {
"__GROUP__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : { }
}
},
"__ACCOUNT__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : { }
}
}
}
},
"TEST" : {
"objectFeatures" : { }
},
"SCRIPT_ON_CONNECTOR" : {
"objectFeatures" : { }
},
"SCRIPT_ON_RESOURCE" : {
"objectFeatures" : { }
},
"GET" : {
"objectFeatures" : {
"__GROUP__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : {
"SORT_KEYS" : {
"type" : "string",
"nativeType" : null
},
"PAGED_RESULTS_OFFSET" : {
"type" : "integer",
"nativeType" : "integer"
},
"ATTRS_TO_GET" : {
"type" : "array",
"items" : {
"type" : "string",
"nativeType" : "string"
},
"nativeType" : "string"
},
"PAGE_SIZE" : {
"type" : "integer",
"nativeType" : "integer"
},
"PAGED_RESULTS_COOKIE" : {
"type" : "string",
"nativeType" : "string"
}
}
}
},
"__ACCOUNT__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : {
"SORT_KEYS" : {
"type" : "string",
"nativeType" : null
},
"PAGED_RESULTS_OFFSET" : {
"type" : "integer",
"nativeType" : "integer"
},
"ATTRS_TO_GET" : {
"type" : "array",
"items" : {
"type" : "string",
"nativeType" : "string"
},
"nativeType" : "string"
},
"PAGE_SIZE" : {
"type" : "integer",
"nativeType" : "integer"
},
"PAGED_RESULTS_COOKIE" : {
"type" : "string",
"nativeType" : "string"
}
}
}
}
}
},
"RESOLVEUSERNAME" : {
"objectFeatures" : { }
},
"AUTHENTICATE" : {
"objectFeatures" : { }
},
"SEARCH" : {
"objectFeatures" : {
"__GROUP__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : {
"SORT_KEYS" : {
"type" : "string",
"nativeType" : null
},
"PAGED_RESULTS_OFFSET" : {
"type" : "integer",
"nativeType" : "integer"
},
"ATTRS_TO_GET" : {
"type" : "array",
"items" : {
"type" : "string",
"nativeType" : "string"
},
"nativeType" : "string"
},
"PAGE_SIZE" : {
"type" : "integer",
"nativeType" : "integer"
},
"PAGED_RESULTS_COOKIE" : {
"type" : "string",
"nativeType" : "string"
}
}
}
},
"__ACCOUNT__" : {
"operationOptionInfo" : {
"id" : "FIX_ME",
"type" : "object",
"properties" : {
"SORT_KEYS" : {
"type" : "string",
"nativeType" : null
},
"PAGED_RESULTS_OFFSET" : {
"type" : "integer",
"nativeType" : "integer"
},
"ATTRS_TO_GET" : {
"type" : "array",
"items" : {
"type" : "string",
"nativeType" : "string"
},
"nativeType" : "string"
},
"PAGE_SIZE" : {
"type" : "integer",
"nativeType" : "integer"
},
"PAGED_RESULTS_COOKIE" : {
"type" : "string",
"nativeType" : "string"
}
}
}
}
}
},
"VALIDATE" : {
"objectFeatures" : { }
},
"SYNC" : {
"objectFeatures" : { }
},
"SCHEMA" : {
"objectFeatures" : { }
}
}
}
{
"mappings" : [
{
"target" : "system/scim/user",
"source" : "managed/user",
"name" : "managedUser_systemScimUser",
"onCreate" : {
"type" : "text/javascript",
"file" : "script/MU2SCIM-sync.js"
},
"onUpdate" : {
"type" : "text/javascript",
"file" : "script/MU2SCIM-sync.js"
},
"properties" : [
{
"target" : "userName",
"source" : "userName"
},
{
"target" : "name/givenName",
"source" : "givenName"
},
{
"target" : "name/familyName",
"source" : "sn"
},
{
"target" : "displayName",
"source" : "description"
},
{
"target" : "active",
"default" : true
},
{
"target" : "password",
"source" : "password",
"transform" : {
"type" : "text/javascript",
"globals" : { },
"source" : "openidm.decrypt(source);"
},
"condition" : {
"type" : "text/javascript",
"globals" : { },
"source" : "object.password != null"
}
}
],
"policies" : [
{
"situation" : "CONFIRMED",
"action" : "UPDATE"
},
{
"situation" : "FOUND",
"action" : "UPDATE"
},
{
"situation" : "ABSENT",
"action" : "CREATE"
}
],
"correlationQuery" : {
"type" : "text/javascript",
"source" : "var qry = { '_queryFilter': 'userName eq \"' + source.userName + '\"' }; qry;"
}
},
{
"source" : "system/scim/user",
"target" : "managed/user",
"name" : "systemScimUser_managedUser",
"sourceQueryFullEntry" : false,
"onCreate" : {
"type" : "text/javascript",
"file" : "script/SCIM2MU-sync.js"
},
"onUpdate" : {
"type" : "text/javascript",
"file" : "script/SCIM2MU-sync.js"
},
"properties" : [
{
"target" : "userName",
"source" : "userName"
},
{
"target" : "givenName",
"source" : "name/givenName"
},
{
"target" : "sn",
"source" : "name/familyName"
},
{
"target" : "description",
"source" : "displayName"
}
],
"policies" : [
{
"situation" : "CONFIRMED",
"action" : "UPDATE"
},
{
"situation" : "FOUND",
"action" : "UPDATE"
},
{
"situation" : "ABSENT",
"action" : "CREATE"
}
],
"correlationQuery" : {
"type" : "text/javascript",
"source" : "var qry = {'_queryId' : 'for-userName', 'uid' : source.userName}; qry;"
}
}
]
}
You also need the following onCreate/onUpdate scripts to deal with the complex SCIM objects:
SCIM2MU-sync.js
/*
* Copyright 2017-2017 ForgeRock AS. All Rights Reserved
*
* Use of this code requires a commercial software license with ForgeRock AS.
* or with one of its affiliates. All use shall be exclusively subject
* to such license between the licensee and ForgeRock AS.
*/
// Addresses
// a Managed user contains by default the following "Address" attributes:
// postalAddress
// address2
// city
// postalCode
// country
// stateProvince
if (source.addresses != null) {
print("Found addresses")
var address = source.addresses[0]
target.city = address.locality
target.country = address.country
target.stateProvince = address.region
target.postalCode = address.postalCode
target.postalAddress = address.streetAddress
}
// Email
if (source.emails != null) {
print("Found mail")
var email = source.emails[0]
target.mail = email.value
}
// Telephone
if (source.phoneNumbers != null) {
print("Found phones")
var phone = source.phoneNumbers[0]
target.telephoneNumber = phone.value
}
MU2SCIM-sync.js
/*
* Copyright 2017-2017 ForgeRock AS. All Rights Reserved
*
* Use of this code requires a commercial software license with ForgeRock AS.
* or with one of its affiliates. All use shall be exclusively subject
* to such license between the licensee and ForgeRock AS.
*/
target.__NAME__ = source.userName
// Addresses
// a Managed user contains by default the following "Address" attributes:
// postalAddress
// address2
// city
// postalCode
// country
// stateProvince
var address = {
locality: source.city,
country: source.country,
region: source.stateProvince,
postalCode: source.postalCode,
streetAddress: source.postalAddress,
type: "work"
}
target.addresses = [address]
// Email
if (source.mail != null) {
var email = {
value: source.mail,
type: "work"
}
target.emails = [email]
}
// Telephone
if (source.telephoneNumber != null) {
var phone = {
value: source.telephoneNumber,
type: "work"
}
target.phoneNumbers = [phone]
}
Conclusion
ForgeRock Identity Management is now ready to take advantage of the new SCIM connector and to provision users to a SCIM compliant service. In our case we are utilizing WSO2 Identity Server that has a SCIM compliant interface to accept inbound provisioning requests.
SCIM has long been the attempt to clean up the mess that was called SPML using a RESTful approach but has seen little adoption in the industry aside from a few advocate names. ForgeRock now recognizes the importance of this standard and provides an easy and efficient way to integrate with SCIM compliant services.