Skip to content
Try For Free

Groovy-Connector

Groovy-Connectors allow creating connectors by writing Groovy-code directly in the configuration. This allows

  • Synchronizing users from backends where no Connector is available

  • Implementing business-logic for the user-synchronization

User Sync has two modes of operation: "Sync" and "Single User Sync"

Sync is an asynchronous process updating all users or a limited subset of users from the backend system (e.g. Azure-AD) in a specific directory of the Atlassian-application- similar to LDAP or Crowd-directories.

Single User Sync creates or updates one specific user. This us usually triggered when a user logs in using SAML and "update user from Usersync-connector" is enabled.

Single User Sync

Sync

Examples

Pass SAML-Attributes to Connector and uses Advanced Transformations

Attribute-transformations in UserSync are more powerful as in SAML JIT. They allow taking the existing user into account. 

To allow this with SAML-Attributes, a Groovy-Connector can be used.

Create a Groovy-connector with this code:

  1. class ConnectorCode extends GroovyConnectorCode {
  2. public FindUserResult findUser(SyncSingleUserWrapper syncSingleUserWrapper) {
  3. // This just passes the additionalData (in this case the SAML-attributes) so
  4. // they can be used in the Attribute-transformations
  5. return FindUserResult.found(syncSingleUserWrapper.additionalData);
  6. }
  7. }

In the Provisioning-tab, configure transformations for all required attributes, (similar to what to configure in the SAML-config for JIT). The main-difference is that the attributes are now available under the con-prefix.

So when using a Groovy-Transformation for groups, this may look like this example merging existing groups with the new ones:

  1. def newGroups = existing?.ATTR_GROUPS ?: [] // Existing groups or an empty list if there is no existing user
  2. newGroups.addAll(con.groups) // add all groups from the SAML-Response (passed in here by the Groovy-connector)
  3. return newGroups.toUnique() // toUnique() removes duplicate entries

Set the user-update-method to "Update from UserSync-connector" and select this connector.


Update user via Jira REST-API


  1. class ConnectorCode extends GroovyConnectorCode {
  2. // This is the base url for the Jira REST-API (put in your base-url),
  3. // see https://docs.atlassian.com/software/jira/docs/api/REST/8.18.0/
  4. def restBase = "https://<Base-URL>/rest/api/2"
  5. void init() {
  6. // Add the Authorization-header to all further requests
  7. http.addDefaultHeader("Authorization",Credentials.basic("<username>","<password>"))
  8. }
  9. // Adds a user to a group
  10. void addUserToGroup(username, groupName) {
  11. debug("Adding $username to $groupName")
  12. def jsonToSend = """{ "name": "$username" }"""
  13. ResponseWrapper response = http.postJson(
  14. "$restBase/group/user?groupname=$groupName",
  15. jsonToSend)
  16. if(response.code != 201 && response.code != 400) {
  17. fail("Unexpected response adding $username to group $groupName: ${response.code}: ${response.body}")
  18. }
  19. }
  20. // Checks if a user exists
  21. boolean userExists(username) {
  22. debug("Checking if $username exists already")
  23. def response = http.get("$restBase/user?username=$username")
  24. int responseCode = response.code
  25. if(responseCode == 404) {
  26. return false
  27. } else if(responseCode == 200) {
  28. return true
  29. } else {
  30. fail("REST-call to check user existence returned unexpected result $responseCode: ${response.body}")
  31. }
  32. }
  33. // Creates a new user
  34. void createUser(username, email, displayName) {
  35. debug("Creating $username with email $email and display name $displayName")
  36. def jsonToSend = """{
  37. "name": "$username",
  38. "emailAddress": "$email",
  39. "displayName": "$displayName"
  40. }"""
  41. ResponseWrapper response = http.postJson("$restBase/user",jsonToSend)
  42. if(response.code != 201) {
  43. fail("Unexpected response creating user: ${response.code} ${response.body}")
  44. }
  45. }
  46. // This method is called for a single-user sync
  47. @Override
  48. public SyncSingleUserResult syncSingleUser(SyncSingleUserWrapper wrapper) {
  49. if(!userExists(wrapper.identifier)) {
  50. // In this example, we assume that the SAML-attributes for email and displayname
  51. // are 'email' and 'displayName'
  52. // The SAML-attributes come as lists, os we need to pick the first element
  53. def email = wrapper.additionalData.email?.first()
  54. def displayName = wrapper.additionalData.displayName?.first()
  55. // Just fail if the necessary attributes are not present
  56. if( !email || !displayName) {
  57. fail("email and displayname must be present to create a user")
  58. }
  59. createUser(wrapper.identifier,email,displayName)
  60. }
  61. def groupsToAdd = wrapper.additionalData.groups?.asList() ?: []
  62. groupsToAdd.forEach{
  63. group -> addUserToGroup(wrapper.identifier,group)
  64. }
  65. // Create SingleUSerResult of type OTHER because we don't return a user here
  66. return SyncSingleUserResult.createOther("Update triggered");
  67. }
  68. }

