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