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:
- Authenticate with the Anaplan Auth API
- Read a CSV file containing users. This example uses an export from the Users tab in model settings.
- 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!