Update/Create user with SAML-Attributes

and some logic

  1. class ConnectorCode extends GroovyConnectorCode {
  2. @Override
  3. public SyncSingleUserResult syncSingleUser(SyncSingleUserWrapper wrapper) {
  4. // Messages from debug() are visible in the log file when enabling
  5. // debug-logging for de.resolution.usersync.builtin.groovyconnector.GroovyConnectorCode
  6. debug("Single User Sync started for $wrapper.identifier")
  7. // Read out the required data from the SAML-response
  8. // The attributes come as lists, so we take the first list-entry using Groovy's safe dereference-operator
  9. def username = wrapper.additionalData.get("ATTR_NAMEID")?.first()
  10. if(!username) {
  11. return SyncSingleUserResult.createFailure("no username in SAML-response")
  12. }
  13. def email = wrapper.additionalData.get("email")?.first()
  14. if(!email) {
  15. return SyncSingleUserResult.createFailure("no email in SAML-response")
  16. }
  17. def fullname = wrapper.additionalData.get("fullname")?.first()
  18. if(!fullname) {
  19. return SyncSingleUserResult.createFailure("no fullname in SAML-response")
  20. }
  21. def groups = wrapper.additionalData.get("groups") // no .first() groups is really a list
  22. if(groups == null) {
  23. return SyncSingleUserResult.createFailure("no groups in SAML-response")
  24. }
  25. // Search for an existing user with the SAML-NameID as username
  26. AtlasUserResult findExistingResult = wrapper.findByUsername(username)
  27. // Fail here if the search-result is unexpected (anything other than successful or not found)
  28. if(!findExistingResult.notFound && !findExistingResult.success) {
  29. return SyncSingleUserResult.createFailure("Unexpected result searching existing user by username $username : ${Utils.asJson(findExistingUserResult)}")
  30. }
  31. // Search the user by the email address (as username) if not found by the userid
  32. if(findExistingResult.notFound) {
  33. debug("User with username username not found, searching user by email $email")
  34. findExistingResult = wrapper.findByUsername(email)
  35. // Fail here if the search-result is unexpected (anything other than successful or not found)
  36. if(!findExistingResult.notFound && !findExistingResult.success) {
  37. return SyncSingleUserResult.createFailure("Unexpected result searching existing user by email address $email : ${Utils.asJson(findExistingUserResult)}")
  38. }
  39. }
  40. // Create a new user if also not found by email address
  41. if(findExistingResult.notFound) {
  42. debug("No user found with username username or email $email, creating a new user.")
  43. // Create new users in the configured directory
  44. long directoryId = connector.configuration.directoryId
  45. AtlasUser userToCreate = AtlasUser.builder()
  46. .findBy(AtlasUserKeys.ATTRIBUTE_USERNAME,username)
  47. .in(directoryId)
  48. .with(AtlasUserKeys.ATTRIBUTE_USERNAME, username)
  49. .with(AtlasUserKeys.ATTRIBUTE_EMAIL,email)
  50. .with(AtlasUserKeys.ATTRIBUTE_FULLNAME,fullname)
  51. .with(AtlasUserKeys.ATTRIBUTE_GROUPS,groups)
  52. .with("CREATED_BY_SAML",true) // Set this attribute so the user is considered as created by SAML SSO
  53. .build()
  54. AtlasUserResult createResult = wrapper.create(userToCreate)
  55. return SyncSingleUserResult.createFound(createResult)
  56. }
  57. AtlasUser existingUser = findExistingResult.resultingUser.get() // resultingUser is a Java-Optional, so we need to call .get()
  58. // If a user is found and the user has the CREATED_BY_SAML-attribute, update the user
  59. // with the data from the SAML-response
  60. if(!existingUser.containsKey("CREATED_BY_SAML")) {
  61. debug("Existing user found has the CREATED_BY_SAML-attribute, updating attributes")
  62. AtlasUser userToUpdate = existingUser.newBuilder()
  63. .with(AtlasUserKeys.ATTRIBUTE_USERNAME,username)
  64. .with(AtlasUserKeys.ATTRIBUTE_EMAIL,email)
  65. .with(AtlasUserKeys.ATTRIBUTE_FULLNAME,fullname)
  66. .with(AtlasUserKeys.ATTRIBUTE_GROUPS,groups)
  67. .build()
  68. AtlasUserResult updateResult = wrapper.update(userToUpdate)
  69. return SyncSingleUserResult.createFound(updateResult)
  70. } else {
  71. // Don't update users without the CREATED_BY_SAML-attribute, just return
  72. // success with the found user
  73. debug("Existing user found has no CREATED_BY_SAML-attribute, NOT updating attributes")
  74. return SyncSingleUserResult.createFound(findExistingResult)
  75. }
  76. }
  77. }

