001/* 002 * Copyright 2017 Anyware Services 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 */ 016package org.ametys.core.authentication; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Enumeration; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.Optional; 029import java.util.Set; 030import java.util.regex.Pattern; 031import java.util.stream.Collectors; 032 033import org.apache.avalon.framework.activity.Initializable; 034import org.apache.avalon.framework.parameters.Parameters; 035import org.apache.avalon.framework.thread.ThreadSafe; 036import org.apache.cocoon.ProcessingException; 037import org.apache.cocoon.acting.ServiceableAction; 038import org.apache.cocoon.environment.ObjectModelHelper; 039import org.apache.cocoon.environment.Redirector; 040import org.apache.cocoon.environment.Request; 041import org.apache.cocoon.environment.Session; 042import org.apache.cocoon.environment.SourceResolver; 043import org.apache.commons.lang3.StringUtils; 044import org.apache.commons.lang3.Strings; 045 046import org.ametys.core.ObservationConstants; 047import org.ametys.core.authentication.token.AuthenticationTokenManager; 048import org.ametys.core.observation.Event; 049import org.ametys.core.observation.ObservationManager; 050import org.ametys.core.trace.ForensicLogger; 051import org.ametys.core.user.CurrentUserProvider; 052import org.ametys.core.user.User; 053import org.ametys.core.user.UserIdentity; 054import org.ametys.core.user.UserManager; 055import org.ametys.core.user.directory.ModifiableUserDirectory; 056import org.ametys.core.user.directory.WeakPasswordException; 057import org.ametys.core.user.population.PopulationContextHelper; 058import org.ametys.core.user.population.UserPopulation; 059import org.ametys.core.user.population.UserPopulationDAO; 060import org.ametys.core.user.status.UserStatusManager; 061import org.ametys.core.util.URIUtils; 062import org.ametys.plugins.core.impl.authentication.FormCredentialProvider; 063import org.ametys.plugins.core.user.UserDAO; 064import org.ametys.plugins.core.user.management.UserPasswordManager; 065import org.ametys.runtime.authentication.AccessDeniedException; 066import org.ametys.runtime.authentication.AuthorizationRequiredException; 067import org.ametys.runtime.maintenance.MaintenanceAction; 068import org.ametys.runtime.servlet.RuntimeServlet; 069import org.ametys.runtime.servlet.RuntimeServlet.RunMode; 070import org.ametys.runtime.workspace.WorkspaceMatcher; 071 072/** 073 * Cocoon action to perform authentication.<br> 074 * The {@link CredentialProvider} define the authentication method and retrieves {@link Credentials}.<br> 075 * Finally, the Users instance extract the Principal corresponding to the {@link Credentials}. 076 */ 077public class AuthenticateAction extends ServiceableAction implements ThreadSafe, Initializable 078{ 079 /** The request attribute to allow internal action from an internal request. */ 080 public static final String REQUEST_ATTRIBUTE_INTERNAL_ALLOWED = "Runtime:InternalAllowedRequest"; 081 082 /** The request attribute meaning that the request was not authenticated but granted */ 083 public static final String REQUEST_ATTRIBUTE_GRANTED = "Runtime:GrantedRequest"; 084 /** The request attribute name for transmitting the list of user populations */ 085 public static final String REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST = "Runtime:UserPopulationsList"; 086 /** The request attribute name for transmitting the currently chosen user population */ 087 public static final String REQUEST_ATTRIBUTE_USER_POPULATION_ID = "Runtime:CurrentUserPopulationId"; 088 /** The request attribute name for transmitting the login page url */ 089 public static final String REQUEST_ATTRIBUTE_LOGIN_URL = "Runtime:RequestLoginURL"; 090 091 /** The session attribute name for storing the identity of the connected user */ 092 public static final String SESSION_USERIDENTITY = "Runtime:UserIdentity"; 093 094 /** Name of the user population HTML field */ 095 public static final String REQUEST_PARAMETER_POPULATION_NAME = "UserPopulation"; 096 /** Name of the credential provider index HTML field */ 097 public static final String REQUEST_PARAMETER_CREDENTIALPROVIDER_INDEX = "CredentialProviderIndex"; 098 /** Name of a parameter to change non blocking CP behavior */ 099 public static final String REQUEST_PARAMETER_NONBLOCING = "NonBlocking"; 100 101 /** The request attribute name for indicating that the authentication process has been made. */ 102 public static final String REQUEST_ATTRIBUTE_AUTHENTICATED = "Runtime:RequestAuthenticated"; 103 104 /** The request parameter holding the token */ 105 public static final String REQUEST_PARAMETER_TOKEN = "token"; 106 /** The header parameter that can be set to handle the token */ 107 public static final String HEADER_TOKEN = "X-Ametys-Token"; 108 109 /** The sitemap parameter holding the token */ 110 protected static final String PARAMETERS_PARAMETER_TOKEN = "token"; 111 /** The sitemap parameter holding the token context */ 112 protected static final String PARAMETERS_PARAMETER_TOKEN_CONTEXT = "tokenContext"; 113 /** The request attribute name for transmitting a boolean that tell if there is a list of credential provider to choose */ 114 protected static final String REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_LIST = "Runtime:RequestListCredentialProvider"; 115 /** The request attribute name for transmitting the index in the list of chosen credential provider */ 116 protected static final String REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_INDEX = "Runtime:RequestCredentialProviderIndex"; 117 /** The request attribute name to know if user population list should be proposed */ 118 protected static final String REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST = "Runtime:UserPopulationsListDisplay"; 119 /** The request attribute name for transmitting the potential list of user populations to the login screen . */ 120 protected static final String REQUEST_ATTRIBUTE_INVALID_POPULATION = "Runtime:RequestInvalidPopulation"; 121 /** The request attribute name for transmitting the list of contexts */ 122 protected static final String REQUEST_ATTRIBUTE_CONTEXTS = "Runtime:Contexts"; 123 124 /** The session attribute name for storing the credential provider index of the authentication (during connection process) */ 125 protected static final String SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX = "Runtime:ConnectingCredentialProviderIndex"; 126 /** The session attribute name for storing the last known credential provider index of the authentication (during connection process)*/ 127 protected static final String SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN = "Runtime:ConnectingCredentialProviderIndexLastKnown"; 128 /** The session attribute name for storing the credential provider mode of the authentication: non-blocking=>false, blocking=>true (during connection process) */ 129 protected static final String SESSION_CONNECTING_CREDENTIALPROVIDER_MODE = "Runtime:ConnectingCredentialProviderMode"; 130 /** The session attribute name for storing the id of the user population (during connection process) */ 131 protected static final String SESSION_CONNECTING_USERPOPULATION_ID = "Runtime:ConnectingUserPopulationId"; 132 133 /** The session attribute name for storing the credential provider of the authentication */ 134 protected static final String SESSION_CREDENTIALPROVIDER = "Runtime:CredentialProvider"; 135 /** The session attribute name for storing the credential provider mode of the authentication: non-blocking=>false, blocking=>true */ 136 protected static final String SESSION_CREDENTIALPROVIDER_MODE = "Runtime:CredentialProviderMode"; 137 138 /** The sitemap parameter to set the token mode of the action */ 139 protected static final String SITEMAP_PARAMETER_TOKEN_MODE = "token-mode"; 140 141 /** The DAO for user populations */ 142 protected UserPopulationDAO _userPopulationDAO; 143 /** The user manager */ 144 protected UserManager _userManager; 145 /** The helper for the associations population/context */ 146 protected PopulationContextHelper _populationContextHelper; 147 /** The current user provider */ 148 protected CurrentUserProvider _currentUserProvider; 149 150 /** url requires for authentication */ 151 protected Collection<Pattern> _acceptedUrlPatterns = Arrays.asList(new Pattern[]{Pattern.compile("^plugins/core/authenticate/[0-9]+$"), Pattern.compile("^plugins/core/reset-password.html$")}); 152 153 /** The authentication token manager */ 154 protected AuthenticationTokenManager _authenticateTokenManager; 155 /** The observation manager */ 156 protected ObservationManager _observationManager; 157 /** The user account manager */ 158 protected UserPasswordManager _userPasswordManager; 159 /** The user status manager */ 160 protected UserStatusManager _userStatusManager; 161 162 /** 163 * The token mode of this authentication action 164 */ 165 protected enum TOKEN_MODE 166 { 167 /** In this mode, only the token will be taken in account. If no token is found, authentication will not be considered done */ 168 TOKEN_ONLY, 169 /** In this mode, the token will be taken in account but if no token is found, user will be considered as anonymous and authentication will be considered done */ 170 ALLOW_ANONYMOUS, 171 /** In this default mode, the token will be taken in account, but if no token is found, the authentication process will continue */ 172 DEFAULT 173 } 174 175 @Override 176 public void initialize() throws Exception 177 { 178 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 179 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 180 _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE); 181 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 182 183 // These component are not safe but the action is 184 if (manager.hasService(UserPasswordManager.ROLE)) 185 { 186 _userPasswordManager = (UserPasswordManager) manager.lookup(UserPasswordManager.ROLE); 187 _userStatusManager = (UserStatusManager) manager.lookup(UserStatusManager.ROLE); 188 _authenticateTokenManager = (AuthenticationTokenManager) manager.lookup(AuthenticationTokenManager.ROLE); 189 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 190 } 191 } 192 193 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 194 { 195 Request request = ObjectModelHelper.getRequest(objectModel); 196 197 if (_preFlightCheck(redirector, resolver, objectModel, source, parameters) || _handleAuthenticationToken(request, parameters)) 198 { 199 // We passed the authentication, let's mark it now 200 request.setAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED, "true"); 201 202 // We passed the authentication (with a user) 203 return EMPTY_MAP; 204 } 205 206 // At this point, the user is still anonymous 207 208 // If only token are authorized for authentication, stop authentication process. There is no user authenticated here. 209 if (_getTokenMode(parameters) != TOKEN_MODE.DEFAULT) 210 { 211 if (_getTokenMode(parameters) == TOKEN_MODE.ALLOW_ANONYMOUS) 212 { 213 request.setAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED, "true"); 214 } 215 return null; 216 } 217 218 // At this point, we already know that the entire process will be executed, whatever the outcome 219 // Set the flag, so that the authentication process won't repeat 220 request.setAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED, "true"); 221 222 // Get population and if possible credential providers 223 List<UserPopulation> chosenUserPopulations = new ArrayList<>(); 224 List<CredentialProvider> credentialProviders = new ArrayList<>(); 225 if (!_prepareUserPopulationsAndCredentialProviders(request, parameters, redirector, chosenUserPopulations, credentialProviders)) 226 { 227 // Let's display the population screen 228 return EMPTY_MAP; 229 } 230 231 // Get the currently running credential provider 232 int runningCredentialProviderIndex = _getCurrentCredentialProviderIndex(request, credentialProviders); 233 request.setAttribute(REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_INDEX, runningCredentialProviderIndex); 234 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_URL, getLoginURL(request)); 235 236 // Let's process non-blocking 237 if (!_isCurrentCredentialProviderInBlockingMode(request)) 238 { 239 // if there was no one running, let's start with the first one 240 runningCredentialProviderIndex = Math.max(0, runningCredentialProviderIndex); 241 242 for (; runningCredentialProviderIndex < credentialProviders.size(); runningCredentialProviderIndex++) 243 { 244 CredentialProvider runningCredentialProvider = credentialProviders.get(runningCredentialProviderIndex); 245 if (_process(request, false, runningCredentialProvider, runningCredentialProviderIndex, redirector, chosenUserPopulations)) 246 { 247 // Whatever the user was correctly authenticated or he just required a redirect: let's stop here for the moment 248 return EMPTY_MAP; 249 } 250 } 251 252 // No one matches 253 runningCredentialProviderIndex = -1; 254 } 255 256 _saveLastKnownBlockingCredentialProvider(request, runningCredentialProviderIndex); 257 258 // Let's process the current blocking one or the only existing one 259 if (_shouldRunFirstBlockingCredentialProvider(runningCredentialProviderIndex, credentialProviders, request, chosenUserPopulations)) 260 { 261 CredentialProvider runningCredentialProvider = runningCredentialProviderIndex == -1 ? _getFirstBlockingCredentialProvider(credentialProviders) : credentialProviders.get(runningCredentialProviderIndex); 262 if (_process(request, true, runningCredentialProvider, runningCredentialProviderIndex, redirector, chosenUserPopulations)) 263 { 264 // Whatever the user was correctly authenticated or he just required a redirect: let's stop here for the moment 265 return EMPTY_MAP; 266 } 267 268 throw new AuthorizationRequiredException(); 269 } 270 271 // At this step we have two kind off requests 272 // 1) A secondary request of a blocking cp (such as captcha image...) 273 Integer formerRunningCredentialProviderIndex = (Integer) request.getSession(true).getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN); 274 if (formerRunningCredentialProviderIndex != null && credentialProviders.get(formerRunningCredentialProviderIndex).grantAnonymousRequest(true)) 275 { 276 // Anonymous request 277 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 278 _saveConnectingStateToSession(request, -1, true); 279 return EMPTY_MAP; 280 } 281 282 // 2) Or a main stream request that should display the list of available blocking cp 283 return _displayBlockingList(redirector, request, credentialProviders); 284 } 285 286 /** 287 * Prepare authentication 288 * @param redirector The redirector 289 * @param resolver The source resolver 290 * @param objectModel The object model 291 * @param source The source 292 * @param parameters The action parameters 293 * @return <code>true</code> if a user was authenticated, <code>false</code> otherwise 294 * @throws Exception if failed to prepare the authentication 295 */ 296 protected boolean _preFlightCheck(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 297 { 298 Request request = ObjectModelHelper.getRequest(objectModel); 299 300 return _handleLogout(redirector, objectModel, source, parameters) // Test if user wants to logout 301 || _internalRequest(request) // Test if this request was already authenticated or it the request is marked as an internal one 302 || _acceptedUrl(request) // Test if the url is used for authentication 303 || _validateCurrentlyConnectedUser(request, redirector, parameters) // Test if the currently connected user is still valid 304 || redirector.hasRedirected(); 305 } 306 307 /** 308 * Authenticate a user using the token in request (if configured so) 309 * @param request The request 310 * @param parameters The action parameters 311 * @return true if the user was authenticated 312 */ 313 protected boolean _handleAuthenticationToken(Request request, Parameters parameters) 314 { 315 String token = request.getHeader(HEADER_TOKEN); 316 if (StringUtils.isBlank(token)) 317 { 318 token = parameters.getParameter(PARAMETERS_PARAMETER_TOKEN, _getTokenFromRequest(request)); 319 } 320 321 if (StringUtils.isNotBlank(token)) 322 { 323 String context = parameters.getParameter(PARAMETERS_PARAMETER_TOKEN_CONTEXT, "application"); 324 UserIdentity userIdentity = _validateToken(token, context); 325 if (userIdentity != null) 326 { 327 // Save user identity 328 _setUserIdentityInSession(request, userIdentity, new UserDAO.ImpersonateCredentialProvider(), true); 329 _validateCurrentlyConnectedUserIsInAuthorizedPopulation(userIdentity, request, parameters); 330 331 if (_userStatusManager != null) 332 { 333 _userStatusManager.updateConnectionDate(userIdentity); 334 } 335 336 Map<String, Object> tokenArgs = Map.of("user", userIdentity); 337 ForensicLogger.info("authentication.token", tokenArgs, userIdentity); 338 339 return true; 340 } 341 } 342 343 return false; 344 } 345 346 /** 347 * Get the token from the request 348 * @param request The request 349 * @return The token from the request or null 350 */ 351 protected String _getTokenFromRequest(Request request) 352 { 353 // FIXME RUNTIME-2501 check the parameter is provided in POST, e.g. by seeking if '?token=' and '&token=' are not used in request uri... 354 return request.getParameter(REQUEST_PARAMETER_TOKEN); 355 } 356 357 /** 358 * Validate the given token 359 * @param token The non empty token to validate 360 * @param context the context on which the token should be validated 361 * @return The corresponding user identity or null 362 */ 363 protected UserIdentity _validateToken(String token, String context) 364 { 365 return _authenticateTokenManager != null ? _authenticateTokenManager.validateToken(token, context) : null; 366 } 367 368 private TOKEN_MODE _getTokenMode(Parameters parameters) 369 { 370 return TOKEN_MODE.valueOf(parameters.getParameter(SITEMAP_PARAMETER_TOKEN_MODE, TOKEN_MODE.DEFAULT.toString()).toUpperCase()); 371 } 372 373 private void _saveLastKnownBlockingCredentialProvider(Request request, int runningCredentialProviderIndex) 374 { 375 if (runningCredentialProviderIndex != -1) 376 { 377 request.getSession(true).setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN, runningCredentialProviderIndex); 378 } 379 } 380 381 private Map _displayBlockingList(Redirector redirector, Request request, List<CredentialProvider> credentialProviders) throws IOException, ProcessingException, AuthorizationRequiredException 382 { 383 if (credentialProviders.stream().filter(cp -> cp instanceof BlockingCredentialProvider).findFirst().isPresent()) 384 { 385 _saveConnectingStateToSession(request, -1, true); 386 redirector.redirect(false, getLoginURL(request)); 387 return EMPTY_MAP; 388 } 389 else 390 { 391 // No way to login 392 throw new AuthorizationRequiredException(); 393 } 394 } 395 396 @SuppressWarnings("unchecked") 397 private boolean _shouldRunFirstBlockingCredentialProvider(int runningCredentialProviderIndex, List<CredentialProvider> credentialProviders, Request request, List<UserPopulation> chosenUserPopulations) 398 { 399 return runningCredentialProviderIndex >= 0 // There is a running credential provider 400 || credentialProviders.stream().filter(cp -> cp instanceof BlockingCredentialProvider).count() == 1 // There is a single blocking credential provider AND 401 && ( 402 ((List<UserPopulation>) request.getAttribute(REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST)).size() == chosenUserPopulations.size() // no population choice screen 403 || _getFirstBlockingCredentialProvider(credentialProviders).requiresNewWindow() // it does not requires a window opening 404 ); 405 } 406 407 private BlockingCredentialProvider _getFirstBlockingCredentialProvider(List<CredentialProvider> credentialProviders) 408 { 409 Optional<CredentialProvider> findFirst = credentialProviders.stream().filter(cp -> cp instanceof BlockingCredentialProvider).findFirst(); 410 if (findFirst.isPresent()) 411 { 412 return (BlockingCredentialProvider) findFirst.get(); 413 } 414 else 415 { 416 return null; 417 } 418 } 419 420 /** 421 * Fill the list of available users populations and credential providers 422 * @param request The request 423 * @param parameters The action parameters 424 * @param redirector The cocoon redirector 425 * @param chosenUserPopulations An empty non-null list to fill with with chosen populations 426 * @param credentialProviders An empty non-null list to fill with chosen credential providers 427 * @return true, if the population was determined, false if a redirection was required to choose 428 * @throws IOException If an error occurred 429 * @throws ProcessingException If an error occurred 430 */ 431 protected boolean _prepareUserPopulationsAndCredentialProviders(Request request, Parameters parameters, Redirector redirector, List<UserPopulation> chosenUserPopulations, List<CredentialProvider> credentialProviders) throws ProcessingException, IOException 432 { 433 // Get contexts 434 List<String> contexts = _getContexts(request, parameters); 435 request.setAttribute(REQUEST_ATTRIBUTE_CONTEXTS, contexts); 436 437 // All user populations for this context 438 List<UserPopulation> availableUserPopulations = _getAvailableUserPopulationsIds(request, contexts).stream().map(_userPopulationDAO::getUserPopulation).collect(Collectors.toList()); 439 request.setAttribute(REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST, availableUserPopulations); 440 441 // Chosen population 442 String userPopulationId = _getChosenUserPopulationId(request, availableUserPopulations); 443 request.setAttribute(REQUEST_ATTRIBUTE_USER_POPULATION_ID, userPopulationId); 444 445 chosenUserPopulations.addAll(userPopulationId == null ? availableUserPopulations : Collections.singletonList(_userPopulationDAO.getUserPopulation(userPopulationId))); 446 if (chosenUserPopulations.size() == 0) 447 { 448 String redirection = parameters.getParameter("nocontext-redirection", null); 449 if (redirection == null) 450 { 451 throw new IllegalStateException("There is no populations available for contexts '" + StringUtils.join(contexts, "', '") + "'"); 452 } 453 else 454 { 455 redirector.redirect(false, redirection); 456 return false; 457 } 458 } 459 460 // Get possible credential providers 461 boolean availableCredentialProviders = _hasCredentialProviders(chosenUserPopulations); 462 request.setAttribute(REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_LIST, availableCredentialProviders); 463 464 // null means the credential providers cannot be determine without knowing population first 465 if (!availableCredentialProviders) 466 { 467 request.setAttribute(REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST, true); 468 469 // if we are on this screen after a 'back' button hit, we need to reset connecting information 470 _resetConnectingStateToSession(request); 471 472 // Screen "Where Are You From?" with the list of populations to select 473 if (redirector != null) 474 { 475 redirector.redirect(false, getLoginURL(request)); 476 } 477 return false; 478 } 479 else 480 { 481 credentialProviders.addAll(chosenUserPopulations.get(0).getCredentialProviders()); 482 if (credentialProviders.size() == 0) 483 { 484 throw new IllegalStateException("There is no populations credential provider available for contexts '" + StringUtils.join(contexts, "', '") + "'"); 485 } 486 request.setAttribute(REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST, userPopulationId == null || _hasCredentialProviders(availableUserPopulations) || credentialProviders.size() == 1 && !credentialProviders.stream().filter(cp -> cp instanceof FormCredentialProvider).findAny().isPresent()); 487 return true; 488 } 489 } 490 491 /** 492 * Get the url for the redirector to display the login screen 493 * @param request The request 494 * @return The url. Cannot be null or empty 495 */ 496 protected String getLoginURL(Request request) 497 { 498 return getLoginURLParameters(request, "cocoon://_plugins/core/login.html"); 499 } 500 501 502 /** 503 * Get the url for the redirector to display the login screen 504 * @param request The request 505 * @param baseURL The url to complete with parameters 506 * @return The url. Cannot be null or empty 507 */ 508 @SuppressWarnings("unchecked") 509 protected String getLoginURLParameters(Request request, String baseURL) 510 { 511 List<String> parameters = new ArrayList<>(); 512 513 Boolean invalidPopulationIds = (Boolean) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INVALID_POPULATION); 514 parameters.add("invalidPopulationIds=" + (invalidPopulationIds == Boolean.TRUE ? "true" : "false")); 515 516 boolean shouldDisplayUserPopulationsList = (boolean) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST); 517 parameters.add("shouldDisplayUserPopulationsList=" + (shouldDisplayUserPopulationsList ? "true" : "false")); 518 519 List<UserPopulation> usersPopulations = (List<UserPopulation>) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST); 520 if (usersPopulations != null) 521 { 522 parameters.add("usersPopulations=" + URIUtils.encodeParameter(usersPopulations.stream().map(UserPopulation::getId).collect(Collectors.joining(",")))); 523 } 524 525 String chosenPopulationId = (String) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_USER_POPULATION_ID); 526 if (chosenPopulationId != null) 527 { 528 parameters.add("chosenPopulationId=" + URIUtils.encodeParameter(chosenPopulationId)); 529 } 530 531 boolean availableCredentialProviders = (boolean) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_LIST); 532 parameters.add("availableCredentialProviders=" + (availableCredentialProviders ? "true" : "false")); 533 534 Integer credentialProviderIndex = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_INDEX); 535 parameters.add("credentialProviderIndex=" + String.valueOf(credentialProviderIndex != null ? credentialProviderIndex : -1)); 536 537 List<String> contexts = (List<String>) request.getAttribute(REQUEST_ATTRIBUTE_CONTEXTS); 538 parameters.add("contexts=" + URIUtils.encodeParameter(StringUtils.join(contexts, ","))); 539 540 return baseURL + (baseURL.contains("?") ? "&" : "?") + StringUtils.join(parameters, "&"); 541 } 542 543 /** 544 * Get the url for the redirector to display the logout screen 545 * @param request The request 546 * @return The url. Cannot be null or empty 547 */ 548 protected String getLogoutURL(Request request) 549 { 550 return "cocoon://_plugins/core/logout.html"; 551 } 552 553 /** 554 * Determine if there is a list of credential providers to use 555 * @param userPopulations The list of applicable user populations 556 * @return true if credentialproviders can be used 557 */ 558 protected boolean _hasCredentialProviders(List<UserPopulation> userPopulations) 559 { 560 // Is there only one population or all populations have the same credential provider list as the first one? 561 if (userPopulations.size() == 1 562 || userPopulations.stream().map(UserPopulation::getCredentialProviders).distinct().count() == 1 563 && userPopulations.stream().map(this::_needsResetLinkOnFormCredential).distinct().count() == 1) 564 { 565 return true; 566 } 567 568 // Cannot determine the list 569 return false; 570 } 571 572 /** 573 * Check if the user population include a {@link FormCredentialProvider} 574 * that requires display of a reset password link 575 * @param userPopulation the user population 576 * @return true if a reset password link needs to be displayed 577 */ 578 private boolean _needsResetLinkOnFormCredential(UserPopulation userPopulation) 579 { 580 if (userPopulation.getCredentialProviders().stream() 581 .filter(FormCredentialProvider.class::isInstance) 582 .map(FormCredentialProvider.class::cast) 583 .map(FormCredentialProvider::displayResetLink) 584 .findAny().orElse(false)) 585 { 586 return userPopulation.getUserDirectories().stream() 587 .anyMatch(ModifiableUserDirectory.class::isInstance); 588 } 589 return false; 590 } 591 592 /** 593 * Get the available populations for the given contexts 594 * @param request The request 595 * @param contexts The contexts 596 * @return The non-null list of populations 597 */ 598 protected Set<String> _getAvailableUserPopulationsIds(Request request, List<String> contexts) 599 { 600 return _populationContextHelper.getUserPopulationsOnContexts(contexts, false, false); 601 } 602 603 /** 604 * Get the population for the given context 605 * @param request The request 606 * @param availableUserPopulations The available users populations 607 * @return The chosen population id. Can be null. 608 */ 609 protected String _getChosenUserPopulationId(Request request, List<UserPopulation> availableUserPopulations) 610 { 611 // Get request population choice 612 String userPopulationId = request.getParameter(REQUEST_PARAMETER_POPULATION_NAME); 613 if (userPopulationId == null) 614 { 615 // Get memorized population choice 616 Session session = request.getSession(false); 617 if (session != null) 618 { 619 userPopulationId = (String) session.getAttribute(SESSION_CONNECTING_USERPOPULATION_ID); 620 } 621 } 622 623 // A population choice was already made 624 if (StringUtils.isNotBlank(userPopulationId)) 625 { 626 final String finalUserPopulationId = userPopulationId; 627 if (availableUserPopulations.stream().anyMatch(userPopulation -> userPopulation.getId().equals(finalUserPopulationId))) 628 { 629 return userPopulationId; 630 } 631 else 632 { 633 // Wrong submitted population id 634 request.setAttribute(REQUEST_ATTRIBUTE_INVALID_POPULATION, true); 635 } 636 } 637 638 return null; 639 } 640 641 /** 642 * Try to authenticate with this credential provider in this mode. Delegates to _doProcess 643 * @param request The request 644 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 645 * @param runningCredentialProvider the Credential provider to test 646 * @param runningCredentialProviderIndex The index of the currently tested credential provider 647 * @param redirector The cocoon redirector 648 * @param userPopulations The list of possible user populations 649 * @return false if we should try with another Credential provider, true otherwise 650 * @throws Exception If an error occurred 651 */ 652 protected boolean _process(Request request, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider, int runningCredentialProviderIndex, Redirector redirector, List<UserPopulation> userPopulations) throws Exception 653 { 654 boolean existingSession = request.getSession(false) != null; 655 _saveConnectingStateToSession(request, runningBlockingkMode ? -1 : runningCredentialProviderIndex, runningBlockingkMode); 656 if (_doProcess(request, runningBlockingkMode, runningCredentialProvider, redirector, userPopulations)) 657 { 658 return true; 659 } 660 if (existingSession) 661 { 662 // A session was created but finally we do not need it 663 request.getSession().invalidate(); 664 } 665 return false; 666 } 667 668 /** 669 * Try to authenticate with this credential provider in this mode 670 * @param request The request 671 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 672 * @param runningCredentialProvider the Credential provider to test 673 * @param redirector The cocoon redirector 674 * @param userPopulations The list of possible user populations 675 * @return false if we should try with another Credential provider, true otherwise 676 * @throws Exception If an error occurred 677 */ 678 679 protected boolean _doProcess(Request request, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider, Redirector redirector, List<UserPopulation> userPopulations) throws Exception 680 { 681 if (runningCredentialProvider.grantAnonymousRequest(runningBlockingkMode)) 682 { 683 // Anonymous request 684 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 685 return true; 686 } 687 688 UserIdentity potentialUserIdentity = null; 689 try 690 { 691 692 potentialUserIdentity = runningCredentialProvider.getUserIdentity(runningBlockingkMode, redirector); 693 } 694 catch (WeakPasswordException e) 695 { 696 // User credentials are ok but user use a weak password 697 potentialUserIdentity = e.getUserIdentity(); 698 _handleWeakPassword(request, runningCredentialProvider, redirector, potentialUserIdentity); 699 } 700 701 if (redirector.hasRedirected()) 702 { 703 // getCredentials require a redirection, save state and proceed 704 return true; 705 } 706 else if (potentialUserIdentity == null) 707 { 708 // Let us try another credential provider 709 return false; 710 } 711 712 // Check if user exists 713 UserIdentity userIdentity = _getUserIdentity(userPopulations, potentialUserIdentity, redirector, runningBlockingkMode, runningCredentialProvider); 714 if (redirector.hasRedirected()) 715 { 716 // getCredentials require a redirection, save state and proceed 717 return true; 718 } 719 else if (userIdentity == null) 720 { 721 // Let us try another credential provider 722 return false; 723 } 724 725 // Save user identity 726 _setUserIdentityInSession(request, userIdentity, runningCredentialProvider, runningBlockingkMode); 727 728 // Authentication succeeded 729 runningCredentialProvider.userAllowed(runningBlockingkMode, userIdentity, redirector); 730 731 if (_userStatusManager != null) 732 { 733 _userStatusManager.updateConnectionDate(userIdentity); 734 } 735 736 _logLoginEvent(runningCredentialProvider, userIdentity); 737 738 return true; 739 } 740 741 /** 742 * Handle weak password exception 743 * @param request the request 744 * @param runningCredentialProvider the credential provider that detected the weak password 745 * @param redirector the redirector 746 * @param userIdentity the user identity with a weak password 747 * @throws Exception if an error occurred 748 */ 749 protected void _handleWeakPassword(Request request, CredentialProvider runningCredentialProvider, Redirector redirector, UserIdentity userIdentity) throws Exception 750 { 751 ForensicLogger.info("authentication.form.weak.password", Map.of("userIdentity", userIdentity), userIdentity); 752 Optional<String> resetPasswordURI = _getWeakPasswordURI(request, userIdentity); 753 if (resetPasswordURI.isPresent()) 754 { 755 // Force redirect to weak password url to force change password 756 getLogger().info("Password of user " + userIdentity + " does not meet the security requirements. Force to change password."); 757 redirector.redirect(false, resetPasswordURI.get()); 758 return; 759 } 760 // Defaut implementation only log that user has a weak password. User will be authenticated 761 getLogger().warn("Password of user " + userIdentity + " does not meet the security requirements. User is authenticated despite the risk for security."); 762 } 763 764 /** 765 * Get the URI where the user should be redirected after a weak password is detected 766 * @param request the current request 767 * @param userIdentity the user identity with a weak password 768 * @return the absolute uri 769 */ 770 protected Optional<String> _getWeakPasswordURI(Request request, UserIdentity userIdentity) 771 { 772 // do not force password change in safe or maintenance mode 773 if (_userPasswordManager != null && RuntimeServlet.getRunMode() == RunMode.NORMAL) 774 { 775 return _userPasswordManager.getChangePasswordURI(request, userIdentity, true); 776 } 777 return Optional.empty(); 778 } 779 780 /** 781 * Log login event 782 * @param credentialProvider the running credential provider 783 * @param userIdentity the user identity 784 */ 785 protected void _logLoginEvent(CredentialProvider credentialProvider, UserIdentity userIdentity) 786 { 787 Map<String, Object> loginArgs = Map.of("credential-provider", credentialProvider.getCredentialProviderModelId(), "user", userIdentity); 788 ForensicLogger.info("authentication.login", loginArgs, userIdentity); 789 } 790 791 /** 792 * Log logout event 793 * @param userIdentity the user identity 794 */ 795 protected void _logLogoutEvent(UserIdentity userIdentity) 796 { 797 Map<String, Object> logoutArgs = Map.of("user", userIdentity); 798 ForensicLogger.info("authentication.logout", logoutArgs, userIdentity); 799 } 800 801 /** 802 * Reset the connecting information in session 803 * @param request The request 804 */ 805 protected static void _resetConnectingStateToSession(Request request) 806 { 807 Session session = request.getSession(false); 808 if (session != null) 809 { 810 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 811 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 812 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN); 813 session.removeAttribute(SESSION_CONNECTING_USERPOPULATION_ID); 814 } 815 } 816 817 /** 818 * When the process end successfully, save the state 819 * @param request The request 820 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 821 * @param runningCredentialProviderIndex the currently tested credential provider 822 */ 823 protected void _saveConnectingStateToSession(Request request, int runningCredentialProviderIndex, boolean runningBlockingkMode) 824 { 825 Session session = request.getSession(true); 826 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX, runningCredentialProviderIndex); 827 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE, runningBlockingkMode); 828 session.setAttribute(SESSION_CONNECTING_USERPOPULATION_ID, request.getAttribute(REQUEST_ATTRIBUTE_USER_POPULATION_ID)); 829 } 830 831 /** 832 * Save user identity in request 833 * @param request The request 834 * @param userIdentity The useridentity to save 835 * @param credentialProvider The credential provider used to connect 836 * @param blockingMode The mode used for the credential provider 837 */ 838 protected void _setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 839 { 840 setUserIdentityInSession(request, userIdentity, credentialProvider, blockingMode); 841 if (_observationManager != null) 842 { 843 Map<String, Object> eventParams = new HashMap<>(); 844 eventParams.put(ObservationConstants.ARGS_USER, userIdentity); 845 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_AUTHENTICATED, UserPopulationDAO.SYSTEM_USER_IDENTITY, eventParams)); 846 } 847 } 848 849 /** 850 * Save user identity in request 851 * @param request The request 852 * @param userIdentity The useridentity to save 853 * @param credentialProvider The credential provider used to connect 854 * @param blockingMode The mode used for the credential provider 855 */ 856 public static void setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 857 { 858 Session session = renewSession(request); 859 _resetConnectingStateToSession(request); 860 session.setAttribute(SESSION_USERIDENTITY, userIdentity); 861 session.setAttribute(SESSION_CREDENTIALPROVIDER, credentialProvider); 862 session.setAttribute(SESSION_CREDENTIALPROVIDER_MODE, blockingMode); 863 } 864 865 /** 866 * Change the session id (for security purposes) 867 * @param request The current request 868 * @return The new session 869 */ 870 public static Session renewSession(Request request) 871 { 872 Session session = request.getSession(true); 873 874 Map<String, Object> attributesCopy = new HashMap<>(); 875 876 Enumeration<String> attributeNames = session.getAttributeNames(); 877 while (attributeNames.hasMoreElements()) 878 { 879 String attributeName = attributeNames.nextElement(); 880 attributesCopy.put(attributeName, session.getAttribute(attributeName)); 881 } 882 883 session.invalidate(); 884 885 session = request.getSession(true); 886 887 for (Entry<String, Object> attribute : attributesCopy.entrySet()) 888 { 889 session.setAttribute(attribute.getKey(), attribute.getValue()); 890 } 891 892 return session; 893 } 894 895 /** 896 * Get the user identity of the connected user from the session 897 * @param request The request 898 * @return The connected useridentity or null 899 */ 900 protected UserIdentity _getUserIdentityFromSession(Request request) 901 { 902 return getUserIdentityFromSession(request); 903 } 904 905 /** 906 * Get the user identity of the connected user from the session 907 * @param request The request 908 * @return The connected useridentity or null 909 */ 910 public static UserIdentity getUserIdentityFromSession(Request request) 911 { 912 Session session = request.getSession(false); 913 if (session != null) 914 { 915 return (UserIdentity) session.getAttribute(SESSION_USERIDENTITY); 916 } 917 return null; 918 } 919 920 /** 921 * Get the credential provider used for the current connection 922 * @param request The request 923 * @return The credential provider used or null 924 */ 925 protected CredentialProvider _getCredentialProviderFromSession(Request request) 926 { 927 return getCredentialProviderFromSession(request); 928 } 929 930 /** 931 * Get the credential provider used for the current connection 932 * @param request The request 933 * @return The credential provider used or null 934 */ 935 public static CredentialProvider getCredentialProviderFromSession(Request request) 936 { 937 Session session = request.getSession(false); 938 if (session != null) 939 { 940 return (CredentialProvider) session.getAttribute(SESSION_CREDENTIALPROVIDER); 941 } 942 return null; 943 } 944 945 /** 946 * Get the credential provider mode used for the current connection 947 * @param request The request 948 * @return The credential provider mode used or null 949 */ 950 protected Boolean _getCredentialProviderModeFromSession(Request request) 951 { 952 return getCredentialProviderModeFromSession(request); 953 } 954 955 /** 956 * Get the credential provider mode used for the current connection 957 * @param request The request 958 * @return The credential provider mode used or null 959 */ 960 public static Boolean getCredentialProviderModeFromSession(Request request) 961 { 962 Session session = request.getSession(false); 963 if (session != null) 964 { 965 return (Boolean) session.getAttribute(SESSION_CREDENTIALPROVIDER_MODE); 966 } 967 return null; 968 } 969 970 /** 971 * If there is a running credential provider, was it in non-blocking or blocking mode? 972 * @param request The request 973 * @return false if non-blocking, true if blocking 974 */ 975 protected boolean _isCurrentCredentialProviderInBlockingMode(Request request) 976 { 977 if (Strings.CS.equals(request.getParameter(REQUEST_PARAMETER_NONBLOCING), "force")) 978 { 979 return false; 980 } 981 982 Integer requestedCredentialParameterIndex = _getCurrentCredentialProviderIndexFromParameter(request); 983 if (requestedCredentialParameterIndex != null && requestedCredentialParameterIndex != -1) 984 { 985 return true; 986 } 987 988 Session session = request.getSession(false); 989 if (session != null) 990 { 991 Boolean mode = (Boolean) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 992 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 993 if (mode != null) 994 { 995 return mode.booleanValue(); 996 } 997 } 998 return false; 999 } 1000 1001 /** 1002 * Call this to skip the currently used credential provider and proceed to the next one. 1003 * Useful for non blocking 1004 * @param request The request 1005 */ 1006 public static void skipCurrentCredentialProvider(Request request) 1007 { 1008 Session session = request.getSession(); 1009 if (session != null) 1010 { 1011 Integer cpIndex = (Integer) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1012 if (cpIndex != null) 1013 { 1014 cpIndex++; 1015 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX, cpIndex); 1016 } 1017 } 1018 } 1019 1020 /** 1021 * Get the current credential provider index or -1 if there no running provider FROM REQUEST PARAMETER 1022 * @param request The request 1023 * @return The credential provider index to use in the availablesCredentialProviders list or -1 or null 1024 */ 1025 protected Integer _getCurrentCredentialProviderIndexFromParameter(Request request) 1026 { 1027 // Is the CP requested? 1028 String requestedCredentialParameterIndex = request.getParameter(REQUEST_PARAMETER_CREDENTIALPROVIDER_INDEX); 1029 if (StringUtils.isNotBlank(requestedCredentialParameterIndex)) 1030 { 1031 int index = Integer.parseInt(requestedCredentialParameterIndex); 1032 return index; 1033 } 1034 return null; 1035 } 1036 1037 /** 1038 * Get the current credential provider index or -1 if there no running provider 1039 * @param request The request 1040 * @param availableCredentialProviders The list of available credential provider 1041 * @return The credential provider index to use in the availablesCredentialProviders list or -1 1042 */ 1043 protected int _getCurrentCredentialProviderIndex(Request request, List<CredentialProvider> availableCredentialProviders) 1044 { 1045 // Is the CP requested? 1046 Integer requestedCredentialParameterIndex = _getCurrentCredentialProviderIndexFromParameter(request); 1047 if (requestedCredentialParameterIndex != null) 1048 { 1049 if (requestedCredentialParameterIndex < availableCredentialProviders.size()) 1050 { 1051 return requestedCredentialParameterIndex; 1052 } 1053 else 1054 { 1055 return -1; 1056 } 1057 } 1058 1059 // Was the CP memorized? 1060 Session session = request.getSession(false); 1061 if (session != null) 1062 { 1063 Integer cpIndex = (Integer) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1064 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1065 1066 if (cpIndex != null) 1067 { 1068 return cpIndex; 1069 } 1070 } 1071 1072 // Default value 1073 return -1; 1074 } 1075 1076 /** 1077 * Get the authentication context 1078 * @param request The request 1079 * @param parameters The action parameters 1080 * @return The context 1081 * @throws IllegalArgumentException If there is no context set 1082 */ 1083 protected List<String> _getContexts(Request request, Parameters parameters) 1084 { 1085 String context = parameters.getParameter("context", null); 1086 if (context == null) 1087 { 1088 throw new IllegalArgumentException("The authentication is not parameterized correctly: an authentication context must be specified"); 1089 } 1090 return Collections.singletonList(context); 1091 } 1092 1093 /** 1094 * Determine if the request is internal and do not need authentication 1095 * @param request The request 1096 * @return true to bypass this authentication 1097 */ 1098 protected boolean _internalRequest(Request request) 1099 { 1100 return "true".equals(request.getAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED)) || request.getAttribute(REQUEST_ATTRIBUTE_INTERNAL_ALLOWED) != null; 1101 } 1102 1103 /** 1104 * Determine if the request is one of the authentication process (except the credential providers) 1105 * @param request The request 1106 * @return true to bypass this authentication 1107 */ 1108 protected boolean _acceptedUrl(Request request) 1109 { 1110 // URL without server context and leading slash. 1111 String url = (String) request.getAttribute(WorkspaceMatcher.IN_WORKSPACE_URL); 1112 for (Pattern pattern : _acceptedUrlPatterns) 1113 { 1114 if (pattern.matcher(url).matches()) 1115 { 1116 // Anonymous request 1117 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 1118 1119 return true; 1120 } 1121 } 1122 1123 return false; 1124 } 1125 1126 /** 1127 * This method ensure that there is a currently connected user and that it is still valid 1128 * @param request The request 1129 * @param redirector The cocoon redirector 1130 * @param parameters The action parameters 1131 * @return true if the user is connected and valid 1132 * @throws Exception if an error occurred 1133 */ 1134 protected boolean _validateCurrentlyConnectedUser(Request request, Redirector redirector, Parameters parameters) throws Exception 1135 { 1136 Session session = request.getSession(false); 1137 UserIdentity userCurrentlyConnected = _getUserIdentityFromSession(request); 1138 CredentialProvider runningCredentialProvider = _getCredentialProviderFromSession(request); 1139 Boolean runningBlockingkMode = _getCredentialProviderModeFromSession(request); 1140 1141 if (runningCredentialProvider == null || userCurrentlyConnected == null || runningBlockingkMode == null || !runningCredentialProvider.isStillConnected(runningBlockingkMode, userCurrentlyConnected, redirector)) 1142 { 1143 if (redirector.hasRedirected()) 1144 { 1145 return true; 1146 } 1147 1148 // There is an invalid connected user 1149 if (session != null && userCurrentlyConnected != null) 1150 { 1151 session.invalidate(); 1152 } 1153 return false; 1154 } 1155 1156 // let us make an exception for the user image url since we need it on the 403 page 1157 if (RuntimeServlet.getRunMode() == RunMode.MAINTENANCE && MaintenanceAction.acceptedUrl(request)) 1158 { 1159 return true; 1160 } 1161 1162 _validateCurrentlyConnectedUserIsInAuthorizedPopulation(userCurrentlyConnected, request, parameters); 1163 1164 return true; 1165 } 1166 1167 /** 1168 * This method is the second part of the process that ensure that there is a currently connected user and that it is still valid 1169 * @param userCurrentlyConnected The user to test 1170 * @param request The request 1171 * @param parameters The action parameters 1172 */ 1173 protected void _validateCurrentlyConnectedUserIsInAuthorizedPopulation(UserIdentity userCurrentlyConnected, Request request, Parameters parameters) 1174 { 1175 if (_getTokenMode(parameters) == TOKEN_MODE.DEFAULT) 1176 { 1177 // we know this is a valid user, but we need to check if the context is correct 1178 List<String> contexts = _getContexts(request, parameters); 1179 // All user populations for this context 1180 Set<String> availableUserPopulationsIds = _getAvailableUserPopulationsIds(request, contexts); 1181 1182 if (!availableUserPopulationsIds.contains(userCurrentlyConnected.getPopulationId())) 1183 { 1184 throw new AccessDeniedException("The user " + userCurrentlyConnected + " cannot be authenticated to the contexts '" + StringUtils.join(contexts, "', '") + "' because its populations are not part of the " + availableUserPopulationsIds.size() + " granted populations."); 1185 } 1186 } 1187 else 1188 { 1189 // In 'token only' mode, check if user is part of the active populations (regardless of the context) 1190 List<String> availableUserPopulationsIds = _userPopulationDAO.getEnabledUserPopulations(false).stream().map(UserPopulation::getId).collect(Collectors.toList()); 1191 1192 if (!availableUserPopulationsIds.contains(userCurrentlyConnected.getPopulationId())) 1193 { 1194 throw new AccessDeniedException("The user " + userCurrentlyConnected + " cannot be authenticated because its populations does not exist or it is disabled."); 1195 } 1196 } 1197 } 1198 1199 /** 1200 * Test if user wants to logout and handle it 1201 * @param redirector The cocoon redirector 1202 * @param objectModel The cocoon object model 1203 * @param source The sitemap source 1204 * @param parameters The sitemap parameters 1205 * @return true if the user was logged out 1206 * @throws Exception if an error occurred 1207 */ 1208 protected boolean _handleLogout(Redirector redirector, Map objectModel, String source, Parameters parameters) throws Exception 1209 { 1210 Request request = ObjectModelHelper.getRequest(objectModel); 1211 if (Strings.CS.equals(request.getContextPath() + request.getAttribute(WorkspaceMatcher.WORKSPACE_URI) + "/logout.html", request.getRequestURI()) 1212 || Strings.CS.equals("true", parameters.getParameter("logout", "false"))) 1213 { 1214 // The user logs out 1215 UserIdentity currentUser = _currentUserProvider.getUser(); 1216 if (currentUser != null) // user can be null when calling the url with an expired session 1217 { 1218 _currentUserProvider.logout(redirector); 1219 _logLogoutEvent(currentUser); 1220 } 1221 1222 if (!redirector.hasRedirected()) 1223 { 1224 redirector.redirect(false, getLogoutURL(request)); 1225 } 1226 1227 return true; 1228 } 1229 return false; 1230 } 1231 1232 /** 1233 * Check the authentications of the authentication manager 1234 * @param userPopulations The list of available matching populations 1235 * @param redirector The cocoon redirector 1236 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 1237 * @param runningCredentialProvider The Credential provider to test 1238 * @param potentialUserIdentity A possible user identity. Population can be null. User may not exist either. 1239 * @return The user population matching credentials or null 1240 * @throws Exception If an error occurred 1241 * @throws AccessDeniedException If the user is rejected 1242 */ 1243 protected UserIdentity _getUserIdentity(List<UserPopulation> userPopulations, UserIdentity potentialUserIdentity, Redirector redirector, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider) throws Exception 1244 { 1245 if (potentialUserIdentity.getPopulationId() == null) 1246 { 1247 for (UserPopulation up : userPopulations) 1248 { 1249 User user = _userManager.getUser(up, potentialUserIdentity.getLogin()); 1250 if (_isLoginCaseExact(user, potentialUserIdentity)) 1251 { 1252 return user.getIdentity(); 1253 } 1254 } 1255 } 1256 else 1257 { 1258 User user = _userManager.getUser(potentialUserIdentity.getPopulationId(), potentialUserIdentity.getLogin()); 1259 if (_isLoginCaseExact(user, potentialUserIdentity)) 1260 { 1261 return user.getIdentity(); 1262 } 1263 } 1264 1265 runningCredentialProvider.userNotAllowed(runningBlockingkMode, redirector); 1266 1267 if (getLogger().isWarnEnabled()) 1268 { 1269 getLogger().warn("The user '" + potentialUserIdentity + "' was authenticated by the credential provider '" + runningCredentialProvider.getCredentialProviderModelId() + "' but it does not match any user of the " + userPopulations.size() + " granted populations."); 1270 } 1271 1272 return null; 1273 } 1274 1275 private boolean _isLoginCaseExact(User user, UserIdentity potentialUserIdentity) 1276 { 1277 return user != null 1278 && (user.getUserDirectory().isCaseSensitive() && Strings.CS.equals(user.getIdentity().getLogin(), potentialUserIdentity.getLogin()) 1279 || !user.getUserDirectory().isCaseSensitive() && Strings.CI.equals(user.getIdentity().getLogin(), potentialUserIdentity.getLogin())); 1280 } 1281}