Restore Access from the Announcement Banner Mashup

Today’s mashup come to us from a company in a highly regulated industry that needed to remove users who had not used Jira in the past 90 days. Users would come back from a leave of absence (or just start using Jira again) and flood the support team with requests to restore their access to Jira.

Since this Data Center instance did not have Service Management they didn’t have a way for non-licensed users (or customers) to create a ticket easily. Yes, they could have sent an email but they wanted an easy way for the users to solve the problem immediately. They wanted a way to send the user to a form (of some sort) from the announcement banner so they could request their access to be restored.

Since this was on a corporate intranet only users who would already be allowed to access Jira could get to the instance. And, all users already existed in Jira because of LDAP integration.

 

Since I didn’t have access to their intranet and the form they were planning on using I made a simplified version that I shared with them to get started with.

Part 1

The first part of this mashup is the announcement banner JavaScript and HTML.

HTML

The HTML is a simple panel with a shaded border. The text provides a link that would go to the form that the company would need to build. I will use the <div> tags id and the link id in the JavaScript.

<div id="restore_message" style="border: 1px solid #27798d; background-color: #fff; padding: 10px;"> <b>Restore Jira License!</b><br />You can restore the Jira license <a id="restore_link">Click here</a> to know more!</div>

Check User Function

The check user function has two main purposes:

  1. Get the users identity from the session.

  2. Check to see if the user is an active/licensed user.

We need to know if the user is currently licensed or not so we can hide the message from users who don’t need to see it in the first place. Assuming we are even able to determine the user that is. If the user can not be identified automatically the form the user would (ultimately) get redirected to would need to collect that information from the user. For this example we will assume we can collect it.

var isUser = false; var userKey; function checkUser() { AJS.$.ajax({ dataType: "json", url: "/rest/api/2/myself", type: "GET", data: { expand: "groups" }, async: false, success: function(data) { userKey = data.key; for (var i = 0; i < data.groups.size; i++) { if (data.groups.items[i].name == "jira-software-users") { isUser = true; break; } } } }); }

The code above is using the group called jira-software-users to determine if the user is already licensed or not. This may need to be changed according to your Jira’s settings.

Page Load Script Part 1

The next bit of code checks to see of the user is on the login page. If not, then the message does not need to be displayed. Next, the code calls the check user function (above) and if the user is already licensed it hides the message as well.

var url = window.location.href; if (url.includes("Dashboard") == true) { checkUser(); if(isUser == true) { AJS.$("#restore_message").hide(); } } else { AJS.$("#restore_message").hide(); }

Page Load Script Part 2

This next bit of code is the function that gets triggered when the user clicks the link. Ideally, this is the part that the form would have taken care of. However, in my example I included it here but it has some inherent flaws like the fact that I needed to provide authentication to call the webhook that completes the task.

Basically, all this code does is call a REST endpoint that I created and passes it some data. If there are any errors it updates the text in the message box with information regarding those errors and different instructions if necessary.

AJS.$("#restore_link").click(function() { var data = {}; data["key"] = userKey; var settings = { "url": "http://localhost:8080/rest/keplerrominfo/refapp/latest/webhooks/RestoreUser/run", "method": "POST", "timeout": 0, "data": JSON.stringify(data), "headers": { "Content-Type": "application/json", "Authorization": "Basic am11ckjhjhgdfhgfd8wNTE4" }, success: function (response) { $('#restore_message').html('Success! Please try logging in again.'); }, error: function (jqXHR, exception) { var msg = ''; if (jqXHR.status === 0) { msg = 'Not connected.\n Verify Network.'; } else if (jqXHR.status == 404) { msg = 'Your account was not found. Please contact support.'; } else if (exception === 'timeout') { msg = 'Time out error.'; } else { msg = 'Uncaught Error.\n' + jqXHR.responseText; } $('#restore_message').html(msg); } }; $.ajax(settings).done(function (response) { console.log(response); }); });

Complete Script

<script> var isUser = false; var userKey; function checkUser() { AJS.$.ajax({ dataType: "json", url: "/rest/api/2/myself", type: "GET", data: { expand: "groups" }, async: false, success: function(data) { userKey = data.key; for (var i = 0; i < data.groups.size; i++) { if (data.groups.items[i].name == "jira-software-users") { isUser = true; break; } } } }); } $(document).ready(function() { var url = window.location.href; if (url.includes("Dashboard") == true) { checkUser(); if(isUser == true) { AJS.$("#restore_message").hide(); } } else { AJS.$("#restore_message").hide(); } AJS.$("#restore_link").click(function() { var data = {}; data["key"] = userKey; var settings = { "url": "http://localhost:8080/rest/keplerrominfo/refapp/latest/webhooks/RestoreUser/run", "method": "POST", "timeout": 0, "data": JSON.stringify(data), "headers": { "Content-Type": "application/json", "Authorization": "Basic am11ckjhjhgdfhgfd8wNTE4" }, success: function (response) { $('#restore_message').html('Success! Please try logging in again.'); }, error: function (jqXHR, exception) { var msg = ''; if (jqXHR.status === 0) { msg = 'Not connected.\n Verify Network.'; } else if (jqXHR.status == 404) { msg = 'Your account was not found. Please contact support.'; } else if (exception === 'timeout') { msg = 'Time out error.'; } else { msg = 'Uncaught Error.\n' + jqXHR.responseText; } $('#restore_message').html(msg); } }; $.ajax(settings).done(function (response) { console.log(response); }); }); }); </script> <div id="restore_message" style="border: 1px solid #27798d; background-color: #fff; padding: 10px;"> <b>Restore Jira License!</b><br />You can restore the Jira license <a id="restore_link">Click here</a> to know more!</div>

