Skip to content

Expiration Reminder E-Mails with Adaptavist Script Runner

If you are using API Token Authentication for Jira and also Adaptavist ScriptRunner, you can add a script job that runs once a day and sends expiration warnings to various users.
Here is how you can do this. 


Add Adaptavist Script Runner Job

  • provide a name for the job

  • pick a user with appropriate permissions to run the script

  • schedule the script to run once every day


image2022-5-2_14-26-10.png
  • add the following script to the inline script section

you can ignore the type check and other warnings like the below, the script will still work

image2022-5-2_15-10-35.png


  1. iimport static groovyx.net.http.ContentType.JSON
  2. import groovyx.net.http.RESTClient
  3. import com.atlassian.jira.user.util.UserManager
  4. import com.atlassian.jira.user.UserPropertyManager
  5. import com.atlassian.jira.security.groups.GroupManager
  6. import com.opensymphony.module.propertyset.PropertySet;
  7. import com.atlassian.jira.component.ComponentAccessor
  8. import com.atlassian.jira.mail.Email
  9. import com.atlassian.jira.mail.settings.MailSettings
  10. import com.atlassian.mail.MailException
  11. import com.atlassian.mail.server.SMTPMailServer
  12. import com.atlassian.plugin.util.ContextClassLoaderSwitchingUtil
  13. import org.apache.log4j.Level
  14. import org.apache.log4j.Logger
  15. import java.text.SimpleDateFormat
  16. import groovy.time.TimeCategory
  17. // send email function
  18. String sendEmail(String emailAddr, String subject, String body) {
  19. def logger = Logger.getLogger(getClass())
  20. logger.setLevel(Level.DEBUG)
  21. // Stop emails being sent if the outgoing mail server gets disabled (useful if you start a script sending emails and need to stop it)
  22. def mailSettings = ComponentAccessor.getComponent(MailSettings)
  23. if (mailSettings?.send()?.disabled) {
  24. return 'Your outgoing mail server has been disabled'
  25. }
  26. def mailServer = ComponentAccessor.mailServerManager.defaultSMTPMailServer
  27. if (!mailServer) {
  28. logger.debug('Your mail server Object is Null, make sure to set the SMTP Mail Server Settings Correctly on your Server')
  29. return 'Failed to Send Mail. No SMTP Mail Server Defined'
  30. }
  31. def email = new Email(emailAddr)
  32. email.setMimeType('text/html')
  33. email.setSubject(subject)
  34. email.setBody(body)
  35. try {
  36. // This is needed to avoid the exception about IMAPProvider
  37. ContextClassLoaderSwitchingUtil.runInContext(SMTPMailServer.classLoader) {
  38. mailServer.send(email)
  39. }
  40. } catch (MailException e) {
  41. logger.debug("Send mail failed with error: ${e.message}")
  42. 'Failed to Send Mail, Check Logs for error'
  43. }
  44. }
  45. String getManagerEmail(tokenOwnerUser) {
  46. def logger = Logger.getLogger(getClass())
  47. logger.setLevel(Level.DEBUG)
  48. UserPropertyManager userPropertyManager = ComponentAccessor.getUserPropertyManager()
  49. PropertySet userProperties = userPropertyManager.getPropertySet(tokenOwnerUser)
  50. //logger.debug("All user properties: ${userProperties}")
  51. def managerEmail = userProperties.getString("jira.meta.myManager")
  52. return managerEmail
  53. }
  54. Boolean sendToManager(tokenOwner) {
  55. GroupManager groupManager = ComponentAccessor.getComponent(GroupManager)
  56. if (groupManager.getUserNamesInGroup("your-group-name-here").contains(tokenOwner.getUsername())) {
  57. return true
  58. }
  59. return false
  60. }
  61. Boolean ExpirationReminder(warnEpochFrom, warnEpochUntil, nextPageURL) {
  62. // Jira BaseURL, change accordingly
  63. def baseURL = "https://your-jira-base-url.com"
  64. // User with create token on behalf permission
  65. def userName = "admin"
  66. // API Token or password
  67. def password = "passwordOrToken"
  68. // Add additional recipient e-mail addresses to the array [] in double quotes and separated by comma
  69. def additionalRecipients = []
  70. String encodedAuthString = "Basic " + ("$userName:$password".bytes.encodeBase64().toString())
  71. def pathFrom = "/rest/de.resolution.apitokenauth/latest/user/tokensByFilter?fromExpiresDuring="
  72. def pathUntil = "&untilExpiresDuring="
  73. String queryURL = baseURL + pathFrom + warnEpochFrom + pathUntil + warnEpochUntil
  74. if (nextPageURL != "") {
  75. queryURL = nextPageURL
  76. }
  77. def restClient = new RESTClient(queryURL)
  78. restClient.setHeaders([Authorization: encodedAuthString, Accept: 'application/json'])
  79. def resp = restClient.get(contentType: JSON)
  80. def resultObject = resp.data
  81. def tokenArray = resultObject['content']
  82. // send warning email for each token about to expire that day
  83. tokenArray.each {
  84. def tokenDescription = it['description']
  85. def date = new Date(it['validUntil'])
  86. SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd" )
  87. def dateString = dateFormat.format(date)
  88. def userManager = ComponentAccessor.getUserManager()
  89. def recipient = userManager.getUserByKey(it['tokenForUserKey'])
  90. def tokenOwnerEmail = recipient.getEmailAddress()
  91. // mail to token owner
  92. sendEmail(tokenOwnerEmail, "API Token Expiration Warning", "Your token with description '${tokenDescription}' will expire ${dateString}")
  93. // mail to manager
  94. def managerEmail = getManagerEmail(recipient)
  95. if (managerEmail != null && sendToManager(recipient) == true) {
  96. sendEmail(managerEmail, "API Token Expiration Warning for user ${tokenOwnerEmail}", "API token of user ${tokenOwnerEmail} with description '${tokenDescription}' will expire ${dateString}")
  97. }
  98. // mail to additional recipients
  99. additionalRecipients.each {
  100. sendEmail(it, "API Token Expiration Warning for user ${tokenOwnerEmail}", "API token of user ${tokenOwnerEmail} with description '${tokenDescription}' will expire ${dateString}")
  101. }
  102. }
  103. // if there is a next page with more tokens expiring soon, process it as well
  104. if (resultObject['paginationLinks']['nextPage'] != "") {
  105. ExpirationReminder("","", resultObject['paginationLinks']['nextPage'])
  106. }
  107. }
  108. // check and warn for tokens expiring in 1 to 5 days
  109. for (int i = 1; i<6; i++) {
  110. use (groovy.time.TimeCategory) {
  111. warnDateFrom = i.day.from.now
  112. warnEpochFrom = warnDateFrom.getTime()
  113. warnEpochUntil = warnEpochFrom + (24 * 60 * 60 * 1000) - 1
  114. ExpirationReminder(warnEpochFrom, warnEpochUntil, "")
  115. }
  116. }

