Skip to content

Instantly share code, notes, and snippets.

@rhodessteve
Created June 18, 2025 14:09
Show Gist options
  • Save rhodessteve/66189c56337bddc962df3cafee555ae3 to your computer and use it in GitHub Desktop.
Save rhodessteve/66189c56337bddc962df3cafee555ae3 to your computer and use it in GitHub Desktop.
import requests
import json
import base64
import time
#import re # Import the regular expression library
JIRA_DOMAIN = "yourdomain.atlassian.net"
API_TOKEN = "yourlongtokenhere"
EMAIL = "youremail@domain.com"
PROJECT_KEYS = ["ABC", "KLF", "UW"] # Add all your desired project keys here
# --- EMAIL DOMAIN EXCLUSION CONFIGURATION ---
# Set this to a domain name (e.g., "domain.com") to exclude users with that email domain.
# Set this to "none" (lowercase string) to include all active human users regardless of their email domain.
DOMAIN_EXCLUDE = "domain.com"
# DOMAIN_EXCLUDE = "none" # Uncomment this line and comment the above to include all users regardless of domain
# ---------------------------------------------
AUTH_STRING = base64.b64encode(f"{EMAIL}:{API_TOKEN}".encode()).decode()
HEADERS = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Basic {AUTH_STRING}",
}
# --- Global Cache for User Details ---
# This prevents fetching the same user's details multiple times if they are in multiple projects
user_details_cache = {}
# -------------------------------------
# This will store the final filtered users, grouped by project
all_project_filtered_users = {}
print(f"Starting multi-project user retrieval for: {', '.join(PROJECT_KEYS)}")
try:
for current_project_key in PROJECT_KEYS:
print(f"\n--- Processing Project: {current_project_key} ---")
potential_account_ids_for_project = set()
current_project_filtered_users = []
# 1. Get Project Roles for the current project
roles_url = f"https://{JIRA_DOMAIN}/rest/api/3/project/{current_project_key}/role"
print(f" Calling roles API for {current_project_key}: {roles_url}")
roles_response = requests.get(roles_url, headers=HEADERS)
roles_response.raise_for_status()
project_roles = roles_response.json()
print(" Project Roles successfully retrieved.")
# Iterate through each role
for role_name, role_url in project_roles.items():
role_id = role_url.split("/")[-1]
# 2. Get Actors for the specific role in the current project
role_actors_url = f"https://{JIRA_DOMAIN}/rest/api/3/project/{current_project_key}/role/{role_id}"
actors_response = requests.get(role_actors_url, headers=HEADERS)
actors_response.raise_for_status()
role_data = actors_response.json()
# Collect all 'atlassian-user-role-actor' account IDs for this project
for actor in role_data.get("actors", []):
if actor.get("type") == "atlassian-user-role-actor":
actor_user_data = actor.get("actorUser")
if actor_user_data and "accountId" in actor_user_data:
account_id = actor_user_data["accountId"]
potential_account_ids_for_project.add(account_id)
print(
f" DEBUG: Finished collecting potential account IDs for {current_project_key}."
f" Found {len(potential_account_ids_for_project)} unique IDs."
)
# 3. For each unique account in this project, query details (using cache) and apply filters
if potential_account_ids_for_project:
print(f" Checking 'active' status, 'accountType', and custom email criteria for {current_project_key}...")
for account_id in potential_account_ids_for_project:
user_data = None
if account_id in user_details_cache:
user_data = user_details_cache[account_id]
# print(f" (Cached) Details for {account_id}") # Optional: show when cache is used
else:
# Make API call only if not in cache
user_details_url = f"https://{JIRA_DOMAIN}/rest/api/3/user?accountId={account_id}"
user_details_response = requests.get(user_details_url, headers=HEADERS)
if user_details_response.status_code == 200:
user_data = user_details_response.json()
user_details_cache[account_id] = user_data # Add to cache
# print(f" (Fetched) Details for {account_id}") # Optional: show when fetched
elif user_details_response.status_code == 403:
print(f" WARNING: Permission denied to fetch details for {account_id}. Skipping.")
print(f" Response content: {user_details_response.text}")
else:
print(f" ERROR fetching details for {account_id}: {user_details_response.status_code}")
print(f" Response content: {user_details_response.text}")
time.sleep(0.1) # Small delay after each API call
if user_data: # Only proceed if user_data was successfully obtained
user_email = user_data.get("emailAddress")
is_active = user_data.get("active")
is_human_account = user_data.get("accountType") == "atlassian"
meets_email_criteria = False
if DOMAIN_EXCLUDE.lower() == "none":
meets_email_criteria = True
else:
exclude_domain_lower = DOMAIN_EXCLUDE.lower()
if (
not user_email # User has no email listed
or (user_email and not user_email.lower().endswith(f"@{exclude_domain_lower}")) # User has email, but not the excluded domain
):
meets_email_criteria = True
if is_active and is_human_account and meets_email_criteria:
current_project_filtered_users.append({
"displayName": user_data.get("displayName"),
"accountId": user_data.get("accountId"),
"emailAddress": user_email,
})
else:
print(" No potential account IDs collected for this project.")
# Store the filtered users for the current project
all_project_filtered_users[current_project_key] = current_project_filtered_users
# --- Final Summary for All Projects ---
print("\n" + "="*50)
print("--- Consolidated Summary of Filtered Active Human Users by Project ---")
print("="*50)
total_overall_filtered_users = 0
for project_key, users_list in all_project_filtered_users.items():
print(f"\nProject: {project_key} (Total: {len(users_list)})")
if users_list:
for user_info in users_list:
print(
f" Display Name: {user_info['displayName']}, Account ID:"
f" {user_info['accountId']}, Email:"
f" {user_info.get('emailAddress', 'N/A')}"
)
total_overall_filtered_users += len(users_list)
else:
print(" No active human users matching the criteria found for this project.")
print(f"\n--- Overall Total Active Human Users Across All Projects: {total_overall_filtered_users} ---")
print("="*50)
except requests.exceptions.HTTPError as err:
print(f"HTTP Error occurred: {err}")
print(f"Response content: {err.response.text}")
except requests.exceptions.RequestException as err:
print(f"An error occurred: {err}")
except json.JSONDecodeError:
print("Failed to parse JSON response. Check if API Token/Email are correct or response format.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment