Skip to main content
Example scripts
arrow icon
To homepage
Confluence
Cloud icon
Cloud

Deactivate inactive cloud user accounts

Created 6 month(s) ago, Updated 27 day(s) ago
App in script
ScriptRunner For Confluence
ScriptRunner For Confluence
by Adaptavist
Compatibility
compatibility bullet
Confluence
Language |
groovy
import java.text.SimpleDateFormat
import com.atlassian.confluence.rest.clientv1.model.User
import com.atlassian.confluence.rest.clientv1.model.Group

// NOTE: Make sure to setup ORG_ID and ORG_API_KEY before running this script.
int USER_LIMIT_PER_GROUP = 10 // Define the user limit you want to maintain for the group.
def CUTOFF_DATE = "2024-01-01" // Follow the format YYYY-MM-DD.
def GROUP_NAME = "confluence-users" // Define the group where you want to find the inactive users.
def DISABLED_USERS_GROUP = "disabled-users-group" // Create the group before you run the script.
def FORCE_DISABLED = false // True: Disable inactive Confluence users, no matter if users has other product access (e.g. Jira).

def disabledUsersGroupId = fetchDisabledUsersGroupId(DISABLED_USERS_GROUP)
def groups = fetchGroups(GROUP_NAME)
def inactiveUsers = fetchInactiveUserIds(groups, CUTOFF_DATE, FORCE_DISABLED, USER_LIMIT_PER_GROUP)
logger.info("Found ${inactiveUsers.size()} users to disable")
disableUsers(groups, inactiveUsers, CUTOFF_DATE, disabledUsersGroupId)

void disableUsers(List<Group> groups, def inactiveUsers, def cutoffDateStr, def disabledUsersGroupId) {
    inactiveUsers.each { userId ->
        def res = post("https://api.atlassian.com/users/${userId}/manage/lifecycle/disable")
                .header('Content-Type', 'application/json')
                .header("Authorization", "Bearer ${ORG_API_KEY}")
                .body([
                        "message": "Disable user for cutoff date: ${cutoffDateStr}",
                ])
                .asObject(Map)
        if (res.status != 204) {
            logger.warn("Failed to disable user with id: ${userId}")
            return
        }
        removeUserFromTheGroup(groups, userId)
        moveUserToDisabledGroup(userId, disabledUsersGroupId)
    }
}

void moveUserToDisabledGroup(def accountId, def disabledUsersGroupId) {
    def res = post("https://api.atlassian.com/admin//v1/orgs/${ORG_ID}/directory/groups/${disabledUsersGroupId}/memberships")
            .header('Content-Type', 'application/json')
            .header("Authorization", "Bearer ${ORG_API_KEY}")
            .body([
                    "account_id": accountId,
            ])
            .asObject(Map)

    if (res.status != 200) {
        logger.warn("Failed to move user: ${accountId} to disabled-users-group")
    }
}

void removeUserFromTheGroup(List<Group> groups, def accountId) {
    groups.each { group ->
        def res = delete("https://api.atlassian.com/admin/v1/orgs/${ORG_ID}/directory/groups/${group.id}/memberships/${accountId}")
                .header("Authorization", "Bearer ${ORG_API_KEY}")
                .asObject(Map)
        if (res.status != 200) {
            logger.warn("Failed to remove user: ${accountId} from the group: ${group.name}")
        }
    }
}

Set<String> fetchInactiveUserIds(List<Group> groups, String cutoffDateStr, def forceEnabled, int userLimit) {
    def usersToDisable = []
    groups.each { group ->
        def users = fetchMembersInGroup(group.id)
        if (users.size() < userLimit) {
            return
        }
        users.each { user ->
            def res = get("https://api.atlassian.com/admin/v1/orgs/${ORG_ID}/directory/users/${user.accountId}/last-active-dates")
                    .header("Authorization", "Bearer ${ORG_API_KEY}")
                    .asObject(Map).body
            def dateFormat = new SimpleDateFormat("yyyy-MM-dd")
            def cutoffDate = dateFormat.parse(cutoffDateStr)
            def products = (List) res["data"]["product_access"]
            def targetProduct = products.find { product ->
                product["key"] == "confluence"
            }
            if (!targetProduct) {
                logger.warn("The user ${user.displayName} has no access to confluence product.")
                return
            }
            if (!forceEnabled && products.size() > 1) {
                logger.warn("The user ${user.displayName} has access to other products. If you want to disable it by force. Please set the variable FORCE_DISABLED to true")
                return
            }
            def lastActiveDate = dateFormat.parse((String) targetProduct["last_active"])
            def productKey = targetProduct["key"]
            if (productKey == "confluence" && lastActiveDate.before(cutoffDate)) {
                usersToDisable.add((String) user.accountId)
            }
        }
    }
    // eliminate duplicated userIds
    new HashSet(usersToDisable)
}

List<User> fetchMembersInGroup(def groupId) {
    def members = get("/wiki/rest/api/group/${groupId}/membersByGroupId").asObject(Map).body
    if (!members) {
        throw new RuntimeException("Failed to fetch members or no members found.")
    }
    (List<User>) members.results
}

List<Group> fetchGroups(def groupNames) {
    def groups = get("/wiki/rest/api/group/picker?query=${groupNames}").asObject(Map).body
    if (!groups) {
        throw new RuntimeException("Failed to fetch groups or no groups found.")
    }
    (List<Group>) groups.results
}

def fetchDisabledUsersGroupId(def disabledUserGroupName) {
    def disabledUsersGroup = fetchGroups(disabledUserGroupName)
    if (disabledUsersGroup.size() !== 1) {
        throw new RuntimeException("Failed to find ${disabledUserGroupName} or you have more than one group with the same name")
    }
    disabledUsersGroup[0].id
}
Having an issue with this script?
Report it here