401 Unauthorized when accessing plugin servlet

Hey all,

I am writing a custom plugin for Bitbucket that allows users in our internal instance to navigate to an Admin list page. This page will expose the admins for each project in our instance, and will allow the user to mail those admins.

I believe that the built in authentication is blocking a user with normal privileges from accessing this page. It is probably due to the usage of PermissionAdminService objects.

Is there some standard to setting “custom” permissions on a plugin servlet page? Is there a way to execute the servlet code as the atlassian user/service user? I have tried implementing a servlet filter but am unsure if that is best practice.

With a Servlet you’re pretty much on your own. Can you post your code? the piece in the atlassian-plugin.xml and the java class would be good. :slight_smile:

You will want to make use of the SecurityService, where you can temporarily run some operations as escalated or even impersonating another user.
https://developer.atlassian.com/static/javadoc/bitbucket-server/4.4.1/api/reference/com/atlassian/bitbucket/user/SecurityService.html

Here is something similar

I would post a snippet but I couldn’t find one a could copy/paste decently with my phone browser

Adding in a snippet of code that will work across products for to restrict servlets to admins(and above):

public class SampleServlet extends HttpServlet
{

    @ComponentImport
    private final UserManager userManager;

    @Inject
    public SampleServlet(  final UserManager userManager)
    {
        this.userManager = userManager;
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        resp.setContentType("text/html");


        if( (this.userManager.getRemoteUser()==null ))
        {
            resp.sendError(401);
            return;
        }
        if( !(this.userManager.isAdmin(this.userManager.getRemoteUserKey())|| this.userManager.isSystemAdmin(this.userManager.getRemoteUserKey())) )
        {
            resp.sendError(403);
            return;
        }

       // ... do stuff here .... //

     }
}

Hey Daniel,

Thanks for the replies! What I am trying to do is actually expose the servlet for any authenticated user. Right now I get

AbstractAccessDecisionManager.accessDenied

errors when attempting to access the plugin servlet. Even if I add code to my servlet or servlet filter, I get a feeling this error will still occur because the check happens before any code is executed (that is my hunch).

EDIT: Got it using the SecurityService class. Updated code below:

package com.atlassian.bitbucket.plugin.servlet;

import com.atlassian.soy.renderer.SoyTemplateRenderer;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.project.ProjectService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionAdminService;
import com.atlassian.bitbucket.permission.SetPermissionRequest;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.permission.PermittedUser;
import com.atlassian.bitbucket.permission.PermittedGroup;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.bitbucket.user.EscalatedSecurityContext;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Base64;

public class ProjectServlet extends AbstractExampleServlet {
    private final ProjectService projectService;
    private final PermissionAdminService permissionAdminService;
    private final SecurityService securityService;

    public ProjectServlet(SoyTemplateRenderer soyTemplateRenderer, ProjectService projectService, PermissionAdminService permissionAdminService, SecurityService securityService) {
        super(soyTemplateRenderer);
        this.projectService = projectService;
        this.permissionAdminService = permissionAdminService;
        this.securityService = securityService;
    }


    public class Admin {

        public String name;
        public String email;

        public Admin(String _name, String _email) {
            name = _name;
            email = _email;
        }

        public String getName(){
            return name;
        }

        public String getEmail(){
            return email;
        }

    }

    public class ExtendedProject {
        public String key;
        public String name;
        public ArrayList<Admin> adminList;
        public int listSize;

        public ExtendedProject(String _key, String _name, ArrayList<Admin> _adminList){
            key = _key;
            name = _name;
            adminList = _adminList;
            listSize = adminList.size();
        }

        public String getKey(){
            return key;
        }

        public String getName(){
            return name;
        }

        public ArrayList<Admin> getAdminList(){
            return adminList;
        }

