IMPORTANT! With LDAP authentication a user that is disabled in AD will be unable to log in to Strategy – there is no security issue here. This user, however, will still use/block Strategy license. This can lead to an out-of-compliance situation in companies with high user rotation. When Strategy users are created automatically on logon of LDAP users the number of used licenses may grow fast.
This script reads users from Active Directory (disabled users), compares them with users from a Strategy user group (enabled users) and disables MSTR users that are in the intersection of those two sets.

Prerequisites
- script must be executed on a computer with access to AD catalog
- script must be executed on a computer with access to Strategy Library
- Python libraries that must be installed: ldap3, requests (pip install ldap3)
- fill in parameter details both for AD and MSTR
- in MSTR create a user group for disabled users; note down its ID
- when the script disables a user it does 2 things on MSTR side:
-- sets user parameter 'enabled' to 'false'
-- adds the user to 'Disabled Users' user group
- those two actions are optional - you may want to do just one of them - it's up to you
- you may want to add logging functions in order to keep track of changes performed by the program in time
Note - I've added color formatting in the code below (http://hilite.me/). Code copied from here might not work due to indentation issues and some problems with quotes (""). In that case, use attached file with sample code.
# Author: Robert Prochowicz, 2019-11-14
# Active Directory + MSTR user disabling program
# This script reads users from Active Directory (disabled users),
# compares them with users from a Strategy user group (enabled users)
# and disables MSTR users that are in the intersection of those two sets
'''
Prerequisites
- script must be executed on a computer with access to AD catalog
- script must be executed on a computer with access to Strategy Library
- Python libraries that must be installed: ldap3, requests (pip install ldap3)
- fill in parameter details both for AD and MSTR
- in MSTR create a user group for disabled users; note down its ID
- when the script disables a user it does 2 things on MSTR side:
-- sets user status 'enabled' to 'false'
-- adds the user to 'Disabled Users' user group
- those actions are optional - you may want to do just one of them - it's up to you
- you may want to add logging functions in order to keep track about changes performed by the program in time
'''
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, AUTO_BIND_NO_TLS, SUBTREE
from datetime import datetime
import requests
import credentials # additional python file credentials.py that contains AD and MSTR user credentials.
######## AD PARAMETERS ###########
server_name = 'YOUR_AD_SERVER'
search_base = 'DC=XXXXX,DC=XXXXXXXX,DC=XXXXX' # entry point into AD tree
# filter that defines AD users you want to import - use your own filter!
search_filter = '(&(objectclass=person)(userAccountControl=514)(|(company=XXX1)(company=XXX2)))'
# make sure that in your organization you can use AD 'sAMAccountName' attribute for mapping MSTR users
# you might need to use something else, like 'uid'
attributes=['sAMAccountName', 'userPrincipalName', 'mailNickname', 'mail', 'userAccountControl', 'lastLogon', 'cn', 'memberOf']
# alternatively use: attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES]
user_name = credentials.user_name
password = credentials.password
### MSTR Parameters ###
api_login = credentials.api_login
api_password = credentials.api_password
login_mode = 1 # 1-standard, 16-LDAP authentication
base_url = 'http://YOUR_MSTR_SERVER_ADDRESSS/MicroStrategyLibrary/api/'
ug_ldap = 'YYYYYYYYYYYYYYY' # User Group in MSTR - LDAP users
ug_disabled = 'ZZZZZZZZZZZZZ' # User Group in MSTR - disabled users (disabled MSTR users will be moved to this group)
#### FUNCTIONS ###
def login(base_url,api_login,api_password,login_mode):
print("Getting token...")
data_get = {'username': api_login,
'password': api_password,
'loginMode': login_mode}
r = requests.post(base_url + 'auth/login', data=data_get)
if r.ok:
auth_token = r.headers['X-MSTR-AuthToken']
cookies = dict(r.cookies)
print("Token: " + auth_token)
return auth_token, cookies
else:
print("HTTP %i - %s, Message %s" % (r.status_code, r.reason, r.text))
def set_headers(auth_token):
headers = {'X-MSTR-AuthToken':auth_token,
'Content-Type':'application/json',#IMPORTANT!
'Accept':'application/json'}
return headers
def usergroupMembers(auth_token, ug_ldap, cookies):
headers = set_headers(auth_token)
usergroup_url = (base_url + "usergroups/" + ug_ldap + '/members?includeAccess=false')
print("\nGetting MSTR User Group info...")
r = requests.get(usergroup_url, headers=headers, cookies=cookies)
if r.ok:
print("Error: " + str(r.raise_for_status()) + " || HTTP Status Code: " + str(r.status_code))
usergroup_members = r.json()
print("Number of users in the selected MSTR group: ", str(len(usergroup_members)), " ")
return usergroup_members
else:
print("HTTP %i - %s, Message %s" % (r.status_code, r.reason, r.text))
def modifyUser(auth_token, user_id, cookies, operationList):
headers = set_headers(auth_token)
usermod_url = (base_url + "users/" + user_id)
r = requests.patch(usermod_url, headers=headers, cookies=cookies, data=operationList)
if r.ok:
print("Error: " + str(r.raise_for_status()) + " || HTTP Status Code: " + str(r.status_code))
user_details = r.json()
return user_details
else:
print("HTTP %i - %s, Message %s" % (r.status_code, r.reason, r.text))
def main():
###### AD Part ######
server = Server(server_name, get_info=ALL)
conn = Connection(server, user=user_name, password=password, authentication=NTLM, auto_bind=True)
conn.extend.standard.paged_search(search_base, search_filter=search_filter, attributes=attributes,
generator=False)
# [extend.standard.paged_search + generator=False] - this returns all the elements
# without it, there will be pagination by 2k elements
ad_dataset=conn.entries
print(str(len(ad_dataset)), " disabled AD users (with status 514) downloaded from AD\n")
users_514=[[str(r.sAMAccountName),str(r.userPrincipalName),str(r.mailNickname),str(r.mail),
str(r.userAccountControl),str(r.lastLogon),str(r.cn),str(r.memberOf)] for r in ad_dataset]
users_514_uid=[str(r.sAMAccountName) for r in ad_dataset]
# write outcome as JSON file (just in case)
file1 = open("ad_users_"+str(datetime.timestamp(datetime.now()))+".json","a")
file1.write(conn.response_to_json())
file1.close()
###### MSTR Part ######
auth_token, cookies = login(base_url,api_login,api_password,login_mode)
usergroup_members = usergroupMembers(auth_token, ug_ldap, cookies)
mstr_users_details=[row for row in usergroup_members if row['subtype']==8704 and row['enabled']==True]
mstr_users=[row['username'] for row in mstr_users_details]
joint_userlist = list(set(users_514_uid).intersection(set(mstr_users)))
print('\nJoint users MSTR + AD that are going to be disabled: ', str(len(joint_userlist)))
print(joint_userlist)
processed_users=[]
# this operation will disable MSTR user
operationList1 = '{"operationList": [{"op": "replace", "path": "/enabled", "value": false}]}'
# this operation will add the user to Disabled Users MSTR group
operationList2 = '{"operationList": [{"op": "add", "path": "/memberships", "value": ["'+ ug_disabled +'"]}]}'
for row in mstr_users_details:
if row['username'] in joint_userlist:
user_id=row['id']
print('\nGetting user details of user: ')
print(str(user_id), " - ", str(row['username']).rjust(15), " - ", str(row['fullName']))
print('Setting user status to "disabled"')
user_details = modifyUser(auth_token, user_id, cookies, operationList1)
# Checking if user is currently member of Disabled Users group
disabled_user=False # default setting
for gr in user_details['memberships']:
if gr['id'] == ug_disabled:
disabled_user=True
print("User already in Disabled Users group")
# If user is not in the disabled group, she will be added there
if disabled_user==False:
print("Adding user to 'Disabled Users' MSTR group")
modifyUser(auth_token, user_id, cookies, operationList2)
processed_users.append(row)
return processed_users
### Main program
processed_users = main()