Use Tomcat URL rewriting to serve external images for Atlassian JIRA avatars

Atlassian makes some great workflow and knowledge management tools.  They are perhaps best well known for JIRA, originally used for tracking development issues.  In recent releases JIRA has become much more of team-friendly tool to manage projects during their entire life cycles.  As such there are a bunch of niceties such as personal profiles.

I recently installed and configured JIRA at Mullen.  We use ActiveDirectory (as I’m sure many of Atlassian’s clients do) and so I configured JIRA to delegate authentication to ActiveDirectory.  This means that users created in JIRA must have the same username as their ActiveDirectory account.  Mullen also has an internal personnel directory which has headshots of everyone in the agency with filenames that match their ActiveDirectory account (first initial, last name, e.g.: dfeinzeig).  I wanted to find a way to have JIRA use these pre-existing headshots without having to manage each user’s JIRA profile.

Some folks configure Apache to run in front of the Tomcat server that is installed with JIRA, but that is totally unnecessary to pull profile images from another location.  It turns out that you can easily do URL rewriting with Tomcat just as you would with Apache’s mod_rewrite (in fact, Atlassian is doing that behind the scenes with the vanilla JIRA install) and pull profile images from outside of JIRA.

You need to locate the urlrewrite.xml file.  For a Windows install it should be located at C:\Program Files\Atlassian\JIRA\atlassian-jira\WEB-INF\urlrewrite.xml .  When you open this file you will see that Atlassian is using the Tuckey UrlRewriteFilter, a Java web filter that works with Tomcat.

With the default rules that Atlassian is using for JIRA, it should look something like:

 XML |  copy code |? 
01
<?xml version="1.0" encoding="utf-8"?>
02
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 2.3//EN" "http://tuckey.org/res/dtds/urlrewrite2.3.dtd">
03
 
04
<!--
05
    URL Rewrite files to make issue navigator URL backwards compatible and some other things
06
    @since JIRA 3.3
07
-->
08
<urlrewrite>
09
 
10
    <!-- Caching of static resources -->
11
    <rule>
12
        <!-- because Orion and other application server apply filters to requests, you end in an infinite loop here
13
             unless you only apply the filter once per request.  That is what the 'condition' and 'set' params are for
14
              -->
15
        <condition name="cachingHeadersApplied" type="attribute" operator="notequal">true</condition>
16
        <from>^/s/(.*)/_/(.*)</from>
17
        <run class="com.atlassian.plugin.servlet.ResourceDownloadUtils" method="addCachingHeaders" />
18
        <to type="forward">/$2</to>
19
        <set name="cachingHeadersApplied">true</set>
20
        <set name="_statichash">$1</set>
21
    </rule>
22
 
23
    <!-- @since 5.0 [KickAss]-->
24
    <rule>
25
        <from>^/issues(\?.*)?$</from>
26
        <to type="permanent-redirect">issues/$1</to>
27
    </rule>
28
 
29
</urlrewrite>

Using Chrome’s inspector I figured out which URL is used to request most of the user avatars, /secure/useravatar .  There are a few parameters set in the URL including the person’s username (which I need since our headshots are named to match the usernames) and the size of the image to return (which unfortunately I cannot use since we have static images in a single size).  Using this information, here is the new rule that I wrote:

 XML |  copy code |? 
1
<rule enabled="true">
2
    <condition type="request-uri" operator="equal">^/secure/useravatar</condition>
3
    <from>^/secure/useravatar\?ownerId=(\w+)?&.*$</from>
4
    <to type="permanent-redirect">http://<our_internal_url>/images/people/$1.jpg</to>
5
</rule>

This is a pretty basic rule, looking for the /secure/useravatar URL to match in line 2, capturing the username (ownerID) querystring parameter in line 3, and redirecting to our internal URL which serves the user headshots using the captured group as the filename in line 4. 

This rule will catch the images used for the activity stream, user profile, and other large versions of the avatar images.  Unfortunately it won’t catch the small ones used for things like comments on issues.  Since I only have access to one image size I didn’t bother writing a rule to handle the smaller size, but with a little snooping via Chrome you can adapt this rule to use a regex to capture the requested image size as well.

The final urlrewrite.xml file looks like:

 XML |  copy code |? 
01
<?xml version="1.0" encoding="utf-8"?>
02
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 2.3//EN" "http://tuckey.org/res/dtds/urlrewrite2.3.dtd">
03
 
04
<!--
05
    URL Rewrite files to make issue navigator URL backwards compatible and some other things
06
    @since JIRA 3.3
07
-->
08
<urlrewrite>
09
 
10
    <!-- Caching of static resources -->
11
    <rule>
12
        <!-- because Orion and other application server apply filters to requests, you end in an infinite loop here
13
             unless you only apply the filter once per request.  That is what the 'condition' and 'set' params are for
14
              -->
15
        <condition name="cachingHeadersApplied" type="attribute" operator="notequal">true</condition>
16
        <from>^/s/(.*)/_/(.*)</from>
17
        <run class="com.atlassian.plugin.servlet.ResourceDownloadUtils" method="addCachingHeaders" />
18
        <to type="forward">/$2</to>
19
        <set name="cachingHeadersApplied">true</set>
20
        <set name="_statichash">$1</set>
21
    </rule>
22
 
23
    <!-- @since 5.0 [KickAss]-->
24
    <rule>
25
        <from>^/issues(\?.*)?$</from>
26
        <to type="permanent-redirect">issues/$1</to>
27
    </rule>
28
 
29
    <!-- David Feinzeig, redirects requests for JIRA avatar images to internal images -->
30
    <!-- activity stream, profile, and other large versions -->
31
    <rule enabled="true">
32
        <condition type="request-uri" operator="equal">^/secure/useravatar</condition>
33
        <from>^/secure/useravatar\?ownerId=(\w+)?&.*$</from>
34
        <to type="permanent-redirect">http://<our_internal_url>/images/people/$1.jpg</to>
35
    </rule>
36
 
37
</urlrewrite>

You need to restart JIRA after saving those changes in order for the new rules to take effect.  There is one last thing to note: when a new user is created in JIRA, they are assigned one of the default cartoony avatar images.  It seems that those internal avatar images use a different URL to fetch the images than when a user uploads their own photo.  The internal avatar URL does not contain the username, only the uploaded image URL does.  So when a new user is created their profile needs to have a photo uploaded once in order to change the type of avatar request URL used.  I just used Mullen’s logo since it would be an easy thing for our internal IT department to upload when they create new people’s accounts and that way it is also consistent across users.  Once this onetime upload has occurred, all avatar images will be served via the redirect rule.  I tried to figure out where the data was being stored in the database for the internal vs. external avatar image information, but didn’t have too much time to spend on it.  If someone figures that out, I’d love to hear about it.


Leave a Reply