How to automatically Add or Deactivate Jira Align users from Active Directory
Summary
Many customers have reached out to Jira Align Technical Support to ask if there is a way to automate user creation or deactivation in Jira Align through Active Directory (AD).
Although JiraAlign does not natively support such integration, there is a way that a customer can implement a workaround to setup such automation.
This article discusses a scripting process and provides a sample Python script to match that process, should adjustments be needed for a specific environment please work with your own Python scripting and Rest API specialists (and possibly your Atlassian Partner or Atlassian Advisory Services Team contact) as script authoring is outside of the scope of services offered by the Jira Align Technical Support Team.
Atlassian recommend always testing scripts in a Test / UAT / Sandbox environment before implementing their use in a Production environment
The information in this article was originally presented as a Community Article, to see the original article use this link:
Environment
Jira Align
Solution
Integrating Jira Align with AD to automate user management within Jira Align can be achieved by implementing an external script which makes calls using JA Rest APIs.
Such a script might use the below logic:
Get a list of all Jira Align users (activated and deactivated)
Get the list of users in the Active Directory group
Compare the lists
If the user account exists in the Active Directory group and not in Jira Align
ACTION: create a user with a low-level access role
If the user account exists in Active Directory and in Jira Align
Then check if a user is deactivated in Jira Align,
If no, then no action
If yes, then ACTION: reactivate user in Jira Align
If the user account does not exist in the Active Directory group and does exist in Jira Align
Check if a user is active in Jira Align
If yes, ACTION: Deactivate user in Jira Align
If no, no action
Pre-requisites for using the Sample Script:
- In Jira Align the following items (which are all required fields in the details of a user) must be configured and the IDs for a default value known
Cost center (costCenterId)
Organization Structure (divisionId)
Region (regionId)
City (cityId)
- In Jira Align a low-level role must be configured as a default for users to be assign to by the script
- An API 2.0 Token is needed to an existing Jira Align User that has Super Admin permissions
- An Active Directory Group for Jira Align Users must be created
Limitations of the Sample Script:
- The same low-level System Role is used for all Jira Align users created by the script
- The same Cost Center is used for all Jira Align users created by the script
- The same Organization Structure is used for all Jira Align users created by the script
- The same Region and City are used for all Jira Align Users created by the script
If any of the fields, that are subject to limitations, can be set as fields in AD or an accessible Human Resources (HR) system, then it should be possible to adjust the sample script to use this information instead of setting a single value
# This code sample uses the 'requests' library:
# http://docs.python-requests.org
import requests
base_url = "insert_your_Jira_Align_API2_URL" # eg "https://company.jiraalign.com/rest/align/api/2"
session = requests.Session()
# storing the API bearer token in plain text is a security risk. Be guided by your comany's security policies
bearer_token = "insert_your_Jira_Align_API2_token_here"
session.headers.update({"Authorization": f"Bearer {bearer_token}"})
# Gets and returns a list of all Jira Align users
def get_all_users():
url = f"{base_url}/Users"
params = {"$skip": 0, "$select": "id,externalId,isLocked,divisionId,cityId"}
users = []
while True:
response = session.get(url, params=params)
response.raise_for_status()
page = response.json()
users += page
if len(page) != 100:
break
params["$skip"] += 100
return users
# Adds users to Jira Align
# user_to_add dictionary is built in the logic of the function perform_full_sync mentioned later
def add_user(user_to_add):
url = f"{base_url}/Users"
# print(f"Adding user {user_to_add['externalId']} with the following params:")
# for k, v in user_to_add.items():
# print(f"{k}: {v}")
# if input("Enter Y to proceed...").lower() != "y":
# raise Exception("String other than 'y' detected, interrupting.")
response = session.post(url, json=user_to_add)
response.raise_for_status()
return response.json()
# Disables active users in Jira Align
def disable_user(user_id):
url = f"{base_url}/Users/{user_id}"
# isLocked=-1 means that the user will be deactivated
data = [{"op": "replace", "path": "/isLocked", "value": -1}]
print(f"Disabling user {user_id}")
response = session.patch(url, json=data)
response.raise_for_status()
# Enables deactivated users in Jira Align
def enable_user(user_id):
url = f"{base_url}/Users/{user_id}"
# isLocked=0 means that the user will be activated
# userEndDate is a field set during deactivation.
# This parameter clears userEndDate so that the user can login
data = [
{"op": "replace", "path": "/isLocked", "value": 0},
{"op": "replace", "path": "/userEndDate", "value": None},
]
print(f"Enabling user {user_id}")
response = session.patch(url, json=data)
response.raise_for_status()
# if users were created by connector, they will be missing divisionId and cityId
# we need to add divisionId and cityId before we can enable/disable them
# this function adds divisionId and cityId
def add_missing_div_city(user_id, divisionId, cityId):
url = f"{base_url}/Users/{user_id}"
data = [
{"op": "replace", "path": "/divisionId", "value": divisionId},
{"op": "replace", "path": "/cityId", "value": cityId},
]
print(f"Adding divisionId ({divisionId}) and cityId ({cityId}) to user {user_id}")
response = session.patch(url, json=data)
response.raise_for_status()
# Synchronises all users
def perform_full_sync():
# ad_users - you will need to get your user list from your Active Directory.
# get_ad_users will be YOUR function to get your Active Directory user list.
# It will be a list of userIds, such as SAmAccountName that corresponds to
# externalId's in Jira Align
# if your JA SSO is set to use emails instead of externalId, adapt the code to emails
ad_users = get_ad_users()
# ja_users we get from Jira Align API
ja_users = get_all_users()
# changes ja_users from a list to a dictionary
ja_users = {user["externalId"]: user for user in ja_users}
# output will be a dictionary in the format
# {
# "user1externalId": {
# "id": 1032,
# "externalId": "user1externalId",
# "isLocked": 0,
# },
# "user2externalId": {
# "id": 1033,
# "externalId": "user2externalId",
# "isLocked": 0,
# },
# }
# if users are in Active Directory but not in Jira Align create the user
for username in ad_users:
if username not in ja_users:
user_to_add = {
"externalId": username,
# Some of the parameters came from our HR System.
# You could take this from Active Directory
"firstName": hr_user.preferred_first_name,
"lastName": hr_user.preferred_last_name,
"title": hr_user.title,
"email": hr_user.primary_work_email,
# These mandatory region & city parameters were set according to a conversion that we made.
"regionId": default_region_id,
"cityId": default_city_id,
# These mandatory role, cost center and organistion structure were all set to a default for the purposes of our provisioning script.
"roleId": default_role_id,
"costCenterId": default_cost_center_id,
"divisionId": default_division_id,
# isExternal=0 means that the user is an Internal User
"isExternal": 0,
}
# Now we use our API to add the user into Jira Align via API
add_user(user_to_add)
# if users were created by connector, they will be missing divisionId and cityId
# we need to add divisionId and cityId before we can enable/disable them
# an alternative solution may be to ignore them based on their roleId - if they are an integrated user
for username, ja_attrs in ja_users.items():
if not ja_attrs['divisionId'] or not ja_attrs['cityId']:
# assign default divisionId and cityId
add_missing_div_city(ja_attrs['id'], default_division_id, default_city_id)
# Enable deactivated Jira Align users who are in Active Directory group
for username, ja_attrs in ja_users.items():
if username in ad_users and ja_attrs["isLocked"] == -1:
enable_user(ja_attrs["id"])
# Disable active Jira Align users that are not in Active Directory group
for username, ja_attrs in ja_users.items():
if username not in ad_users and ja_attrs["isLocked"] == 0:
disable_user(ja_attrs["id"])