001/*
002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Thierry Delprat
018 *     Bogdan Stefanescu
019 *     Anahide Tchertchian
020 *     Florent Guillaume
021 */
022package org.nuxeo.ecm.platform.ui.web.auth;
023
024import static java.nio.charset.StandardCharsets.UTF_8;
025import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.CALLBACK_URL_PARAMETER;
026import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY;
027import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DRIVE_PROTOCOL;
028import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.ERROR_AUTHENTICATION_FAILED;
029import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.ERROR_CONNECTION_FAILED;
030import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORCE_ANONYMOUS_LOGIN;
031import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORM_SUBMITTED_MARKER;
032import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGINCONTEXT_KEY;
033import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_ERROR;
034import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_FAILED;
035import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_MISSING;
036import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_PAGE;
037import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_STATUS_CODE;
038import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGOUT_PAGE;
039import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.MOBILE_PROTOCOL;
040import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.PAGE_AFTER_SWITCH;
041import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REDIRECT_URL;
042import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REQUESTED_URL;
043import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SECURITY_ERROR;
044import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SESSION_TIMEOUT;
045import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SSO_INITIAL_URL_REQUEST_KEY;
046import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.START_PAGE_SAVE_KEY;
047import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SWITCH_USER_KEY;
048import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SWITCH_USER_PAGE;
049import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.USERIDENT_KEY;
050
051import java.io.IOException;
052import java.io.Serializable;
053import java.io.UnsupportedEncodingException;
054import java.net.SocketException;
055import java.net.URI;
056import java.net.URISyntaxException;
057import java.net.URLDecoder;
058import java.security.Principal;
059import java.util.ArrayList;
060import java.util.HashMap;
061import java.util.List;
062import java.util.Map;
063import java.util.concurrent.locks.ReentrantReadWriteLock;
064
065import javax.naming.NamingException;
066import javax.security.auth.callback.CallbackHandler;
067import javax.security.auth.login.LoginContext;
068import javax.security.auth.login.LoginException;
069import javax.servlet.Filter;
070import javax.servlet.FilterChain;
071import javax.servlet.FilterConfig;
072import javax.servlet.ServletException;
073import javax.servlet.ServletRequest;
074import javax.servlet.ServletResponse;
075import javax.servlet.http.Cookie;
076import javax.servlet.http.HttpServletRequest;
077import javax.servlet.http.HttpServletResponse;
078import javax.servlet.http.HttpServletResponseWrapper;
079import javax.servlet.http.HttpSession;
080import javax.ws.rs.core.Response;
081import javax.ws.rs.core.UriBuilder;
082
083import org.apache.commons.lang3.StringUtils;
084import org.apache.commons.lang3.exception.ExceptionUtils;
085import org.apache.commons.logging.Log;
086import org.apache.commons.logging.LogFactory;
087import org.apache.http.NameValuePair;
088import org.apache.http.client.utils.URLEncodedUtils;
089import org.nuxeo.common.utils.URIUtils;
090import org.nuxeo.ecm.core.api.NuxeoPrincipal;
091import org.nuxeo.ecm.core.api.impl.UserPrincipal;
092import org.nuxeo.ecm.core.api.local.ClientLoginModule;
093import org.nuxeo.ecm.core.event.EventContext;
094import org.nuxeo.ecm.core.event.EventProducer;
095import org.nuxeo.ecm.core.event.impl.UnboundEventContext;
096import org.nuxeo.ecm.directory.DirectoryException;
097import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
098import org.nuxeo.ecm.platform.api.login.UserIdentificationInfoCallbackHandler;
099import org.nuxeo.ecm.platform.login.PrincipalImpl;
100import org.nuxeo.ecm.platform.login.TrustingLoginPlugin;
101import org.nuxeo.ecm.platform.ui.web.auth.interfaces.LoginResponseHandler;
102import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
103import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPluginLogoutExtension;
104import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPropagator;
105import org.nuxeo.ecm.platform.ui.web.auth.service.AuthenticationPluginDescriptor;
106import org.nuxeo.ecm.platform.ui.web.auth.service.OpenUrlDescriptor;
107import org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService;
108import org.nuxeo.ecm.platform.usermanager.UserManager;
109import org.nuxeo.ecm.platform.web.common.session.NuxeoHttpSessionMonitor;
110import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
111import org.nuxeo.runtime.api.Framework;
112import org.nuxeo.runtime.metrics.MetricsService;
113import org.nuxeo.runtime.model.ComponentManager;
114
115import com.codahale.metrics.Counter;
116import com.codahale.metrics.MetricRegistry;
117import com.codahale.metrics.SharedMetricRegistries;
118import com.codahale.metrics.Timer;
119
120/**
121 * Servlet filter handling Nuxeo authentication (JAAS + EJB).
122 * <p>
123 * Also handles logout and identity switch.
124 *
125 * @author Thierry Delprat
126 * @author Bogdan Stefanescu
127 * @author Anahide Tchertchian
128 * @author Florent Guillaume
129 */
130public class NuxeoAuthenticationFilter implements Filter {
131
132    private static final Log log = LogFactory.getLog(NuxeoAuthenticationFilter.class);
133
134    // protected static final String EJB_LOGIN_DOMAIN = "nuxeo-system-login";
135
136    /**
137     * @deprecated Since 8.4. Use {@link LoginScreenHelper#getStartupPagePath()} instead.
138     * @see LoginScreenHelper
139     */
140    @Deprecated
141    public static final String DEFAULT_START_PAGE = "nxstartup.faces";
142
143    /**
144     * LoginContext domain name in use by default in Nuxeo.
145     */
146    public static final String LOGIN_DOMAIN = "nuxeo-ecm-web";
147
148    protected static final String XMLHTTP_REQUEST_TYPE = "XMLHttpRequest";
149
150    protected static final String LOGIN_CATEGORY = "NuxeoAuthentication";
151
152    protected static volatile Boolean isLoginSynchronized;
153
154    /** Used internally as a marker. */
155    protected static final Principal DIRECTORY_ERROR_PRINCIPAL = new PrincipalImpl("__DIRECTORY_ERROR__\0\0\0");
156
157    protected static final String INDEX_JSP = "index.jsp";
158
159    protected static final String SLASH_INDEX_JSP = "/" + INDEX_JSP;
160
161    /** The Seam conversation id query parameter. */
162    protected static final String CONVERSATION_ID = "conversationId";
163
164    private static String anonymous;
165
166    protected final boolean avoidReauthenticate = true;
167
168    protected volatile PluggableAuthenticationService service;
169
170    protected ReentrantReadWriteLock unAuthenticatedURLPrefixLock = new ReentrantReadWriteLock();
171
172    protected List<String> unAuthenticatedURLPrefix;
173
174    /**
175     * On WebEngine (Jetty) we don't have JMS enabled so we should disable log
176     */
177    protected boolean byPassAuthenticationLog = false;
178
179    /**
180     * Which security domain to use
181     */
182    protected String securityDomain = LOGIN_DOMAIN;
183
184    // @since 5.7
185    protected final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
186
187    protected final Timer requestTimer = registry.timer(
188            MetricRegistry.name("nuxeo", "web", "authentication", "requests", "count"));
189
190    protected final Counter concurrentCount = registry.counter(
191            MetricRegistry.name("nuxeo", "web", "authentication", "requests", "concurrent", "count"));
192
193    protected final Counter concurrentMaxCount = registry.counter(
194            MetricRegistry.name("nuxeo", "web", "authentication", "requests", "concurrent", "max"));
195
196    protected final Counter loginCount = registry.counter(
197            MetricRegistry.name("nuxeo", "web", "authentication", "logged-users"));
198
199    @Override
200    public void destroy() {
201    }
202
203    protected static boolean sendAuthenticationEvent(UserIdentificationInfo userInfo, String eventId, String comment) {
204
205        LoginContext loginContext = null;
206        try {
207            try {
208                loginContext = Framework.login();
209            } catch (LoginException e) {
210                log.error("Unable to log in in order to log Login event" + e.getMessage());
211                return false;
212            }
213
214            EventProducer evtProducer = Framework.getService(EventProducer.class);
215            NuxeoPrincipal principal = new UserPrincipal(userInfo.getUserName(), null, false, false);
216
217            Map<String, Serializable> props = new HashMap<>();
218            props.put("AuthenticationPlugin", userInfo.getAuthPluginName());
219            props.put("LoginPlugin", userInfo.getLoginPluginName());
220            props.put("category", LOGIN_CATEGORY);
221            props.put("comment", comment);
222
223            EventContext ctx = new UnboundEventContext(principal, props);
224            evtProducer.fireEvent(ctx.newEvent(eventId));
225            return true;
226        } finally {
227            if (loginContext != null) {
228                try {
229                    loginContext.logout();
230                } catch (LoginException e) {
231                    log.error("Unable to logout: " + e.getMessage());
232                }
233            }
234        }
235    }
236
237    protected boolean logAuthenticationAttempt(UserIdentificationInfo userInfo, boolean success) {
238        if (byPassAuthenticationLog) {
239            return true;
240        }
241        String userName = userInfo.getUserName();
242        if (userName == null || userName.length() == 0) {
243            userName = userInfo.getToken();
244        }
245
246        String eventId;
247        String comment;
248        if (success) {
249            eventId = "loginSuccess";
250            comment = userName + " successfully logged in using " + userInfo.getAuthPluginName() + " authentication";
251            loginCount.inc();
252        } else {
253            eventId = "loginFailed";
254            comment = userName + " failed to authenticate using " + userInfo.getAuthPluginName() + " authentication";
255        }
256
257        return sendAuthenticationEvent(userInfo, eventId, comment);
258    }
259
260    protected boolean logLogout(UserIdentificationInfo userInfo) {
261        if (byPassAuthenticationLog) {
262            return true;
263        }
264        loginCount.dec();
265        String userName = userInfo.getUserName();
266        if (userName == null || userName.length() == 0) {
267            userName = userInfo.getToken();
268        }
269
270        String eventId = "logout";
271        String comment = userName + " logged out";
272
273        return sendAuthenticationEvent(userInfo, eventId, comment);
274    }
275
276    protected static boolean isLoginSynchronized() {
277        if (isLoginSynchronized != null) {
278            return isLoginSynchronized;
279        }
280        if (Framework.getRuntime() == null) {
281            return false;
282        }
283        synchronized (NuxeoAuthenticationFilter.class) {
284            if (isLoginSynchronized != null) {
285                return isLoginSynchronized;
286            }
287            return isLoginSynchronized = !Boolean.parseBoolean(Framework.getProperty(
288                    "org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter.isLoginNotSynchronized", "true"));
289        }
290    }
291
292    protected Principal doAuthenticate(CachableUserIdentificationInfo cachableUserIdent,
293            HttpServletRequest httpRequest) {
294
295        LoginContext loginContext;
296        try {
297            CallbackHandler handler = service.getCallbackHandler(cachableUserIdent.getUserInfo());
298            loginContext = new LoginContext(securityDomain, handler);
299
300            if (isLoginSynchronized()) {
301                synchronized (NuxeoAuthenticationFilter.class) {
302                    loginContext.login();
303                }
304            } else {
305                loginContext.login();
306            }
307
308            Principal principal = (Principal) loginContext.getSubject().getPrincipals().toArray()[0];
309            cachableUserIdent.setPrincipal(principal);
310            cachableUserIdent.setAlreadyAuthenticated(true);
311            // re-set the userName since for some SSO based on token,
312            // the userName is not known before login is completed
313            cachableUserIdent.getUserInfo().setUserName(principal.getName());
314
315            logAuthenticationAttempt(cachableUserIdent.getUserInfo(), true);
316        } catch (LoginException e) {
317            if (log.isInfoEnabled()) {
318                log.info(String.format("Login failed for %s on request %s",
319                        cachableUserIdent.getUserInfo().getUserName(), httpRequest.getRequestURI()));
320            }
321            log.debug(e, e);
322            logAuthenticationAttempt(cachableUserIdent.getUserInfo(), false);
323            Throwable cause = e.getCause();
324            if (cause instanceof DirectoryException) {
325                Throwable rootCause = ExceptionUtils.getRootCause(cause);
326                if (rootCause instanceof NamingException
327                        && rootCause.getMessage().contains("LDAP response read timed out")
328                        || rootCause instanceof SocketException) {
329                    if (log.isDebugEnabled()) {
330                        log.debug(String.format(
331                                "Exception root cause is either a NamingException with \"LDAP response read timed out\""
332                                        + " or a SocketException, setting the status code to %d,"
333                                        + " more relevant than an illegitimate 401.",
334                                HttpServletResponse.SC_GATEWAY_TIMEOUT));
335                    }
336                    httpRequest.setAttribute(LOGIN_STATUS_CODE, HttpServletResponse.SC_GATEWAY_TIMEOUT);
337                }
338                return DIRECTORY_ERROR_PRINCIPAL;
339            }
340            return null;
341        }
342
343        // store login context for the time of the request
344        // TODO logincontext is also stored in cachableUserIdent - it is really
345        // needed to store it??
346        httpRequest.setAttribute(LOGINCONTEXT_KEY, loginContext);
347
348        // store user ident
349        cachableUserIdent.setLoginContext(loginContext);
350        boolean createSession = needSessionSaving(cachableUserIdent.getUserInfo());
351        HttpSession session = httpRequest.getSession(createSession);
352        if (session != null) {
353            session.setAttribute(USERIDENT_KEY, cachableUserIdent);
354        }
355
356        service.onAuthenticatedSessionCreated(httpRequest, session, cachableUserIdent);
357
358        return cachableUserIdent.getPrincipal();
359    }
360
361    private boolean switchUser(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
362        HttpServletRequest httpRequest = (HttpServletRequest) request;
363
364        String deputyLogin = (String) httpRequest.getAttribute(SWITCH_USER_KEY);
365        String targetPageAfterSwitch = (String) httpRequest.getAttribute(PAGE_AFTER_SWITCH);
366        if (targetPageAfterSwitch == null) {
367            targetPageAfterSwitch = LoginScreenHelper.getStartupPagePath();
368        }
369
370        CachableUserIdentificationInfo cachableUserIdent = retrieveIdentityFromCache(httpRequest);
371        String originatingUser = cachableUserIdent.getUserInfo().getUserName();
372
373        if (deputyLogin == null) {
374            // simply switch back to the previous identity
375            NuxeoPrincipal currentPrincipal = (NuxeoPrincipal) cachableUserIdent.getPrincipal();
376            String previousUser = currentPrincipal.getOriginatingUser();
377            if (previousUser == null) {
378                return false;
379            }
380            deputyLogin = previousUser;
381            originatingUser = null;
382        }
383
384        try {
385            cachableUserIdent.getLoginContext().logout();
386        } catch (LoginException e1) {
387            log.error("Error while logout from main identity", e1);
388        }
389
390        httpRequest.getSession(false);
391        service.reinitSession(httpRequest);
392
393        CachableUserIdentificationInfo newCachableUserIdent = new CachableUserIdentificationInfo(deputyLogin,
394                deputyLogin);
395
396        newCachableUserIdent.getUserInfo().setLoginPluginName(TrustingLoginPlugin.NAME);
397        newCachableUserIdent.getUserInfo().setAuthPluginName(cachableUserIdent.getUserInfo().getAuthPluginName());
398
399        Principal principal = doAuthenticate(newCachableUserIdent, httpRequest);
400        if (principal != null && principal != DIRECTORY_ERROR_PRINCIPAL) {
401            NuxeoPrincipal nxUser = (NuxeoPrincipal) principal;
402            if (originatingUser != null) {
403                nxUser.setOriginatingUser(originatingUser);
404            }
405            propagateUserIdentificationInformation(cachableUserIdent);
406        }
407
408        // reinit Seam so the afterResponseComplete does not crash
409        // ServletLifecycle.beginRequest(httpRequest);
410
411        // flag redirect to avoid being caught by URLPolicy
412        request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE);
413        String baseURL = service.getBaseURL(request);
414        ((HttpServletResponse) response).sendRedirect(baseURL + targetPageAfterSwitch);
415
416        return true;
417    }
418
419    @Override
420    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
421            throws IOException, ServletException {
422        final Timer.Context contextTimer = requestTimer.time();
423        concurrentCount.inc();
424        if (concurrentCount.getCount() > concurrentMaxCount.getCount()) {
425            concurrentMaxCount.inc();
426        }
427        try {
428            doInitIfNeeded();
429            doFilterInternal(request, response, chain);
430        } finally {
431            ClientLoginModule.clearThreadLocalLogin();
432            contextTimer.stop();
433            concurrentCount.dec();
434        }
435    }
436
437    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
438            throws IOException, ServletException {
439
440        if (bypassAuth((HttpServletRequest) request)) {
441            chain.doFilter(request, response);
442            return;
443        }
444
445        String tokenPage = getRequestedPage(request);
446        if (tokenPage.equals(SWITCH_USER_PAGE)) {
447            boolean result = switchUser(request, response, chain);
448            if (result) {
449                return;
450            }
451        }
452
453        if (request instanceof NuxeoSecuredRequestWrapper) {
454            log.debug("ReEntering Nuxeo Authentication Filter ... exiting directly");
455            chain.doFilter(request, response);
456            return;
457        } else if (service.canBypassRequest(request)) {
458            log.debug("ReEntering Nuxeo Authentication Filter after URL rewrite ... exiting directly");
459            chain.doFilter(request, response);
460            return;
461        } else {
462            log.debug("Entering Nuxeo Authentication Filter");
463        }
464
465        String targetPageURL = null;
466        HttpServletRequest httpRequest = (HttpServletRequest) request;
467        HttpServletResponse httpResponse = (HttpServletResponse) response;
468        Principal principal = httpRequest.getUserPrincipal();
469
470        NuxeoAuthenticationPropagator.CleanupCallback propagatedAuthCb = null;
471
472        String forceAnonymousLoginParam = httpRequest.getParameter(FORCE_ANONYMOUS_LOGIN);
473        boolean forceAnonymousLogin = Boolean.parseBoolean(forceAnonymousLoginParam);
474
475        try {
476            if (principal == null) {
477                log.debug("Principal not found inside Request via getUserPrincipal");
478                // need to authenticate !
479
480                // retrieve user & password
481                CachableUserIdentificationInfo cachableUserIdent;
482                if (avoidReauthenticate) {
483                    log.debug("Try getting authentication from cache");
484                    cachableUserIdent = retrieveIdentityFromCache(httpRequest);
485                } else {
486                    log.debug("Principal cache is NOT activated");
487                }
488
489                if (cachableUserIdent != null && cachableUserIdent.getUserInfo() != null) {
490                    if (cachableUserIdent.getUserInfo().getUserName().equals(getAnonymousId())) {
491                        if (forceAnonymousLogin) {
492                            cachableUserIdent = null;
493                        }
494                    }
495
496                    if (service.needResetLogin(request)) {
497                        HttpSession session = httpRequest.getSession(false);
498                        if (session != null) {
499                            session.removeAttribute(USERIDENT_KEY);
500                        }
501                        // first propagate the login because invalidation may
502                        // require
503                        // an authenticated session
504                        propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent);
505                        // invalidate Session !
506                        try {
507                            service.invalidateSession(request);
508                        } finally {
509                            if (propagatedAuthCb != null) {
510                                propagatedAuthCb.cleanup();
511                                propagatedAuthCb = null;
512                            }
513                        }
514                        // TODO perform logout?
515                        cachableUserIdent = null;
516                    }
517                }
518
519                // identity found in cache
520                if (cachableUserIdent != null && cachableUserIdent.getUserInfo() != null) {
521                    log.debug("userIdent found in cache, get the Principal from it without reloggin");
522
523                    NuxeoHttpSessionMonitor.instance().updateEntry(httpRequest);
524
525                    principal = cachableUserIdent.getPrincipal();
526                    log.debug("Principal = " + principal.getName());
527                    propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent);
528
529                    String requestedPage = getRequestedPage(httpRequest);
530                    if (LOGOUT_PAGE.equals(requestedPage)) {
531                        boolean redirected = handleLogout(request, response, cachableUserIdent);
532                        cachableUserIdent = null;
533                        principal = null;
534                        if (redirected && httpRequest.getParameter(FORM_SUBMITTED_MARKER) == null) {
535                            return;
536                        }
537                    } else if (LOGIN_PAGE.equals(requestedPage)) {
538                        if (handleLogin(httpRequest, httpResponse)) {
539                            return;
540                        }
541                    } else {
542                        targetPageURL = getSavedRequestedURL(httpRequest, httpResponse);
543                    }
544                }
545
546                // identity not found in cache or reseted by logout
547                if (cachableUserIdent == null || cachableUserIdent.getUserInfo() == null) {
548                    UserIdentificationInfo userIdent = handleRetrieveIdentity(httpRequest, httpResponse);
549                    if (userIdent != null && userIdent.containsValidIdentity()
550                            && userIdent.getUserName().equals(getAnonymousId())) {
551                        if (forceAnonymousLogin) {
552                            userIdent = null;
553                        }
554                    }
555                    if ((userIdent == null || !userIdent.containsValidIdentity()) && !bypassAuth(httpRequest)) {
556                        boolean res = handleLoginPrompt(httpRequest, httpResponse);
557                        if (res) {
558                            return;
559                        }
560                    } else {
561                        String redirectUrl = VirtualHostHelper.getRedirectUrl(httpRequest);
562                        HttpSession session = httpRequest.getSession(false);
563                        if (session != null) {
564                            session.setAttribute(REDIRECT_URL, redirectUrl);
565                        }
566                        // restore saved Starting page
567                        targetPageURL = getSavedRequestedURL(httpRequest, httpResponse);
568                    }
569                    if (userIdent != null && userIdent.containsValidIdentity()) {
570                        // do the authentication
571                        cachableUserIdent = new CachableUserIdentificationInfo(userIdent);
572                        principal = doAuthenticate(cachableUserIdent, httpRequest);
573                        if (principal != null && principal != DIRECTORY_ERROR_PRINCIPAL) {
574                            // Do the propagation too ????
575                            propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent);
576                            // setPrincipalToSession(httpRequest, principal);
577                            // check if the current authenticator is a
578                            // LoginResponseHandler
579                            NuxeoAuthenticationPlugin plugin = getAuthenticator(cachableUserIdent);
580                            if (plugin instanceof LoginResponseHandler) {
581                                // call the extended error handler
582                                if (((LoginResponseHandler) plugin).onSuccess((HttpServletRequest) request,
583                                        (HttpServletResponse) response)) {
584                                    return;
585                                }
586                            }
587                        } else {
588                            // first check if the current authenticator is a
589                            // LoginResponseHandler
590                            NuxeoAuthenticationPlugin plugin = getAuthenticator(cachableUserIdent);
591                            if (plugin instanceof LoginResponseHandler) {
592                                // call the extended error handler
593                                if (((LoginResponseHandler) plugin).onError((HttpServletRequest) request,
594                                        (HttpServletResponse) response)) {
595                                    return;
596                                }
597                            } else {
598                                // use the old method
599                                String err = principal == DIRECTORY_ERROR_PRINCIPAL ? ERROR_CONNECTION_FAILED
600                                        : ERROR_AUTHENTICATION_FAILED;
601                                httpRequest.setAttribute(LOGIN_ERROR, err);
602                                boolean res = handleLoginPrompt(httpRequest, httpResponse);
603                                if (res) {
604                                    return;
605                                }
606                            }
607                        }
608
609                    }
610                }
611            }
612
613            if (principal != null) {
614                if (targetPageURL != null && targetPageURL.length() > 0) {
615                    // forward to target page
616                    String baseURL = service.getBaseURL(request);
617
618                    // httpRequest.getRequestDispatcher(targetPageURL).forward(new
619                    // NuxeoSecuredRequestWrapper(httpRequest, principal),
620                    // response);
621                    if (XMLHTTP_REQUEST_TYPE.equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {
622                        // httpResponse.setStatus(200);
623                        return;
624                    } else {
625                        // In case of a download redirection, the base url is already contained in the target
626                        String url = targetPageURL.startsWith(baseURL) ? targetPageURL : baseURL + targetPageURL;
627                        httpResponse.sendRedirect(url);
628                        return;
629                    }
630
631                } else {
632                    // simply continue request
633                    chain.doFilter(new NuxeoSecuredRequestWrapper(httpRequest, principal), response);
634                }
635            } else {
636                chain.doFilter(request, response);
637            }
638        } finally {
639            if (propagatedAuthCb != null) {
640                propagatedAuthCb.cleanup();
641            }
642        }
643        if (!avoidReauthenticate) {
644            // destroy login context
645            log.debug("Log out");
646            LoginContext lc = (LoginContext) httpRequest.getAttribute("LoginContext");
647            if (lc != null) {
648                try {
649                    lc.logout();
650                } catch (LoginException e) {
651                    log.error(e, e);
652                }
653            }
654        }
655        log.debug("Exit Nuxeo Authentication filter");
656    }
657
658    public NuxeoAuthenticationPlugin getAuthenticator(CachableUserIdentificationInfo ci) {
659        String key = ci.getUserInfo().getAuthPluginName();
660        if (key != null) {
661            NuxeoAuthenticationPlugin authPlugin = service.getPlugin(key);
662            return authPlugin;
663        }
664        return null;
665    }
666
667    protected static CachableUserIdentificationInfo retrieveIdentityFromCache(HttpServletRequest httpRequest) {
668
669        HttpSession session = httpRequest.getSession(false);
670        if (session != null) {
671            CachableUserIdentificationInfo cachableUserInfo = (CachableUserIdentificationInfo) session.getAttribute(
672                    USERIDENT_KEY);
673            if (cachableUserInfo != null) {
674                return cachableUserInfo;
675            }
676        }
677
678        return null;
679    }
680
681    private String getAnonymousId() throws ServletException {
682        if (anonymous == null) {
683            anonymous = Framework.getService(UserManager.class).getAnonymousUserId();
684        }
685        return anonymous;
686    }
687
688    protected void doInitIfNeeded() throws ServletException {
689        if (service == null && Framework.getRuntime() != null) {
690            synchronized (this) {
691                if (service != null) {
692                    return;
693                }
694                PluggableAuthenticationService svc = (PluggableAuthenticationService) Framework.getRuntime()
695                                                                                               .getComponent(
696                                                                                                       PluggableAuthenticationService.NAME);
697                new ComponentManager.Listener() {
698                    // nullify service field if components are restarting
699                    @Override
700                    public void beforeStart(ComponentManager mgr, boolean isResume) {
701                        service = null;
702                        uninstall();
703                    }
704                }.install();
705                service = svc;
706            }
707        }
708    }
709
710    @Override
711    public void init(FilterConfig config) throws ServletException {
712        String val = config.getInitParameter("byPassAuthenticationLog");
713        if (val != null && Boolean.parseBoolean(val)) {
714            byPassAuthenticationLog = true;
715        }
716        val = config.getInitParameter("securityDomain");
717        if (val != null) {
718            securityDomain = val;
719        }
720
721    }
722
723    /**
724     * Save requested URL before redirecting to login form.
725     * <p>
726     * Returns true if target url is a valid startup page.
727     */
728    public boolean saveRequestedURLBeforeRedirect(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
729
730        final boolean hasRequestedSessionId = !StringUtils.isBlank(httpRequest.getRequestedSessionId());
731
732        HttpSession session = httpRequest.getSession(false);
733        final boolean isTimeout = session == null && hasRequestedSessionId;
734
735        if (!httpResponse.isCommitted()) {
736            session = httpRequest.getSession(true);
737        }
738
739        if (session == null) {
740            return false;
741        }
742
743        String requestPage;
744        boolean requestPageInParams = false;
745        if (httpRequest.getParameter(REQUESTED_URL) != null) {
746            requestPageInParams = true;
747            requestPage = httpRequest.getParameter(REQUESTED_URL);
748        } else {
749            requestPage = getRequestedUrl(httpRequest);
750        }
751
752        if (requestPage == null) {
753            return false;
754        }
755
756        // add a flag to tell that the Session looks like having timed out
757        if (isTimeout && !requestPage.equals(LoginScreenHelper.getStartupPagePath())) {
758            session.setAttribute(SESSION_TIMEOUT, Boolean.TRUE);
759        } else {
760            session.removeAttribute(SESSION_TIMEOUT);
761        }
762
763        // avoid saving to session is start page is not valid or if it's
764        // already in the request params
765        if (isStartPageValid(requestPage)) {
766            if (!requestPageInParams) {
767                String uri = URIUtils.getURIPath(requestPage);
768                // strip force anonymous login parameter if it exists
769                if (requestPage.contains("?")) {
770                    Map<String, String> params = URIUtils.getRequestParameters(requestPage);
771                    params.remove(FORCE_ANONYMOUS_LOGIN);
772                    if (!params.isEmpty()) {
773                        uri += '?' + URIUtils.getURIQuery(params);
774                    }
775                }
776                session.setAttribute(START_PAGE_SAVE_KEY, uri);
777            }
778            return true;
779        }
780
781        // avoid redirect if not useful
782        for (String startupPagePath : LoginScreenHelper.getStartupPagePaths()) {
783            if (requestPage.startsWith(startupPagePath)
784                    && LoginScreenHelper.getStartupPagePath().equals(startupPagePath)) {
785                return true;
786            }
787        }
788
789        return false;
790    }
791
792    /**
793     * The requested URL is like the requested page BUT is not decoded AND also includes the query string (except
794     * without conversation id).
795     */
796    public static String getRequestedUrl(HttpServletRequest request) {
797        String path = getRequestedPage(request);
798        // re-encode path, as it's what the caller expects
799        try {
800            path = new URI(null, null, path, null).toASCIIString();
801        } catch (URISyntaxException e) {
802            // keep path as is
803        }
804        String qs = request.getQueryString();
805        if (StringUtils.isNotEmpty(qs)) {
806            // strip conversation id
807            if (qs.contains(CONVERSATION_ID)) {
808                List<NameValuePair> list = URLEncodedUtils.parse(qs, UTF_8);
809                if (list.removeIf(pair -> pair.getName().equals(CONVERSATION_ID))) {
810                    qs = URLEncodedUtils.format(list, UTF_8);
811                }
812            }
813            if (!qs.isEmpty()) {
814                path = path + '?' + qs;
815            }
816        }
817        return path;
818    }
819
820    protected static String getSavedRequestedURL(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
821
822        HttpSession session = httpRequest.getSession(false);
823        String requestedPage = httpRequest.getParameter(REQUESTED_URL);
824        if (StringUtils.isBlank(requestedPage)) {
825            // retrieve from session
826            if (session != null) {
827                requestedPage = (String) session.getAttribute(START_PAGE_SAVE_KEY);
828            }
829
830            // retrieve from SSO cookies
831            Cookie[] cookies = httpRequest.getCookies();
832            if (cookies != null) {
833                for (Cookie cookie : cookies) {
834                    if (SSO_INITIAL_URL_REQUEST_KEY.equals(cookie.getName())) {
835                        requestedPage = cookie.getValue();
836                        cookie.setPath("/");
837                        // enforce cookie removal
838                        cookie.setMaxAge(0);
839                        httpResponse.addCookie(cookie);
840                    }
841                }
842            }
843        }
844
845        if (requestedPage != null) {
846            // retrieve URL fragment from cookie
847            Cookie[] cookies = httpRequest.getCookies();
848            if (cookies != null) {
849                for (Cookie cookie : cookies) {
850                    if (NXAuthConstants.START_PAGE_FRAGMENT_KEY.equals(cookie.getName())) {
851                        try {
852                            requestedPage = UriBuilder.fromUri(requestedPage)
853                                                      .fragment(URLDecoder.decode(cookie.getValue(), "UTF-8"))
854                                                      .build()
855                                                      .toString();
856                        } catch (UnsupportedEncodingException e) {
857                            log.error("Failed to decode start page url fragment", e);
858                        }
859                        // enforce cookie removal
860                        cookie.setMaxAge(0);
861                        httpResponse.addCookie(cookie);
862                    }
863                }
864            }
865        }
866
867        // clean up session
868        if (session != null) {
869            session.removeAttribute(START_PAGE_SAVE_KEY);
870        }
871
872        return requestedPage;
873    }
874
875    protected boolean isStartPageValid(String startPage) {
876        if (startPage == null) {
877            return false;
878        }
879        try {
880            // Sometimes, the service is not initialized at startup
881            doInitIfNeeded();
882        } catch (ServletException e) {
883            return false;
884        }
885        for (String prefix : service.getStartURLPatterns()) {
886            if (startPage.startsWith(prefix)) {
887                return true;
888            }
889        }
890        return false;
891    }
892
893    protected boolean handleLogout(ServletRequest request, ServletResponse response,
894            CachableUserIdentificationInfo cachedUserInfo) throws ServletException {
895        logLogout(cachedUserInfo.getUserInfo());
896
897        request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE);
898        Map<String, String> parameters = new HashMap<>();
899        String securityError = request.getParameter(SECURITY_ERROR);
900        if (securityError != null) {
901            parameters.put(SECURITY_ERROR, securityError);
902        }
903        if (cachedUserInfo.getPrincipal().getName().equals(getAnonymousId())) {
904            parameters.put(FORCE_ANONYMOUS_LOGIN, "true");
905        }
906        String requestedUrl = request.getParameter(REQUESTED_URL);
907        if (requestedUrl != null) {
908            parameters.put(REQUESTED_URL, requestedUrl);
909        }
910        // Reset JSESSIONID Cookie
911        HttpServletResponse httpResponse = (HttpServletResponse) response;
912        Cookie cookie = new Cookie("JSESSIONID", null);
913        cookie.setMaxAge(0);
914        cookie.setPath("/");
915        httpResponse.addCookie(cookie);
916
917        String pluginName = cachedUserInfo.getUserInfo().getAuthPluginName();
918        NuxeoAuthenticationPlugin authPlugin = service.getPlugin(pluginName);
919        NuxeoAuthenticationPluginLogoutExtension logoutPlugin = null;
920
921        if (authPlugin instanceof NuxeoAuthenticationPluginLogoutExtension) {
922            logoutPlugin = (NuxeoAuthenticationPluginLogoutExtension) authPlugin;
923        }
924
925        boolean redirected = false;
926        if (logoutPlugin != null) {
927            redirected = Boolean.TRUE.equals(
928                    logoutPlugin.handleLogout((HttpServletRequest) request, (HttpServletResponse) response));
929        }
930
931        // invalidate Session !
932        service.invalidateSession(request);
933
934        HttpServletRequest httpRequest = (HttpServletRequest) request;
935        if (!redirected && !XMLHTTP_REQUEST_TYPE.equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {
936            String baseURL = service.getBaseURL(request);
937            String callbackURL = request.getParameter(CALLBACK_URL_PARAMETER);
938            try {
939                httpResponse.sendRedirect(getLogoutRedirectURL(callbackURL, baseURL, parameters));
940                redirected = true;
941            } catch (IOException e) {
942                log.error("Unable to redirect to default start page after logout : " + e.getMessage());
943            }
944        }
945
946        try {
947            cachedUserInfo.getLoginContext().logout();
948        } catch (LoginException e) {
949            log.error("Unable to logout " + e.getMessage());
950        }
951        return redirected;
952    }
953
954    /**
955     * @since 10.3
956     */
957    protected String getLogoutRedirectURL(String callbackURL, String baseURL, Map<String, String> parameters) {
958        if (isCallbackURLValid(callbackURL, baseURL)) {
959            return callbackURL;
960        }
961
962        String url = baseURL + LoginScreenHelper.getStartupPagePath();
963        return URIUtils.addParametersToURIQuery(url, parameters);
964    }
965
966    /**
967     * @since 10.3
968     */
969    protected boolean isCallbackURLValid(String callbackURL, String baseURL) {
970        return StringUtils.isNotBlank(callbackURL) && ((baseURL != null && callbackURL.startsWith(baseURL))
971                || callbackURL.startsWith(MOBILE_PROTOCOL) || callbackURL.startsWith(DRIVE_PROTOCOL));
972    }
973
974    // App Server JAAS SPI
975    protected void propagateUserIdentificationInformation(CachableUserIdentificationInfo cachableUserIdent) {
976        service.propagateUserIdentificationInformation(cachableUserIdent);
977    }
978
979    // Plugin API
980    protected void initUnAuthenticatedURLPrefix() {
981        // gather unAuthenticated URLs
982        unAuthenticatedURLPrefix = new ArrayList<>();
983        for (String pluginName : service.getAuthChain()) {
984            NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName);
985            if (plugin == null) {
986                throw new NullPointerException("Could not find plugin for name '" + pluginName + "'");
987            }
988            List<String> prefix = plugin.getUnAuthenticatedURLPrefix();
989            if (prefix != null && !prefix.isEmpty()) {
990                unAuthenticatedURLPrefix.addAll(prefix);
991            }
992        }
993    }
994
995    protected boolean bypassAuth(HttpServletRequest httpRequest) {
996        if (unAuthenticatedURLPrefix == null) {
997            try {
998                unAuthenticatedURLPrefixLock.writeLock().lock();
999                // late init to allow plugins registered after this filter init
1000                initUnAuthenticatedURLPrefix();
1001            } finally {
1002                unAuthenticatedURLPrefixLock.writeLock().unlock();
1003            }
1004        }
1005
1006        try {
1007            unAuthenticatedURLPrefixLock.readLock().lock();
1008            String requestPage = getRequestedPage(httpRequest);
1009            for (String prefix : unAuthenticatedURLPrefix) {
1010                if (requestPage.startsWith(prefix)) {
1011                    return true;
1012                }
1013            }
1014        } finally {
1015            unAuthenticatedURLPrefixLock.readLock().unlock();
1016        }
1017
1018        List<OpenUrlDescriptor> openUrls = service.getOpenUrls();
1019        for (OpenUrlDescriptor openUrl : openUrls) {
1020            if (openUrl.allowByPassAuth(httpRequest)) {
1021                return true;
1022            }
1023        }
1024
1025        return false;
1026    }
1027
1028    public static String getRequestedPage(ServletRequest request) {
1029        if (request instanceof HttpServletRequest) {
1030            HttpServletRequest httpRequest = (HttpServletRequest) request;
1031            return getRequestedPage(httpRequest);
1032        } else {
1033            return null;
1034        }
1035    }
1036
1037    protected static String getRequestedPage(HttpServletRequest httpRequest) {
1038        String path = httpRequest.getServletPath(); // use decoded and normalized servlet path
1039        if (path.endsWith(SLASH_INDEX_JSP)) {
1040            // the welcome file (index.jsp) is present in the servlet path
1041            if (!httpRequest.getRequestURI().contains(SLASH_INDEX_JSP)) {
1042                // remove it if it was not specified explicitly
1043                path = path.substring(0, path.length() - INDEX_JSP.length());
1044            }
1045        }
1046        String info = httpRequest.getPathInfo();
1047        if (info != null) {
1048            path = path + info;
1049        }
1050        if (!path.isEmpty()) {
1051            path = path.substring(1); // strip initial /
1052        }
1053        return path;
1054    }
1055
1056    protected boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
1057
1058        // A specific auth chain may prevent the filter to relay to a login prompt.
1059        if (!service.doHandlePrompt(httpRequest)) {
1060            buildUnauthorizedResponse(httpRequest, httpResponse);
1061            return true;
1062        }
1063
1064        return handleLogin(httpRequest, httpResponse);
1065
1066    }
1067
1068    private boolean handleLogin(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
1069        String baseURL = service.getBaseURL(httpRequest);
1070
1071        // go through plugins to get UserIndentity
1072        for (String pluginName : service.getAuthChain(httpRequest)) {
1073            NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName);
1074            AuthenticationPluginDescriptor descriptor = service.getDescriptor(pluginName);
1075            if (plugin == null) {
1076                throw new NullPointerException("Could not find plugin for name '" + pluginName + "'");
1077            }
1078            if (Boolean.TRUE.equals(plugin.needLoginPrompt(httpRequest))) {
1079                if (descriptor.getNeedStartingURLSaving()) {
1080                    saveRequestedURLBeforeRedirect(httpRequest, httpResponse);
1081                }
1082
1083                HttpServletResponse response = new HttpServletResponseWrapper(httpResponse) {
1084                    @Override
1085                    public void sendRedirect(String location) throws IOException {
1086                        Map<String, String> parameters =  URIUtils.getRequestParameters(location);
1087                        // failed form authentication, fragment is already stored
1088                        if (parameters.containsKey(LOGIN_MISSING) || parameters.containsKey(LOGIN_FAILED)) {
1089                            super.sendRedirect(location);
1090                            return;
1091                        }
1092                        HttpServletResponse response = (HttpServletResponse) getResponse();
1093                        StringBuilder sb = new StringBuilder();
1094                        sb.append("<script type=\"text/javascript\">\n");
1095                        sb.append("document.cookie = '" + NXAuthConstants.START_PAGE_FRAGMENT_KEY
1096                                + "=' + encodeURIComponent(window.location.hash.substring(1) || '') + '; path=/';\n");
1097                        sb.append("window.location = '" + location + "';\n");
1098                        sb.append("</script>");
1099                        String script = sb.toString();
1100
1101                        response.setStatus(SC_UNAUTHORIZED);
1102                        response.setContentType("text/html;charset=UTF-8");
1103                        response.setContentLength(script.length());
1104                        response.getWriter().write(script);
1105                    }
1106                };
1107                return Boolean.TRUE.equals(plugin.handleLoginPrompt(httpRequest, response, baseURL));
1108            }
1109        }
1110
1111        log.warn("No auth plugin can be found to do the Login Prompt");
1112        return false;
1113    }
1114
1115    private void buildUnauthorizedResponse(HttpServletRequest req, HttpServletResponse resp) {
1116
1117        try {
1118            String loginUrl = VirtualHostHelper.getBaseURL(req) + LOGIN_PAGE;
1119            resp.addHeader("Location", loginUrl);
1120            resp.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
1121            resp.getWriter().write("Please log in at: " + loginUrl);
1122        } catch (IOException e) {
1123            log.error("Unable to write login page on unauthorized response", e);
1124        }
1125    }
1126
1127    protected UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest,
1128            HttpServletResponse httpResponse) {
1129
1130        UserIdentificationInfo userIdent = null;
1131
1132        // go through plugins to get UserIdentity
1133        for (String pluginName : service.getAuthChain(httpRequest)) {
1134            NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName);
1135            if (plugin != null) {
1136                log.debug("Trying to retrieve userIdentification using plugin " + pluginName);
1137                userIdent = plugin.handleRetrieveIdentity(httpRequest, httpResponse);
1138                if (userIdent != null && userIdent.containsValidIdentity()) {
1139                    // fill information for the Login module
1140                    userIdent.setAuthPluginName(pluginName);
1141
1142                    // get the target login module
1143                    String loginModulePlugin = service.getDescriptor(pluginName).getLoginModulePlugin();
1144                    userIdent.setLoginPluginName(loginModulePlugin);
1145
1146                    // get the additional parameters
1147                    Map<String, String> parameters = service.getDescriptor(pluginName).getParameters();
1148                    if (userIdent.getLoginParameters() != null) {
1149                        // keep existing parameters set by the auth plugin
1150                        if (parameters == null) {
1151                            parameters = new HashMap<>();
1152                        }
1153                        parameters.putAll(userIdent.getLoginParameters());
1154                    }
1155                    userIdent.setLoginParameters(parameters);
1156
1157                    break;
1158                }
1159            } else {
1160                log.error("Auth plugin " + pluginName + " can not be retrieved from service");
1161            }
1162        }
1163
1164        // Fall back to cache (used only when avoidReautenticated=false)
1165        if (userIdent == null || !userIdent.containsValidIdentity()) {
1166            log.debug("user/password not found in request, try into identity cache");
1167            HttpSession session = httpRequest.getSession(false);
1168            if (session == null) {
1169                // possible we need a new session
1170                if (httpRequest.isRequestedSessionIdValid()) {
1171                    session = httpRequest.getSession(true);
1172                }
1173            }
1174            if (session != null) {
1175                CachableUserIdentificationInfo savedUserInfo = retrieveIdentityFromCache(httpRequest);
1176                if (savedUserInfo != null) {
1177                    log.debug("Found User identity in cache :" + savedUserInfo.getUserInfo().getUserName());
1178                    userIdent = new UserIdentificationInfo(savedUserInfo.getUserInfo());
1179                    savedUserInfo.setPrincipal(null);
1180                }
1181            }
1182        } else {
1183            log.debug("User/Password found as parameter of the request");
1184        }
1185
1186        return userIdent;
1187    }
1188
1189    protected boolean needSessionSaving(UserIdentificationInfo userInfo) {
1190        String pluginName = userInfo.getAuthPluginName();
1191
1192        AuthenticationPluginDescriptor desc = service.getDescriptor(pluginName);
1193
1194        if (desc.getStateful()) {
1195            return true;
1196        } else {
1197            return desc.getNeedStartingURLSaving();
1198        }
1199    }
1200
1201    /**
1202     * Does a forced login as the given user. Bypasses all authentication checks.
1203     *
1204     * @param username the user name
1205     * @return the login context, which MUST be used for logout in a {@code finally} block
1206     * @throws LoginException
1207     */
1208    public static LoginContext loginAs(String username) throws LoginException {
1209        UserIdentificationInfo userIdent = new UserIdentificationInfo(username, "");
1210        userIdent.setLoginPluginName(TrustingLoginPlugin.NAME);
1211        PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime()
1212                                                                                               .getComponent(
1213                                                                                                       PluggableAuthenticationService.NAME);
1214        CallbackHandler callbackHandler;
1215        if (authService != null) {
1216            callbackHandler = authService.getCallbackHandler(userIdent);
1217        } else {
1218            callbackHandler = new UserIdentificationInfoCallbackHandler(userIdent);
1219        }
1220        LoginContext loginContext = new LoginContext(LOGIN_DOMAIN, callbackHandler);
1221
1222        if (isLoginSynchronized()) {
1223            synchronized (NuxeoAuthenticationFilter.class) {
1224                loginContext.login();
1225            }
1226        } else {
1227            loginContext.login();
1228        }
1229        return loginContext;
1230    }
1231
1232}