sirius.web.security.UserContext Maven / Gradle / Ivy
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.web.security;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.typesafe.config.Config;
import sirius.kernel.async.CallContext;
import sirius.kernel.async.SubContext;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.GlobalContext;
import sirius.kernel.di.std.Context;
import sirius.kernel.di.std.Part;
import sirius.kernel.extensions.Extension;
import sirius.kernel.extensions.Extensions;
import sirius.kernel.health.Log;
import sirius.kernel.nls.NLS;
import sirius.web.controller.Message;
import sirius.web.http.WebContext;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Used to access the current user and scope.
*
* An instance of this class is present in the {@link sirius.kernel.async.CallContext} and takes care of
* picking the right user manager to authenticate users or store / load them from a session.
*
* This class also manages messages shown to the user.
*/
public class UserContext implements SubContext {
/**
* The key used to store the current scope in the MDC
*/
public static final String MDC_SCOPE = "scope";
/**
* The key used to store the current user id in the MDC
*/
public static final String MDC_USER_ID = "userId";
/**
* The key used to store the current user name in the MDC
*/
public static final String MDC_USER_NAME = "username";
/**
* Contains the logger user used by the auth framework
*/
public static final Log LOG = Log.get("user");
@Part
private static ScopeDetector detector;
private static Map managers = Maps.newConcurrentMap();
private UserInfo currentUser = null;
/*
* As getUserForScope will most probalby only hit one other scope
* we cache the last user and scope here to speed up almost all cases
* without the need for a map.
*/
private String spaceIdOfCachedUser = null;
private UserInfo cachedUser = null;
private ScopeInfo currentScope = null;
private List msgList = Lists.newArrayList();
private Map fieldErrors = Maps.newHashMap();
@Context
private static GlobalContext context;
/*
* Determines which UserManager to use for a given scope
*/
private static UserManager getManager(ScopeInfo scope) {
Extension ext = Extensions.getExtension("security.scopes", scope.getScopeType());
return context.getPart(ext.get("manager").asString("public"), UserManagerFactory.class)
.createManager(scope, ext);
}
/**
* Retrieves the current UserContext from the {@link sirius.kernel.async.CallContext}.
*
* @return the current user context.
*/
public static UserContext get() {
return CallContext.getCurrent().get(UserContext.class);
}
/**
* Boilerplate method to quickly access the current user.
*
* @return the current user
* @see #getUser()
*/
public static UserInfo getCurrentUser() {
return get().getUser();
}
public static Config getConfig() {
return get().getUser().getConfig();
}
public static H getHelper(Class helperType) {
return getCurrentScope().getHelper(helperType);
}
/**
* Boilerplate method to quickly access the current scope.
*
* @return the currently active scope
* @see #getScope()
*/
public static ScopeInfo getCurrentScope() {
return get().getScope();
}
/**
* Handles the given exception by passing it to {@link sirius.kernel.health.Exceptions} and by creating an
* appropriate message for the user.
*
* @param e the exception to handle. If the given exception is null nothing will happen.
*/
public static void handle(@Nullable Throwable e) {
if (e == null) {
return;
}
message(Message.error(e));
}
/**
* Adds a message to the current UserContext.
*
* @param msg the message to add
*/
public static void message(Message msg) {
get().addMessage(msg);
}
/**
* Adds a field error to the current UserContext.
*
* @param field the field for which an error occurred
* @param value the value which was rejected
*/
public static void setFieldError(String field, Object value) {
get().addFieldError(field, NLS.toUserString(value));
}
/*
* Loads the current scope from the given web context.
*/
private void bindScopeToRequest(WebContext ctx) {
if (ctx != null && ctx.isValid() && detector != null) {
ScopeInfo scope = detector.detectScope(ctx);
setCurrentScope(scope);
CallContext.getCurrent().setLang(scope.getLang());
} else {
setCurrentScope(ScopeInfo.DEFAULT_SCOPE);
}
}
/*
* Loads the current user from the given web context.
*/
private void bindUserToRequest(WebContext ctx) {
if (ctx != null && ctx.isValid()) {
UserManager manager = getUserManager();
UserInfo user = manager.bindToRequest(ctx);
setCurrentUser(user);
CallContext.getCurrent().setLang(user.getLang());
} else {
setCurrentUser(UserInfo.NOBODY);
}
}
/**
* Installs the given scope as current scope.
*
* For generic web requests, this is not necessary, as the scope is auto-detected.
*
* @param scope the scope to set
*/
public void setCurrentScope(ScopeInfo scope) {
this.currentScope = scope == null ? ScopeInfo.DEFAULT_SCOPE : scope;
if (this.currentUser != null) {
this.currentUser = null;
CallContext.getCurrent().addToMDC(MDC_USER_ID, null);
CallContext.getCurrent().addToMDC(MDC_USER_NAME, null);
}
Message msg =
this.currentScope.tryAs(MaintenanceInfo.class).map(MaintenanceInfo::maintenanceMessage).orElse(null);
if (msg != null) {
addMessage(msg);
}
CallContext.getCurrent().addToMDC(MDC_SCOPE, currentScope.getScopeId());
}
/**
* Installs the given user as current user.
*
* For generic web requests, this is not necessary, as the user is auto-detected.
*
* @param user the user to set
*/
public void setCurrentUser(UserInfo user) {
this.currentUser = user == null ? UserInfo.NOBODY : user;
CallContext.getCurrent().addToMDC(MDC_USER_ID, currentUser.getUserId());
CallContext.getCurrent().addToMDC(MDC_USER_NAME, currentUser.getUserName());
}
/**
* Adds a message to be shown to the user.
*
* @param msg the message to be shown to the user
*/
public void addMessage(Message msg) {
msgList.add(msg);
}
/**
* Returns all messages to be shown to the user.
*
* @return a list of messages to be shown to the user
*/
public List getMessages() {
return msgList;
}
/**
* Adds an error for a given field
*
* @param field the name of the field
* @param value the value which was supplied and rejected
*/
public void addFieldError(String field, String value) {
fieldErrors.put(field, value);
}
/**
* Determines if there is an error for the given field
*
* @param field the field to check for errors
* @return true if an error was added for the field, false otherwise
*/
public boolean hasError(String field) {
return fieldErrors.containsKey(field);
}
/**
* Returns "error" if an error was added for the given field.
*
* @param field the field to check
* @return "error" if an error was added for the given field, an empty string otherwise
*/
public String signalFieldError(String field) {
return hasError(field) ? "error" : "";
}
/**
* Returns the originally submitted field value even if it was rejected due to an error.
*
* @param field the name of the form field
* @param value the entity value (used if no error occurred)
* @return the originally submitted value (if an error occurred), the given value otherwise
*/
public String getFieldValue(String field, Object value) {
if (fieldErrors.containsKey(field)) {
return fieldErrors.get(field);
}
return NLS.toUserString(value);
}
/**
* Returns the originally submitted field value even if it was rejected due to an error.
*
* @param field the name of the form field
* @return the originally submitted value (if an error occurred) or the parameter (using field as name)
* from the current {@link WebContext} otherwise
*/
public String getFieldValue(String field) {
if (fieldErrors.containsKey(field)) {
return fieldErrors.get(field);
}
return CallContext.getCurrent().get(WebContext.class).get(field).getString();
}
/**
* Returns all values submitted for the given field
*
* @param field the name of the field which values should be extracted
* @return a list of values submitted for the given field
*/
public Collection getFieldValues(String field) {
return CallContext.getCurrent().get(WebContext.class).getParameters(field);
}
/**
* Returns the current user.
*
* If no user is present yet, it tries to parse the current {@link WebContext} and retireve the user from the
* session.
*
* @return the currently active user
*/
public UserInfo getUser() {
if (currentUser == null) {
bindUserToRequest(CallContext.getCurrent().get(WebContext.class));
}
return currentUser;
}
/**
* Returns the used which would be the current user if the space with the given id would be active.
*
* You can use {@link ScopeInfo#DEFAULT_SCOPE} to access the user of the default scope which will
* most probably the administrative backend.
*
* Note that this method will only check the session ({@link UserManager#findUserForRequest(WebContext)}) and will
* not try to perform a login via credentials as given in the current request.
*
* @param scopeId the id of the scope to fethc the user for
* @return the user found for the given scope or {@link UserInfo#NOBODY} if no user was found
*/
public UserInfo getUserForScope(String scopeId) {
if (cachedUser != null && Strings.areEqual(scopeId, spaceIdOfCachedUser)) {
return cachedUser;
}
cachedUser = getUserManagerForScope(scopeId).findUserForRequest(CallContext.getCurrent().get(WebContext.class));
spaceIdOfCachedUser = scopeId;
return cachedUser;
}
/**
* Determines if the user is present.
*
* This can be either direclty via a {@link #setCurrentUser(UserInfo)} or implicitely via {@link #getUser()}
*
* @return the currently active user
*/
public boolean isUserPresent() {
return currentUser != null;
}
/**
* Binds the currently active user to the session.
*
* This will make the authentication of a user persistent als long as the session remains
*/
public void attachUserToSession() {
WebContext ctx = CallContext.getCurrent().get(WebContext.class);
if (ctx == null || !ctx.isValid()) {
return;
}
if (!getUser().isLoggedIn()) {
return;
}
UserManager manager = getUserManager();
manager.attachToSession(getUser(), ctx);
}
/**
* Determines and returns the current user manager.
*
* The user manager is determined by the current scope.
*
* @return the currently active user manager
* @see #getCurrentScope()
* @see ScopeDetector
*/
public UserManager getUserManager() {
return getUserManagerForScope(getScope().getScopeId());
}
private UserManager getUserManagerForScope(String scopeId) {
UserManager manager = managers.get(scopeId);
if (manager == null) {
manager = getManager(currentScope);
managers.put(scopeId, manager);
}
return manager;
}
/**
* Removes the authentication and user identity from the session.
*
* This can be considered a logout.
*/
public void detachUserFromSession() {
WebContext ctx = CallContext.getCurrent().get(WebContext.class);
if (ctx == null || !ctx.isValid()) {
return;
}
UserManager manager = getUserManager();
manager.detachFromSession(getCurrentUser(), ctx);
}
/**
* Returns the currently active scope.
*
* This is determined using the {@link ScopeDetector} or it will always be the {@link ScopeInfo#DEFAULT_SCOPE} if
* no scope detector is present.
*
* @return the currently active scope
*/
public ScopeInfo getScope() {
if (currentScope == null) {
bindScopeToRequest(CallContext.getCurrent().get(WebContext.class));
}
return currentScope;
}
@Override
public void detach() {
// No action needed when this is detached from the current thread...
}
}