Hi Jira Community
I need a simple overview that shows me which active users (internal and external) have access to which Jira spaces. How do I do that? Even after trying various methods with “Rovo,” I haven't been able to achieve the desired result.
I look forward to your suggestions.
Thank you very much
CRB, Daniel Pfeffer
You can use Python for this where it will export users and projects they belong to into CSV.
You will need your API token that you can get through Account Settings -> Security -> API
Script:
import csv
import sys
import requests
from requests.auth import HTTPBasicAuth
# ==========================================
# CONFIGURATION
# ==========================================
JIRA_URL = "" # Replace with your Jira domain
EMAIL = "" # Your Jira login email
API_TOKEN = "" # Generated from https://id.atlassian.com/manage-profile/security/api-tokens
def get_all_projects(auth):
"""Fetches all visible projects in Jira."""
url = f"{JIRA_URL}/rest/api/3/project"
response = requests.get(url, auth=auth, headers={"Accept": "application/json"})
if response.status_code != 200:
print(f"Error fetching projects: {response.status_code} - {response.text}")
sys.exit(1)
return response.json()
def get_project_access_map(auth):
"""
Iterates through projects and roles to build a mapping of:
Active User -> List of Accessible Projects
"""
projects = get_all_projects(auth)
print(f"Found {len(projects)} projects. Mapping user permissions...\n")
# Mapping structure: { "User Display Name (email)": set("Project Key 1", "Project Key 2") }
user_project_map = {}
for project in projects:
project_key = project.get("key")
project_name = project.get("name")
print(f"Processing project: {project_key} ({project_name})...")
# Get all defined roles for this project
roles_url = f"{JIRA_URL}/rest/api/3/project/{project_key}/role"
roles_resp = requests.get(roles_url, auth=auth, headers={"Accept": "application/json"})
if roles_resp.status_code != 200:
continue
roles = roles_resp.json()
for role_name, role_url in roles.items():
# Get detailed membership data for each role
role_detail_resp = requests.get(role_url, auth=auth, headers={"Accept": "application/json"})
if role_detail_resp.status_code != 200:
continue
role_data = role_detail_resp.json()
# Inspect actors (users/groups) assigned to this role
for actor in role_data.get("actors", []):
# Direct user assignment
if actor.get("type") == "atlassian-user-role-actor":
user_data = actor.get("actorUser", {})
# Only include active users
if user_data.get("active", False):
user_id = f"{user_data.get('displayName')} ({user_data.get('emailAddress', 'No Public Email')})"
user_project_map.setdefault(user_id, set()).add(f"{project_name} [{project_key}]")
# Group assignment (resolves group members)
elif actor.get("type") == "atlassian-group-role-actor":
group_name = actor.get("name")
group_users_url = f"{JIRA_URL}/rest/api/3/group/member?groupname={group_name}"
group_resp = requests.get(group_users_url, auth=auth, headers={"Accept": "application/json"})
if group_resp.status_code == 200:
for user in group_resp.json().get("values", []):
if user.get("active", False):
user_id = f"{user.get('displayName')} ({user.get('emailAddress', 'No Public Email')})"
user_project_map.setdefault(user_id, set()).add(f"{project_name} [{project_key}]")
return user_project_map
def main():
auth = HTTPBasicAuth(EMAIL, API_TOKEN)
access_data = get_project_access_map(auth)
print("\n" + "=" * 60)
print("ACTIVE USER PROJECT ACCESS REPORT")
print("=" * 60)
# Print to console
for user, projects in sorted(access_data.items()):
print(f"\nUser: {user}")
print("Accessible Projects:")
for proj in sorted(projects):
print(f" - {proj}")
# Export to CSV
csv_file = "jira_user_project_access.csv"
with open(csv_file, mode="w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["User Info", "Project Count", "Projects"])
for user, projects in sorted(access_data.items()):
writer.writerow([user, len(projects), ", ".join(sorted(projects))])
print(f"\nReport exported successfully to '{csv_file}'!")
if __name__ == "__main__":
main()
Hi Daniel,
Before building the report, it is worth separating two questions, because they
produce very different lists:
1. Who has access to a project - this is a configuration question.
2. Who is actually working in a project - this is a data question.
Nikola's script answers the first one. If you need the second one, the reliable
source is the issue data itself. Run a search per project over a time window and
collect the actors from the changelog - the issue search endpoint accepts
expand=changelog, and every history entry carries an author, so you get everyone
who really touched the project, not everyone who could have. A lighter version:
assignee, reporter and worklogAuthor over the last 90 days already covers most
real activity.
If you do need the access list, the script is a good base, but walking project
roles covers only one of the places access comes from. These are the gaps that
usually make such a list wrong:
- Permission scheme grants that are not roles. Browse Projects can be granted to
a group directly, to "Any logged in user", to an application role, or through a
user/group picker custom field. Any of those effectively means "everyone with a
license", and it silently makes the role list meaningless. Worth checking
GET /rest/api/3/permissionscheme/{id}/permission?expand=all per project.
- Team-managed projects never appear under Jira admin > Project roles - that
screen is company-managed only. They use a separate model (Open / Limited /
Private, with Administrator / Member / Viewer) and need their own pass.
- Product access. Membership in the Jira product access group
(Admin > Products > Jira > User access) is exactly what makes an "Any logged in
user" grant dangerous, so without it the picture is incomplete.
- Issue security schemes narrow visibility below Browse Projects, so a
project-level list can overstate what a person can actually see.
- Service project customers are unlicensed and do not appear in roles at all. If
"external" in your question means portal customers, they live in Project
settings > Customer permissions, not in the role model.
One practical detail for the script: /rest/api/3/group/member is paginated and
includes inactive users by default, so pass includeInactiveUsers=false if you
want an active-only list - otherwise deactivated accounts inflate the numbers.
Which of the two do you actually need, access or activity? The approach is quite
different, and I can be more specific.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.