JIRA attachment upload via XDM / AP.request()

492 views
Skip to first unread message

b...@site9.com

unread,
Sep 5, 2013, 7:09:38 PM9/5/13
to atlassian-...@googlegroups.com
Has anyone been successful uploading an attachment file to a JIRA issue via client-side javascript? I was able to accomplish this in Plugins v2 by creating an HTML5 file object using the FormData API. This approach doesn't seem to work using AP.request() though, presumably because the additional request config is not passed through the xdm bridge? Here's an abbreviated example of how I was doing it in Plugins v2:

var formData = new FormData();
var blob = new Blob( [ theFileContents ], { type: "text/html" });

formData.append("file", blob, "test.html");

jQuery.ajax({
url: '/rest/api/2/issue/' + issueId + '/attachments/',
type: 'POST',
data: formData,
processData: false,
contentType: false,

beforeSend: function (request) {
request.setRequestHeader("X-Atlassian-Token", "nocheck");
},

....
});


thanks,
Ben

Robert Bergman

unread,
Sep 6, 2013, 11:45:00 AM9/6/13
to atlassian-...@googlegroups.com, b...@site9.com
AP.request is intentionally quite restrictive on what request information it transports over the bridge for security reasons, so it doesn't surprise me that some APIs require more request headers that aren't currently supported.  Feel free to raise an issue, though, so this additional scenario case can be reviewed for support by AP.request.

--
Bob Bergman
--
You received this message because you are subscribed to the Google Groups "Atlassian Connect Dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connec...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

b...@site9.com

unread,
Sep 6, 2013, 3:01:28 PM9/6/13
to atlassian-...@googlegroups.com, b...@site9.com
Thanks Bob,

In the mean time I've decided to just proxy that call on the server side, however using the approach described on the connect-express home page, I am getting a 403 from my JIRA install for any REST endpoint I try to access. The proxied request appears to have the correct uri, params, and oauth headers added, so I'm not sure how to proceed? Can you point me at a working express example? The TaskMaster example appears to do something similar, but I get an error when running 'npm install' after cloning it.

My test code looks like this:

...
app.get('/proxytest',

addon.authenticate(),

function(req, res) {
    var httpClient = addon.httpClient(req);
httpClient.get( '/rest/api/2/issue/10000', function(err, res, body) {
console.log('Proxy Response: ', res );
});
}
);

thanks,
Ben
To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connect-dev+unsub...@googlegroups.com.

Rich Manalang

unread,
Sep 9, 2013, 1:26:08 PM9/9/13
to atlassian-connect-dev
Hi Ben. Sorry for the late reply. I just tested this. Make sure you have the following scope permission:

<permission>browse_projects</permission>

Rich


To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connec...@googlegroups.com.

b...@site9.com

unread,
Sep 9, 2013, 4:48:15 PM9/9/13
to atlassian-...@googlegroups.com
Thanks Rich!

I didn't think of adding a <permission> entry to my XML because my app was already reading issue data on the client via the XDM bridge. Is it supposed to be the case that client-side access via AP.request is not subject to the same permission restrictions?

Also, is there an example or recommended approach for signing a request with connect-express that does not originate from JIRA? The addon.authenticate() middleware is dependent on the OAuth headers being part of the request, but if I want to proxy an XHR that originates directly from my client there are no OAuth headers.

cheers,
Ben
To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connect-dev+unsubscri...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.

Rich Manalang

unread,
Sep 10, 2013, 1:04:31 AM9/10/13
to atlassian-connect-dev

AP.request() is not bound to permission scopes because it operates as the logged in user -- user impersonation is not possible with AP.request().

Re: your second question... Are you trying to call JIRA from the server? There's a few examples on the connect-express readme of how to use addon.httpClient() to make oauth signed requests back up to the host... https://bitbucket.org/atlassian/atlassian-connect-express

Rich

To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connec...@googlegroups.com.

b...@site9.com

unread,
Sep 10, 2013, 1:34:01 AM9/10/13
to atlassian-...@googlegroups.com
Thanks, I saw those examples on the connect-express readme page. They imply that any relative url called with an httpClient created from addon.httpClient() will be auto-signed, but that seems to only be the case if the addon.authenticate() middleware is also used and the originating request has the correct oauth headers? In other words, I am able to successfully make a call using the code below, but only if the '/test' route is invoked by JIRA directly. If I call '/test/' on my server via ajax within my client iframe I get a "Login Required" error message. I'm fairly new to node.js so pardon me if I'm missing something obvious, like needing my own session management to store the auth info.

