Is it possible to restrict the acces to a web-item?

Hello :slight_smile:
i’m new in the atlassian developing and actually stuck with my web-item, x-work…
How can i restrict the access to my web-item, that it’s only visible when you’re logged in as admin?

I came across some statements, but only for jira?

Regards
Shawn

Conditions work in both JIRA and Confluence: https://developer.atlassian.com/server/framework/atlassian-sdk/web-item-plugin-module/

Have a read through the conditions documentation on that page.

Thank you rmassaioli
I just wrote my own condition! But is there a way to build the condition into the x-work module?
It works fine with displaying the web-item but when a user knows direct web-item link he can trigger it anyways…

You have to track all possible access to the code. Indeed, if a user knows the url, he has access unless code otherwise. You have to write your own checks in all access methods. I usually redirect to the admin login (if an admin page), the regular login or the dashboard.

EDIT : Found some old code for you

// Inject these two components
@ComponentImport
private LoginUriProvider loginUriProvider;
@ComponentImport
private PermissionManager permissionManager;

// then use them in access methods. Here's a simple example
public String doDefault() throws Exception {
    ConfluenceUser user = this.getAuthenticatedUser();
    if (this.permissionManager.isConfluenceAdministrator(user)) {
        return INPUT;
    } else {
        URI uri = new URI("/yourAction.action");
        return this.loginUriProvider.getLoginUriForRole(uri, UserRole.ADMIN).toString();
    }
}
1 Like

Writing access restriction on all methods under x-work results in a lot of boilerplate code. What if you want to change functionality after and you got 20 actions?
Interceptor comes handy in the above scenario.

Let me know if you need some sample code

I for one would like your sample code ! I’m always looking for ways to improve my code :slight_smile: Do you mean like a servlet filter that checks privileges level for all you urls ?

EDIT : think I found what you aare referring to : example from Bamboo

Exactly that, and the same (some other way of course) should be applied for REST urls.
This is from production code, removed package names
atlassian-plugin.xml:

 <xwork name="SomeName" key="SomeUniqueKey">
        <description>Some description</description>
        <package name="downloadActions" extends="default" namespace="/MY_NAME_SPACE">
            <!-- declaration of interceptor -->
            <interceptor name="validContractInterceptor"
                         class="my.full.package.ValidContractInterceptor"/>
            <!-- end of declaration -->

            <!-- declaration of stack, it uses the defaultStack and our inteceptor -->
            <interceptor-stack name="validContractStack">
                <interceptor-ref name="defaultStack"/>
                <interceptor-ref name="validContractInterceptor"/>
            </interceptor-stack>
             <!-- end of declaration -->

            <!-- use the stack we just created -->
            <default-interceptor-ref name="validContractStack"/>

            <!-- results coming from the interceptor action-->
            <global-results>
                <result name="NOTPERMITTED" type="dispatcher">/error/notpermitted.action</result>
                <result name="HAS_UPDATES_WITHOUT_CONTRACT" type="velocity">/layouts/downloads/no_contract.vm</result>
                <result name="SHOULD_UPDATE" type="velocity">/layouts/downloads/shouldUpdate.vm</result>
            </global-results>
            <!-- end of global results-->

            <!-- action(s) protected by the interceptor -->
            <action name="listUpdates" class="${project.groupId}.products.actions.ListUpgradesAction">
                <result name="success" type="velocity">/layouts/downloads/valid_contract.vm</result>
            </action>
        </package>
</xwork>

The interceptor class looks like:

import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.confluence.user.ConfluenceUser;
import com.atlassian.confluence.user.UserAccessor;
import com.atlassian.confluence.user.UserDetailsManager;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.interceptor.Interceptor;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Optional;
public class ValidContractInterceptor implements Interceptor {
    @Autowired
    UserDetailsManager userDetailsManager;
    @Autowired
    UserAccessor userAccessor;

    @Override
    public void init() {
        AutoWirer.autowire(this); //This is needed, at least in my version 5.8.8.
    }

    @Override
    public void destroy() {
    }

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
    //You can get action params such as:
    Map<String, String[]> params = invocation.getInvocationContext().getParameters();
    //do work here
    //....
    //....
    //Return some global result as declared in the xml such as:
    return "NOTPERMITTED";
    }
}

The AutoWirer class is a helper that helps autowiring via static methods


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

public class AutoWirer
{
    private volatile static ApplicationContext applicationContext;

    private AutoWirer()
    {
    }

    public static Object autowire(Object bean)
    {
        applicationContext.getAutowireCapableBeanFactory().autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
        return bean;
    }

    @Component
    public static class Helper
    {
        @Autowired
        public Helper(ApplicationContext applicationContext)
        {
            AutoWirer.applicationContext = applicationContext;
        }

        @PreDestroy
        public void preDestroy()
        {
            AutoWirer.applicationContext = null;
        }
    }
}

In the case of REST, you need a provider such as:

@Provider
@Scanned
public class AdminOnlyResourceFilter implements ResourceFilter, ContainerRequestFilter {
    private final AuthService authService;

    @Inject
    public AdminOnlyResourceFilter(AuthService authService) {
        this.authService = authService;
    }

    public ContainerRequestFilter getRequestFilter() {
        return this;
    }

    public ContainerResponseFilter getResponseFilter() {
        return null;
    }

    public ContainerRequest filter(final ContainerRequest containerRequest) {
        ConfluenceUser user = AuthenticatedUserThreadLocal.get();

        if (authService.canAdmin(user)||authService.canOnlyyView(user))
            return containerRequest;
        throw new AuthenticationRequiredException();

    }
}

As you might have guessed, the ResourceFilter ties to to path within the element in the xml file, not sure if/how you can uncouple it

1 Like