Introduction

Managing user permissions in Confluence can be a daunting task, especially for large organizations with numerous spaces. The need to update permissions, whether it’s adding new groups to spaces or removing outdated permissions, requires a solution that can automate these processes efficiently. This blog post introduces a Python script that automates the fetching of space keys, processing of space permissions, and management of group permissions within Atlassian Confluence spaces.

Prerequisites

Before diving into the script, ensure you have the following prerequisites:

  • Python 3: The script is written for Python 3.6 or newer.
  • Requests Library: This script uses the requests library for making HTTP requests to the Confluence REST API. Install it via pip if you haven’t already: pip install requests.
  • Confluence Cloud Access: You need access to a Confluence Cloud instance with administrative permissions.
  • API Token: An API token from your Atlassian account for authentication.

Configuration

The script uses a configuration file (config.py) to store sensitive information such as the Confluence domain, user email, and API token. Here’s an example configuration:

cloud = {
'domain': 'your-confluence-domain',
'username': 'your-email@example.com',
'token': 'your-api-token',
}

The Script

import requests
from requests.auth import HTTPBasicAuth
import json
import os
import logging
from config import cloud  # Import cloud configurations

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')

# Use configurations from cloud dictionary
USER_EMAIL = cloud['username']
USER_TOKEN = cloud['token']
CLOUD_BASE_URL = f"https://{cloud['domain']}.atlassian.net"

OLD_GROUP = 'confluence-users'
NEW_GROUP_ID = '9ad65bbc-7a0a-4c2d-a503-dde5e36c6e7d'


# Authentication
auth = HTTPBasicAuth(USER_EMAIL, USER_TOKEN)

headers = {
    "Accept": "application/json",
    "Content-Type": "application/json"
}

# Ask the user if they want to delete old permissions
try:
    delete_old_permissions = input("Do you want to delete the old permissions? [Y/n]: ").strip().lower() or "y"
    delete_old_permissions = delete_old_permissions == "y"
except Exception as e:
    logging.info("Running in a non-interactive environment. Defaulting to delete old permissions.")
    delete_old_permissions = True


def fetch_space_keys(limit=50):
    """
    Fetch space keys using Confluence REST API with pagination.
    """
    space_keys = []
    start_at = 0
    while True:
        url = f"{CLOUD_BASE_URL}/wiki/rest/api/space?limit={limit}&start={start_at}"
        response = requests.get(url, auth=auth, headers=headers)
        if response.status_code != 200:
            logging.error(f"Failed to fetch spaces. Status code: {response.status_code}")
            break
        data = response.json()
        spaces = data.get('results', [])
        if not spaces:
            break
        for space in spaces:
            space_keys.append(space.get('key'))
        if 'next' not in data['_links']:
            break
        start_at += limit
    return space_keys

def process_space_permissions(space_key):
    """
    Process permissions for a given space key.
    """
    permissions_url = f'{CLOUD_BASE_URL}/wiki/rest/api/space/{space_key}?expand=permissions'
    try:
        permissions_response = requests.get(permissions_url, auth=auth, headers=headers)
        if permissions_response.status_code == 200:
            permissions = permissions_response.json()['results'][0]['permissions']
            permission_list, delete_list = [], []

            for permission in permissions:
                if 'group' in permission['subjects']:
                    for subject in permission['subjects']['group']['results']:
                        if subject["name"] == OLD_GROUP:
                            permission_list.append({
                                'operation': permission["operation"],
                                'targetType': permission["targetType"]
                            })
                            delete_list.append({'id': permission["id"]})

            if permission_list:
                add_group(space_key, permission_list)
                if delete_old_permissions:
                    remove_permissions(delete_list, space_key)
        else:
            logging.warn(f"Failed to retrieve permissions for space {space_key}. Status code: {permissions_response.status_code}")
    except requests.RequestException as e:
        logging.error(f"Error processing space permissions: {e}")

def remove_permissions(delete_list, space_key):
    """
    Remove permissions based on a given list and space key.
    """
    for permission in delete_list:
        url = f'{CLOUD_BASE_URL}/wiki/rest/api/space/{space_key}/permission/{permission["id"]}'
        try:
            response = requests.delete(url, auth=auth)
            if response.status_code == 204:
                logging.info(f"Permission {permission['id']} removed successfully.")
            else:
                logging.warn(f"Failed to remove permission {permission['id']}. Status code: {response.status_code}")
        except requests.RequestException as e:
            logging.error(f"Error removing permission: {e}")

def add_group(space_key, permission_list):
    """
    Add a group to a space based on the permission list.
    """
    url = f'{CLOUD_BASE_URL}/wiki/rest/api/space/{space_key}/permission'
    for permission in permission_list:
        payload = json.dumps({
            "subject": {
                "type": "group",
                "identifier": NEW_GROUP_ID
            },
            "operation": permission,
            "_links": {}
        })
        try:
            response = requests.post(url, data=payload, headers=headers, auth=auth)
            if response.status_code == 200:
                logging.info(f"Group added successfully to space {space_key}.")
            else:
                logging.warn(f"Failed to add group to space {space_key}. Status code: {response.status_code}")
        except requests.RequestException as e:
            logging.error(f"Error adding group: {e}")
# Main execution
if __name__ == "__main__":
    space_keys = fetch_space_keys()

    for space_key in space_keys:
        process_space_permissions(space_key)

Understanding the Script

The script is structured into several key functions, each handling a specific part of the permission management process:

  1. Logging Setup: Utilizes Python’s logging library for clean and informative output regarding the script’s execution status.
  2. Configuration and Authentication: Fetches user credentials and API token from the config.py file for authenticating API requests.
  3. User Input for Permission Deletion: Asks the user whether they wish to delete old permissions, defaulting to True to facilitate automation in non-interactive environments.
  4. Fetching Space Keys: Implements pagination to fetch all space keys from Confluence, ensuring scalability for instances with a large number of spaces.
  5. Processing Space Permissions: For each space, the script fetches current permissions, identifies which permissions are associated with the old group, and prepares lists for adding new permissions and deleting old ones.
  6. Adding and Removing Permissions: Executes API calls to add a new group to spaces and, based on user input, removes permissions associated with an old group.

Running the Script

To run the script, simply execute it in your terminal or command prompt:

python script_name.py

Make sure to replace script_name.py with the actual name of your Python script.

Conclusion

This Python script offers a streamlined approach to managing Confluence space permissions, significantly reducing the manual effort required for administrators. By automating the addition of new groups to spaces and the removal of outdated permissions, organizations can ensure their Confluence instance remains secure and well-organized with minimal effort.