Create from JSON

  1. // The class must extend GroovyConnectorCode
  2. class ConnectorCode extends GroovyConnectorCode {
  3. StructuredData testData
  4. @Override
  5. void init() {
  6. testData = StructuredData.parseJson(dataJson)
  7. }
  8. /*
  9. * This method is run when a sync is started.
  10. * It should call syncFunction.apply() with all users.
  11. * Remove this method if the Connector should not be able to run full syncs.
  12. */
  13. @Override
  14. void sync(SyncWrapper sync) {
  15. testData.forEach {
  16. sync.syncUser(it)
  17. }
  18. }
  19. /*
  20. * This method is run for a single user sync.
  21. * Remove this method if the Connector should not be able to run single user syncs.
  22. */
  23. @Override
  24. FindUserResult findUser(SyncSingleUserWrapper wrapper) {
  25. StructuredData foundData = testData.find { it.USERID == wrapper.identifier}
  26. if(foundData == null) {
  27. return notFound();
  28. } else {
  29. return found(foundData)
  30. }
  31. }
  32. def dataJson = """
  33. [
  34. {
  35. "EMAIL": "devin.bashirian@example.com",
  36. "FULLNAME": "Devin Bashirian",
  37. "GROUPS": [
  38. "Aerosmith",
  39. "Blondie"
  40. ],
  41. "USERID": "devin.bashirian",
  42. "Unique_ID": "83d992e7-d326-4f54-9e07-1980497849f6"
  43. },
  44. {
  45. "EMAIL": "ambrose.nader@example.com",
  46. "FULLNAME": "Ambrose Naderx",
  47. "GROUPS": [
  48. "Aerosmith",
  49. "Blondie",
  50. "U2",
  51. "Genesis"
  52. ],
  53. "USERID": "ambrose.nader",
  54. "Unique_ID": "50930ca4-f7e9-4a57-911c-369fd90ef736"
  55. },
  56. {
  57. "EMAIL": "gabriela.price@example.com",
  58. "FULLNAME": "Gabriela Price",
  59. "GROUPS": [
  60. "Aerosmith",
  61. "Genesis",
  62. "Blondie"
  63. ],
  64. "USERID": "gabriela.price",
  65. "Unique_ID": "ed5698ad-f5ae-414a-a257-3b719cb86322"
  66. },
  67. {
  68. "EMAIL": "joseph.crona@example.com",
  69. "FULLNAME": "Joseph Crona",
  70. "GROUPS": [
  71. "Aerosmith",
  72. "Blondie",
  73. "U2"
  74. ],
  75. "USERID": "joseph.crona",
  76. "Unique_ID": "f717f7d1-5cff-46a0-8b08-ce1a5a11b2b9"
  77. },
  78. {
  79. "EMAIL": "jamee.breitenberg@example.com",
  80. "FULLNAME": "Jamee Breitenberg",
  81. "GROUPS": [
  82. "U2",
  83. "Aerosmith",
  84. "U2",
  85. "Genesis",
  86. "Blondie"
  87. ],
  88. "USERID": "jamee.breitenberg",
  89. "Unique_ID": "5446e3d2-5686-4b9b-a595-82c3ba83378f"
  90. }
  91. ]
  92. """
  93. }