Adjust Script

You need to adjust the values in the lines below. Some of them are mandatory, some aren't.

Mandatory Variables

  • line 74, def baseURL - enter your Jira base URL in double-quotes, without the trailing forward-slash

  • line 76, def userName - provide a username that has the Create & Delete Token On Behalf Permission 

  • line 78, def password - provide a password or better Read-Only API Token for that user

Optional Variables

  • line 58, replace myManager with the Jira user properties field name that contains the e-mail address of the manager of the token owner

    • be careful and don't remove the jira.meta. prefix

  • line 64, replace your-group-name-here with a group name that the token owner needs to be a member of if their manager should receive a notification as well 

  • line 80, def additionalRecipients = []  - add one or more e-mail addresses in double quotes and separated by a comma who should always receive the same expiration warning the token owner receives or leave the array empty

    • Example: def additionalRecipients = ["user1@company.com","user2@company.com"]

  • lines 107, 112 and 118 contain the function calls for the sendEmail function and you could adjust the second and third parameters (subject and text body of the email)

    • variables available in the function scope are enclosed in in ${}, i.e. ${tokenOwnerEmail} so that you can build custom subjects and text bodies 

Result

The script will run at the scheduled time and search for tokens that are about to expire in the next 5 days. You could adjust line 132 and increase or decrease the counter if you want to cover more or fewer days.
There will be an e-mail for each expiring token.