        public int getListSize(){
            return listSize;
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Get projectKey from path
        String pathInfo = req.getContextPath();
        EscalatedSecurityContext esc = securityService.withPermission(Permission.ADMIN,"Accessing Admin List page."); //Create an Escalated Context
        esc.applyToRequest(); //apply the context to this request for this authenticated user.
        PageRequestImpl pageReq = new PageRequestImpl(0,5000);
        PageRequestImpl adminPageReq = new PageRequestImpl(0,5000);
        PageRequestImpl groupPageReq = new PageRequestImpl(0,5000);

        String[] components = pathInfo.split("/");

        Project[] projectList;
        projectList = projectService.findAll(pageReq).getOrdinalIndexedValues().values().toArray(new Project[0]);

        PermittedUser[] projectAdmins;

        ArrayList<ExtendedProject> fullProjectList = new ArrayList<ExtendedProject>();
        //PermittedGroup[] projectAdminGroups;
        for(int i=0;i<projectList.length;i++){
            Project project = projectList[i];
            ArrayList<Admin> adminList = new ArrayList<Admin>();
            projectAdmins = permissionAdminService.findUsersWithProjectPermission(project,"",adminPageReq).getOrdinalIndexedValues().values().toArray(new PermittedUser[0]);
            for(int k=0;k<projectAdmins.length;k++){

                if(projectAdmins[k].getPermission() == Permission.PROJECT_ADMIN){ //If the user is a project admin

                    if(projectAdmins[k].getUser().getDisplayName().indexOf("_") != 0) {//If the user is not a service account
                        String adminName=projectAdmins[k].getUser().getDisplayName();
                        String adminEmail=projectAdmins[k].getUser().getEmailAddress();
                        Admin newAdmin = new Admin(adminName,adminEmail);
                        adminList.add(newAdmin);
                    }
                }
            }

            ExtendedProject eProject = new ExtendedProject(project.getKey(),project.getName(),adminList);
            fullProjectList.add(eProject);

        }
        
        //projectAdminGroups = permissionAdminService.findGroupsWithProjectPermission(project,"",groupPageReq).getOrdinalIndexedValues().values().toArray(new PermittedGroup[0]);

        boolean isSettings = false;
        if (components.length == 3 && "settings".equalsIgnoreCase(components[2])) {
            isSettings = true;
        }

        String template = isSettings ? "plugin.example.projectSettings" : "plugin.example.project";

        HashMap<String, Object> data = new HashMap<String, Object>();
        data.put("fullProjectList",fullProjectList);
        render(resp, template, data);
    }
}

atlassian-plugin.xml:

<?xml version="1.0" encoding="UTF-8"?>

<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="3">
    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}"/>
        <permissions>
            <permission>execute_java</permission>
        </permissions>
    </plugin-info>

    <!-- Servlets -->

    <servlet-filter name="Project Servlet Filter" key="project-servlet-filter" class="com.atlassian.bitbucket.plugin.filter.ProjectServletFilter" location="before-dispatch">
        <description>Adds Service account headers.</description>
        <url-pattern>/plugins/servlet/administration-list/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </servlet-filter>

    <servlet name="Project Servlet" key="project-servlet" class="com.atlassian.bitbucket.plugin.servlet.ProjectServlet">
        <description key="project-servlet.description">Administration list</description>
        <url-pattern>/administration-list/*</url-pattern>
    </servlet>

    <!-- Client web resources -->

    <client-resource key="example-soy" name="Example Soy Templates">
        <directory location="/templates/"/>
    </client-resource>

    <web-resource key="adminstyle" name="Admin Style">
        <context>atl.general</context>
        <resource type="download" name="admin-list-style.css" location="includes/admin-list-style.css"/>
    </web-resource>

    <web-resource key="pluginstyle" name="Plugin Style">
        <context>atl.general</context>
        <resource type="download" name="plugin-page-style.css" location="includes/plugin-page-style.css"/>
    </web-resource>

    <web-resource key="pluginscript" name="Plugin Script">
        <context>atl.general</context>
        <resource type="download" name="admin-list-script.js" location="includes/admin-list-script.js"/>
    </web-resource>

    <!-- Web items -->
    
    <web-item key="project-plugin-tab" name="Project navigation tab" section="bitbucket.project.list.sidebar.items" weight="1">
        <label>Need Access?</label>
        <link>/plugins/servlet/administration-list/list</link>
        <tooltip>Find Admins for the project you need access to.</tooltip>
        <!-- optional style for the icon: see https://design.atlassian.com/2.0/product/foundations/iconography for a list of available images -->
        <styleClass>admin-list-main</styleClass>
    </web-item>

</atlassian-plugin>

I’m unsure of an easy way to verify the filter is in fact in front of the servlet.

You shouldn’t have to worry about the filter. The servlet will get called and then you take care of it in there. Just inject the AuthenticationContext into your servlet and check if the user is authenticated in it.

Now you might want to add a condition to your web-item though (everyone will be seeing it).

/Daniel