Trigger single user sync on other connector

  1. class ConnectorCode extends GroovyConnectorCode {
  2. //
  3. // After the sync, all users in the directory configured for this connector are disabled
  4. // during the cleanup-phase. So make sure to configure an separate, empty directory
  5. // for this connector!
  6. /*
  7. * This method is called when a sync is started.
  8. */
  9. @Override
  10. void sync(SyncWrapper sync) {
  11. // Unique id of the connector to call
  12. def otherConnectorId = "4264572f-e481-4ee2-914c-7774a4c9b80d"
  13. // This will throw an Exception if the connector is not found and the sync
  14. // will stop as failed
  15. Connector otherConnector = connectorService.getConnectorByUniqueId(otherConnectorId)
  16. // We need to work on the other connector's directory
  17. def directory = otherConnector.configuration.directoryId
  18. SyncStatus syncStatus = sync.getSyncStatus()
  19. // With this method, apply() from CallSyncSingleUserFunction is called for
  20. // each user in the directory, so see below for what's happening there
  21. atlasUserAdapter.applyToAll(
  22. directory,
  23. new CallSyncSingleUserFunction(otherConnector,sync),
  24. syncStatus)
  25. }
  26. class CallSyncSingleUserFunction implements AtlasUserFunction {
  27. private final Connector connectorToCall
  28. private final SyncWrapper syncWrapper
  29. CallSyncSingleUserFunction(Connector con, SyncWrapper wrap) {
  30. connectorToCall = con
  31. syncWrapper = wrap
  32. }
  33. // This is called by atlasUserAdapter.applyToAll() above
  34. Optional<AtlasUserResult> apply(AtlasUser atlasUser, AtlasUserStatusObject status) {
  35. def username = atlasUser.get("ATTR_NAME").get()
  36. // Trigger the single user sync for the user
  37. def syncSingleResult = connectorToCall.syncSingleUser(username,null,null,null)
  38. // If the user was found in the connector's backend, there should be
  39. // an AtlasUserResult indicating the update.result
  40. if(syncSingleResult.status == SyncSingleUserResult.Status.FOUND) {
  41. AtlasUserResult updateResult = syncSingleResult.getUpdateUserResult()
  42. status.add(updateResult)
  43. return Optional.ofNullable(updateResult)
  44. } else if(syncSingleResult.status == SyncSingleUserResult.Status.NOT_FOUND) {
  45. syncWrapper.log("Sync single user for $username returned NOT_FOUND, disabling user")
  46. AtlasUser userToDisable = new AtlasUserBuilder()
  47. .findBy(atlasUser.getReference())
  48. .active(false)
  49. .build()
  50. AtlasUserResult disableResult = atlasUserAdapter.update(userToDisable)
  51. status.add(disableResult)
  52. return Optional.of(disableResult)
  53. } else {
  54. syncWrapper.log("Sync single user for $username returned unexpected result: ${syncSingleResult.status}")
  55. return Optional.empty()
  56. }
  57. }
  58. }
  59. }