Warning

When adding JavaScript in the announcement banner it is important to be very careful. Any mistake in the code could make Jira completely unusable… like all of it.

I have done it a couple times so I should know. They only way to dig yourself out of this mess is to remove the code from the announcement banner. However, you will no longer be able to do this from the UI since you broke it. You will need to fix it in the database. I suggest having a terminal or database app running while doing this so you can implement the fix immediately before anyone finds out that you didn’t have enough sense to know that you shouldn’t listen to me.

Here is the SQL code I needed to use a couple times to fix my mistakes:

DELETE from propertytext WHERE ID = (select id from propertyentry where property_key='jira.alertheader'); DELETE from propertystring where ID = (select id from propertyentry where property_key='jira.alertheader.visibility'); DELETE from propertyentry where property_key in ('jira.alertheader','jira.alertheader.visibility');

See this post for more information about clearing the announcement banner from the database.

Part 2

Webhook

The next part of this mashup is a SIL Webhook. However, this could have been done some other ways and I wonder if they may have actually been better. To be far it is the webhooks fault that authentication is required, however, security is generally a good thing. I also considered using:

  • Jira issue collector - I think this would have worked pretty well. The issue could have gone to an issue type with a specific workflow that had code similar to what is posted below as a post function. When approved the user could have been automatically restored.

  • SIL Mail Handler - The users could have emailed their request in with specific key words in the subject line. The user could have been identified from the email address contained in the email. This info could have been directed to a ticket (like with the issue collector above) or the user could be restored right there in the mail handler code.

  • Jira Service Management - Since external users can create requests a portal can be used just for internal users as well. Problems like this and many others can be created as request types.

However, that's not what I did. What I did was make a webhook. So lets look at that:

First Half of the Script

The fist thing we do in the script is define a struct to be used to parse the incoming JSON data. See more about the HTTP routines are parsing JSON by checking out the fromJSON() routine page.

Next, we use some structures and functions specific to webhooks to get the data that is being passed to it. See more about webhhook routines.

After that, we check to see if the call made to this REST endpoint was a POST. If not we return an error message and error code.

struct _payload { string key; } WebhookPayload httpRequestPayload = getWebhookPayload(); string httpMethod = httpRequestPayload.httpMethod; string httpPayload = httpRequestPayload.payload; if(httpMethod != "POST") { appendToWebhookResponse("Unknown method"); return false, 405; }

How do we know it should be a POST call? Because we created the endpoint so we can decided what type it should be! Since we are receiving some data from this call it really should be a POST or maybe a PUT, but really a POST. See more about configuring SIL Webhooks.

Second Half of the Script

This part of the script takes the JSON payload and tries to read the data from it. If the user key is null it will return an error message and code.

After that, the script impersonates an admin user to its permissions can be increased. This is done as a (perhaps lame) attempt to compensate for the authentication within the JavaScript. The thought being that a low privileged user could be used just so the webhook can be triggered. That way, if the user information is compromised the malicious user will not have enough privileges to cause any real harm. Ideally, the form would have been created in a more secure fashion so that the authentication information would not be exposed to the end user in this way.

After switching to an admin user the script checks to see if the data provided does in fact represent a Jira user. If so, the user will be reactivated, if not, another error will be sent.

_payload p = fromJson(httpPayload); if(isNull(p.key)) { appendToWebhookResponse("Key can not be null"); return false, 400; } else { runAs("admin"); string user = userKeyToUsername(p.key); if(userExists(user)) { admActivateUser(user); } else { appendToWebhookResponse("User does not exist"); return false, 404; } } return true, 200;

At the end of the script a code representing a successful execution is returned. These are the same codes used by the announcement banner JavaScript to update the messages displayed to the user.

Complete Script

struct _payload { string key; } WebhookPayload httpRequestPayload = getWebhookPayload(); string httpMethod = httpRequestPayload.httpMethod; string httpPayload = httpRequestPayload.payload; if(httpMethod != "POST") { appendToWebhookResponse("Unknown method"); return false, 405; } _payload p = fromJson(httpPayload); if(isNull(p.key)) { appendToWebhookResponse("Key can not be null"); return false, 400; } else { runAs("admin"); string user = userKeyToUsername(p.key); if(userExists(user)) { admActivateUser(user); } else { appendToWebhookResponse("User does not exist"); return false, 404; } } return true, 200;

Contents

Be advised: The scripts in this example are not perfect and assume that additional work will be done to make them more acceptable, like creating a new form page that collects additional information and hides the authentication information. It is not the intent of this author to offer up a complete solution but to inspire and educate the reader to consider new possibilities when looking to solve problems similar to their own.