Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Using Scriptrunner in Jira to communicate to Confluence

Daniel Volinski December 18, 2020

Hello all, 

I am trying to using Scriptrunner (inside of Jira Service Desk) to communicate to Confluence (linked application) and create a Confluence space.

So far I have:

1. Parse Jira Service Desk ticket.

2. Use Rest API to pass assign some values in creating a Confluence space (title, key, description, etc.)

 

The main issue that I am having is that the API does not let me pass/set admins for the space upon creation. Is there something in the API that allows me to do this? What I have read is there is not and one alternative method is using Rest Endpoints (on the Confluence side) to wait and listen for actions to happen (in this case when the space is created.

The ideal solution would be to allow me to parse the Jira Service Desk ticket, pass the admins as a parameter, and set the admins for the Confluence space upon creation of the space.

So far, to my knowledge, I have a good understanding of this, and this is my current implementation, just a snippet of what is important:

def params = [
      type : "global",
      name: spaceName,
      key: someKey, // set the space key (in Confluence - or retrieve it from the Jira ticket
      description: [
             plain: [
                   value: someDescription
             ]
      ]
]

authenticatedRequestFactory
        .createRequest(Request.MethodType.POST, "rest/api/space")
        .addHeader("Content-Type", "application/json")
        .setRequestBody(new JsonBuilder(params).toString())
        .execute(new ResponseHandler<Response>() {
              @Override
               void handle(Response response) throws ResponseException {
                      if (response.statusCode != HttpURLConnection.HTTP_OK) {
                          throw new Exception(response.getResponseBodyAsString())
                      } else { "... do some other stuff"

 

Any help would be greatly appreciated, thank you!

1 answer

1 accepted

Suggest an answer

Log in or Sign up to answer
0 votes
Answer accepted
Radek Dostál
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.
December 18, 2020

Is this Cloud or Server?

 

For Server there's no public REST API to update Space permissions as I remember doing this recently - I was able to automate around it by just simulating the form POST.

With dev tools just add a user, find the POST request, and use that as your template - you will probably have to bypass XSRF token though (https://developer.atlassian.com/server/jira/platform/form-token-handling/#scripting).

Daniel Volinski December 18, 2020

Hi Radek,

This is for data center. And what exactly do you mean "simulating the form POST." Could you please provide some more detail please.

Alternatively, if there is a way to pass parameters from Jira Scriptrunner, to Confluence Scriptrunner, this would be an alternative, as I believe Confluence Scriptrunner has some methods specifically for Confluence that allows you to manipulate permissions/groups/etc.

Thank you,

Daniel

Radek Dostál
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.
December 18, 2020

Hi Daniel,

 

I meant to look up the request via Web Dev Tools (Network) -- you add a user and press "Add", then you will find the HTTP Request in the web development tools from which you can copy it out. Then you might be able to modify that to create a more generic template for your http requests.

 

First you can add the user to the Space via POST (following the same process as in UI, first add the user, than later add permissions)

curl 'https://<baseUrl>/spaces/doeditspacepermissions.action?key=<SPACE KEY>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'X-Atlassian-Token: no-check' \
  -H 'Cookie: JSESSIONID=XsanitizedX' \
  --data 'usersToAdd=<username>&usersToAddButton=Add'

 

Then you need the userKey to modify space permissions for the user, which for we can use this interface

curl 'https://<baseUrl>/rest/api/user?username=<username>' -b 'JSESSIONID=XsanitizedX' | python -c 'import json,sys;data=json.load(sys.stdin);print data["userKey"]'

 

With the userKey, it's possible to give the user additionally full space permissions

curl 'https://<baseUrl>/spaces/doeditspacepermissions.action?key=<SPACE KEY>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: JSESSIONID=XsanitizedX' \
  -H 'X-Atlassian-Token: no-check' \
  --data 'confluence_checkbox_viewspace_user_<USERKEY>=on&confluence_initial_viewspace_user_<USERKEY>=on&confluence_checkbox_removeowncontent_user_<USERKEY>=on&confluence_checkbox_editspace_user_<USERKEY>=on&confluence_checkbox_removepage_user_<USERKEY>=on&confluence_checkbox_editblog_user_<USERKEY>=on&confluence_checkbox_removeblog_user_<USERKEY>=on&confluence_checkbox_createattachment_user_<USERKEY>=on&confluence_checkbox_removeattachment_user_<USERKEY>=on&confluence_checkbox_comment_user_<USERKEY>=on&confluence_checkbox_removecomment_user_<USERKEY>=on&confluence_checkbox_setpagepermissions_user_<USERKEY>=on&confluence_checkbox_removemail_user_<USERKEY>=on&confluence_checkbox_exportspace_user_<USERKEY>=on&confluence_checkbox_setspacepermissions_user_<USERKEY>=on'

That last request creeps me out but it's solid enough unless that rest endpoint changes in the future versions.

 

Note that '-H 'Cookie: JSESSIONID=XsanitizedX'' is basically the same as basic authentication via '-u username:password', just more performant.

I'm not sure how you do authentication in your implementation but I assume it's no problem so just mentioning this in case.

 

Now is this a pretty implementation, heeeeelll noo, no, no no. It was my makeshift because of missing API.

 

If you have ScriptRunner on Confluence also then you could probably make your own REST endpoint which would be TON better: https://scriptrunner.adaptavist.com/6.16.0/confluence/rest-endpoints.html

 

I've found this post with a supposedly working example: https://community.atlassian.com/t5/Confluence-articles/Update-Space-Permissions-without-Space-Admin-Part-1/ba-p/1274567

I imagine it could be simplified if you are always going to give the user the same permissions, because that example goes in extra detail.

 

Sorry I didn't realize at first you have ScriptRunner on Confluence also, I was under the wrong impression from the beginning, otherwise I'd suggest the rest endpoint instead :)

Maybe you could unify everything into a single script on Confluence and just pass username(s) as parameters, effectively just skipping the entire http request chain.

Do please take a look at the link above - this should work perfectly for your use case.

 

Thanks,
Radek

Like Daniel Volinski likes this
Daniel Volinski December 18, 2020

Hi Radek,

 

Thank you for all of the information, this is great!

To follow-up, this command:

curl 'https://<baseUrl>/rest/api/user?username=<username>' -b 'JSESSIONID=XsanitizedX' | python -c 'import json,sys;data=json.load(sys.stdin);print data["userKey"]'

 I see there are some python library imports, is this necessary? Let me be more specific, I am implementing this inside of a post function on Scriptrunner.

I took a look at the link you provided above: https://community.atlassian.com/t5/Confluence-articles/Update-Space-Permissions-without-Space-Admin-Part-1/ba-p/1274567 and I am not sure this exactly works (please correct me if I'm wrong)...

Based on the example, it looks like the script is adding users to an already defined admin group. In my case, I am creating a space from a Jira Service Desk ticket. My instance of Jira and Confluence are linked, so they can communicate to each other:

/**
 * Retrieve the primary confluence application link
 * @return confluence app link
 */
def ApplicationLink getPrimaryConfluenceLink() {
    def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)
    final ApplicationLink conflLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)
    conflLink
}

 

However, once the space is created (from the post function in Jira Scriptrunner), the last piece I need is to set the admins of that space in Confluence (space tools -> permissions -> Individual Users) and add them here. This user(s) would be some field parsed from the Jira ticket. So how would I pass this parameter (either one item - "String" or a list[] of admins) to the Confluence side.

 

I hope this makes sense, and I can try to explain further if you need more explanation.

 

Thank you again for all of the content, much appreciated!

Daniel

Radek Dostál
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.
December 21, 2020

Hi Daniel,

 

 I see there are some python library imports, is this necessary? Let me be more specific, I am implementing this inside of a post function on Scriptrunner.

Above snippet is from shell, python is not needed there.

With ScriptRunner you'd be working with Java classes, there are out of box libraries available to work with json, but it would not be needed here either.

 

Based on the example, it looks like the script is adding users to an already defined admin group. In my case, I am creating a space from a Jira Service Desk ticket. My instance of Jira and Confluence are linked, so they can communicate to each other:

Well it's just an example, it might need to be modified to fit your use case, but from my understanding it's not adding any users to any groups, that's just a limitation who can use this rest endpoint (probably don't want users exploiting it nilly-willy). It's commented in it like so:

//Change <Your Admin Group> to the name of you group you use for system administrators. 
//This will only allow users in the admin group to execute the REST call.

 

In the example you can see it adds the user provided in the URL parameter to specific space's permissions, like here among all the other permissions:

spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.VIEWSPACE_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)

 

What you could do is not shard your integration/script in multiple pieces, but instead you could just make a single REST endpoint on Confluence, and just invoke a single request to it. This single endpoint could do all of and more:

 - creating a Space (if Space Key available, etc.)

 - creating Users (if necessary, not found on the system, etc.)

 - adding Users to the created Space's permissions

 

So overall, you would not need to do any scrappy form hacking or running several dependent requests. All you would need is to just get a single endpoint up and running, and then you just send an HTTP request from the workflow post-function to it to have the Space created alongside the users added.

 

The whole thing is from the example linked - it also includes an example http url how to invoke it. If you'd have a problem with the groovy code I could give it a try and tweak it a bit, but would be good if you gave it a shot to get a better understanding how it works -- it's pretty generic and should be usable already by just copy pasting it and trying out a remote http request to check if it works and how (preferably on a test instance if in doubts :))

 