Uni 

  1. package groovy
  2. class ConnectorCode extends GroovyConnectorCode {
  3. private static final String KEY_ACCESS_TOKEN = "accessToken";
  4. private static final String KEY_EXPIRE_DATE = "expireDate";
  5. //
  6. // Diese Werte müssen entsprechend angepasst werden
  7. //
  8. private static final String userUrl = "<https://.../api/v1.0/">
  9. private static final String tokenUrl = "<https://...">
  10. private static final String clientId = "removed"
  11. private static final String clientSecret = "removed"
  12. private static final String scope = "uaccount_jira_api"
  13. private static final String audience = null
  14. //
  15. // Hier muss das von Shibboleth gesendete Gruppenattribut eingetragen werden
  16. //
  17. private static final String GROUP_ATTR = "group"
  18. // Identifiers for testing:
  19. // student1@univie.ac.at -> mailForwardingAddress
  20. // test1@univie.ac.at -> primary email
  21. // test3@univie.ac.at -> no email
  22. @Override
  23. public SyncSingleUserResult syncSingleUser( String identifier,
  24. Map<String, Collection<String>> additionalData,
  25. Map<String, Set<String>> attributesToOverride) {
  26. def userFromBackend = findUserInBackend(identifier,false)
  27. // For debugging, user attributes can be specified here instead of loading them from the backend
  28. /*
  29. def userFromBackend = [
  30. uid : "test2",
  31. displayName : "Test User 2 ABC x",
  32. mail : "test2.user@univie.ac.at",
  33. eduPersonPrincipalName : "test2@univie.ac.at",
  34. mailForwardingAddress : ["test2.user@univie.ac.at", "test2.user@gmail.com", "test2.user@web.de"]
  35. ]
  36. */
  37. if(userFromBackend == null) {
  38. return SyncSingleUserResult.createNotFound()
  39. }
  40. // 1. Ein Benutzer mit aktiver primärer e-mail Adresse (Uni Personal oder Studierende) loggt mittels Shibboleth ins Servicedesk ein.
  41. if(userFromBackend.mail) {
  42. // Falls bereits ein Jira Account mit der verwendeten UserID vorhanden ist dann soll nur eine Aktualisierung erfolgen falls diese primäre e-mail Adresse
  43. // von der im Jira Directory vorhandenen e-mail Adresse abweicht, und/oder der Realname ein anderer ist. Die UserID bleibt unverändert.
  44. AtlasUserResult resultByName = findExistingUser(userFromBackend.uid)
  45. if (resultByName.isSuccess()) {
  46. return SyncSingleUserResult.createFound(
  47. updateUser(resultByName.getResultingUser().get(),userFromBackend,additionalData))
  48. // Ist aber kein Jira Account mit der verwendeten UserID vorhanden
  49. // dann soll nach einem Jira Account gesucht werden dessen UserID der primären e-mail Adresse entspricht.
  50. } else if (resultByName.notFound) {
  51. AtlasUserResult resultByEmail = findExistingUser(userFromBackend.mail)
  52. if (resultByEmail.isSuccess()) {
  53. return SyncSingleUserResult.createFound(
  54. updateUser(resultByEmail.getResultingUser().get(),userFromBackend,additionalData))
  55. } else if (resultByEmail.notFound) {
  56. return SyncSingleUserResult.createFound(
  57. createNewUser(userFromBackend,additionalData))
  58. } else {
  59. return SyncSingleUserResult.createFailure("Unexpected result : ${Utils.asJson(resultByEmail)}")
  60. }
  61. }
  62. }
  63. /* 2. Ein Benutzer ohne primäre e-mail Adresse (vorläufiger Account, Rolle PROSPECTIVE) loggt mittels Shibboleth ins Servicedesk ein. */
  64. if(userFromBackend.mailForwardingAddress) {
  65. AtlasUserResult resultByName = findExistingUser(userFromBackend.uid)
  66. // Falls bereits ein Jira Account mit der verwendeten UserID vorhanden ist dann soll nur eine Aktualisierung erfolgen falls diese primäre e-mail Adresse
  67. // von der im Jira Directory vorhandenen e-mail Adresse abweicht, und/oder der Realname ein anderer ist. Die UserID bleibt unverändert.
  68. if (resultByName.isSuccess()) {
  69. return SyncSingleUserResult.createFound(
  70. updateUser(resultByName.getResultingUser().get(),userFromBackend,additionalData))
  71. // Ist aber kein Jira Account mit der verwendeten UserID vorhanden dann soll nach einem Jira Account gesucht werden
  72. // dessen UserID der Weiterleitungsadresse entspricht.
  73. // Ist so ein Jira Account vorhanden so soll dessen UserID aktualisiert werden (und abweichende Daten auch).
  74. } else if (resultByName.notFound) {
  75. if(!userFromBackend.mailForwardingAddress) {
  76. return SyncSingleUserResult.createFailure("No mailForwardingAddress is set for this user")
  77. }
  78. for(forwardingAddress in userFromBackend.mailForwardingAddress) {
  79. AtlasUserResult resultByForwardingAddress = findExistingUser(forwardingAddress)
  80. if(resultByForwardingAddress.isSuccess()) {
  81. return SyncSingleUserResult.createFound(
  82. updateUser(resultByForwardingAddress.getResultingUser().get(),userFromBackend,additionalData))
  83. } else if(!resultByForwardingAddress.notFound) {
  84. return SyncSingleUserResult.createFailure("Unexpected result searching by forwardingAddress : ${Utils.asJson(resultByForwardingAddress)}")
  85. }
  86. }
  87. return SyncSingleUserResult.createFound(
  88. createNewUser(userFromBackend,additionalData))
  89. }
  90. }
  91. return SyncSingleUserResult.createFailure("User has neither a primary email nor a mailForwardingAddress")
  92. }
  93. /*
  94. * Finds a user by the username
  95. */
  96. AtlasUserResult findExistingUser(String username) {
  97. return atlasUserAdapter.readFirstUniqueUser(
  98. AtlasUserReference.create(
  99. AtlasUserKeys.ATTRIBUTE_USERNAME,
  100. username,
  101. AtlasUserKeys.ANY_DIRECTORY));
  102. }
  103. /*
  104. * Updates an existing user with the attributes from the backend
  105. */
  106. AtlasUserResult updateUser(AtlasUser existingUser, attributesFromBackend, additionalData) {
  107. AtlasUserBuilder userToUpdate = existingUser.newBuilder();
  108. if(attributesFromBackend.uid) {
  109. userToUpdate.with(AtlasUserKeys.ATTRIBUTE_USERNAME, attributesFromBackend.uid)
  110. }
  111. if(attributesFromBackend.mail) {
  112. userToUpdate.with(AtlasUserKeys.ATTRIBUTE_EMAIL,attributesFromBackend.mail)
  113. }
  114. if(attributesFromBackend.displayName) {
  115. userToUpdate.with(AtlasUserKeys.ATTRIBUTE_FULLNAME,attributesFromBackend.displayName)
  116. }
  117. if(attributesFromBackend.uid) {
  118. userToUpdate.with("uid",attributesFromBackend.uid)
  119. }
  120. if(additionalData != null && additionalData.get(GROUP_ATTR) != null) {
  121. // use getAttributeValues here, get() will return the first group only
  122. def existingGroups = existingUser.getAttributeValues('ATTR_GROUPS')
  123. def newGroups = additionalData.get(GROUP_ATTR)
  124. def effectiveGroups = existingGroups + newGroups
  125. userToUpdate.with(AtlasUserKeys.ATTRIBUTE_GROUPS,effectiveGroups)
  126. }
  127. return atlasUserAdapter.update(userToUpdate.build())
  128. }
  129. /*
  130. * Creates a new user from the attributeMap
  131. */
  132. AtlasUserResult createNewUser(attributeMap,additionalData) {
  133. logger.warn(attributeMap as String)
  134. ConnectorConfiguration cfg = connector.getConfiguration();
  135. long directory = cfg.getDirectoryId();
  136. AtlasUserBuilder userBuilder = AtlasUser.builder()
  137. .findBy(AtlasUserKeys.ATTRIBUTE_USERNAME,attributeMap.uid)
  138. .in(directory)
  139. .with(AtlasUserKeys.ATTRIBUTE_USERNAME, attributeMap.uid)
  140. .with(AtlasUserKeys.ATTRIBUTE_EMAIL,attributeMap.mail ?: attributeMap.mailForwardingAddress)
  141. .with(AtlasUserKeys.ATTRIBUTE_FULLNAME,attributeMap.displayName)
  142. .with("uid",attributeMap.uid)
  143. if(additionalData != null && additionalData.get(GROUP_ATTR) != null) {
  144. userBuilder.with(AtlasUserKeys.ATTRIBUTE_GROUPS,additionalData.get(GROUP_ATTR))
  145. }
  146. AtlasUser userToCreate = userBuilder.build()
  147. return atlasUserAdapter.create(userToCreate);
  148. }
  149. /*
  150. * Searches the user in the backend
  151. */
  152. def findUserInBackend(String identifier, boolean isRetry) {
  153. if(!read(KEY_ACCESS_TOKEN) || (new Date().getTime() > Long.valueOf(read(KEY_EXPIRE_DATE) ?: "0"))) {
  154. requestAccessToken();
  155. }
  156. def accessToken = read(KEY_ACCESS_TOKEN)
  157. if(!accessToken) {
  158. fail("No Access-Token present")
  159. }
  160. def resp = http.get("${userUrl}${identifier}",["Authorization": "Bearer " + accessToken])
  161. if(resp.code == 401) {
  162. if(isRetry) {
  163. fail("Could not load user, unauthorized after retry")
  164. } else {
  165. return findUserInBackend(identifer,true)
  166. }
  167. }
  168. if(resp.code == 404) {
  169. return null
  170. }
  171. if(!(resp.code in 200..299)) {
  172. fail("Unexpected response ${resp.code}")
  173. }
  174. return resp.parsedJson
  175. }
  176. /*
  177. * Requests a new OAUTH-Token using the client_credentials flow
  178. */
  179. void requestAccessToken() {
  180. def formData = [
  181. "grant_type" : "client_credentials",
  182. "client_id" : clientId,
  183. "client_secret": clientSecret ]
  184. if(scope) {
  185. formData.put("scope",scope)
  186. }
  187. if(audience) {
  188. formData.put("audience",audience)
  189. }
  190. def resp = http.postAsForm(tokenUrl,formData,[:])
  191. if(resp.code in (200..299)) {
  192. def parsed = resp.parsedJson
  193. def accessToken = parsed.access_token
  194. def expires = parsed.expires_in
  195. def expireDate = new Date().getTime() + ((expires - 1) * 1000)
  196. write(KEY_ACCESS_TOKEN,accessToken)
  197. write(KEY_EXPIRE_DATE,expireDate)
  198. } else {
  199. fail("Failed to load access token: $resp.code")
  200. }
  201. }