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:

  1. You have an Alation Cloud Service instance with SCIM enabled. See Enable SCIM Integration for User and Group Management.

  2. You have SCIM authentication credentials configured in Alation. You can use either:

  3. Open a Support case with Alation to assist with backend configuration. In the initial request, ask Support to set the SCIM client to sailpoint on your Alation Cloud Service instance. Depending on your configuration choices, you may need to add additional requests to the same ticket later.

  4. You have IdentityIQ with the SCIM 2.0 connector available.

  5. You have admin access to IdentityIQ to create applications, tasks, and provisioning policies.

  6. 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

  1. In IdentityIQ, navigate to Applications > Application Definition.

  2. 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

  3. 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/v2

    Authentication 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)

  4. 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

  1. Navigate to the Schema tab.

  2. 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

  1. Select ObjectType: group and add the following attributes:

    Attribute

    Type

    Properties

    displayName

    string

    members.value

    string

    Multi-valued

    id

    string

  2. 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.

  1. Navigate to the IdentityIQ debug console at http://<sailpoint_tenant_url>/identityiq/debug/debug.jsf.

  2. In the object browser page, select Application from the object dropdown.

  3. Click on the application that you created (for example, Alation IdentityIQ Integration).

  4. 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==&apos;home&apos;)].country"/>
          <entry key="addresses.home.primary.formatted" value="addresses[*][?(@.primary==true)][?(@.type==&apos;home&apos;)].formatted"/>
          <entry key="addresses.home.primary.locality" value="addresses[*][?(@.primary==true)][?(@.type==&apos;home&apos;)].locality"/>
          <entry key="addresses.home.primary.postalCode" value="addresses[*][?(@.primary==true)][?(@.type==&apos;home&apos;)].postalCode"/>
          <entry key="addresses.home.primary.region" value="addresses[*][?(@.primary==true)][?(@.type==&apos;home&apos;)].region"/>
          <entry key="addresses.home.primary.streetAddress" value="addresses[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].streetAddress"/>
          <entry key="addresses.home.secondary.country" value="addresses[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].country"/>
          <entry key="addresses.home.secondary.formatted" value="addresses[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].formatted"/>
          <entry key="addresses.home.secondary.locality" value="addresses[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].locality"/>
          <entry key="addresses.home.secondary.postalCode" value="addresses[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].postalCode"/>
          <entry key="addresses.home.secondary.region" value="addresses[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].region"/>
          <entry key="addresses.home.secondary.streetAddress" value="addresses[*][?(@.primary==true)][?(@.type==&apos;home&apos;)].streetAddress"/>
          <entry key="addresses.other.primary.country" value="addresses[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].country"/>
          <entry key="addresses.other.primary.formatted" value="addresses[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].formatted"/>
          <entry key="addresses.other.primary.locality" value="addresses[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].locality"/>
          <entry key="addresses.other.primary.postalCode" value="addresses[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].postalCode"/>
          <entry key="addresses.other.primary.region" value="addresses[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].region"/>
          <entry key="addresses.other.primary.streetAddress" value="addresses[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].streetAddress"/>
          <entry key="addresses.other.secondary.country" value="addresses[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].country"/>
          <entry key="addresses.other.secondary.formatted" value="addresses[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].formatted"/>
          <entry key="addresses.other.secondary.locality" value="addresses[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].locality"/>
          <entry key="addresses.other.secondary.postalCode" value="addresses[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].postalCode"/>
          <entry key="addresses.other.secondary.region" value="addresses[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].region"/>
          <entry key="addresses.other.secondary.streetAddress" value="addresses[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].streetAddress"/>
          <entry key="addresses.work.primary.country" value="addresses[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].country"/>
          <entry key="addresses.work.primary.formatted" value="addresses[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].formatted"/>
          <entry key="addresses.work.primary.locality" value="addresses[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].locality"/>
          <entry key="addresses.work.primary.postalCode" value="addresses[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].postalCode"/>
          <entry key="addresses.work.primary.region" value="addresses[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].region"/>
          <entry key="addresses.work.primary.streetAddress" value="addresses[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].streetAddress"/>
          <entry key="addresses.work.secondary.country" value="addresses[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].country"/>
          <entry key="addresses.work.secondary.formatted" value="addresses[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].formatted"/>
          <entry key="addresses.work.secondary.locality" value="addresses[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].locality"/>
          <entry key="addresses.work.secondary.postalCode" value="addresses[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].postalCode"/>
          <entry key="addresses.work.secondary.region" value="addresses[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].region"/>
          <entry key="addresses.work.secondary.streetAddress" value="addresses[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].streetAddress"/>
          <entry key="costCenter" value="[&apos;urn:ietf:params:scim:schemas:extension:enterprise:2.0:User&apos;].costCenter"/>
          <entry key="department" value="[&apos;urn:ietf:params:scim:schemas:extension:enterprise:2.0:User&apos;].department"/>
          <entry key="displayName" value="displayName"/>
          <entry key="division" value="[&apos;urn:ietf:params:scim:schemas:extension:enterprise:2.0:User&apos;].division"/>
          <entry key="emails.home.primary.value" value="emails[*][?(@.primary==true)][?(@.type==&apos;home&apos;)].value"/>
          <entry key="emails.home.secondary.value" value="emails[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].value"/>
          <entry key="emails.other.primary.value" value="emails[*][?(@.primary==true)][?(@.type==&apos;other&apos;)].value"/>
          <entry key="emails.other.secondary.value" value="emails[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].value"/>
          <entry key="emails.primary.value" value="emails[*][?(@.primary==true)].value"/>
          <entry key="emails.work.primary.value" value="emails[*][?(@.primary==true)][?(@.type==&apos;work&apos;)].value"/>
          <entry key="emails.work.secondary.value" value="emails[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].value"/>
          <entry key="employeeNumber" value="[&apos;urn:ietf:params:scim:schemas:extension:enterprise:2.0:User&apos;].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="[&apos;urn:ietf:params:scim:schemas:extension:enterprise:2.0:User&apos;].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="[&apos;urn:ietf:params:scim:schemas:extension:enterprise:2.0:User&apos;].organization"/>
          <entry key="phoneNumbers.fax.primary.value" value="phoneNumbers[*][?(@.type==&apos;fax&apos;)].value"/>
          <entry key="phoneNumbers.fax.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type==&apos;fax&apos;)].value"/>
          <entry key="phoneNumbers.home.primary.value" value="phoneNumbers[*][?(@.type==&apos;home&apos;)].value"/>
          <entry key="phoneNumbers.home.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type==&apos;home&apos;)].value"/>
          <entry key="phoneNumbers.mobile.primary.value" value="phoneNumbers[*][?(@.type==&apos;mobile&apos;)].value"/>
          <entry key="phoneNumbers.mobile.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type==&apos;mobile&apos;)].value"/>
          <entry key="phoneNumbers.other.primary.value" value="phoneNumbers[*][?(@.type==&apos;other&apos;)].value"/>
          <entry key="phoneNumbers.other.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type==&apos;other&apos;)].value"/>
          <entry key="phoneNumbers.pager.primary.value" value="phoneNumbers[*][?(@.type==&apos;pager&apos;)].value"/>
          <entry key="phoneNumbers.pager.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type==&apos;pager&apos;)].value"/>
          <entry key="phoneNumbers.work.primary.value" value="phoneNumbers[*][?(@.type==&apos;work&apos;)].value"/>
          <entry key="phoneNumbers.work.secondary.value" value="phoneNumbers[*][?(@.primary==false)][?(@.type==&apos;work&apos;)].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>
    
  5. Add the following entries to ensure proper group provisioning:

    <entry key="updateGroupsViaUsers" value="false"/>
    <entry key="provisionMultivaluedRFCCompatible">
      <value>
        <Boolean>true</Boolean>
      </value>
    </entry>
    
  6. Save the application XML.

