To save on licensing costs, it is sometimes useful to automatically deactivate Jira users who haven't logged in within a certain period, say 6 months. Here we review the options, and provide a ScriptRunner script (source in github) that does the job. |
First, it's worth thinking through the rules for which users you want to be automatically deactivated:
How about users that have never logged in? It depends on the age of their user account: if it was created yesterday, but they haven't logged in yet, leave the account alone; if it was created last year and they haven't logged in, it should be deactivated. So let's also deactivate users whose account was created more than X months ago, AND who have never logged in.
Incidentally, if you do a web search for Jira deactivate inactive users' you will see many solutions, like this ScriptRunner script from Adaptavist, that don't handle this edge case (probably because Jira's regular API doesn't expose the 'created' date). |
Our first generic solution is a ScriptRunner for Jira Groovy script. It deactivates users matching rules 1, 2 and 3, namely users in the Internal Directory (1) who have not logged in X months, or who have never logged in to an account created more than X months ago.
To put this script into production:
$JIRAHOME/scripts/deactivate_inactive_users.groovy
. Make it owned by root but readable by group jira.I am finding that users are not actually deactivated, when the script is run like this as a service. The script runs successfully judging by the logs, but users are unaffected. YMMV - I have not yet debugged this, and it may affect only my Jira 8.5.1 instance. |
If all looks good, go to Jira's Services admin page, and add a service of type com.onresolve.jira.groovy.GroovyService
How about if your rules for who to deactivate need to be more sophisticated than just 'user hasn't logged in in 6 months'?
Consider the use of role accounts, as would exist if you crowdsource the triaging of issues. Role accounts are assigned issues, but never log in. The script above would deactivate role accounts, causing chaos.
So we need to refine our rule for which accounts can be deactivated. For role accounts, we know they are being frequently assigned issues. So we can use the "date of last assign" as another indicator that the account is used.
Figuring out our last login date in code was painful enough: calculating the last assign is a bridge too far. This is a job for SQL, not code.
Our solution is as follows:
Here is Postgres-flavoured SQL, creating a queries.inactive_users
materialized view, of users that can be deactivated:
Here is a corresponding Groovy script that reads usernames from the view, and deactivates those accounts:
The script should be installed in $JIRAHOME/scripts/deactivate_inactive_users.groovy
and invoked automatically as a service, as described above.
Before writing the ScriptRunner Groovy scripts above, I considered (and discarded) a few other options.
As of , the only relevant plugin is Manage Inactive Users. This free plugin also supports deactivating users in external user bases like Okta and Google Apps.
I am waiting on feedback from the author before passing judgement. The MIU plugin author released new versions that IMO bring the plugin into the realms of usability (previously even the definition of 'inactive' was completely opaque and unmodifiable). For users not keen on Groovy, I suggest giving this plugin a serious try.
Without any plugins, the cleanest solution would be a script utilitizing Jira's REST interface. The script would search for inactive users with Crowd CQL, then deactivate them. A REST solution would have the advantage of also working on Cloud Jira.
As a preliminary experiment, here is a demonstration of running Crowd Query Language against Jira:
# curl --silent --get -u cli:cli http://jira.localhost/rest/usermanagement/1/search -d 'entity-type=user' --data-urlencode 'restriction=active=true and email=jeff@redradishtech.com and createdDate>2013-09-02' --header 'Accept: application/json' | jq . { "expand": "user", "users": [ { "link": { "href": "http://jira.localhost/rest/usermanagement/1/user?username=jturner", "rel": "self" }, "name": "jturner" } ] } |
(create the 'cli' username/password in JIra's "User Server" admin page)
In a perfect world Jira would let us find exactly the users we want to deactivate with Crowd Query Language expression lastLogin > -6m OR (!lastLogin AND createdDate<-6m)
. Sadly 'lastLogin.lastLoginMillis' is considered a 'secondary' property which Crowd CQL doesn't support. Crowd CQL also doesn't support relative dates like '-6m'.
Without decent CQL support, our REST script would need to retrieve every active user, iterate through them, and check each user's last login date / created date. This may be slow and memory-intensive.
Another spanner in the works: Jira only gained a user deactivate REST method in JIRA 8.3+. See . Users of earlier releases would have to write their own REST endpoint using ScriptRunner: https://www.mos-eisley.dk/display/ATLASSIAN/Deactivate+a+User+via+REST
Given the potential slowness, and lack of REST support, I didn't pursue this route too far.
We have SQL identifying exactly what accounts we want to deactivate. Couldn't we just change the SELECT to an UPDATE that sets active=0
, and do the deactivation directly in the database?
Atlassian apps generally have caching layers that prevent direct database changes from working, but in my experience, Crowd picks up changes to cwd_user
immediately, so this approach could work. The Crowd Query Language (CQL) is presumably implemented with Lucene, and would have stale results. Is this critical?
I haven't researched this much further, as instances I work with all have ScriptRunner available.
Using ScriptRunner, we have implemented a means for Jira to automatically deactivate inactive users, thus saving license slots. This is (to my knowledge, as of ) the only implementation that handles never-logged-in users. Users who require more flexibility can use the SQL-augmented approach.