app.get('/test',
    addon.authenticate(),
    
    function(req, res) {
var httpClient = addon.httpClient(req);
var myReq = httpClient.get( '/rest/api/2/issue/[TEST-ID]', function(err, res, body) {
console.log("response", res );
});
    }
);

b...@site9.com

unread,
Sep 10, 2013, 12:54:50 PM9/10/13
to atlassian-...@googlegroups.com
Ok I think I found the problem.... the requests do get signed properly, I was just missing the fact that addon.httpClient requires the userId in the context. So if I call an entry point directly on my server I have to pass the current userId from the client ( or use a static one ) in the same manner as the requests that originate from JIRA. For a local ajax request the request's session/context have hostBaseUrl and appKey but no userId, however this does not raise an exception in the httpClient constructor like it does when you construct from raw options ( see code snippet below ). Anyhow, sorry for the confusion! 

proto.httpClient = function (reqOrOpts) {
  var ctx = reqOrOpts.context;
  if (ctx) return ctx.http;
  var opts = reqOrOpts;
  if (!opts.hostBaseUrl) throw new Error('Http client options must specify a hostBaseUrl');
  if (!opts.userId) throw new Error('Http client options must specify a userId');
  opts = _.extend({appKey: this.key}, opts);
  return hostRequest(opts, this.config.privateKey());
};

Rich Manalang

unread,
Sep 10, 2013, 3:31:52 PM9/10/13
to atlassian-connect-dev

Ah right. Protip: if you need to debug the http client, set the NODE_DEBUG=request env variable to see all the raw traffic.

Rich

To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connec...@googlegroups.com.

b...@site9.com

unread,
Sep 10, 2013, 5:23:21 PM9/10/13
to atlassian-...@googlegroups.com
Just in case anyone is still paying attention, here's a full example of how to proxy upload an issue attachment through node.js:

---------- atlassian-plugin.xml -----------
....
    <permissions>
      <permission>create_issues</permission>
    </permissions>
....

---------- client-side javascript ---------

AP.getUser( function( user ) {
jQuery.ajax({
//must append user_id for addon.httpClient(req) to work on the server
url: '/doUpload?user_id=' + user.id,
type: 'POST',
data: {
issueId: theIssueId,
fileData: theData
},
error: function( xhr, status, error ) {
//handle error
},
success: function( data, status, xhr ) {
//refresh UI
}
});
});
---------- server-side javascript --------

var FormData = require('form-data');

module.exports = function (app, addon) {

app.post('/doUpload',
addon.authenticate(),
function(req, res) {    
//Extract the raw fileData
var fileData = req.body.fileData;
//Create a FormData object and append a field named 'file' in the format JIRA is looking for
var form = new FormData();
form.append('file', fileData, { ContentType: 'text/plain', filename: 'test.txt' } );
//Create custom headers from the FormData ( adds content-type: mulipart/form-data and boundary identifier )
var custHeaders = form.getHeaders();
//Add the special Atlassian nocheck header
custHeaders['X-Atlassian-Token'] = 'nocheck';
var httpClient = addon.httpClient(req);
var proxyReq = httpClient.post( {
url: '/rest/api/2/issue/' + req.body.issueId + '/attachments',
headers: custHeaders,
}, function(err, proxyRes, body) {
res.send( body, 200 );
});
//Upload the data
form.pipe( proxyReq );
}
);

Rich Manalang

unread,
Sep 10, 2013, 5:25:48 PM9/10/13
to atlassian-connect-dev
You'll probably want to prevent user_id passing through a query param and instead create a session for your users.

Rich


To unsubscribe from this group and stop receiving emails from it, send an email to atlassian-connec...@googlegroups.com.

b...@site9.com

unread,
Sep 10, 2013, 5:53:53 PM9/10/13
to atlassian-...@googlegroups.com
Yes, good point.... you don't need addon.authenticate() either in the case of a local proxy route. The server part is the important stuff :)
Reply all
Reply to author
Forward
0 new messages