001/* 002 * (C) Copyright 2006-2017 Nuxeo (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 */ 022package org.nuxeo.ecm.platform.ui.web.auth; 023 024import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY; 025import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.ERROR_AUTHENTICATION_FAILED; 026import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.ERROR_CONNECTION_FAILED; 027import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORCE_ANONYMOUS_LOGIN; 028import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORM_SUBMITTED_MARKER; 029import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGINCONTEXT_KEY; 030import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_ERROR; 031import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_PAGE; 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.REDIRECT_URL; 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.metrics.MetricsService; 103import org.nuxeo.runtime.model.ComponentManager; 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<>(); 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 contextTimer.stop(); 413 concurrentCount.dec(); 414 } 415 } 416 417 public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) 418 throws IOException, ServletException { 419 420 if (bypassAuth((HttpServletRequest) request)) { 421 chain.doFilter(request, response); 422 return; 423 } 424 425 String tokenPage = getRequestedPage(request); 426 if (tokenPage.equals(SWITCH_USER_PAGE)) { 427 boolean result = switchUser(request, response, chain); 428 if (result) { 429 return; 430 } 431 } 432 433 if (request instanceof NuxeoSecuredRequestWrapper) { 434 log.debug("ReEntering Nuxeo Authentication Filter ... exiting directly"); 435 chain.doFilter(request, response); 436 return; 437 } else if (service.canBypassRequest(request)) { 438 log.debug("ReEntering Nuxeo Authentication Filter after URL rewrite ... exiting directly"); 439 chain.doFilter(request, response); 440 return; 441 } else { 442 log.debug("Entering Nuxeo Authentication Filter"); 443 } 444 445 String targetPageURL = null; 446 HttpServletRequest httpRequest = (HttpServletRequest) request; 447 HttpServletResponse httpResponse = (HttpServletResponse) response; 448 Principal principal = httpRequest.getUserPrincipal(); 449 450 NuxeoAuthenticationPropagator.CleanupCallback propagatedAuthCb = null; 451 452 try { 453 if (principal == null) { 454 log.debug("Principal not found inside Request via getUserPrincipal"); 455 // need to authenticate ! 456 457 // retrieve user & password 458 CachableUserIdentificationInfo cachableUserIdent; 459 if (avoidReauthenticate) { 460 log.debug("Try getting authentication from cache"); 461 cachableUserIdent = retrieveIdentityFromCache(httpRequest); 462 } else { 463 log.debug("Principal cache is NOT activated"); 464 } 465 466 if (cachableUserIdent != null && cachableUserIdent.getUserInfo() != null 467 && service.needResetLogin(request)) { 468 HttpSession session = httpRequest.getSession(false); 469 if (session != null) { 470 session.removeAttribute(USERIDENT_KEY); 471 } 472 // first propagate the login because invalidation may 473 // require 474 // an authenticated session 475 propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent); 476 // invalidate Session ! 477 try { 478 service.invalidateSession(request); 479 } finally { 480 if (propagatedAuthCb != null) { 481 propagatedAuthCb.cleanup(); 482 propagatedAuthCb = null; 483 } 484 } 485 // TODO perform logout? 486 cachableUserIdent = null; 487 } 488 489 // identity found in cache 490 if (cachableUserIdent != null && cachableUserIdent.getUserInfo() != null) { 491 log.debug("userIdent found in cache, get the Principal from it without reloggin"); 492 493 NuxeoHttpSessionMonitor.instance().updateEntry(httpRequest); 494 495 principal = cachableUserIdent.getPrincipal(); 496 log.debug("Principal = " + principal.getName()); 497 propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent); 498 499 String requestedPage = getRequestedPage(httpRequest); 500 if (LOGOUT_PAGE.equals(requestedPage)) { 501 boolean redirected = handleLogout(request, response, cachableUserIdent); 502 cachableUserIdent = null; 503 principal = null; 504 if (redirected && httpRequest.getParameter(FORM_SUBMITTED_MARKER) == null) { 505 return; 506 } 507 } else if (LOGIN_PAGE.equals(requestedPage)) { 508 if (handleLogin(httpRequest, httpResponse)) { 509 return; 510 } 511 } else { 512 targetPageURL = getSavedRequestedURL(httpRequest, httpResponse); 513 } 514 } 515 516 // identity not found in cache or reseted by logout 517 if (cachableUserIdent == null || cachableUserIdent.getUserInfo() == null) { 518 UserIdentificationInfo userIdent = handleRetrieveIdentity(httpRequest, httpResponse); 519 if (userIdent != null && userIdent.containsValidIdentity() 520 && userIdent.getUserName().equals(getAnonymousId())) { 521 String forceAuth = httpRequest.getParameter(FORCE_ANONYMOUS_LOGIN); 522 if (forceAuth != null && forceAuth.equals("true")) { 523 userIdent = null; 524 } 525 } 526 if ((userIdent == null || !userIdent.containsValidIdentity()) && !bypassAuth(httpRequest)) { 527 boolean res = handleLoginPrompt(httpRequest, httpResponse); 528 if (res) { 529 return; 530 } 531 } else { 532 String redirectUrl = VirtualHostHelper.getRedirectUrl(httpRequest); 533 HttpSession session = httpRequest.getSession(false); 534 if (session != null) { 535 session.setAttribute(REDIRECT_URL, redirectUrl); 536 } 537 // restore saved Starting page 538 targetPageURL = getSavedRequestedURL(httpRequest, httpResponse); 539 } 540 if (userIdent != null && userIdent.containsValidIdentity()) { 541 // do the authentication 542 cachableUserIdent = new CachableUserIdentificationInfo(userIdent); 543 principal = doAuthenticate(cachableUserIdent, httpRequest); 544 if (principal != null && principal != DIRECTORY_ERROR_PRINCIPAL) { 545 // Do the propagation too ???? 546 propagatedAuthCb = service.propagateUserIdentificationInformation(cachableUserIdent); 547 // setPrincipalToSession(httpRequest, principal); 548 // check if the current authenticator is a 549 // LoginResponseHandler 550 NuxeoAuthenticationPlugin plugin = getAuthenticator(cachableUserIdent); 551 if (plugin instanceof LoginResponseHandler) { 552 // call the extended error handler 553 if (((LoginResponseHandler) plugin).onSuccess((HttpServletRequest) request, 554 (HttpServletResponse) response)) { 555 return; 556 } 557 } 558 } else { 559 // first check if the current authenticator is a 560 // LoginResponseHandler 561 NuxeoAuthenticationPlugin plugin = getAuthenticator(cachableUserIdent); 562 if (plugin instanceof LoginResponseHandler) { 563 // call the extended error handler 564 if (((LoginResponseHandler) plugin).onError((HttpServletRequest) request, 565 (HttpServletResponse) response)) { 566 return; 567 } 568 } else { 569 // use the old method 570 String err = principal == DIRECTORY_ERROR_PRINCIPAL ? ERROR_CONNECTION_FAILED 571 : ERROR_AUTHENTICATION_FAILED; 572 httpRequest.setAttribute(LOGIN_ERROR, err); 573 boolean res = handleLoginPrompt(httpRequest, httpResponse); 574 if (res) { 575 return; 576 } 577 } 578 } 579 580 } 581 } 582 } 583 584 if (principal != null) { 585 if (targetPageURL != null && targetPageURL.length() > 0) { 586 // forward to target page 587 String baseURL = service.getBaseURL(request); 588 589 // httpRequest.getRequestDispatcher(targetPageURL).forward(new 590 // NuxeoSecuredRequestWrapper(httpRequest, principal), 591 // response); 592 if (XMLHTTP_REQUEST_TYPE.equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) { 593 // httpResponse.setStatus(200); 594 return; 595 } else { 596 // In case of a download redirection, the base url is already contained in the target 597 String url = targetPageURL.startsWith(baseURL) ? targetPageURL : baseURL + targetPageURL; 598 httpResponse.sendRedirect(url); 599 return; 600 } 601 602 } else { 603 // simply continue request 604 chain.doFilter(new NuxeoSecuredRequestWrapper(httpRequest, principal), response); 605 } 606 } else { 607 chain.doFilter(request, response); 608 } 609 } finally { 610 if (propagatedAuthCb != null) { 611 propagatedAuthCb.cleanup(); 612 } 613 } 614 if (!avoidReauthenticate) { 615 // destroy login context 616 log.debug("Log out"); 617 LoginContext lc = (LoginContext) httpRequest.getAttribute("LoginContext"); 618 if (lc != null) { 619 try { 620 lc.logout(); 621 } catch (LoginException e) { 622 log.error(e, e); 623 } 624 } 625 } 626 log.debug("Exit Nuxeo Authentication filter"); 627 } 628 629 public NuxeoAuthenticationPlugin getAuthenticator(CachableUserIdentificationInfo ci) { 630 String key = ci.getUserInfo().getAuthPluginName(); 631 if (key != null) { 632 NuxeoAuthenticationPlugin authPlugin = service.getPlugin(key); 633 return authPlugin; 634 } 635 return null; 636 } 637 638 protected static CachableUserIdentificationInfo retrieveIdentityFromCache(HttpServletRequest httpRequest) { 639 640 HttpSession session = httpRequest.getSession(false); 641 if (session != null) { 642 CachableUserIdentificationInfo cachableUserInfo = (CachableUserIdentificationInfo) session.getAttribute( 643 USERIDENT_KEY); 644 if (cachableUserInfo != null) { 645 return cachableUserInfo; 646 } 647 } 648 649 return null; 650 } 651 652 private String getAnonymousId() throws ServletException { 653 if (anonymous == null) { 654 anonymous = Framework.getService(UserManager.class).getAnonymousUserId(); 655 } 656 return anonymous; 657 } 658 659 protected void doInitIfNeeded() throws ServletException { 660 if (service == null && Framework.getRuntime() != null) { 661 synchronized (this) { 662 if (service != null) { 663 return; 664 } 665 service = (PluggableAuthenticationService) Framework.getRuntime() 666 .getComponent(PluggableAuthenticationService.NAME); 667 // init preFilters 668 service.initPreFilters(); 669 if (service == null) { 670 log.error("Unable to get Service " + PluggableAuthenticationService.NAME); 671 throw new ServletException("Can't initialize Nuxeo Pluggable Authentication Service"); 672 } else { 673 new ComponentManager.LifeCycleHandler() { 674 // nullify service field if components are restarting 675 @Override 676 public void beforeStart(ComponentManager mgr, boolean isResume) { 677 service = null; 678 uninstall(); 679 } 680 }.install(); 681 } 682 } 683 } 684 } 685 686 @Override 687 public void init(FilterConfig config) throws ServletException { 688 String val = config.getInitParameter("byPassAuthenticationLog"); 689 if (val != null && Boolean.parseBoolean(val)) { 690 byPassAuthenticationLog = true; 691 } 692 val = config.getInitParameter("securityDomain"); 693 if (val != null) { 694 securityDomain = val; 695 } 696 697 } 698 699 /** 700 * Save requested URL before redirecting to login form. 701 * <p> 702 * Returns true if target url is a valid startup page. 703 */ 704 public boolean saveRequestedURLBeforeRedirect(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { 705 706 final boolean hasRequestedSessionId = !StringUtils.isBlank(httpRequest.getRequestedSessionId()); 707 708 HttpSession session = httpRequest.getSession(false); 709 final boolean isTimeout = session == null && hasRequestedSessionId; 710 711 if (!httpResponse.isCommitted()) { 712 session = httpRequest.getSession(true); 713 } 714 715 if (session == null) { 716 return false; 717 } 718 719 String requestPage; 720 boolean requestPageInParams = false; 721 if (httpRequest.getParameter(REQUESTED_URL) != null) { 722 requestPageInParams = true; 723 requestPage = httpRequest.getParameter(REQUESTED_URL); 724 } else { 725 requestPage = getRequestedUrl(httpRequest); 726 } 727 728 if (requestPage == null) { 729 return false; 730 } 731 732 // add a flag to tell that the Session looks like having timed out 733 if (isTimeout && !requestPage.equals(LoginScreenHelper.getStartupPagePath())) { 734 session.setAttribute(SESSION_TIMEOUT, Boolean.TRUE); 735 } else { 736 session.removeAttribute(SESSION_TIMEOUT); 737 } 738 739 // avoid redirect if not useful 740 for (String startupPagePath : LoginScreenHelper.getStartupPagePaths()) { 741 if (requestPage.startsWith(startupPagePath) 742 && LoginScreenHelper.getStartupPagePath().equals(startupPagePath)) { 743 return true; 744 } 745 } 746 747 // avoid saving to session is start page is not valid or if it's 748 // already in the request params 749 if (isStartPageValid(requestPage)) { 750 if (!requestPageInParams) { 751 session.setAttribute(START_PAGE_SAVE_KEY, requestPage); 752 } 753 return true; 754 } 755 756 return false; 757 } 758 759 public static String getRequestedUrl(HttpServletRequest httpRequest) { 760 String completeURI = httpRequest.getRequestURI(); 761 String qs = httpRequest.getQueryString(); 762 String context = httpRequest.getContextPath() + '/'; 763 String requestPage = completeURI.substring(context.length()); 764 if (qs != null && qs.length() > 0) { 765 // remove conversationId if present 766 if (qs.contains("conversationId")) { 767 qs = qs.replace("conversationId", "old_conversationId"); 768 } 769 requestPage = requestPage + '?' + qs; 770 } 771 return requestPage; 772 } 773 774 protected static String getSavedRequestedURL(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { 775 776 String requestedPage = null; 777 HttpSession session = httpRequest.getSession(false); 778 if (httpRequest.getParameter(REQUESTED_URL) != null) { 779 String requestedUrl = httpRequest.getParameter(REQUESTED_URL); 780 if (requestedUrl != null && !"".equals(requestedUrl)) { 781 try { 782 requestedPage = URLDecoder.decode(requestedUrl, "UTF-8"); 783 } catch (UnsupportedEncodingException e) { 784 log.error("Unable to get the requestedUrl parameter" + e); 785 } 786 } 787 } else { 788 // retrieve from session 789 if (session != null) { 790 requestedPage = (String) session.getAttribute(START_PAGE_SAVE_KEY); 791 } 792 793 // retrieve from SSO cookies 794 Cookie[] cookies = httpRequest.getCookies(); 795 if (cookies != null) { 796 for (Cookie cookie : cookies) { 797 if (SSO_INITIAL_URL_REQUEST_KEY.equals(cookie.getName())) { 798 requestedPage = cookie.getValue(); 799 cookie.setPath("/"); 800 // enforce cookie removal 801 cookie.setMaxAge(0); 802 httpResponse.addCookie(cookie); 803 } 804 } 805 } 806 } 807 808 // clean up session 809 if (session != null) { 810 session.removeAttribute(START_PAGE_SAVE_KEY); 811 } 812 813 // add locale if not in the URL params 814 String localeStr = httpRequest.getParameter(NXAuthConstants.LANGUAGE_PARAMETER); 815 if (requestedPage != null && !"".equals(requestedPage) && localeStr != null) { 816 Map<String, String> params = new HashMap<>(); 817 if (!URIUtils.getRequestParameters(requestedPage).containsKey(NXAuthConstants.LANGUAGE_PARAMETER)) { 818 params.put(NXAuthConstants.LANGUAGE_PARAMETER, localeStr); 819 } 820 return URIUtils.addParametersToURIQuery(requestedPage, params); 821 } 822 823 return requestedPage; 824 } 825 826 protected boolean isStartPageValid(String startPage) { 827 if (startPage == null) { 828 return false; 829 } 830 try { 831 // Sometimes, the service is not initialized at startup 832 doInitIfNeeded(); 833 } catch (ServletException e) { 834 return false; 835 } 836 for (String prefix : service.getStartURLPatterns()) { 837 if (startPage.startsWith(prefix)) { 838 return true; 839 } 840 } 841 return false; 842 } 843 844 protected boolean handleLogout(ServletRequest request, ServletResponse response, 845 CachableUserIdentificationInfo cachedUserInfo) throws ServletException { 846 logLogout(cachedUserInfo.getUserInfo()); 847 848 request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE); 849 Map<String, String> parameters = new HashMap<>(); 850 String securityError = request.getParameter(SECURITY_ERROR); 851 if (securityError != null) { 852 parameters.put(SECURITY_ERROR, securityError); 853 } 854 if (cachedUserInfo.getPrincipal().getName().equals(getAnonymousId())) { 855 parameters.put(FORCE_ANONYMOUS_LOGIN, "true"); 856 } 857 String requestedUrl = request.getParameter(REQUESTED_URL); 858 if (requestedUrl != null) { 859 parameters.put(REQUESTED_URL, requestedUrl); 860 } 861 // Reset JSESSIONID Cookie 862 HttpServletResponse httpResponse = (HttpServletResponse) response; 863 Cookie cookie = new Cookie("JSESSIONID", null); 864 cookie.setMaxAge(0); 865 cookie.setPath("/"); 866 httpResponse.addCookie(cookie); 867 868 String pluginName = cachedUserInfo.getUserInfo().getAuthPluginName(); 869 NuxeoAuthenticationPlugin authPlugin = service.getPlugin(pluginName); 870 NuxeoAuthenticationPluginLogoutExtension logoutPlugin = null; 871 872 if (authPlugin instanceof NuxeoAuthenticationPluginLogoutExtension) { 873 logoutPlugin = (NuxeoAuthenticationPluginLogoutExtension) authPlugin; 874 } 875 876 boolean redirected = false; 877 if (logoutPlugin != null) { 878 redirected = Boolean.TRUE.equals( 879 logoutPlugin.handleLogout((HttpServletRequest) request, (HttpServletResponse) response)); 880 } 881 882 // invalidate Session ! 883 service.invalidateSession(request); 884 885 HttpServletRequest httpRequest = (HttpServletRequest) request; 886 if (!redirected && !XMLHTTP_REQUEST_TYPE.equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) { 887 String baseURL = service.getBaseURL(request); 888 try { 889 String url = baseURL + LoginScreenHelper.getStartupPagePath(); 890 url = URIUtils.addParametersToURIQuery(url, parameters); 891 ((HttpServletResponse) response).sendRedirect(url); 892 redirected = true; 893 } catch (IOException e) { 894 log.error("Unable to redirect to default start page after logout : " + e.getMessage()); 895 } 896 } 897 898 try { 899 cachedUserInfo.getLoginContext().logout(); 900 } catch (LoginException e) { 901 log.error("Unable to logout " + e.getMessage()); 902 } 903 return redirected; 904 } 905 906 // App Server JAAS SPI 907 protected void propagateUserIdentificationInformation(CachableUserIdentificationInfo cachableUserIdent) { 908 service.propagateUserIdentificationInformation(cachableUserIdent); 909 } 910 911 // Plugin API 912 protected void initUnAuthenticatedURLPrefix() { 913 // gather unAuthenticated URLs 914 unAuthenticatedURLPrefix = new ArrayList<>(); 915 for (String pluginName : service.getAuthChain()) { 916 NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName); 917 List<String> prefix = plugin.getUnAuthenticatedURLPrefix(); 918 if (prefix != null && !prefix.isEmpty()) { 919 unAuthenticatedURLPrefix.addAll(prefix); 920 } 921 } 922 } 923 924 protected boolean bypassAuth(HttpServletRequest httpRequest) { 925 if (unAuthenticatedURLPrefix == null) { 926 try { 927 unAuthenticatedURLPrefixLock.writeLock().lock(); 928 // late init to allow plugins registered after this filter init 929 initUnAuthenticatedURLPrefix(); 930 } finally { 931 unAuthenticatedURLPrefixLock.writeLock().unlock(); 932 } 933 } 934 935 try { 936 unAuthenticatedURLPrefixLock.readLock().lock(); 937 String requestPage = getRequestedPage(httpRequest); 938 for (String prefix : unAuthenticatedURLPrefix) { 939 if (requestPage.startsWith(prefix)) { 940 return true; 941 } 942 } 943 } finally { 944 unAuthenticatedURLPrefixLock.readLock().unlock(); 945 } 946 947 List<OpenUrlDescriptor> openUrls = service.getOpenUrls(); 948 for (OpenUrlDescriptor openUrl : openUrls) { 949 if (openUrl.allowByPassAuth(httpRequest)) { 950 return true; 951 } 952 } 953 954 return false; 955 } 956 957 public static String getRequestedPage(ServletRequest request) { 958 if (request instanceof HttpServletRequest) { 959 HttpServletRequest httpRequest = (HttpServletRequest) request; 960 return getRequestedPage(httpRequest); 961 } else { 962 return null; 963 } 964 } 965 966 protected static String getRequestedPage(HttpServletRequest httpRequest) { 967 String requestURI = httpRequest.getRequestURI(); 968 String context = httpRequest.getContextPath() + '/'; 969 String requestedPage = requestURI.substring(context.length()); 970 int i = requestedPage.indexOf(';'); 971 return i == -1 ? requestedPage : requestedPage.substring(0, i); 972 } 973 974 protected boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { 975 976 // A specific auth chain may prevent the filter to relay to a login prompt. 977 if (!service.doHandlePrompt(httpRequest)) { 978 buildUnauthorizedResponse(httpRequest, httpResponse); 979 return true; 980 } 981 982 return handleLogin(httpRequest, httpResponse); 983 984 } 985 986 private boolean handleLogin(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { 987 String baseURL = service.getBaseURL(httpRequest); 988 989 // go through plugins to get UserIndentity 990 for (String pluginName : service.getAuthChain(httpRequest)) { 991 NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName); 992 AuthenticationPluginDescriptor descriptor = service.getDescriptor(pluginName); 993 994 if (Boolean.TRUE.equals(plugin.needLoginPrompt(httpRequest))) { 995 if (descriptor.getNeedStartingURLSaving()) { 996 saveRequestedURLBeforeRedirect(httpRequest, httpResponse); 997 } 998 return Boolean.TRUE.equals(plugin.handleLoginPrompt(httpRequest, httpResponse, baseURL)); 999 } 1000 } 1001 1002 log.warn("No auth plugin can be found to do the Login Prompt"); 1003 return false; 1004 } 1005 1006 private void buildUnauthorizedResponse(HttpServletRequest req, HttpServletResponse resp) { 1007 1008 try { 1009 String loginUrl = VirtualHostHelper.getBaseURL(req) + LOGIN_PAGE; 1010 resp.addHeader("Location", loginUrl); 1011 resp.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); 1012 resp.getWriter().write("Please log in at: " + loginUrl); 1013 } catch (IOException e) { 1014 log.error("Unable to write login page on unauthorized response", e); 1015 } 1016 } 1017 1018 protected UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest, 1019 HttpServletResponse httpResponse) { 1020 1021 UserIdentificationInfo userIdent = null; 1022 1023 // go through plugins to get UserIdentity 1024 for (String pluginName : service.getAuthChain(httpRequest)) { 1025 NuxeoAuthenticationPlugin plugin = service.getPlugin(pluginName); 1026 if (plugin != null) { 1027 log.debug("Trying to retrieve userIdentification using plugin " + pluginName); 1028 userIdent = plugin.handleRetrieveIdentity(httpRequest, httpResponse); 1029 if (userIdent != null && userIdent.containsValidIdentity()) { 1030 // fill information for the Login module 1031 userIdent.setAuthPluginName(pluginName); 1032 1033 // get the target login module 1034 String loginModulePlugin = service.getDescriptor(pluginName).getLoginModulePlugin(); 1035 userIdent.setLoginPluginName(loginModulePlugin); 1036 1037 // get the additional parameters 1038 Map<String, String> parameters = service.getDescriptor(pluginName).getParameters(); 1039 if (userIdent.getLoginParameters() != null) { 1040 // keep existing parameters set by the auth plugin 1041 if (parameters == null) { 1042 parameters = new HashMap<>(); 1043 } 1044 parameters.putAll(userIdent.getLoginParameters()); 1045 } 1046 userIdent.setLoginParameters(parameters); 1047 1048 break; 1049 } 1050 } else { 1051 log.error("Auth plugin " + pluginName + " can not be retrieved from service"); 1052 } 1053 } 1054 1055 // Fall back to cache (used only when avoidReautenticated=false) 1056 if (userIdent == null || !userIdent.containsValidIdentity()) { 1057 log.debug("user/password not found in request, try into identity cache"); 1058 HttpSession session = httpRequest.getSession(false); 1059 if (session == null) { 1060 // possible we need a new session 1061 if (httpRequest.isRequestedSessionIdValid()) { 1062 session = httpRequest.getSession(true); 1063 } 1064 } 1065 if (session != null) { 1066 CachableUserIdentificationInfo savedUserInfo = retrieveIdentityFromCache(httpRequest); 1067 if (savedUserInfo != null) { 1068 log.debug("Found User identity in cache :" + savedUserInfo.getUserInfo().getUserName()); 1069 userIdent = new UserIdentificationInfo(savedUserInfo.getUserInfo()); 1070 savedUserInfo.setPrincipal(null); 1071 } 1072 } 1073 } else { 1074 log.debug("User/Password found as parameter of the request"); 1075 } 1076 1077 return userIdent; 1078 } 1079 1080 protected boolean needSessionSaving(UserIdentificationInfo userInfo) { 1081 String pluginName = userInfo.getAuthPluginName(); 1082 1083 AuthenticationPluginDescriptor desc = service.getDescriptor(pluginName); 1084 1085 if (desc.getStateful()) { 1086 return true; 1087 } else { 1088 return desc.getNeedStartingURLSaving(); 1089 } 1090 } 1091 1092 /** 1093 * Does a forced login as the given user. Bypasses all authentication checks. 1094 * 1095 * @param username the user name 1096 * @return the login context, which MUST be used for logout in a {@code finally} block 1097 * @throws LoginException 1098 */ 1099 public static LoginContext loginAs(String username) throws LoginException { 1100 UserIdentificationInfo userIdent = new UserIdentificationInfo(username, ""); 1101 userIdent.setLoginPluginName(TrustingLoginPlugin.NAME); 1102 PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime() 1103 .getComponent( 1104 PluggableAuthenticationService.NAME); 1105 CallbackHandler callbackHandler; 1106 if (authService != null) { 1107 callbackHandler = authService.getCallbackHandler(userIdent); 1108 } else { 1109 callbackHandler = new UserIdentificationInfoCallbackHandler(userIdent); 1110 } 1111 LoginContext loginContext = new LoginContext(LOGIN_DOMAIN, callbackHandler); 1112 1113 if (isLoginSynchronized()) { 1114 synchronized (NuxeoAuthenticationFilter.class) { 1115 loginContext.login(); 1116 } 1117 } else { 1118 loginContext.login(); 1119 } 1120 return loginContext; 1121 } 1122 1123}