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