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