Best regards,

Radek

Like Daniel Volinski likes this
Daniel Volinski December 21, 2020

Hi Radek,

 

Thank you for all of the help! I will go ahead and try to play around with this endpoint and configure it as best as I can. If I run into any issues, I will definitely reach back out, and of course update here if I do end up figuring it out.

 

Thank you!

Daniel

Daniel Volinski January 4, 2021

Hi Radek,

 

Happy New Year!

 

I wanted to follow-up on the REST endpoint we were talking about in that one example you linked.

 

1. This section, where you make the HTTP request looks like it only adds the user to the space permissions if that user is already an admin:

 

"So now all you have to do is make a rest call with user that belongs to the admin group identified in the script using a format of 

https://<base-url>/rest/scriptrunner/latest/custom/updatePermissions?spaceKey=<Your Space Key>&user=<username of the user granting permission to>&view=<true/false>&createEditPage=<true/false>&removePage=<true/false>&editBlog=<true/false>&createAttachment=<true/false>&removeAttachment=<true/false>&comment=<true/false>&setPagePermissions=<true/false>&exportSpace=<true/false>&removeOwn=<true/false>

 

I wasn't able to execute this for two reason, one for the stated above. In my case I am parsing a regular user (non-admin) and adding them as an admin for that particular Confluence space that was just created. The second reason, it appears that some of these methods have deprecated, and there is some loss of functionality (or I need to update some things - not entirely sure). But I am getting an error here (bolded code is where errors are thrown) - This is the endpoint function that sits on Confluence Scriptrunner: 

