All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.api.auth.StructureAuth Maven / Gradle / Ivy

The newest version!
package com.almworks.jira.structure.api.auth;

import com.almworks.jira.structure.api.structure.Structure;
import com.almworks.jira.structure.api.util.*;
import com.almworks.jira.structure.api.view.StructureView;
import io.atlassian.fugue.Option;
import io.atlassian.fugue.Pair;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.util.log.Log4jKit;
import org.apache.log4j.MDC;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import static io.atlassian.fugue.Option.option;
import static io.atlassian.fugue.Pair.pair;

/**
 * 

* This class manages the current authentication context assumed when accessing, managing * and updating Structure entities such as {@link Structure} or {@link StructureView}.

* *

The context is local to the current thread.

* *

By default, the context equals to {@link JiraAuthenticationContext JIRA's authentication context}. * It is possible to specify another user or disable security checks; see {@link #sudo(ApplicationUser, boolean, CallableE)}) * and {@link #sudo(CallableE)}.

* */ public class StructureAuth { private static final Logger log = LoggerFactory.getLogger(StructureAuth.class); /** * Not using custom class to avoid leaking our classes through ThreadLocal. * Second component is "override security" * */ private static final ThreadLocal, Boolean>> sudoContext = new ThreadLocal<>(); private static final AtomicBoolean reportedNoAuthContext = new AtomicBoolean(); /** * Returns the current user. If the user is not authenticated, i.e. the context is anonymous, * returns {@code null}. "Override security" setting does not influence this method. * * @return the current user * */ @Nullable public static ApplicationUser getUser() { Pair, Boolean> context = sudoContext.get(); return context == null ? getJiraUser() : context.left().getOrNull(); } /** *

Returns {@link ApplicationUser#getKey() user key} of the current user. If the user is not authenticated, i.e. * the context is anonymous, returns {@code null}. "Override security" setting does not influence this method.

* * @return key of the current user * */ @Nullable public static String getUserKey() { return JiraUsers.getKeyFor(getUser()); } @Nullable private static ApplicationUser getJiraUser() { JiraAuthenticationContext jiraContext = JiraComponents.getJiraAuthenticationContext(); if (jiraContext == null) { if (reportedNoAuthContext.compareAndSet(false, true)) { log.error("JIRA's ComponentAccessor does not provide authentication context. " + "Structure will behave as if everything is executed under anonymous user."); } return null; } return jiraContext.getLoggedInUser(); } /** * Returns {@code true} if permission checks shouldn't be carried out in this context. * */ public static boolean isSecurityOverridden() { Pair, Boolean> context = sudoContext.get(); return context != null && context.right(); } /** * Execute actions with Structure under a different authentication context - as another user, * and/or with all security checks disabled. * If you only need to override security while keeping user context intact, you can use * {@link #sudo(CallableE) a shorthand override} * * @param user the user, can be {@code null} to represent anonymous * @param overrideSecurity if true, user access level will not be checked for {@code f} * @param f the code to execute, this method will return the computed value. * Can throw {@link E}, it will be propagated. * */ public static R sudo(@Nullable ApplicationUser user, boolean overrideSecurity, CallableE f) throws E { JiraAuthenticationContext jiraContext = JiraComponents.getJiraAuthenticationContext(); ApplicationUser oldUser = jiraContext.getLoggedInUser(); boolean sameUser = Objects.equals(user, oldUser); Pair, Boolean> oldContext = sudoContext.get(); Boolean securityOverridden = oldContext != null && oldContext.right(); if (sameUser && Objects.equals(overrideSecurity, securityOverridden)) { // no need to change the context return f.call(); } sudoContext.set(pair(option(user), overrideSecurity)); jiraContext.setLoggedInUser(user); String oldLoggingUsername = getOldLoggingUsername(oldUser); Log4jKit.putUserToMDC(getNewLoggingUsername(user, oldLoggingUsername, sameUser)); try { return f.call(); } finally { if (oldContext != null) { sudoContext.set(oldContext); } else { sudoContext.remove(); } jiraContext.setLoggedInUser(oldUser); Log4jKit.putUserToMDC(oldLoggingUsername); } } private static String getNewLoggingUsername(@Nullable ApplicationUser user, @NotNull String oldLoggingUsername, boolean sameUser) { return sameUser ? oldLoggingUsername : getDisplayedUserName(user) + "<-" + oldLoggingUsername; } @NotNull private static String getOldLoggingUsername(ApplicationUser oldUser) { Object obj = MDC.get(Log4jKit.MDC_JIRA_USERNAME); if (obj != null) return obj.toString(); return getDisplayedUserName(oldUser); } private static String getDisplayedUserName(@Nullable ApplicationUser user) { // start using ApplicationUser.getKey() when Atlassian starts using it return user == null ? "anonymous" : user.getName(); } /** * Execute actions with Structure under the current JIRA user with all security checks disabled. * @param f the code to execute, this method will return the computed value. * Can throw {@link E}, it will be propagated. * */ public static R sudo(CallableE f) throws E { return sudo(getUser(), true, f); } public static AuthContext currentContext() { Pair, Boolean> context = sudoContext.get(); return context == null ? new AuthContext.Custom(getJiraUser(), false) : new AuthContext.Custom(context.left().getOrNull(), context.right()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy