[Part 2] Script example: Leveraging the SCIM API to bulk remove users

This article is a continuation from Part 1: Removing users in bulk: approaches and challenges. In this part, we look at an example of how to write a script that leverages the Anaplan SCIM API to bulk remove user access from Anaplan. 

Note: While all of these scripts have been tested and found to be fully functional, due to the vast amount of potential use cases, Anaplan does not explicitly support custom scripts built by our customers. This article is for information only and does not suggest any future product direction. 

Prerequisites 

  • An understanding of the Anaplan SCIM API 
  • An understanding of Anaplan authentication and the Authentication API 
  • The user account running the script must have the User Administrator role assigned 
  • Permission from your internal security team to install and run Python in your environment, or access to a server where you have such permissions 

Getting started 

This article assumes you have the requests modules installed, as well as Python 3.11 or later. Additionally, the script makes use of the Community Anaplan API 2.0 Python library, which will require additional modules to be installed. Please make sure you are installing these modules with Python 3, and not for an older version of Python. For more information on these modules, please see their respective websites: 

It may also be helpful to test out the SCIM API calls using Postman. 

High level process 

This is a basic Python script that reads in a list of users and removes their Anaplan access. At a high level, the script goes through the following steps: 

  1. Authenticate with the Anaplan Auth API
  2. Read a CSV file containing users. This example uses an export from the Users tab in model settings.
  3. For each user in the file, use the Anaplan SCIM API to disable the user and/or remove them from all workspaces. Disabling them vs removing all workspace access is configurable.

Recommended usage 

This approach is best used for a one-time cleanup of users. For ongoing user management, read more about using the SCIM API to connect to your identity provider, as well as leveraging a User Access Management (UAM) model to manage user access in models. 

Script walkthrough 

Section 1: Libraries 

 

 

import logging
import csv
import requests
from requests.exceptions import HTTPError, ConnectionError, SSLError, Timeout, ConnectTimeout, ReadTimeout
from anaplan_api import anaplan

 

 

In this section, we are importing the various libraries required within the script. 

  • logging: Used to set up a logger object to capture details about the script’s operations 
  • Csv: Used to parse the inbound csv file where the script reads the users from 
  • Requests: Used for making HTTP calls to the Anaplan SCIM API 
  • Requests.exceptions: Used to handle any HTTP exceptions if the calls to Anaplan fail 
  • anaplan_api: While this library supports a number of the Anaplan REST API endpoints, this script only uses it for generating an authentication token 

Section 2: Logging configuration 

 

 

logging.basicConfig(level=logging.INFO)
_logger = logging.getLogger(__name__)

 

 

In this section, we set up a logger so that we can see what the script is doing as it runs. Logging is used throughout the script. 

Section 3: SCIM API configuration 

 

 

URL_SCIM_USERS = 'https://api.anaplan.com/scim/1/0/v2/Users/'

json_edit_user = { "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [] }

 

 

This section includes the core configuration of what we want to do with the SCIM API. 

We will be using the /Users endpoint to “edit” the user details for an existing user. By using this endpoint with the PATCH command as documented, we can append specific commands to change only some attributes of a user.

Compare this to the Replace User Details approach, where we need to overwrite all of the attributes of the user. It would be extra effort to GET and parse the details for each existing user, only to send back the same data.

 

 

JSON_OPERATION_DISABLE_USER = { "op": "replace", "path": "active", "value": False }
JSON_OPERATION_REMOVE_ALL_WORKSPACE_ACCESS = { "op": "remove", "path": "entitlements" }

json_edit_user["Operations"].append(JSON_OPERATION_REMOVE_ALL_WORKSPACE_ACCESS)
json_edit_user["Operations"].append(JSON_OPERATION_DISABLE_USER)

 

 

Using the PATCH command, we “append” the specific operations that we want to use.

We will disable the user by setting the active flag to False.

We will also remove all workspace access by completing removing the entitlements node for the user. “Entitlements” is the SCIM language used to store which workspaces the user has access to in Anaplan. By completely removing this node, we effectively remove the user from all workspaces.

Both of these operations are optional, so you can comment or remove one of the lines if you don’t want to perform one of the operations. 

Section 4: Authentication 

 

username = 'user@domain.com'
password = 'Password123abc'
auth = anaplan.generate_authorization(auth_type='Basic', email=username, password=password)
auth_token = auth.get_auth_token()

 

 

This code leverages the anaplan_api library to get an authentication token from the Authentication API. This token is required to make any further API calls. This script uses basic authentication (username and password), but you can review the Authentication API documentation to learn about other authentication methods.

Section 5: Parse CSV file containing Users 

 

 

with open('Users.csv') as csv_file:
    reader = csv.DictReader(csv_file)

    users_list = []
    for row in reader:
        users_list.append(row['User ID'])

 

 

This section reads the provided CSV file (Users.csv) and creates an array that contains the User ID for each user. The User ID is a unique “GUID” for each user. Note that the SCIM API requires the User ID and not the user’s email address. You must ensure that whichever data source you use includes the User ID. 

Section 6: Call the SCIM to perform the selected operations to remove the users 

 

 

patch_header = { "Authorization": auth_token, "Content-Type": "application/json" }
for user in users_list:
    url = URL_SCIM_USERS + user
    run_action = requests.patch(url, headers=patch_header,json=json_edit_user, timeout=(5, 30))

 

 

The code above is simplified to exclude logging and exception handling. For each user, we append the User ID to the /Users endpoint. We then make a PATCH call to that endpoint with the required header and our configured Edit User command. 

We finalize this section by adding in the logging and exception handling to help debug any potential issues: 

 

 

patch_header = { "Authorization": auth_token, "Content-Type": "application/json" }
for user in users_list:
    url = URL_SCIM_USERS + user

    _logger.info(f"Updating user with User Id={user}")
    _logger.debug(f"Submitting PATCH request with the below detail:\nURL:{url}\nHeader:{patch_header}\nBody:{json_edit_user}")

    try:
        run_action = requests.patch(url, headers=patch_header,json=json_edit_user, timeout=(5, 30))
    except (HTTPError, ConnectionError, SSLError, Timeout, ConnectTimeout, ReadTimeout) as e:
        _logger.error(f"Error running action {e}", exc_info=True)
    if run_action.status_code != 200:
        _logger.debug(f"Request failed")
    else:
        _logger.info(f"Request was successful")
        _logger.debug(run_action)

 

 

Conclusion

There is an example script and sample file attached to this article that you can use as a basis for your own script. Feel free to post any questions or feedback below! 

Answers

  • Awesome article! Thanks @ryan_kohn. Again, you've saved me hours of research.

  • @ryan_kohn Understood that this approach could remove access for users, but is there any way to mimic the 'delete users' action on the users tab in which the user is removed from sight as well? We have some very large user lists which just keep growing. I'm assuming the answer is no given the below


    "The requested operation is not supported by the service provider.

    For example, an capability not supported by the service provider such as DELETE."

    https://scimapi.docs.apiary.io/#/introduction/scim-error-codes/error-codes-and-explanations

  • @BPifer Thanks for the feedback!


    Removing a user from the list of users in a model can be accomplished in one of three ways:

    1. The Delete User button on the Users tab in model settings will remove the user from the workspace associated with the model.
    2. Unchecking the workspace association for that user through the Administration console.
    3. Sending a PATCH command through the SCIM API that excludes that workspace from the entitlements.

    In the example above, the PATCH command is replacing the entitlements with an empty set of entitlements, which is a simple way to remove the user from all workspaces. To do this in a more fine-grained way (e.g. removing a user from a specific workspace), you need to fully replace the old set of entitlements with a new set of entitlements that includes only the workspaces you want to keep access for.

    Note that there is no way as of now to completely remove a user from the tenant, but you can still remove them from the users list in individual workspaces as per above.

  • @ryan_kohn

    -The solution you have provided for deleting multiple users from users list is great.

    Suppose I want to delete a set of users using a csv file which has user_id info in it.

    for example there are set of 100 users list in a csv file & I want to delete those users at once using the above PATCH method. what changes i need to do in the script, so that once i run the process it will delete the entitlements of those 100 users from the database.

    I think from Section 5: Parse CSV file containing Users we need to make changes in the script.

    1. Sending a PATCH command through the SCIM API that excludes that workspace from the entitlements.

  • @ibg1989 Can you clarify your requirement? The sample script above has two operations: disable the user, and remove all workspace access for the user. If you are looking to just remove all workspace access without disabling the user, you can provide just that one operation.

    You can also read through Part 1 for some comparisons of different approaches:

  • @ryan_kohn Thanks, I have previously referred the Part1 post. My doubt is, if i have a csv file with user id information already available.

    so this step i need not include in my script : users_list.append(row['User ID']) .

    To delete all the users(suppose 100 users in column1) in the csv file, what all changes i need to do in the above script? for multiple deletion we need to have some iterations, hope my requirement is clear now.

    I agree that i will be performing both the operations : disable the user, and remove all workspace access for the user.Thanks

  • @ibg1989 Step 5 in the script parses a csv file with user ids in it and creates a local array of users to work with. Step 6 loops through that array and runs the operation(s) user by user.

    You can read more about for loops in python here: https://wiki.python.org/moin/ForLoop