updatePermissions(httpMethod: "GET", groups: ["confluence-administrators"]){ MultivaluedMap queryParams ->

def spaceManager = ComponentLocator.getComponent(SpaceManager)
def spacePermissionManager = ComponentLocator.getComponent(SpacePermissionManager)
// def spacePermissionManager = ComponentLocator.getComponent(SpacePermissionSaverInternal)
def userManager = ComponentLocator.getComponent(UserManager)
def dSpacePM = ComponentLocator.getComponent(DefaultSpacePermissionManager)
def space = spaceManager.getSpace(queryParams.getFirst("spaceKey") as String)
def user = userManager.getUser(queryParams.getFirst("user") as String) as ConfluenceUser
def spacePermissionToSave

// There is an issue here, and I am getting "cannot find matching method." Maybe this is deprecated, or there is another way to call this, but I tried several things and was unable to get this working.
def permissions = dSpacePM.getAllPermissionsForUser(user)

// def permissions = DefaultSpacePermissionManager.get(dSpacePM).getAllPermissionsForUser(user)

// Remove all permissions for the user from the space.

// This section, which is not surprising because the permissions variable is unable to set from the first error above.

permissions.each { perm->
if (perm.getSpace().getName() == space.getName())
{
spacePermissionManager.removePermission(perm)
}
}

// Apply the permissions selected based on the parameters in the REST call.
// Note, permissions I never want used will never be set, i.e. no backdoor to just make the REST call to get admin permissions.
if (queryParams.getFirst("view") == "true")
{
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.VIEWSPACE_PERMISSION, space, user)

// This line (and other similar ones that I have not pasted here) does not throw an error, but more of a warning: It is saying to use SpacePermissionSaverInternal.savePermission instead. I tried importing the package, but that did not seem to have any effect.
spacePermissionManager.savePermission(spacePermissionToSave)
}

 

