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