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