/*
* (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Thierry Delprat
* Bogdan Stefanescu
* Anahide Tchertchian
* Florent Guillaume
*/
package org.nuxeo.ecm.platform.ui.web.auth;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.ERROR_AUTHENTICATION_FAILED;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.ERROR_CONNECTION_FAILED;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORCE_ANONYMOUS_LOGIN;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORM_SUBMITTED_MARKER;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGINCONTEXT_KEY;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_ERROR;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_PAGE;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_STATUS_CODE;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGOUT_PAGE;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.PAGE_AFTER_SWITCH;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REQUESTED_URL;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SECURITY_ERROR;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SESSION_TIMEOUT;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SSO_INITIAL_URL_REQUEST_KEY;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.START_PAGE_SAVE_KEY;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SWITCH_USER_KEY;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SWITCH_USER_PAGE;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.USERIDENT_KEY;
import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REDIRECT_URL;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.URLDecoder;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.naming.NamingException;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.URIUtils;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.ecm.core.api.SimplePrincipal;
import org.nuxeo.ecm.core.api.local.ClientLoginModule;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.EventProducer;
import org.nuxeo.ecm.core.event.impl.UnboundEventContext;
import org.nuxeo.ecm.directory.DirectoryException;
import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
import org.nuxeo.ecm.platform.api.login.UserIdentificationInfoCallbackHandler;
import org.nuxeo.ecm.platform.login.PrincipalImpl;
import org.nuxeo.ecm.platform.login.TrustingLoginPlugin;
import org.nuxeo.ecm.platform.ui.web.auth.interfaces.LoginResponseHandler;
import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthPreFilter;
import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPluginLogoutExtension;
import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPropagator;
import org.nuxeo.ecm.platform.ui.web.auth.service.AuthenticationPluginDescriptor;
import org.nuxeo.ecm.platform.ui.web.auth.service.NuxeoAuthFilterChain;
import org.nuxeo.ecm.platform.ui.web.auth.service.OpenUrlDescriptor;
import org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService;
import org.nuxeo.ecm.platform.usermanager.UserManager;
import org.nuxeo.ecm.platform.web.common.session.NuxeoHttpSessionMonitor;
import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.api.login.LoginConfiguration;
import org.nuxeo.runtime.metrics.MetricsService;
import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
/**
* Servlet filter handling Nuxeo authentication (JAAS + EJB).
* <p>
* Also handles logout and identity switch.
*
* @author Thierry Delprat
* @author Bogdan Stefanescu
* @author Anahide Tchertchian
* @author Florent Guillaume
*/
public class NuxeoAuthenticationFilter implements Filter {
private static final Log log = LogFactory.getLog(NuxeoAuthenticationFilter.class);
// protected static final String EJB_LOGIN_DOMAIN = "nuxeo-system-login";
/**
* @deprecated Since 8.4. Use {@link LoginScreenHelper#getStartupPagePath()} instead.
* @see LoginScreenHelper
*/
@Deprecated
public static final String DEFAULT_START_PAGE = "nxstartup.faces";
/**
* LoginContext domain name in use by default in Nuxeo.
*/
public static final String LOGIN_DOMAIN = "nuxeo-ecm-web";
protected static final String XMLHTTP_REQUEST_TYPE = "XMLHttpRequest";
protected static final String LOGIN_JMS_CATEGORY = "NuxeoAuthentication";
protected static Boolean isLoginSynchronized;
/** Used internally as a marker. */
protected static final Principal DIRECTORY_ERROR_PRINCIPAL = new PrincipalImpl("__DIRECTORY_ERROR__\0\0\0");
private static String anonymous;
protected final boolean avoidReauthenticate = true;
protected volatile PluggableAuthenticationService service;
protected ReentrantReadWriteLock unAuthenticatedURLPrefixLock = new ReentrantReadWriteLock();
protected List<String> unAuthenticatedURLPrefix;
/**
* On WebEngine (Jetty) we don't have JMS enabled so we should disable log
*/
protected boolean byPassAuthenticationLog = false;
/**
* Which security domain to use
*/
protected String securityDomain = LOGIN_DOMAIN;
// @since 5.7
protected final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
protected final Timer requestTimer = registry.timer(
MetricRegistry.name("nuxeo", "web", "authentication", "requests", "count"));
protected final Counter concurrentCount = registry.counter(
MetricRegistry.name("nuxeo", "web", "authentication", "requests", "concurrent", "count"));
protected final Counter concurrentMaxCount = registry.counter(
MetricRegistry.name("nuxeo", "web", "authentication", "requests", "concurrent", "max"));
protected final Counter loginCount = registry.counter(
MetricRegistry.name("nuxeo", "web", "authentication", "logged-users"));
@Override
public void destroy() {
}
protected static boolean sendAuthenticationEvent(UserIdentificationInfo userInfo, String eventId, String comment) {
LoginContext loginContext = null;
try {
try {
loginContext = Framework.login();
} catch (LoginException e) {
log.error("Unable to log in in order to log Login event" + e.getMessage());
return false;
}
EventProducer evtProducer = Framework.getService(EventProducer.class);
Principal principal = new SimplePrincipal(userInfo.getUserName());
Map<String, Serializable> props = new HashMap<String, Serializable>();
props.put("AuthenticationPlugin", userInfo.getAuthPluginName());
props.put("LoginPlugin", userInfo.getLoginPluginName());
props.put("category", LOGIN_JMS_CATEGORY);
props.put("comment", comment);
EventContext ctx = new UnboundEventContext(principal, props);
evtProducer.fireEvent(ctx.newEvent(eventId));
return true;
} finally {
if (loginContext != null) {
try {
loginContext.logout();
} catch (LoginException e) {
log.error("Unable to logout: " + e.getMessage());
}
}
}
}
protected boolean logAuthenticationAttempt(UserIdentificationInfo userInfo, boolean success) {
if (byPassAuthenticationLog) {
return true;
}
String userName = userInfo.getUserName();
if (userName == null || userName.length() == 0) {
userName = userInfo.getToken();
}
String eventId;
String comment;
if (success) {
eventId = "loginSuccess";
comment = userName + " successfully logged in using " + userInfo.getAuthPluginName() + "Authentication";
loginCount.inc();
} else {
eventId = "loginFailed";
comment = userName + " failed to authenticate using " + userInfo.getAuthPluginName() + "Authentication";
}
return sendAuthenticationEvent(userInfo, eventId, comment);
}
protected boolean logLogout(UserIdentificationInfo userInfo) {
if (byPassAuthenticationLog) {
return true;
}
loginCount.dec();
String userName = userInfo.getUserName();
if (userName == null || userName.length() == 0) {
userName = userInfo.getToken();
}
String eventId = "logout";
String comment = userName + " logged out";
return sendAuthenticationEvent(userInfo, eventId, comment);
}
protected static boolean isLoginSynchronized() {
if (isLoginSynchronized != null) {
return isLoginSynchronized;
}
if (Framework.getRuntime() == null) {
return false;
}
synchronized (NuxeoAuthenticationFilter.class) {
if (isLoginSynchronized != null) {
return isLoginSynchronized;
}
return isLoginSynchronized = !Boolean.parseBoolean(Framework.getProperty(
"org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter.isLoginNotSynchronized", "true"));
}
}
protected Principal doAuthenticate(CachableUserIdentificationInfo cachableUserIdent,
HttpServletRequest httpRequest) {
LoginContext loginContext;
try {
CallbackHandler handler = service.getCallbackHandler(cachableUserIdent.getUserInfo());
loginContext = new LoginContext(securityDomain, handler);
if (isLoginSynchronized()) {
synchronized (NuxeoAuthenticationFilter.class) {
loginContext.login();
}
} else {
loginContext.login();
}
Principal principal = (Principal) loginContext.getSubject().getPrincipals().toArray()[0];
cachableUserIdent.setPrincipal(principal);
cachableUserIdent.setAlreadyAuthenticated(true);
// re-set the userName since for some SSO based on token,
// the userName is not known before login is completed
cachableUserIdent.getUserInfo().setUserName(principal.getName());
logAuthenticationAttempt(cachableUserIdent.getUserInfo(), true);
} catch (LoginException e) {
log.info("Login failed for " + cachableUserIdent.getUserInfo().getUserName());
logAuthenticationAttempt(cachableUserIdent.getUserInfo(), false);
Throwable cause = e.getCause();
if (cause instanceof DirectoryException) {
Throwable rootCause = ExceptionUtils.getRootCause(cause);
if (rootCause instanceof NamingException
&& rootCause.getMessage().contains("LDAP response read timed out")
|| rootCause instanceof SocketException) {
httpRequest.setAttribute(LOGIN_STATUS_CODE, HttpServletResponse.SC_GATEWAY_TIMEOUT);
}
return DIRECTORY_ERROR_PRINCIPAL;
}
return null;
}
// store login context for the time of the request
// TODO logincontext is also stored in cachableUserIdent - it is really
// needed to store it??
httpRequest.setAttribute(LOGINCONTEXT_KEY, loginContext);
// store user ident
cachableUserIdent.setLoginContext(loginContext);
boolean createSession = needSessionSaving(cachableUserIdent.getUserInfo());
HttpSession session = httpRequest.getSession(createSession);
if (session != null) {
session.setAttribute(USERIDENT_KEY, cachableUserIdent);
}
service.onAuthenticatedSessionCreated(httpRequest, session, cachableUserIdent);
return cachableUserIdent.getPrincipal();
}
private boolean switchUser(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String deputyLogin = (String) httpRequest.getAttribute(SWITCH_USER_KEY);
String targetPageAfterSwitch = (String) httpRequest.getAttribute(PAGE_AFTER_SWITCH);
if (targetPageAfterSwitch == null) {
targetPageAfterSwitch = LoginScreenHelper.getStartupPagePath();
}
CachableUserIdentificationInfo cachableUserIdent = retrieveIdentityFromCache(httpRequest);
String originatingUser = cachableUserIdent.getUserInfo().getUserName();
if (deputyLogin == null) {
// simply switch back to the previous identity
NuxeoPrincipal currentPrincipal = (NuxeoPrincipal) cachableUserIdent.getPrincipal();
String previousUser = currentPrincipal.getOriginatingUser();
if (previousUser == null) {
return false;
}
deputyLogin = previousUser;
originatingUser = null;
}
try {
cachableUserIdent.getLoginContext().logout();
} catch (LoginException e1) {
log.error("Error while logout from main identity", e1);
}
httpRequest.getSession(false);
service.reinitSession(httpRequest);
CachableUserIdentificationInfo newCachableUserIdent = new CachableUserIdentificationInfo(deputyLogin,
deputyLogin);
newCachableUserIdent.getUserInfo().setLoginPluginName(TrustingLoginPlugin.NAME);
newCachableUserIdent.getUserInfo().setAuthPluginName(cachableUserIdent.getUserInfo().getAuthPluginName());
Principal principal = doAuthenticate(newCachableUserIdent, httpRequest);
if (principal != null && principal != DIRECTORY_ERROR_PRINCIPAL) {
NuxeoPrincipal nxUser = (NuxeoPrincipal) principal;
if (originatingUser != null) {
nxUser.setOriginatingUser(originatingUser);
}
propagateUserIdentificationInformation(cachableUserIdent);
}
// reinit Seam so the afterResponseComplete does not crash
// ServletLifecycle.beginRequest(httpRequest);
// flag redirect to avoid being caught by URLPolicy
request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE);
String baseURL = service.getBaseURL(request);
((HttpServletResponse) response).sendRedirect(baseURL + targetPageAfterSwitch);
return true;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final Timer.Context contextTimer = requestTimer.time();
concurrentCount.inc();
if (concurrentCount.getCount() > concurrentMaxCount.getCount()) {
concurrentMaxCount.inc();
}
try {
doInitIfNeeded();
List<NuxeoAuthPreFilter> preFilters = service.getPreFilters();
if (preFilters == null) {
doFilterInternal(request, response, chain);
} else {
NuxeoAuthFilterChain chainWithPreFilters = new NuxeoAuthFilterChain(preFilters, chain, this);
chainWithPreFilters.doFilter(request, response);
}
} finally {
ClientLoginModule.clearThreadLocalLogin();
LoginConfiguration.INSTANCE.cleanupThisThread();
contextTimer.stop();
concurrentCount.dec();
}
}
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (bypassAuth((HttpServletRequest) request)) {
chain.doFilter(request, response);
return;
}
String tokenPage = getRequestedPage(request);
if (tokenPage.equals(SWITCH_USER_PAGE)) {
boolean result = switchUser(request, response, chain);
if (result) {
return;
}
}
if (request instanceof NuxeoSecuredRequestWrapper) {
log.debug("ReEntering Nuxeo Authentication Filter ... exiting directly");
chain.doFilter(request, response);
return;
} else if (service.canBypassRequest(request)) {
log.debug("ReEntering Nuxeo Authentication Filter after URL rewrite ... exiting directly");
chain.doFilter(request, response);
return;
} else {
log.debug("Entering Nuxeo Authentication Filter");
}
String targetPageURL = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
Principal principal = httpRequest.getUserPrincipal();
NuxeoAuthenticationPropagator.CleanupCallback propagatedAuthCb = null;
try {
if (principal == null) {
log.debug("Principal not found inside Request via getUserPrincipal");
// need to authenticate !
// retrieve user & password
CachableUserIdentificationInfo cachableUserIdent;
if (avoidReauthenticate) {
log.debug("Try getting authentication from cache");
cachableUserIdent = retrieveIdentityFromCache(httpRequest);
} else {
log.debug("Principal cache is NOT activated");
}
if (cachableUserIdent != null && cachableUserIdent.getUserInfo() != null
&& service.needResetLogin(request)) {
HttpSession session = httpRequest.getSession(false);
if (session != null) {
session.removeAttribute(USERIDENT_KEY);
}
// first propagate the login because invalidation may
// require
// an authenticated session
propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent);
// invalidate Session !
try {
service.invalidateSession(request);
} finally {
if (propagatedAuthCb != null) {
propagatedAuthCb.cleanup();
propagatedAuthCb = null;
}
}
// TODO perform logout?
cachableUserIdent = null;
}
// identity found in cache
if (cachableUserIdent != null && cachableUserIdent.getUserInfo() != null) {
log.debug("userIdent found in cache, get the Principal from it without reloggin");
NuxeoHttpSessionMonitor.instance().updateEntry(httpRequest);
principal = cachableUserIdent.getPrincipal();
log.debug("Principal = " + principal.getName());
propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent);
String requestedPage = getRequestedPage(httpRequest);
if (LOGOUT_PAGE.equals(requestedPage)) {
boolean redirected = handleLogout(request, response, cachableUserIdent);
cachableUserIdent = null;
principal = null;
if (redirected && httpRequest.getParameter(FORM_SUBMITTED_MARKER) == null) {
return;
}
} else if (LOGIN_PAGE.equals(requestedPage)) {
if (handleLogin(httpRequest, httpResponse)) {
return;
}
} else {
targetPageURL = getSavedRequestedURL(httpRequest, httpResponse);
}
}
// identity not found in cache or reseted by logout
if (cachableUserIdent == null || cachableUserIdent.getUserInfo() == null) {
UserIdentificationInfo userIdent = handleRetrieveIdentity(httpRequest, httpResponse);
if (userIdent != null && userIdent.containsValidIdentity()
&& userIdent.getUserName().equals(getAnonymousId())) {
String forceAuth = httpRequest.getParameter(FORCE_ANONYMOUS_LOGIN);
if (forceAuth != null && forceAuth.equals("true")) {
userIdent = null;
}
}
if ((userIdent == null || !userIdent.containsValidIdentity()) && !bypassAuth(httpRequest)) {
boolean res = handleLoginPrompt(httpRequest, httpResponse);
if (res) {
return;
}
} else {
String redirectUrl = VirtualHostHelper.getRedirectUrl(httpRequest);
HttpSession session = httpRequest.getSession(false);
if (session != null) {
session.setAttribute(REDIRECT_URL, redirectUrl);
}
// restore saved Starting page
targetPageURL = getSavedRequestedURL(httpRequest, httpResponse);
}
if (userIdent != null && userIdent.containsValidIdentity()) {
// do the authentication
cachableUserIdent = new CachableUserIdentificationInfo(userIdent);
principal = doAuthenticate(cachableUserIdent, httpRequest);
if (principal != null && principal != DIRECTORY_ERROR_PRINCIPAL) {
// Do the propagation too ????
propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent);
// setPrincipalToSession(httpRequest, principal);
// check if the current authenticator is a
// LoginResponseHandler
NuxeoAuthenticationPlugin plugin = getAuthenticator(cachableUserIdent);
if (plugin instanceof LoginResponseHandler) {
// call the extended error handler
if (((LoginResponseHandler) plugin).onSuccess((HttpServletRequest) request,
(HttpServletResponse) response)) {
return;
}
}
} else {
// first check if the current authenticator is a
// LoginResponseHandler
NuxeoAuthenticationPlugin plugin = getAuthenticator(cachableUserIdent);
if (plugin instanceof LoginResponseHandler) {
// call the extended error handler
if (((LoginResponseHandler) plugin).onError((HttpServletRequest) request,
(HttpServletResponse) response)) {
return;
}
} else {
// use the old method
String err = principal == DIRECTORY_ERROR_PRINCIPAL ? ERROR_CONNECTION_FAILED
: ERROR_AUTHENTICATION_FAILED;
httpRequest.setAttribute(LOGIN_ERROR, err);
boolean res = handleLoginPrompt(httpRequest, httpResponse);
if (res) {
return;
}
}
}
}
}
}
if (principal != null) {
if (targetPageURL != null && targetPageURL.length() > 0) {
// forward to target page
String baseURL = service.getBaseURL(request);
// httpRequest.getRequestDispatcher(targetPageURL).forward(new
// NuxeoSecuredRequestWrapper(httpRequest, principal),
// response);
if (XMLHTTP_REQUEST_TYPE.equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {
// httpResponse.setStatus(200);
return;
} else {
// In case of a download redirection, the base url is already contained in the target
String url = targetPageURL.startsWith(baseURL) ? targetPageURL : baseURL + targetPageURL;
httpResponse.sendRedirect(url);
return;
}
} else {
// simply continue request
chain.doFilter(new NuxeoSecuredRequestWrapper(httpRequest, principal), response);
}
} else {
chain.doFilter(request, response);
}
} finally {
if (propagatedAuthCb != null) {
propagatedAuthCb.cleanup();
}
}
if (!avoidReauthenticate) {
// destroy login context
log.debug("Log out");
LoginContext lc = (LoginContext) httpRequest.getAttribute("LoginContext");
if (lc != null) {
try {
lc.logout();
} catch (LoginException e) {
log.error(e, e);
}
}
}
log.debug("Exit Nuxeo Authentication filter");
}
public NuxeoAuthenticationPlugin getAuthenticator(CachableUserIdentificationInfo ci) {
String key = ci.getUserInfo().getAuthPluginName();
if (key != null) {
NuxeoAuthenticationPlugin authPlugin = service.getPlugin(key);
return authPlugin;
}
return null;
}
protected static CachableUserIdentificationInfo retrieveIdentityFromCache(HttpServletRequest httpRequest) {
HttpSession session = httpRequest.getSession(false);
if (session != null) {
CachableUserIdentificationInfo cachableUserInfo = (CachableUserIdentificationInfo) session.getAttribute(
USERIDENT_KEY);
if (cachableUserInfo != null) {
return cachableUserInfo;
}
}
return null;
}
private String getAnonymousId() throws ServletException {
if (anonymous == null) {
anonymous = Framework.getService(UserManager.class).getAnonymousUserId();
}
return anonymous;
}
protected void doInitIfNeeded() throws ServletException {
if (service == null && Framework.getRuntime() != null) {
synchronized (this) {
if (service != null) {
return;
}
service = (PluggableAuthenticationService) Framework.getRuntime()
.getComponent(PluggableAuthenticationService.NAME);
// init preFilters
service.initPreFilters();
if (service == null) {
log.error("Unable to get Service " + PluggableAuthenticationService.NAME);
throw new ServletException("Can't initialize Nuxeo Pluggable Authentication Service");
}
}
}
}
@Override
public void init(FilterConfig config) throws ServletException {
String val = config.getInitParameter("byPassAuthenticationLog");
if (val != null && Boolean.parseBoolean(val)) {
byPassAuthenticationLog = true;
}
val = config.getInitParameter("securityDomain");
if (val != null) {
securityDomain = val;
}
}
/**
* Save requested URL before redirecting to login form.
* <p>
* Returns true if target url is a valid startup page.
*/
public boolean saveRequestedURLBeforeRedirect(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
final boolean hasRequestedSessionId = !StringUtils.isBlank(httpRequest.getRequestedSessionId());
HttpSession session = httpRequest.getSession(false);
final boolean isTimeout = session == null && hasRequestedSessionId;
if (!httpResponse.isCommitted()) {
session = httpRequest.getSession(true);
}
if (session == null) {
return false;
}
String requestPage;
boolean requestPageInParams = false;
if (httpRequest.getParameter(REQUESTED_URL) != null) {
requestPageInParams = true;
requestPage = httpRequest.getParameter(REQUESTED_URL);
} else {
requestPage = getRequestedUrl(httpRequest);
}
if (requestPage == null) {
return false;
}
// add a flag to tell that the Session looks like having timed out
if (isTimeout && !requestPage.equals(LoginScreenHelper.getStartupPagePath())) {
session.setAttribute(SESSION_TIMEOUT, Boolean.TRUE);
} else {
session.removeAttribute(SESSION_TIMEOUT);
}
// avoid redirect if not useful
for (String startupPagePath : LoginScreenHelper.getStartupPagePaths()) {
if (requestPage.startsWith(startupPagePath)
&& LoginScreenHelper.getStartupPagePath().equals(startupPagePath)) {
return true;
}
}
// avoid saving to session is start page is not valid or if it's
// already in the request params
if (isStartPageValid(requestPage)) {
if (!requestPageInParams) {
session.setAttribute(START_PAGE_SAVE_KEY, requestPage);
}
return true;
}
return false;
}
public static String getRequestedUrl(HttpServletRequest httpRequest) {
String completeURI = httpRequest.getRequestURI();
String qs = httpRequest.getQueryString();
String context = httpRequest.getContextPath() + '/';
String requestPage = completeURI.substring(context.length());
if (qs != null && qs.length() > 0) {
// remove conversationId if present
if (qs.contains("conversationId")) {
qs = qs.replace("conversationId", "old_conversationId");
}
requestPage = requestPage + '?' + qs;
}
return requestPage;
}
protected static String getSavedRequestedURL(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
String requestedPage = null;
HttpSession session = httpRequest.getSession(false);
if (httpRequest.getParameter(REQUESTED_URL) != null) {
String requestedUrl = httpRequest.getParameter(REQUESTED_URL);
if (requestedUrl != null && !"".equals(requestedUrl)) {
try {
requestedPage = URLDecoder.decode(requestedUrl, "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("Unable to get the requestedUrl parameter" + e);
}
}
} else {
// retrieve from session
if (session != null) {
requestedPage = (String) session.getAttribute(START_PAGE_SAVE_KEY);
}
// retrieve from SSO cookies
Cookie[] cookies = httpRequest.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (SSO_INITIAL_URL_REQUEST_KEY.equals(cookie.getName())) {
requestedPage = cookie.getValue();
cookie.setPath("/");
// enforce cookie removal
cookie.setMaxAge(0);
httpResponse.addCookie(cookie);
}
}
}
}
// clean up session
if (session != null) {
session.removeAttribute(START_PAGE_SAVE_KEY);
}
// add locale if not in the URL params
String localeStr = httpRequest.getParameter(NXAuthConstants.LANGUAGE_PARAMETER);
if (requestedPage != null && !"".equals(requestedPage) && localeStr != null) {
Map<String, String> params = new HashMap<String, String>();
if (!URIUtils.getRequestParameters(requestedPage).containsKey(NXAuthConstants.LANGUAGE_PARAMETER)) {
params.put(NXAuthConstants.LANGUAGE_PARAMETER, localeStr);
}
return URIUtils.addParametersToURIQuery(requestedPage, params);
}
return requestedPage;
}
protected boolean isStartPageValid(String startPage) {
if (startPage == null) {
return false;
}
try {
// Sometimes, the service is not initialized at startup
doInitIfNeeded();
} catch (ServletException e) {
return false;
}
for (String prefix : service.getStartURLPatterns()) {
if (startPage.startsWith(prefix)) {
return true;
}
}
return false;
}
protected boolean handleLogout(ServletRequest request, ServletResponse response,
CachableUserIdentificationInfo cachedUserInfo) throws ServletException {
logLogout(cachedUserInfo.getUserInfo());
request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE);
Map<String, String> parameters = new HashMap<String, String>();
String securityError = request.getParameter(SECURITY_ERROR);
if (securityError != null) {
parameters.put(SECURITY_ERROR, securityError);
}
if (cachedUserInfo.getPrincipal().getName().equals(getAnonymousId())) {
parameters.put(FORCE_ANONYMOUS_LOGIN, "true");
}
String requestedUrl = request.getParameter(REQUESTED_URL);
if (requestedUrl != null) {
parameters.put(REQUESTED_URL, requestedUrl);
}
// Reset JSESSIONID Cookie
HttpServletResponse httpResponse = (HttpServletResponse) response;
Cookie cookie = new Cookie("JSESSIONID", null);
cookie.setMaxAge(0);
cookie.setPath("/");
httpResponse.addCookie(cookie);
String pluginName = cachedUserInfo.getUserInfo().getAuthPluginName();
NuxeoAuthenticationPlugin authPlugin = service.getPlugin(pluginName);
NuxeoAuthenticationPluginLogoutExtension logoutPlugin = null;
if (authPlugin instanceof NuxeoAuthenticationPluginLogoutExtension) {
logoutPlugin = (NuxeoAuthenticationPluginLogoutExtension) authPlugin;
}
boolean redirected = false;
if (logoutPlugin != null) {
redirected = Boolean.TRUE.equals(
logoutPlugin.handleLogout((HttpServletRequest) request, (HttpServletResponse) response));
}
// invalidate Session !
service.invalidateSession(request);
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (!redirected && !XMLHTTP_REQUEST_TYPE.equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {
String baseURL = service.getBaseURL(request);
try {
String url = baseURL + LoginScreenHelper.getStartupPagePath();
url = URIUtils.addParametersToURIQuery(url, parameters);
((HttpServletResponse) response).sendRedirect(url);
redirected = true;
} catch (IOException e) {
log.error("Unable to redirect to default start page after logout : " + e.getMessage());
}
}
try {
cachedUserInfo.getLoginContext().logout();
} catch (LoginException e) {
log.error("Unable to logout " + e.getMessage());
}
return redirected;
}
// App Server JAAS SPI
protected void propagateUserIdentificationInformation(CachableUserIdentificationInfo cachableUserIdent) {
service.propagateUserIdentificationInformation(cachableUserIdent);
}
// Plugin API
protected void initUnAuthenticatedURLPrefix() {
// gather unAuthenticated URLs
unAuthenticatedURLPrefix = new ArrayList<String>();
for (String pluginName : service.getAuthChain()) {
NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName);
List<String> prefix = plugin.getUnAuthenticatedURLPrefix();
if (prefix != null && !prefix.isEmpty()) {
unAuthenticatedURLPrefix.addAll(prefix);
}
}
}
protected boolean bypassAuth(HttpServletRequest httpRequest) {
if (unAuthenticatedURLPrefix == null) {
try {
unAuthenticatedURLPrefixLock.writeLock().lock();
// late init to allow plugins registered after this filter init
initUnAuthenticatedURLPrefix();
} finally {
unAuthenticatedURLPrefixLock.writeLock().unlock();
}
}
try {
unAuthenticatedURLPrefixLock.readLock().lock();
String requestPage = getRequestedPage(httpRequest);
for (String prefix : unAuthenticatedURLPrefix) {
if (requestPage.startsWith(prefix)) {
return true;
}
}
} finally {
unAuthenticatedURLPrefixLock.readLock().unlock();
}
List<OpenUrlDescriptor> openUrls = service.getOpenUrls();
for (OpenUrlDescriptor openUrl : openUrls) {
if (openUrl.allowByPassAuth(httpRequest)) {
return true;
}
}
return false;
}
public static String getRequestedPage(ServletRequest request) {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
return getRequestedPage(httpRequest);
} else {
return null;
}
}
protected static String getRequestedPage(HttpServletRequest httpRequest) {
String requestURI = httpRequest.getRequestURI();
String context = httpRequest.getContextPath() + '/';
String requestedPage = requestURI.substring(context.length());
int i = requestedPage.indexOf(';');
return i == -1 ? requestedPage : requestedPage.substring(0, i);
}
protected boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
// A specific auth chain may prevent the filter to relay to a login prompt.
if (!service.doHandlePrompt(httpRequest)) {
buildUnauthorizedResponse(httpRequest, httpResponse);
return true;
}
return handleLogin(httpRequest, httpResponse);
}
private boolean handleLogin(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
String baseURL = service.getBaseURL(httpRequest);
// go through plugins to get UserIndentity
for (String pluginName : service.getAuthChain(httpRequest)) {
NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName);
AuthenticationPluginDescriptor descriptor = service.getDescriptor(pluginName);
if (Boolean.TRUE.equals(plugin.needLoginPrompt(httpRequest))) {
if (descriptor.getNeedStartingURLSaving()) {
saveRequestedURLBeforeRedirect(httpRequest, httpResponse);
}
return Boolean.TRUE.equals(plugin.handleLoginPrompt(httpRequest, httpResponse, baseURL));
}
}
log.warn("No auth plugin can be found to do the Login Prompt");
return false;
}
private void buildUnauthorizedResponse(HttpServletRequest req, HttpServletResponse resp) {
try {
StringBuilder sb = new StringBuilder(VirtualHostHelper.getBaseURL(req)).append(LOGIN_PAGE);
String loginUrl = sb.toString();
resp.addHeader("Location", loginUrl);
resp.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
resp.getWriter().write("Please log in at: " + loginUrl);
} catch (IOException e) {
log.error("Unable to write login page on unauthorized response", e);
}
}
protected UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
UserIdentificationInfo userIdent = null;
// go through plugins to get UserIdentity
for (String pluginName : service.getAuthChain(httpRequest)) {
NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName);
if (plugin != null) {
log.debug("Trying to retrieve userIdentification using plugin " + pluginName);
userIdent = plugin.handleRetrieveIdentity(httpRequest, httpResponse);
if (userIdent != null && userIdent.containsValidIdentity()) {
// fill information for the Login module
userIdent.setAuthPluginName(pluginName);
// get the target login module
String loginModulePlugin = service.getDescriptor(pluginName).getLoginModulePlugin();
userIdent.setLoginPluginName(loginModulePlugin);
// get the additional parameters
Map<String, String> parameters = service.getDescriptor(pluginName).getParameters();
if (userIdent.getLoginParameters() != null) {
// keep existing parameters set by the auth plugin
if (parameters == null) {
parameters = new HashMap<String, String>();
}
parameters.putAll(userIdent.getLoginParameters());
}
userIdent.setLoginParameters(parameters);
break;
}
} else {
log.error("Auth plugin " + pluginName + " can not be retrieved from service");
}
}
// Fall back to cache (used only when avoidReautenticated=false)
if (userIdent == null || !userIdent.containsValidIdentity()) {
log.debug("user/password not found in request, try into identity cache");
HttpSession session = httpRequest.getSession(false);
if (session == null) {
// possible we need a new session
if (httpRequest.isRequestedSessionIdValid()) {
session = httpRequest.getSession(true);
}
}
if (session != null) {
CachableUserIdentificationInfo savedUserInfo = retrieveIdentityFromCache(httpRequest);
if (savedUserInfo != null) {
log.debug("Found User identity in cache :" + savedUserInfo.getUserInfo().getUserName());
userIdent = new UserIdentificationInfo(savedUserInfo.getUserInfo());
savedUserInfo.setPrincipal(null);
}
}
} else {
log.debug("User/Password found as parameter of the request");
}
return userIdent;
}
protected boolean needSessionSaving(UserIdentificationInfo userInfo) {
String pluginName = userInfo.getAuthPluginName();
AuthenticationPluginDescriptor desc = service.getDescriptor(pluginName);
if (desc.getStateful()) {
return true;
} else {
return desc.getNeedStartingURLSaving();
}
}
/**
* Does a forced login as the given user. Bypasses all authentication checks.
*
* @param username the user name
* @return the login context, which MUST be used for logout in a {@code finally} block
* @throws LoginException
*/
public static LoginContext loginAs(String username) throws LoginException {
UserIdentificationInfo userIdent = new UserIdentificationInfo(username, "");
userIdent.setLoginPluginName(TrustingLoginPlugin.NAME);
PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime()
.getComponent(
PluggableAuthenticationService.NAME);
CallbackHandler callbackHandler;
if (authService != null) {
callbackHandler = authService.getCallbackHandler(userIdent);
} else {
callbackHandler = new UserIdentificationInfoCallbackHandler(userIdent);
}
LoginContext loginContext = new LoginContext(LOGIN_DOMAIN, callbackHandler);
if (isLoginSynchronized()) {
synchronized (NuxeoAuthenticationFilter.class) {
loginContext.login();
}
} else {
loginContext.login();
}
return loginContext;
}
}