Scriptrunner deactivate inactive users

Michael
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 15, 2024

Hi all,

We are trying to keep our user licenses under control, and have found the following code within the ScriptRunner library:

import com.atlassian.jira.bc.JiraServiceContextImpl
import com.atlassian.jira.bc.user.UserService
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.bc.user.search.UserSearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.crowd.embedded.api.CrowdService
import groovy.xml.MarkupBuilder

import java.time.LocalDateTime
import java.time.Instant
import java.time.ZoneId

def crowdService = ComponentAccessor.getComponent(CrowdService)

// Number of days the user was not logged in Date
def numOfDays = 30
def dateLimit = LocalDateTime.now().minusDays(numOfDays)

// Search all active users
UserSearchParams.Builder paramBuilder = UserSearchParams.builder()
.allowEmptyQuery(true)
.includeActive(true)
.includeInactive(false)

def jiraServiceContext = new JiraServiceContextImpl(ComponentAccessor.jiraAuthenticationContext.loggedInUser)
def allActiveUsers = ComponentAccessor.getComponent(UserSearchService).findUsers(jiraServiceContext, '', paramBuilder.build())

// Users which last activity is before than limit date
def usersToDelete = allActiveUsers.findAll { user ->
def userWithAtributes = crowdService.getUserWithAttributes(user.username)
def lastLoginMillis = userWithAtributes.getValue('login.lastLoginMillis')

if (lastLoginMillis?.number) {
def lastLogin = Instant.ofEpochMilli(Long.parseLong(lastLoginMillis)).atZone(ZoneId.systemDefault()).toLocalDateTime()
if (lastLogin.isBefore(dateLimit)) {
user
}
}
}

if (!usersToDelete) {
return 'No Idle users found'
}

def stringWriter = new StringWriter()
def content = new MarkupBuilder(stringWriter)

content.html {
p('Follow users deactivated ') {
ul {
usersToDelete.each {
user -> deactivateUser(user)
}*.username?.each { deactivated ->
li(deactivated)
}
}
}
}

stringWriter.toString()

def deactivateUser(ApplicationUser user) {
def userService = ComponentAccessor.getComponent(UserService)
def updateUser = userService.newUserBuilder(user).active(false).build()
def updateUserValidationResult = userService.validateUpdateUser(updateUser)

if (!updateUserValidationResult.valid) {
log.error "Update of ${user.name} failed. ${updateUserValidationResult.errorCollection}"
return
}

userService.updateUser(updateUserValidationResult)
log.info "${updateUser.name} deactivated"
}

This would work, but we would also like to ensure that none of our admins are ever "De-Activated" if they are working within other Jira instances that month.

Can anyone suggest a way to include multiple groups that are not "searched" for de-activation?

Thanks,

Mike

2 answers

1 accepted

0 votes
Answer accepted
Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 16, 2024

Hi @Michael

For your requirement, you could try something like the below in the ScriptRunner console:-

import com.adaptavist.hapi.jira.groups.Groups
import com.adaptavist.hapi.jira.users.Users
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.login.LoginManager

def adminGroup = Groups.getByName('jira-administrators').members
def jiraUserGroup = Groups.getByName('jira-software-users').members
def jsmUserGroup = Groups.getByName('jira-servicedesk-users').members

def users = adminGroup + jiraUserGroup + jsmUserGroup
users.unique()

def loginManager = ComponentAccessor.getComponentOfType(LoginManager)

users.each {
def username = it.username
def active = it.active

def lastLoginTime = loginManager.getLoginInfo(it.username).lastLoginTime

if (!active || !lastLoginTime) {
Users.getByName(username).deactivate()
}
}

Please note that the sample code above is not 100% exact to your environment. Hence, you will need to make the required modifications.

The code above does a check for any inactive or users that have never logged in. If found they will be deactivated.

I hope this helps to solve your question. :-)

Thank you and Kind regards,

Ram

Michael
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 16, 2024

Hello again @Ram Kumar Aravindakshan _Adaptavist_

Thanks for the help.

To clarify, we really don't want to "de-activate" anyone, we just want to remove their "jira-software-user" group so that they aren't using up a Jira license. (Sorry for the mis-communication before)

Also will this allow us to add specific custom admin groups which will never be checked for removal? If so - can you please point me to the section where I can customize it to include our custom admin groups?

We'd like to make sure none of our admin groups are ever removed from the "jira-software-user" group.

 

**Update**

I've tried my best to put together some code based on the above for what I'm looking for below:

