Retrieving Member Avatar File via API

Jason Terando January 29, 2018

Hi, I'm trying to get the image file data (not just the URL) for a member's avatar.  The URL returned the a user's avatarUrls along the lines of:

https://avatar-cdn.atlassian.com/xxxxyyyyzzz?s=24&d=https://mydomain.atlassian.net/secure/useravatar?size=small&ownerId=my.acct&avatarId=12345&noRedirect=true

The CDN returns a 302 redirect back to our Atlassian site along the lines of:

https://mydomain.atlassian.net/secure/useravatar?size=small&ownerId=my.acct&avatarId=12345&noRedirect=true

The problem I'm running into is that the call to mydomain.atlassian.net needs to be authenticated, and I don't know how to do that via the API, without using cookies, which the API documentation recommends we avoid.   So... what am I missing here?

3 answers

1 accepted

0 votes
Answer accepted
Jason Terando February 5, 2018

Hi, here's what I got to work for our cloud account

  1. Use the API call /rest/1/auth/1/session, include Atlassian API user name and API key in the basic-authentication header (BASE 64 encoded), and Jira user name and password.  Note that these are not the same thing.  My Atlassian user name is my email address, and my Jira user name is not an email address.  Moreover, I can't find where in the recent UI upgrade where you can set API keys anymore, but that is another topic...
  2. On a successful call, grab all cookies from the header "set-cookie" line.  You will probably have to extract out the "name=value" portion by getting everything before the first semi-colon.  If using Node request, make sure to make the request with the option to get get the full response
  3. When calling the URL to retrieve a member avatar, include the cookies extracted from step #2 (joined with a semi-colons), and make sure forwarding is turned on.  Also, in Node, I had to make sure encoding was disabled or my avatar images got messed up

 

Here is some TypeScript that demonstrates a class that handles the authentication and redirect required to get Avatar URLs...

 

import * as request from "request-promise-native";

export interface IAtlassianSiteProxy {
    retrieveImage(url: string): Promise<IAtlassianAvatar>;
}

export interface IAtlassianAvatar {
    readonly data: Buffer;
    readonly contentType: string;
}

export class AtlassianSiteProxy implements IAtlassianSiteProxy {

    private _atlassianDomain: string;
    private _userName: string;
    private _password: string;
    private _apiUserName: string;
    private _apiKey: string;
    private _cookie: string = '';

    public constructor(atlassianDomain: string, userName: string, password: string, apiUserName: string, apiKey: string) {
        this._atlassianDomain = atlassianDomain;
        this._userName = userName;
        this._password = password;
        this._apiUserName = apiUserName;
        this._apiKey = apiKey;
    }

    /**
     * Launch session to get cookie value
     */
    private async getAtlassianCookie(): Promise<string> {
        if (this._cookie) {
            return this._cookie;
        }
        var options = {
            uri: this._atlassianDomain + '/rest/auth/1/session',
            method: 'POST',
            headers: {
                "Accept": "application/json",
                "Content-type": "application/json"
            },
            auth: {
                'user': this._apiUserName,
                'pass': this._apiKey
            },
            body: {
                username: this._userName,
                password: this._password
            },
            json: true,
            resolveWithFullResponse: true,
            followRedirect: true,
            followAllRedirects: true
        };

        let response = await request(options);
        let headers = response.headers;
        /* Flatten out cookies into a single string */
        if (headers['set-cookie']) {
            this._cookie = headers['set-cookie'].map(function (cookie: string) {
                let i = cookie.indexOf(';');
                let j = cookie.indexOf('=');
                if (i > j && i > -1) {
                    let name = cookie.substr(0, j);
                    let value = cookie.substr(j + 1, i - j - 1);
                    return name + '=' + value;
                } else {
                    return '';
                }
            }).join('; ').toString();
            return this._cookie;
        }
        throw "Cookies not received from Atlassian session";
    }

    /**
     * Retrieve the image specified by the URL, handling authentication and redirects
     * @param url
     */
    public async retrieveImage(url: string): Promise<IAtlassianAvatar> {
        let cookie = await this.getAtlassianCookie();
        let avatarOptions = {
            method: 'GET',
            uri: url,
            headers: {
                Connection: 'keep-alive',
                Cookie: cookie
            },
            auth: {
                'user': this._userName,
                'pass': this._apiKey
            },
            encoding: null,
            resolveWithFullResponse: true,
            followRedirect: true
        };

        let response = await request(avatarOptions);
        let contentType = response.headers['content-type'];
        let data = response.body;

        return {
            contentType: contentType,
            data: data
        };
    }
}
0 votes
Jason Terando January 30, 2018

Hi @Thomas Deiler, I tried adding your suggested headers, to no avail.  I suspect the problem is that the second URL, to get the avatar file, is not an API call, but a site call.  Somehow, I have to get the cookies required to access the site, not just the API.

Thomas Deiler
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 30, 2018

I reproduced your problems in my environment - I'll come back when I solved it.

Jason Terando January 30, 2018

Thank you very much!

Jason Terando January 31, 2018

Hi, first - thanks again for taking the time to look into this.  I ran wget on my machine and ended up getting the "generic" icon.

The cookie file from wget looks like this:

````

.atlassian.com    TRUE    /    TRUE    0    cloud.session.token    deleted
id.atlassian.com    FALSE    /    FALSE    0    atlassian.account.ffs.id    (UUID)
.id.atlassian.com    TRUE    /    TRUE    0    cloud.session.token    deleted
id.atlassian.com    FALSE    /    TRUE    0    atlassian.account.xsrf.token    (HexOrBinStringLookingValue)

````

When I browse to the secure/useravatar in Jira (and we're using the hosted version, probably should have said that explicitly) and look at the cookies going over, there are a lot more cookies getting sent:

_utma, _utmz, ajx_user_id, ajs_group_id, ajs_anonymous_id, atlassian.xsrf.token, _utmb, &tmc, &tmt, cloud.session.token, _csrf

The interesting one to me is cloud.session.token, where wget was setting that as "deleted", whereas when hitting the Atlassian site in the browser it is a big long value. 

I guess the next thing I'll try is injecting the cookeis in manually until I figure out which one/s are really required...

Thomas Deiler
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 31, 2018

Dear @Jason Terando,

that sounds fine. Was my answer fine enough to be accepted?

Many thanks in advance

Thomas

0 votes
Thomas Deiler
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 30, 2018

Dear @Jason Terando,

you have to do basic authentication (normally) and add a custom header (Content-Type: application/json), first. Then

GET /api/2/issue/<issue key>

should work.

For some API functions, it only works when adding additional headers:

accept: application/json
X-Atlassian-Token: nocheck

So long

Thomas

Thomas Deiler
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 31, 2018

Dear @Jason Terando

Ok - got it. My solution is with wget, but this can be for sure adapted to any other program (like curl) or programming language.

I have looked at the source code of the login page of Jira. Username and password are stored in variables os_username and os_password. Additionally the 'save login on my computer' has to be checked. This is the variable os_cookie.

wget -d --save-cookies cookies.txt --keep-session-cookie --post-data 'os_username=admin&os_password=admin&os_cookie=true' http://localhost:8080/login.jsp -O login-result.html

This will authorize you, session saved to cookie.

wget -d --load-cookies cookies.txt http://localhost:8080/secure/useravatar?size=medium&avatarId=10122 -O avatar.png

This will download one avatar, taking the session from before. The '-d' option delivers more output, to see the HTTP header information.

Tell me, if you could reproduce what I found out.

So long

Thomas

Suggest an answer

Log in or Sign up to answer