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