import com.adaptavist.hapi.jira.groups.Groups
import com.adaptavist.hapi.jira.users.Users
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.login.LoginManager

//For removal of user from group
def groupManager = ComponentAccessor.groupManager
def userUtil = ComponentAccessor.userUtil
def userManager = ComponentAccessor.userManager

def adminGroup = Groups.getByName('jira-administrators').members
def jiraUserGroup = Groups.getByName('jira-software-users').members
def customAdminGroup = Groups.getByName('CMS Support').members

def users = jiraUserGroup
users.unique()

def loginManager = ComponentAccessor.getComponentOfType(LoginManager)

users.each {
    def username = it.username
    def active = it.active

    def lastLoginTime = loginManager.getLoginInfo(it.username).lastLoginTime

        if (!lastLoginTime && (!adminGroup || !customAdminGroup)) {
            userUtil.removeUserFromGroup(jira-software-user, username)
        }

}

But it's incomplete, and I don't know how to change it to fix it :(

Could you possibly help again?

Thanks,

Mike

 

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 16, 2024

Hi @Michael

Removing the inactive users from the Group is a different requirement from what you mentioened early.

I'll get back to you once I have modified the code.

Thank you and Kinder regards,

Ram

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 17, 2024

Hi @Michael

If you want to remove an inactive user from a particular Group, you can try the updated code below on the ScriptRunner Console:-

import com.adaptavist.hapi.jira.groups.Groups
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.login.LoginManager

def adminGroup = Groups.getByName('jira-administrators')
def jiraUserGroup = Groups.getByName('jira-software-users')
def jsmUserGroup = Groups.getByName('jira-servicedesk-users')

def adminGroupMembers = adminGroup.members
def jiraUserGroupMembers = jiraUserGroup.members
def jsmUserGroupMembers = jsmUserGroup.members

def users = adminGroupMembers + jiraUserGroupMembers + jsmUserGroupMembers
users.unique()

def loginManager = ComponentAccessor.getComponentOfType(LoginManager)

users.each {
def username = it.username
def active = it.active

def lastLoginTime = loginManager.getLoginInfo(it.username).lastLoginTime

if (!active || !lastLoginTime) {
jiraUserGroup.remove(username)
}
}

Please note that the sample code above is not 100% exact to your environment. Hence, you will need to make the required modifications.

Below are a couple of test screenshots for your reference:-

1. Before the script is executed, all the users are in the jira-software-users group as shown in the screenshot below:-

before.png

2. After the script has been executed, all the inactive users are removed from the jira-software-users group as shown in the screenshot below:-

after.png

I hope this helps to solve your question. :-)

Thank you and Kind regards,

Ram

James Carn February 20, 2024

Could this be amended to use in Scriptrunner for Cloud, this would be useful in my system

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 20, 2024

Hi @James Carn

This not applicable for ScriptRunner for Jira cloud.

In the cloud environment you cannot invoke the objects directly. You need to make GET and POST requests to remove the users from the Groups.

Thank you and Kind regards,

Ram

Kristian Walker (Adaptavist)
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 20, 2024

Hi James,

In Jira Cloud you would need to call the Add user to a Group and Remove User from a Group Rest APIs to add and remove users from a group.

However, in Jira Cloud it is not possible to get the last login time for a user as Atlassian does not provide this on the get User api.

However, they provide the Users Last Active Dates API to see when users last accessed each product which could be used to see when users last interacted with a specific Jira feature. 

I hope this helps.

Regards,

Kristian

Like James Carn likes this
Michael
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 20, 2024

Hi again @Ram Kumar Aravindakshan _Adaptavist_

Thanks for the updated code, sorry for the previous miss-communcation about removing users from a group.

Within the most recent code, is it possible to select specific groups which the code does not "scan" for in-active users? We have several custom made admin groups which contain users who will only log-in once every few months, but that we don't want to remove access from. would I be able to add an "&&" statement within the if statement for these groups?

Ex:

if (!active || !lastLoginTime && !Groups.getByName('custom_group_name')) {
jiraUserGroup.remove(username)
}
}

Thanks,

Mike

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 20, 2024

Hi @Michael

So if you want to exclude certain users that belong to other specific groups from being removed from of jira-software-users group, you can try something like this:-

 

import com.adaptavist.hapi.jira.groups.Groups
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.login.LoginManager