Any advice or help would be greatly appreciated, thank you again!

 

Best Regards,

Daniel

Radek Dostál
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.
January 5, 2021

Hi,

 

What Confluence version are you on? Probably a different one than the article was written for - definitely can see some differences in the SpacePermissionManager class between Confluence 6 and 7.

 

If I have some spare time I could try this locally and perhaps modify the code for your version, but can't promise to have that done soon, might take a bit of time afterall,

 

Thanks,
Radek

Daniel Volinski January 5, 2021

Hi Redek,

 

I'm running Confluence 7, that may be the issue, just haven't found the compatibility issue yet, both versions should have access to the same methods, specifically the one it is using, but I am not sure yet.

 

No problem though, I appreciate all the help!

 

Best Regards,

Daniel

Radek Dostál
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.
January 7, 2021

Hi again,

 

So after a few debugs the following seems to work for me:

 

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate

import groovy.transform.BaseScript

import com.atlassian.confluence.user.AuthenticatedUserThreadLocal
import com.atlassian.confluence.internal.security.SpacePermissionContext
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.internal.security.SpacePermissionManagerInternal
import com.atlassian.confluence.security.SpacePermission
import com.atlassian.confluence.security.SpacePermissionManager
import com.atlassian.confluence.user.ConfluenceUserManager
import com.atlassian.user.UserManager
import com.atlassian.confluence.user.ConfluenceUser

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

//This will only allow users in the admin group to execute the REST call.
createSpaceIfNotExistsAndAddUserToPermissions(httpMethod: "GET", groups: ["confluence-administrators"]){ MultivaluedMap queryParams ->

SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager)
SpacePermissionManager spacePermissionManager = ComponentLocator.getComponent(SpacePermissionManager)
UserManager userManager = ComponentLocator.getComponent(UserManager)

ConfluenceUser currentUser = AuthenticatedUserThreadLocal.get()
SpacePermissionContext spacePermissionContext = SpacePermissionContext.createDefault()

Space space = spaceManager.getSpace(queryParams.getFirst("spaceKey") as String)
if (!space) {
space = spaceManager.createSpace(queryParams.getFirst("spaceKey").toString(), queryParams.getFirst("spaceName").toString(), null, currentUser)
}
ConfluenceUser user = userManager.getUser(queryParams.getFirst("user") as String) as ConfluenceUser
if (!user) return Response.status(400).entity("User '" + queryParams.getFirst("user") + "' does not exist on this Confluence instance.").build()
SpacePermission spacePermissionToSave

//Apply the permissions selected based on the parameters in the REST call. Note, permissions I never want used will never be set, i.e. no backdoor to just make the REST call to get admin permissions.
if (queryParams.getFirst("view") == "true")
{
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.VIEWSPACE_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("removeOwn") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.REMOVE_OWN_CONTENT_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("createEditPage") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.CREATEEDIT_PAGE_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("removePage") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.REMOVE_PAGE_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("editBlog") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.EDITBLOG_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("createAttachment") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.CREATE_ATTACHMENT_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("removeAttachment") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.REMOVE_ATTACHMENT_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("comment") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.COMMENT_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("setPagePermissions") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.SET_PAGE_PERMISSIONS_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}
if (queryParams.getFirst("exportSpace") == "true") {
spacePermissionToSave = SpacePermission.createUserSpacePermission(SpacePermission.EXPORT_SPACE_PERMISSION, space, user)
spacePermissionManager.savePermission(spacePermissionToSave)
}

return Response.ok().build();
}

 

