AtlassianHostUser is null in Jira cloud plugin

My controller looks like this

@Controller
@IgnoreJwt
public class MainController {

    private final StorageService storageService;

    @Autowired
    public MainController(StorageService storageService) {
        this.storageService = storageService;
    }
   .
   .
   .
   @RequestMapping(value = "/clipboard", method = RequestMethod.GET)
    public ModelAndView render(@AuthenticationPrincipal AtlassianHostUser hostUser) {

        // in this case, hostUser is not NULL

        ModelAndView modelAndView = new ModelAndView("clipboardDialog");
        return modelAndView;
    }
  @RequestMapping(value = "/upload", method = RequestMethod.POST)
  public String handleFileUpload(@AuthenticationPrincipal AtlassianHostUser hostUser,
   @RequestParam("file") MultipartFile file,
   RedirectAttributes redirectAttributes) {

   // In this case, hostUser is null. The only difference in both the method is
   // the previous method is of type GET and this one is of POST 

   hostUser.getUserKey(); // Causing NPE because hostUser is null

My descriptor is shown below

{
    "key": "copy-paste-plugin",
    "baseUrl": "${addon.base-url}",
    "name": "Copy Paste (Spring Boot)",
    "authentication": {
        "type": "jwt"
    },
    "lifecycle": {
        "installed": "/installed",
        "uninstalled": "/uninstalled"
    },
    "scopes": [
        "READ",
        "WRITE"
        "ACT_AS_USER"
    ],

    "modules": { 
           "dialogs": [
            {
                "url": "/clipboard",

                "options": {
                    "size": "large",
                    "chrome": true,
                    "header": {
                        "value": "${copypaste.clipboard-dialog-name}"
                    }
                },
                "key": "dialog-module-key",
                "conditions": [
                    {
                        "condition": "user_is_logged_in"
                    }
                ]
            }
        ],
        "keyboardShortcuts": [
            {
                "shortcut": "v",
                "target": {
                    "type": "dialogmodule",
                    "key": "dialog-module-key"
                },
                "context": "GLOBAL",
                "name": {
                    "value": "Attach File Keyboard Shortcut"
                },
                "key": "keyboard-shortcut-key-v"
            }
        ],
        "generalPages": [
            {
                "url": "/upload",
                "key": "upload",
                "location": "none",
                "name": {
                    "value": "Attach a file"
                },
                "conditions": [
                    {
                        "condition": "user_is_logged_in"
                    }
                ]
            }
        ]
    }
}

Upload form: clipboardDialog.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout">

    <head th:insert="~{layout :: CommonBaseLayout}">
        <title>Copy Paste Plugin</title>
    </head>
    <body>
        <div style="height:100%;">
            <div id="target" contenteditable="true" style="height:400px;width:inherit;display:block;border: solid 1px #000;margin:10px 0;"></div>
        </div>
        <form class="aui" method="post" id="file-input-form" enctype="multipart/form-data" action="/upload">
            <fieldset>
                <legend><span>File upload</span></legend>
                <div class="field-group">
                    <label for="file-upload-example">Upload
                        file</label>
                    <input class="upfile" type="file" id="file-upload-example" name="file"/>

                </div>
            </fieldset>
            <div class="buttons-container">
                <div class="buttons">
                    <input class="button submit" type="submit" value="Save" id="comment-save-button"/>
                    <a class="cancel" href="#">Cancel</a>
                </div>
            </div>

        </form>
        <script type="text/javascript" src="/js/DateUtil.js"></script>
        <script type="text/javascript" src="/js/main.js"></script>
    </body>
</html>

In the above dialog there are two buttons #1 is input type submit button and #2 is dialog’s submit button. I am currently using #1 input type submit button to submit the form request. Because this plugin is work in progress so the dialog’s submit button is unused.

What am I missing in above code which is causing AtlassianHostUser as null?

Hello @bewithvk,

Is it safe to assume that since this is not visible in the UI a different web module (maybe a dialog) is posting to /upload? Can you paste code snippets of that?

I’m thinking since the product, Jira, is hitting the GET /clipboard endpoint, AuthenticationPrincipal is properly injected.

Thanks,
Ian

That’s right. A dialog is posting the file to /upload controller.
I am getting AuthenticationPrincipal in GET /clipboard endpoint but not in POST /upload. That’s what is the problem I am trying to solve.
If you have a close look into my descriptor file, there are three modules:
keyboardShortcuts: To trigger an event on a key press which in turn triggers the dialog module.
dialogs: This opens a dialogue, the dialog have the html form to select the file and a submit button. On submit the form gets submitted to action /upload which I have defined in another module i.e.
generalPages: With location none I just want to expose an action /upload to submit a file.

I added the dialog’s html content in the original question.

Hi @bewithvk,

to clarify the code you have provided, I would make two changes:

  • Remove the @IgnoreJwt annotation from MainController. This annotation enables anonymous access, which you don’t want, as far as I can tell.
  • Remove the generalPages module from your app descriptor. It serves no purpose.

Now, with those changes, the problem you are encountering should manifest more clearly: when you submit the form, the POST request made to the /upload endpoint is not being authenticated, and your app rejects it.

We currently provide two mechanisms for authenticating such requests:

  1. Use the JWT token provided in the Spring Web MVC model attribute atlassianConnectToken when rendering your template. See the section _ Authenticating requests from iframe content back to the add-on_ of the atlassian-connect-spring-boot README. See iframe.html in our AJAX sample for two ways of using that token.
  2. Use the JavaScript API AP.context.getToken() to obtain a JWT, and submit the form using JavaScript. See macro.html in our Cacheable Macro sample for an example of how to use that.

I hope this helps! :slight_smile:

3 Likes

I feel this is going to be helpful. I will give it a try and let you know if I have any followup question.
Thanks.