Set Up SCIM Integration in SailPoint IdentityIQ¶
Alation Cloud Service Applies to Alation Cloud Service instances of Alation
*Available from release 2026.2.1.0
You can configure SailPoint IdentityIQ (on-premises) to provision users and groups to Alation using SCIM 2.0. This integration enables automated user lifecycle management, including user provisioning, group membership updates, user suspension, and deprovisioning.
This guide explains how to configure the SCIM 2.0 connector in IdentityIQ to integrate with Alation’s SCIM API after enabling SCIM on your Alation Cloud Service instance.
Note
SailPoint offers two identity governance products:
IdentityIQ: On-premises, customer-managed deployment
IdentityNow: SaaS, SailPoint-hosted cloud
This guide covers IdentityIQ (on-premises) integration. The configuration uses Applications (not Sources as in IdentityNow).
Prerequisites¶
Before configuring the integration, ensure the following:
You have an Alation Cloud Service instance with SCIM enabled. See Enable SCIM Integration for User and Group Management.
You have SCIM authentication credentials configured in Alation. You can use either:
API Token authentication: Generate a SCIM bearer token in Alation. See Configure SCIM Integration.
Basic authentication: Set up a username and password using the Alation Django shell (see Configure Basic Authentication).
Open a Support case with Alation to assist with backend configuration. In the initial request, ask Support to set the SCIM client to
sailpointon your Alation Cloud Service instance. Depending on your configuration choices, you may need to add additional requests to the same ticket later.You have IdentityIQ with the SCIM 2.0 connector available.
You have admin access to IdentityIQ to create applications, tasks, and provisioning policies.
Network connectivity is established between IdentityIQ and the Alation Cloud Service instance over HTTPS.
Configure Basic Authentication¶
If you prefer to use basic authentication instead of API token authentication, configure the credentials in the Alation Django shell. Contact Alation Support to perform this configuration on your instance.
# In alation_django_shell
from rosemeta.utils.users_and_groups.configuration_utils import set_scim_basic_auth_username
from rosemeta.utils.users_and_groups.configuration_utils import set_scim_basic_auth_password
# Using an example username and password
set_scim_basic_auth_username('scim_account_username@alation.com')
set_scim_basic_auth_password('scim_account_password_12345&&')
Note
The password must be alphanumeric, longer than 16 characters, and contain at least one special character.
Setting this service account does not create a user in Alation.
This credential can only be used for SCIM integration and cannot access Alation directly.
Configuration Steps¶
Follow these steps to configure the integration:
Step 1: Create the Application in IdentityIQ¶
In IdentityIQ, navigate to Applications > Application Definition.
Click Add New Application and provide the following details:
Setting
Value
Application Type
SCIM 2.0
Name
A descriptive name (for example, Alation IdentityIQ Integration)
Owner
Assign to an appropriate user
Go to the Configuration tab and enter the following settings:
Setting
Value
Non-Compliant Server
Enabled (select the checkbox)
Base URL
https://<tenant_url>/scim/v2Authentication Type
API Token or Basic Authentication
Token
Paste the SCIM bearer token from Alation (if using API Token authentication)
Username
Enter the username configured in Django shell (if using Basic Authentication)
Password
Enter the password configured in Django shell (if using Basic Authentication)
Click Test Connection to verify the configuration. You should see a success message.
Step 2: Configure the Schema¶
Configure the account and group schemas to map attributes between IdentityIQ and Alation.
Account Attributes¶
Navigate to the Schema tab.
Select ObjectType: account and add the following attributes:
Attribute
Type
Required
Properties
userName
string
Yes
name.givenName
string
name.familyName
string
emails.primary.value
string
id
string
active
boolean
groups
group
Managed, Entitlement, Multi-Valued
Group Attributes¶
Select ObjectType: group and add the following attributes:
Attribute
Type
Properties
displayName
string
members.value
string
Multi-valued
id
string
Click Save to save the application configuration.
Step 3: Add JSON Path Mappings¶
Add JSON path mappings to the application XML in debug mode to enable proper attribute parsing.
Navigate to the IdentityIQ debug console at
http://<sailpoint_tenant_url>/identityiq/debug/debug.jsf.In the object browser page, select Application from the object dropdown.
Click on the application that you created (for example, Alation IdentityIQ Integration).
In the object editor dialog that opens, add the following XML entry for JSON path mappings:
<entry key="jsonPathMapping"> <value> <Map> <entry key="active" value="active"/> <entry key="addresses.home.primary.country" value="addresses[*][?(@.primary==true)][?(@.type=='home')].country"/> <entry key="addresses.home.primary.formatted" value="addresses[*][?(@.primary==true)][?(@.type=='home')].formatted"/> <entry key="addresses.home.primary.locality" value="addresses[*][?(@.primary==true)][?(@.type=='home')].locality"/> <entry key="addresses.home.primary.postalCode" value="addresses[*][?(@.primary==true)][?(@.type=='home')].postalCode"/> <entry key="addresses.home.primary.region" value="addresses[*][?(@.primary==true)][?(@.type=='home')].region"/> <entry key="addresses.home.primary.streetAddress" value="addresses[*][?(@.primary==false)][?(@.type=='home')].streetAddress"/> <entry key="addresses.home.secondary.country" value="addresses[*][?(@.primary==false)][?(@.type=='home')].country"/> <entry key="addresses.home.secondary.formatted" value="addresses[*][?(@.primary==false)][?(@.type=='home')].formatted"/> <entry key="addresses.home.secondary.locality" value="addresses[*][?(@.primary==false)][?(@.type=='home')].locality"/> <entry key="addresses.home.secondary.postalCode" value="addresses[*][?(@.primary==false)][?(@.type=='home')].postalCode"/> <entry key="addresses.home.secondary.region" value="addresses[*][?(@.primary==false)][?(@.type=='home')].region"/> <entry key="addresses.home.secondary.streetAddress" value="addresses[*][?(@.primary==true)][?(@.type=='home')].streetAddress"/> <entry key="addresses.other.primary.country" value="addresses[*][?(@.primary==true)][?(@.type=='other')].country"/> <entry key="addresses.other.primary.formatted" value="addresses[*][?(@.primary==true)][?(@.type=='other')].formatted"/> <entry key="addresses.other.primary.locality" value="addresses[*][?(@.primary==true)][?(@.type=='other')].locality"/> <entry key="addresses.other.primary.postalCode" value="addresses[*][?(@.primary==true)][?(@.type=='other')].postalCode"/> <entry key="addresses.other.primary.region" value="addresses[*][?(@.primary==true)][?(@.type=='other')].region"/> <entry key="addresses.other.primary.streetAddress" value="addresses[*][?(@.primary==true)][?(@.type=='other')].streetAddress"/> <entry key="addresses.other.secondary.country" value="addresses[*][?(@.primary==false)][?(@.type=='other')].country"/> <entry key="addresses.other.secondary.formatted" value="addresses[*][?(@.primary==false)][?(@.type=='other')].formatted"/> <entry key="addresses.other.secondary.locality" value="addresses[*][?(@.primary==false)][?(@.type=='other')].locality"/> <entry key="addresses.other.secondary.postalCode" value="addresses[*][?(@.primary==false)][?(@.type=='other')].postalCode"/> <entry key="addresses.other.secondary.region" value="addresses[*][?(@.primary==false)][?(@.type=='other')].region"/> <entry key="addresses.other.secondary.streetAddress" value="addresses[*][?(@.primary==false)][?(@.type=='other')].streetAddress"/> <entry key="addresses.work.primary.country" value="addresses[*][?(@.primary==true)][?(@.type=='work')].country"/> <entry key="addresses.work.primary.formatted" value="addresses[*][?(@.primary==true)][?(@.type=='work')].formatted"/> <entry key="addresses.work.primary.locality" value="addresses[*][?(@.primary==true)][?(@.type=='work')].locality"/> <entry key="addresses.work.primary.postalCode" value="addresses[*][?(@.primary==true)][?(@.type=='work')].postalCode"/> <entry key="addresses.work.primary.region" value="addresses[*][?(@.primary==true)][?(@.type=='work')].region"/> <entry key="addresses.work.primary.streetAddress" value="addresses[*][?(@.primary==true)][?(@.type=='work')].streetAddress"/> <entry key="addresses.work.secondary.country" value="addresses[*][?(@.primary==false)][?(@.type=='work')].country"/> <entry key="addresses.work.secondary.formatted" value="addresses[*][?(@.primary==false)][?(@.type=='work')].formatted"/> <entry key="addresses.work.secondary.locality" value="addresses[*][?(@.primary==false)][?(@.type=='work')].locality"/> <entry key="addresses.work.secondary.postalCode" value="addresses[*][?(@.primary==false)][?(@.type=='work')].postalCode"/> <entry key="addresses.work.secondary.region" value="addresses[*][?(@.primary==false)][?(@.type=='work')].region"/> <entry key="addresses.work.secondary.streetAddress" value="addresses[*][?(@.primary==false)][?(@.type=='work')].streetAddress"/> <entry key="costCenter" value="['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].costCenter"/> <entry key="department" value="['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].department"/> <entry key="displayName" value="displayName"/> <entry key="division" value="['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].division"/> <entry key="emails.home.primary.value" value="emails[*][?(@.primary==true)][?(@.type=='home')].value"/> <entry key="emails.home.secondary.value" value="emails[*][?(@.primary==false)][?(@.type=='home')].value"/> <entry key="emails.other.primary.value" value="emails[*][?(@.primary==true)][?(@.type=='other')].value"/> <entry key="emails.other.secondary.value" value="emails[*][?(@.primary==false)][?(@.type=='other')].value"/> <entry key="emails.primary.value" value="emails[*][?(@.primary==true)].value"/> <entry key="emails.work.primary.value" value="emails[*][?(@.primary==true)][?(@.type=='work')].value"/> <entry key="emails.work.secondary.value" value="emails[*][?(@.primary==false)][?(@.type=='work')].value"/> <entry key="employeeNumber" value="['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].employeeNumber"/> <entry key="entitlements" value="entitlements[*].value"/> <entry key="externalId" value="externalId"/> <entry key="groups" value="groups[*].value"/> <entry key="id" value="id"/> <entry key="locale" value="locale"/> <entry key="manager.value" value="['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].manager.value"/> <entry key="members.value" value="members[*].value"/> <entry key="name.familyName" value="name.familyName"/> <entry key="name.formatted" value="name.formatted"/> <entry key="name.givenName" value="name.givenName"/> <entry key="name.honorificPrefix" value="name.honorificPrefix"/> <entry key="name.honorificSuffix" value="name.honorificSuffix"/> <entry key="nickName" value="nickName"/> <entry key="organization" value="['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].organization"/> <entry key="phoneNumbers.fax.primary.value" value="phoneNumbers[*][?(@.type=='fax')].value"/> <entry key="phoneNumbers.fax.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type=='fax')].value"/> <entry key="phoneNumbers.home.primary.value" value="phoneNumbers[*][?(@.type=='home')].value"/> <entry key="phoneNumbers.home.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type=='home')].value"/> <entry key="phoneNumbers.mobile.primary.value" value="phoneNumbers[*][?(@.type=='mobile')].value"/> <entry key="phoneNumbers.mobile.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type=='mobile')].value"/> <entry key="phoneNumbers.other.primary.value" value="phoneNumbers[*][?(@.type=='other')].value"/> <entry key="phoneNumbers.other.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type=='other')].value"/> <entry key="phoneNumbers.pager.primary.value" value="phoneNumbers[*][?(@.type=='pager')].value"/> <entry key="phoneNumbers.pager.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type=='pager')].value"/> <entry key="phoneNumbers.work.primary.value" value="phoneNumbers[*][?(@.type=='work')].value"/> <entry key="phoneNumbers.work.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type=='work')].value"/> <entry key="preferredLanguage" value="preferredLanguage"/> <entry key="profileUrl" value="profileUrl"/> <entry key="roles" value="roles[*].value"/> <entry key="title" value="title"/> <entry key="userName" value="userName"/> <entry key="userType" value="userType"/> </Map> </value> </entry>
Add the following entries to ensure proper group provisioning:
<entry key="updateGroupsViaUsers" value="false"/> <entry key="provisionMultivaluedRFCCompatible"> <value> <Boolean>true</Boolean> </value> </entry>
Save the application XML.
Step 4: Create Provisioning Policies¶
Create a provisioning policy to define how accounts are created in Alation.
In the application configuration, navigate to Provisioning Policies.
Click Add Policy for the Create type.
Click Create Policy Form and provide a name (for example, Alation Account Creation Policy).
Click Add Section and name it Account Attributes or User Attributes.
Add the following fields under the section:
userName
Display Name: User Name
Required: Yes
Value: Select Rule and choose the email rule
emails.primary.value
Display Name: Email
Required: Yes
Value: Select Rule and choose the email rule
name.givenName
Display Name: First Name
Required: Yes
Value: Select Rule and choose the firstName rule
name.familyName
Display Name: Last Name
Required: Yes
Value: Select Rule and choose the lastName rule
Save the provisioning policy.
Step 5: Create Group Aggregation Task¶
Create a task to push Active Directory groups to Alation as SCIM groups.
Navigate to Setup > Tasks.
Click New Task and select Account Group Aggregation.
Provide a name and description for the task.
From Select applications to scan, select the active directory application.
In Group Aggregation Refresh Rule, paste the appropriate BeanShell script based on your authentication type:
Important
Change the value of
scimAppNamein the script to match your SCIM application name in IdentityIQ.For API Token Authentication
Use the following script if you configured API Token authentication:
import sailpoint.object.*; import sailpoint.api.*; import sailpoint.tools.Util; import java.util.Map; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import org.json.JSONObject; import org.json.JSONArray; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; // ============================================================ // AD Group Aggregation Refresh Rule -> Push groups to Alation // ============================================================ Log log = LogFactory.getLog("sailpoint.partner.alation.GroupAggregationRefresh"); // ------------------------------------------------------------ // Resolve SCIM Application // ------------------------------------------------------------ // >>> CHANGE THIS to your SCIM Application name in IIQ <<< String scimAppName = "Alation IdentityIQ Integration"; Application scimApp = null; try { scimApp = context.getObjectByName(Application.class, scimAppName); if (scimApp == null) { log.error("SCIM Application not found by name: " + scimAppName); return accountGroup; } } catch (Exception e) { log.error("Error loading SCIM Application by name: " + scimAppName, e); return accountGroup; } // ------------------------------------------------------------ // Read required config from SCIM Application attributes map // Keys from your Application XML: oauthBearerToken, host // ------------------------------------------------------------ Map scimAttrs = null; try { scimAttrs = scimApp.getAttributes(); if (scimAttrs == null) { log.error("SCIM Application attributes map is null for app: " + scimAppName); return accountGroup; } } catch (Exception e) { log.error("Error reading SCIM Application attributes for app: " + scimAppName, e); return accountGroup; } // Token String encryptedToken = (String) scimAttrs.get("oauthBearerToken"); if (Util.isEmpty(encryptedToken)) { log.error("Missing SCIM Application attribute 'oauthBearerToken' on app: " + scimAppName); return accountGroup; } String bearerToken = null; try { bearerToken = context.decrypt(encryptedToken); } catch (Exception e) { log.error("Failed to decrypt 'oauthBearerToken' for app: " + scimAppName, e); return accountGroup; } if (Util.isEmpty(bearerToken)) { log.error("Decrypted bearer token is empty for app: " + scimAppName); return accountGroup; } // Host -> Groups URL String host = (String) scimAttrs.get("host"); if (Util.isEmpty(host)) { log.error("Missing SCIM Application attribute 'host' on app: " + scimAppName); return accountGroup; } if (!host.endsWith("/")) { host = host + "/"; } String groupsUrl = host + "Groups"; // ------------------------------------------------------------ // Only process group-type ManagedAttributes (AD groups) // ------------------------------------------------------------ if (accountGroup != null && "group".equalsIgnoreCase(accountGroup.getType())) { // This is usually the DN String groupDN = accountGroup.getValue(); // ---------------------------------------------------------------- // Derive a FRIENDLY group name for SCIM displayName // Priority: // 1. accountGroup.getDisplayName() // 2. "cn" attribute // 3. CN portion of DN // 4. raw DN as last resort // ---------------------------------------------------------------- String groupName = accountGroup.getDisplayName(); if (Util.isEmpty(groupName)) { groupName = (String) accountGroup.getAttribute("cn"); } if (Util.isEmpty(groupName)) { if (!Util.isEmpty(groupDN) && groupDN.startsWith("CN=")) { int endIndex = groupDN.indexOf(','); if (endIndex > 3) { groupName = groupDN.substring(3, endIndex); } } } if (Util.isEmpty(groupName)) { groupName = groupDN; } log.debug("Processing AD group -> DN=" + groupDN + ", displayName=" + groupName); HttpURLConnection connection = null; try { // ----------------------------------------------------- // STEP 1 - CHECK if group already exists in Alation SCIM // ----------------------------------------------------- String encodedName = java.net.URLEncoder.encode(groupName, "UTF-8").replace("+", "%20"); String filterUrl = groupsUrl + "?filter=displayName%20eq%20%22" + encodedName + "%22"; log.debug("SCIM GET (exists check) URL: " + filterUrl); URL urlCheck = new URL(filterUrl); connection = (HttpURLConnection) urlCheck.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("Accept", "application/scim+json"); connection.setRequestProperty("Authorization", "Bearer " + bearerToken); int checkResponse = connection.getResponseCode(); InputStream checkStream = (checkResponse >= 200 && checkResponse <= 299) ? connection.getInputStream() : connection.getErrorStream(); StringBuilder checkResult = new StringBuilder(); if (checkStream != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(checkStream, "UTF-8")); String line; while ((line = reader.readLine()) != null) { checkResult.append(line); } reader.close(); } log.debug("SCIM GET response (exists check): " + checkResult.toString()); boolean groupExists = false; try { if (checkResult.length() > 0) { JSONObject checkJson = new JSONObject(checkResult.toString()); if (checkJson.has("totalResults") && checkJson.getInt("totalResults") > 0) { groupExists = true; } } } catch (Exception jsonEx) { log.warn("Unable to parse SCIM GET response for existence check.", jsonEx); } if (groupExists) { log.debug("Group already exists in Alation by displayName. Skipping creation -> " + groupName); return accountGroup; } log.debug("No existing group found in Alation with displayName=" + groupName + ". Creating..."); // ----------------------------------------------------- // STEP 2 - CREATE GROUP in Alation SCIM // ----------------------------------------------------- URL createUrl = new URL(groupsUrl); connection = (HttpURLConnection) createUrl.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestMethod("POST"); connection.setUseCaches(false); connection.setRequestProperty("Content-Type", "application/scim+json"); connection.setRequestProperty("Accept", "application/scim+json"); connection.setRequestProperty("Authorization", "Bearer " + bearerToken); JSONObject payload = new JSONObject(); JSONArray schemas = new JSONArray(); schemas.put("urn:ietf:params:scim:schemas:core:2.0:Group"); payload.put("schemas", schemas); payload.put("displayName", groupName); payload.put("externalId", groupName); log.debug("SCIM Create Group payload: " + payload.toString()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(payload.toString().getBytes("UTF-8")); out.flush(); out.close(); int createResponse = connection.getResponseCode(); InputStream createStream = (createResponse >= 200 && createResponse <= 299) ? connection.getInputStream() : connection.getErrorStream(); StringBuilder createResult = new StringBuilder(); if (createStream != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(createStream, "UTF-8")); String line; while ((line = reader.readLine()) != null) { createResult.append(line); } reader.close(); } log.debug("SCIM /Groups create response: " + createResult.toString()); } catch (Exception e) { log.error("Error during AD -> Alation SCIM group sync: " + e.getMessage(), e); } finally { if (connection != null) { connection.disconnect(); } } } return accountGroup;
For Basic Authentication
Use the following script if you configured Basic Authentication (username and password):
import sailpoint.object.*; import sailpoint.api.*; import sailpoint.tools.Util; import java.util.Map; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import org.json.JSONObject; import org.json.JSONArray; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; // For Base64 encoding (available in IIQ) import org.apache.commons.codec.binary.Base64; // ============================================================ // AD Group Aggregation Refresh Rule -> Push groups to Alation // - Uses friendly CN/display name instead of full DN // - Auth: BASIC (user/password from Application XML) // ============================================================ Log log = LogFactory.getLog("sailpoint.partner.alation.GroupAggregationRefresh"); // ------------------------------------------------------------ // Resolve SCIM Application // IMPORTANT: This refresh rule is running during AD group aggregation, // so accountGroup.getApplication() will typically be the AD application. // We must load the SCIM application explicitly by name. // ------------------------------------------------------------ // >>> CHANGE THIS to your SCIM Application name in IIQ <<< String scimAppName = "Alation IdentityIQ Integration"; Application scimApp = null; try { scimApp = context.getObjectByName(Application.class, scimAppName); if (scimApp == null) { log.error("SCIM Application not found by name: " + scimAppName); return accountGroup; } } catch (Exception e) { log.error("Error loading SCIM Application by name: " + scimAppName, e); return accountGroup; } // ------------------------------------------------------------ // Read required config from SCIM Application attributes map // Keys from your Application XML: user, password, host // ------------------------------------------------------------ Map scimAttrs = null; try { scimAttrs = scimApp.getAttributes(); if (scimAttrs == null) { log.error("SCIM Application attributes map is null for app: " + scimAppName); return accountGroup; } } catch (Exception e) { log.error("Error reading SCIM Application attributes for app: " + scimAppName, e); return accountGroup; } // ------------------------------------------------------------ // BASIC AUTHENTICATION (user/password) // ------------------------------------------------------------ String username = (String) scimAttrs.get("user"); String encryptedPassword = (String) scimAttrs.get("password"); if (Util.isEmpty(username)) { log.error("Missing SCIM Application attribute 'user' on app: " + scimAppName); return accountGroup; } if (Util.isEmpty(encryptedPassword)) { log.error("Missing SCIM Application attribute 'password' on app: " + scimAppName); return accountGroup; } String password = null; try { password = context.decrypt(encryptedPassword); } catch (Exception e) { log.error("Failed to decrypt SCIM password for app: " + scimAppName, e); return accountGroup; } if (Util.isEmpty(password)) { log.error("Decrypted SCIM password is empty for app: " + scimAppName); return accountGroup; } // Build Basic Auth header: "Basic base64(user:pass)" String authString = username + ":" + password; String basicAuthHeader = "Basic " + new String(Base64.encodeBase64(authString.getBytes("UTF-8")), "UTF-8"); // ------------------------------------------------------------ // Host -> Groups URL // ------------------------------------------------------------ String host = (String) scimAttrs.get("host"); if (Util.isEmpty(host)) { log.error("Missing SCIM Application attribute 'host' on app: " + scimAppName); return accountGroup; } if (!host.endsWith("/")) { host = host + "/"; } String groupsUrl = host + "Groups"; // ------------------------------------------------------------ // Only process group-type ManagedAttributes (AD groups) // ------------------------------------------------------------ if (accountGroup != null && "group".equalsIgnoreCase(accountGroup.getType())) { // This is usually the DN String groupDN = accountGroup.getValue(); // ---------------------------------------------------------------- // Derive a FRIENDLY group name for SCIM displayName // Priority: // 1. accountGroup.getDisplayName() // 2. "cn" attribute // 3. CN portion of DN // 4. raw DN as last resort // ---------------------------------------------------------------- String groupName = accountGroup.getDisplayName(); if (Util.isEmpty(groupName)) { groupName = (String) accountGroup.getAttribute("cn"); } if (Util.isEmpty(groupName)) { if (!Util.isEmpty(groupDN) && groupDN.startsWith("CN=")) { int endIndex = groupDN.indexOf(','); if (endIndex > 3) { groupName = groupDN.substring(3, endIndex); } } } if (Util.isEmpty(groupName)) { groupName = groupDN; } log.debug("Processing AD group -> DN=" + groupDN + ", displayName=" + groupName); HttpURLConnection connection = null; try { // ----------------------------------------------------- // STEP 1 - CHECK if group already exists in Alation SCIM // ----------------------------------------------------- String encodedName = java.net.URLEncoder.encode(groupName, "UTF-8").replace("+", "%20"); String filterUrl = groupsUrl + "?filter=displayName%20eq%20%22" + encodedName + "%22"; log.debug("SCIM GET (exists check) URL: " + filterUrl); URL urlCheck = new URL(filterUrl); connection = (HttpURLConnection) urlCheck.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("Accept", "application/scim+json"); connection.setRequestProperty("Authorization", basicAuthHeader); int checkResponse = connection.getResponseCode(); InputStream checkStream = (checkResponse >= 200 && checkResponse <= 299) ? connection.getInputStream() : connection.getErrorStream(); StringBuilder checkResult = new StringBuilder(); if (checkStream != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(checkStream, "UTF-8")); String line; while ((line = reader.readLine()) != null) { checkResult.append(line); } reader.close(); } log.debug("SCIM GET response (exists check): " + checkResult.toString()); boolean groupExists = false; try { if (checkResult.length() > 0) { JSONObject checkJson = new JSONObject(checkResult.toString()); if (checkJson.has("totalResults") && checkJson.getInt("totalResults") > 0) { groupExists = true; } } } catch (Exception jsonEx) { log.warn("Unable to parse SCIM GET response for existence check.", jsonEx); } if (groupExists) { log.debug("Group already exists in Alation by displayName. Skipping creation -> " + groupName); return accountGroup; } log.debug("No existing group found in Alation with displayName=" + groupName + ". Creating..."); // ----------------------------------------------------- // STEP 2 - CREATE GROUP in Alation SCIM // ----------------------------------------------------- URL createUrl = new URL(groupsUrl); connection = (HttpURLConnection) createUrl.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestMethod("POST"); connection.setUseCaches(false); connection.setRequestProperty("Content-Type", "application/scim+json"); connection.setRequestProperty("Accept", "application/scim+json"); connection.setRequestProperty("Authorization", basicAuthHeader); JSONObject payload = new JSONObject(); JSONArray schemas = new JSONArray(); schemas.put("urn:ietf:params:scim:schemas:core:2.0:Group"); payload.put("schemas", schemas); payload.put("displayName", groupName); // friendly name payload.put("externalId", groupName); // trackable ID log.debug("SCIM Create Group payload: " + payload.toString()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(payload.toString().getBytes("UTF-8")); out.flush(); out.close(); int createResponse = connection.getResponseCode(); InputStream createStream = (createResponse >= 200 && createResponse <= 299) ? connection.getInputStream() : connection.getErrorStream(); StringBuilder createResult = new StringBuilder(); if (createStream != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(createStream, "UTF-8")); String line; while ((line = reader.readLine()) != null) { createResult.append(line); } reader.close(); } log.debug("SCIM /Groups create response: " + createResult.toString()); } catch (Exception e) { log.error("Error during AD -> Alation SCIM group sync: " + e.getMessage(), e); } finally { if (connection != null) { connection.disconnect(); } } } // IdentityIQ requires returning the ManagedAttribute return accountGroup;
Save the task.
Step 6: Create Entitlement Aggregation Task¶
Create a task to read Alation SCIM groups back into IdentityIQ as entitlements.
Navigate to Setup > Tasks.
Click New Task and select Account Group Aggregation.
Provide a name and description for the task.
Select the checkbox Detect deleted account groups to automatically remove entitlements when groups are deleted in Alation.
Add a Group Aggregation Refresh Rule to make entitlements requestable:
import sailpoint.object.*; import sailpoint.tools.Util; if (accountGroup != null && "group".equalsIgnoreCase(accountGroup.getType())) { accountGroup.setRequestable(true); } return accountGroup;
Save the task.
Step 7: Create Account Aggregation Task¶
Create a task to aggregate user accounts from Alation.
Navigate to Setup > Tasks.
Click New Task and select Account Aggregation.
Provide a name and description for the task.
Select the following options:
Disable optimization of unchanged accounts
Promote managed attributes
Save the task.
User and Group Operations¶
After completing the configuration, you can perform the following operations.
Group Provisioning¶
Execute the Group Aggregation task on Active Directory to replicate AD groups as SCIM groups in Alation.
Execute the Entitlement Aggregation task to read Alation SCIM groups back as entitlements in IdentityIQ.
User Provisioning¶
In IdentityIQ, navigate to Lifecycle Manager > Configure.
In Applications that support account only requests, select your SCIM application.
Go to View Identity and select the user to provision.
Click Manage > Accounts > Request Access.
Select your SCIM application and click Submit.
Confirm and submit the request.
The user is provisioned in Alation with the default Viewer role.
Note
Run the Perform Identity Request Maintenance task to verify the request and mark it as completed.
Update Group Membership¶
Navigate to Manage Access > Manage User Access.
Select the user who needs group membership.
Choose the entitlement (SCIM group) and click Next.
Review and submit the request.
Once approved, the user is added to the SCIM group in Alation.
User Suspension¶
User suspension in IdentityIQ removes the user’s SCIM account from Alation, which suspends the user and removes them from all SCIM groups.
Suspend a User¶
Navigate to View Identity and select the user.
Click Manage > Accounts.
Select the SCIM account (your Alation application) and click Delete.
Confirm and submit.
Result
The SCIM account is deprovisioned from Alation.
The user appears under Suspended Users in Alation.
The user is removed from all SCIM groups.
Reactivate a Suspended User¶
Navigate to View Identity and select the suspended user.
Click Manage > Accounts > Request Access.
Select your SCIM application and submit the request.
Result
The SCIM account is re-created in Alation.
The user is reactivated.
The user is automatically re-added to all SCIM groups they were a member of before suspension.
Note
Group memberships are restored during reactivation because IdentityIQ re-applies previously assigned entitlements.
User Deprovisioning¶
To remove a user’s entitlement (remove them from a SCIM group):
Navigate to Manage Access > Manage User Access.
Select the user.
Go to the Remove Access tab.
Select the entitlement to remove and click Next.
Review and submit.
Once approved, the user is removed from the SCIM group in Alation.
Maintenance Tasks¶
Run the following tasks regularly to keep user and group data synchronized:
Task |
Purpose |
|---|---|
Group Aggregation (AD) |
Push AD groups to Alation as SCIM groups |
Entitlement Aggregation |
Read SCIM groups back as IdentityIQ entitlements |
Account Aggregation |
Sync user accounts between Alation and IdentityIQ |
Perform Identity Request Maintenance |
Verify and finalize provisioning requests |
Troubleshooting¶
For error messages and troubleshooting tips, see Troubleshooting SCIM Configuration.
For testing the SCIM configuration, see Testing SCIM Configuration.