def adminGroup = Groups.getByName('jira-administrators')
def jiraUserGroup = Groups.getByName('jira-software-users')
def jsmUserGroup = Groups.getByName('jira-servicedesk-users')
def devGroup = Groups.getByName('Development')

def adminGroupMembers = adminGroup.members
def jiraUserGroupMembers = jiraUserGroup.members
def jsmUserGroupMembers = jsmUserGroup.members
def devGroupMembers = devGroup.members

def users = adminGroupMembers + jiraUserGroupMembers + jsmUserGroupMembers
users.unique()

def loginManager = ComponentAccessor.getComponentOfType(LoginManager)

users.removeAll {
it in devGroupMembers
}

users.each {
def username = it.username
def active = it.active

def lastLoginTime = loginManager.getLoginInfo(it.username).lastLoginTime

if (!active || !lastLoginTime) {
jiraUserGroup.remove(username)
}
}

Please note the sample working code above is not 100% exact to your environment. Hence, you will need to make the required modifications.

In the code above, all users that belong to the jira-administrators, jira-software-users and jira-servicedesk-users are combined in the users list.

However, if any of the users in the users list belong to the Development group, they are excluded from the list.

Hence, the users belonging to the Development group will not be removed from the jira-software-users group. The other users will be taken out.

The only modification made in the code is the inclusion of these lines:-

def devGroupMembers = devGroup.members

....
....
....

users.removeAll {
it in devGroupMembers
}

 I hope this helps to solve your question. :-)

Thank you and Kind regards,

Ram

 

Michael
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 21, 2024

Hello again @Ram Kumar Aravindakshan _Adaptavist_

Thank you for the latest code block! It seems to work if there is only a single group added to that users.removeAll section, however; if I add several groups to that section, it looks like only the last group entered before the "}" seems to be excluded.

Ex:

import com.adaptavist.hapi.jira.groups.Groups
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.login.LoginManager

def adminGroup = Groups.getByName('PC Management')
//def jiraUserGroup = Groups.getByName('jira-software-users')
def testGroup = Groups.getByName('PeopleCloud - Base Users')
def exampleGroup = Groups.getByName('brand-experience-group')

def
adminGroupMembers = adminGroup.members
//def jiraUserGroupMembers = jiraUserGroup.members
def testGroupMembers = testGroup.members
def exampleGroupMembers = exampleGroup.members

def users = exampleGroupMembers
users.unique()

def loginManager = ComponentAccessor.getComponentOfType(LoginManager)

users.removeAll {
    it in testGroupMembers
    it in adminGroupMembers

}

users.each {
    def username = it.username
    def active = it.active

    def lastLoginTime = loginManager.getLoginInfo(it.username).lastLoginTime

        if (!active || !lastLoginTime) {
            exampleGroup.remove(username)

        }

}

I changed the groups in the above to make sure the code worked before unleashing it on all of our users, and after it runs, the only group that was "excluded" from the search was the adminGroupMembers group. All users within the testGroupMembers had their 'brand-experience-group' group removed.

I've also tried to combine the two groups into a single line with a comma as well as an or statement "||" but neither of those seemed to work for me.

Can you please help me figure out how to exclude all groups under the removeAll section?

Thanks,

Mike

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 22, 2024

Hi @Michael

After reading through your last comment, the modification you have made to the code will not work. The line below will fail:-

users.removeAll {
    it in testGroupMembers
    it in adminGroupMembers

}

The parameter is the removeAll { } method is a boolean method and works on boolean values returned in the condition.

Adding in one after the other doesn't mean it will exclude users in multiple groups.

For the condition to work, you must try something like this:-

users.removeAll {
it in adminGroupMembers || it in testGroupMembers
}

The condition above means that if the user is in the Admin Group or the Test Group, that user will be removed from the users list and excluded from group removal.

Please make the suggested change above and rerun the test.

Thank you and Kind regards,

Ram

Michael
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 22, 2024

Thanks Ram,

That worked perfectly! Have a great day ahead.

~Mike

0 votes
Jesus Octavio Gutierrez Villegas March 11, 2024

Hello, quick question @Ram Kumar Aravindakshan _Adaptavist_ // @Kristian Walker (Adaptavist) 

is this possible with Scriptrunner for Jira Cloud yet?

thanks a lot, kind regards

Kristian Walker (Adaptavist)
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 11, 2024

Hi Jesus,

It is still the same as the answer above: you cannot get users last login dates but add or remove users from groups with ScriptRunner for Jira Cloud.

Regards,

Kristian

Suggest an answer

Log in or Sign up to answer