So what does it do:

 - if the Space does not exist, create it

 - if it does exist, then just use it

 - add the parameterized user to Space Permissions, whichever ones are provided as parameters (note they don't have to all be listed, just those that are permissionKey=true)

 

So for example:

curl -u admin:password 'https://confluence-instance.com/rest/scriptrunner/latest/custom/createSpaceIfNotExistsAndAddUserToPermissions?spaceKey=SPACEKEY&spaceName=SPACENAME&user=userNameToAdd&view=true&createEditPage=true&comment=true'

 - would add the user 'userNameToAdd' to Space permissions for 'View' and 'Pages - Add'

 

So all of that combined in a single REST API endpoint.

You do have to call this under a Confluence administrator account, which belongs to the 'confluence-administrators' group (feel free to substitute with whatever group you want to limit this to).

Now it's not perfect - there certainly is room for improvements, fine-tunings and clean ups, but it seems to work as a proof of concept.

If you would need to process the data then you can return an actual response (other than generic 200), e.g. some json with some input to further process on the Jira side, should not be a problem to implement that with some more scripting.

 

Hoping this helps,

Radek

Daniel Volinski January 7, 2021

Hi Radek,

 

Thank you for this, this helps a lot! Currently I am debugging the actual call to execute the curl piece. What I am figuring out is that I am unable to execute a curl command inside of Jira ScriptRunner, so I found a potential alternative method:

authenticatedRequestFactory
.createRequest(Request.MethodType.POST, "/rest/scriptrunner/latest/custom/updatePermissions?spaceKey=CCST103&user=bbuilde&view=true&createEditPage=true&removePage=false&editBlog=true&createAttachment=true&removeAttachment=true&comment=true&setPagePermissions=true&exportSpace=true&removeOwn=true")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("X-Atlassian-Token", "no-check")
.addHeader("Cookie", "JSESSIONID=XsanitizedX")
.execute(new ResponseHandler<Response>() {
@Override
void handle(Response response) throws ResponseException {
if(response.statusCode != HttpURLConnection.HTTP_OK) {
throw new Exception(response.getResponseBodyAsString())
}
else {
def webUrl = new JsonSlurper().parseText(response.responseBodyAsString)["_links"]["webui"]
}
}
})

 

This however gives me an error:

ERROR [workflow.AbstractScriptWorkflowFunction]: Workflow script has failed on issue AT-41 for user 'dvolins'. View here: http://localhost:8080/secure/admin/workflows/ViewWorkflowTransition.jspa?workflowMode=live&workflowName=AT%3A+Service+Request+Fulfilment+workflow+for+Jira+Service+Desk&descriptorTab=postfunctions&workflowTransition=951&highlight=1
java.lang.IllegalArgumentException: Request url 'https://localhost:8084/rest/scriptrunner/latest/custom/updatePermissions?spaceKey=CCST103&user=bbuilde&view=true&createEditPage=true&removePage=false&editBlog=true&createAttachment=true&removeAttachment=true&comment=true&setPagePermissions=true&exportSpace=true&removeOwn=true' isn't in the same origin as the rpc url 'http://localhost:8084'
at org.apache.commons.lang3.Validate.isTrue(Validate.java:158)
at com.atlassian.applinks.core.auth.ApplicationLinkRequestFactoryFactoryImpl$AbsoluteURLRequestFactory.createRequest(ApplicationLinkRequestFactoryFactoryImpl.java:172)
at com.atlassian.applinks.api.ApplicationLinkRequestFactory$createRequest$0.call(Unknown Source)
at Script37.run(Script37.groovy:164)

 

I have also tried an alternative way to call that curl command like this:

def url = "http://localhost:8084/rest/api/space"
def connection = url.toURL().openConnection()
def basicAuth = "username password"

connection.setRequestProperty("Authorization", basicAuth)
connection.connect()


def permissions = "&view=true&removeOwn=false&createEditPage=true&removePage=true&editBlog=true&createAttachment=true&removeAttachment=true&comment=true&setPagePermissions=true&exportSpace=true"

//Get the selected users to apply the permissions to.


//Loop through the users and make the rest call to the rest end point we created in Confluence. Note: cfProjectAdmins has already been parsed and that is a list of users.
cfProjectAdmins.each { user ->
url = "https://localhost:8084/rest/scriptrunner/latest/custom/updatePermissions?" + cfRequestedKey + "&user=" + user + permissions

connection = url.toURL().openConnection()
connection.setRequestProperty("Authorization", basicAuth)
connection.connect()
}
}

 

And here I get an SSL issue:

Unrecognized SSL message, plaintext connection?

 

Not sure if you are familiar with this? Is it possible to implement the curl command to be executed within the scriptrunner post function?

 

I appreciate all of the help, thank you!

 

Best Regards,

Daniel

TAGS
AUG Leaders

Atlassian Community Events