Step 4: Create Provisioning Policies

Create a provisioning policy to define how accounts are created in Alation.

  1. In the application configuration, navigate to Provisioning Policies.

  2. Click Add Policy for the Create type.

  3. Click Create Policy Form and provide a name (for example, Alation Account Creation Policy).

  4. Click Add Section and name it Account Attributes or User Attributes.

  5. 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

  6. Save the provisioning policy.

Step 5: Create Group Aggregation Task

Create a task to push Active Directory groups to Alation as SCIM groups.

  1. Navigate to Setup > Tasks.

  2. Click New Task and select Account Group Aggregation.

  3. Provide a name and description for the task.

  4. From Select applications to scan, select the active directory application.

  5. In Group Aggregation Refresh Rule, paste the appropriate BeanShell script based on your authentication type:

    Important

    Change the value of scimAppName in 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;
    
  6. Save the task.

Step 6: Create Entitlement Aggregation Task

Create a task to read Alation SCIM groups back into IdentityIQ as entitlements.

  1. Navigate to Setup > Tasks.

  2. Click New Task and select Account Group Aggregation.

  3. Provide a name and description for the task.

  4. Select the checkbox Detect deleted account groups to automatically remove entitlements when groups are deleted in Alation.

  5. 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;
    
  6. Save the task.

Step 7: Create Account Aggregation Task

Create a task to aggregate user accounts from Alation.

  1. Navigate to Setup > Tasks.

  2. Click New Task and select Account Aggregation.

  3. Provide a name and description for the task.

  4. Select the following options:

    • Disable optimization of unchanged accounts

    • Promote managed attributes

  5. Save the task.

User and Group Operations

After completing the configuration, you can perform the following operations.

Group Provisioning

  1. Execute the Group Aggregation task on Active Directory to replicate AD groups as SCIM groups in Alation.

  2. Execute the Entitlement Aggregation task to read Alation SCIM groups back as entitlements in IdentityIQ.

User Provisioning

  1. In IdentityIQ, navigate to Lifecycle Manager > Configure.

  2. In Applications that support account only requests, select your SCIM application.

  3. Go to View Identity and select the user to provision.

  4. Click Manage > Accounts > Request Access.

  5. Select your SCIM application and click Submit.

  6. 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

  1. Navigate to Manage Access > Manage User Access.

  2. Select the user who needs group membership.

  3. Choose the entitlement (SCIM group) and click Next.

  4. 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

  1. Navigate to View Identity and select the user.

  2. Click Manage > Accounts.

  3. Select the SCIM account (your Alation application) and click Delete.

  4. 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

  1. Navigate to View Identity and select the suspended user.

  2. Click Manage > Accounts > Request Access.

  3. 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):

  1. Navigate to Manage Access > Manage User Access.

  2. Select the user.

  3. Go to the Remove Access tab.

  4. Select the entitlement to remove and click Next.

  5. 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.