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.service.ServiceException; 036import org.apache.avalon.framework.thread.ThreadSafe; 037import org.apache.cocoon.ProcessingException; 038import org.apache.cocoon.acting.ServiceableAction; 039import org.apache.cocoon.environment.ObjectModelHelper; 040import org.apache.cocoon.environment.Redirector; 041import org.apache.cocoon.environment.Request; 042import org.apache.cocoon.environment.Session; 043import org.apache.cocoon.environment.SourceResolver; 044import org.apache.commons.lang3.StringUtils; 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 request parameter holding the token context */ 107 public static final String REQUEST_PARAMETER_TOKEN_CONTEXT = "tokenContext"; 108 /** The header parameter that can be set to handle the token */ 109 public static final String HEADER_TOKEN = "X-Ametys-Token"; 110 111 /** The sitemap parameter holding the token */ 112 protected static final String PARAMETERS_PARAMETER_TOKEN = "token"; 113 /** The sitemap parameter holding the token context */ 114 protected static final String PARAMETERS_PARAMETER_TOKEN_CONTEXT = "tokenContext"; 115 /** The request attribute name for transmitting a boolean that tell if there is a list of credential provider to choose */ 116 protected static final String REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_LIST = "Runtime:RequestListCredentialProvider"; 117 /** The request attribute name for transmitting the index in the list of chosen credential provider */ 118 protected static final String REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_INDEX = "Runtime:RequestCredentialProviderIndex"; 119 /** The request attribute name to know if user population list should be proposed */ 120 protected static final String REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST = "Runtime:UserPopulationsListDisplay"; 121 /** The request attribute name for transmitting the potential list of user populations to the login screen . */ 122 protected static final String REQUEST_ATTRIBUTE_INVALID_POPULATION = "Runtime:RequestInvalidPopulation"; 123 /** The request attribute name for transmitting the list of contexts */ 124 protected static final String REQUEST_ATTRIBUTE_CONTEXTS = "Runtime:Contexts"; 125 126 /** The session attribute name for storing the credential provider index of the authentication (during connection process) */ 127 protected static final String SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX = "Runtime:ConnectingCredentialProviderIndex"; 128 /** The session attribute name for storing the last known credential provider index of the authentication (during connection process)*/ 129 protected static final String SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN = "Runtime:ConnectingCredentialProviderIndexLastKnown"; 130 /** The session attribute name for storing the credential provider mode of the authentication: non-blocking=>false, blocking=>true (during connection process) */ 131 protected static final String SESSION_CONNECTING_CREDENTIALPROVIDER_MODE = "Runtime:ConnectingCredentialProviderMode"; 132 /** The session attribute name for storing the id of the user population (during connection process) */ 133 protected static final String SESSION_CONNECTING_USERPOPULATION_ID = "Runtime:ConnectingUserPopulationId"; 134 135 /** The session attribute name for storing the credential provider of the authentication */ 136 protected static final String SESSION_CREDENTIALPROVIDER = "Runtime:CredentialProvider"; 137 /** The session attribute name for storing the credential provider mode of the authentication: non-blocking=>false, blocking=>true */ 138 protected static final String SESSION_CREDENTIALPROVIDER_MODE = "Runtime:CredentialProviderMode"; 139 140 /** The sitemap parameter to set the token mode of the action */ 141 protected static final String SITEMAP_PARAMETER_TOKEN_MODE = "token-mode"; 142 143 /** The DAO for user populations */ 144 protected UserPopulationDAO _userPopulationDAO; 145 /** The user manager */ 146 protected UserManager _userManager; 147 /** The helper for the associations population/context */ 148 protected PopulationContextHelper _populationContextHelper; 149 /** The current user provider */ 150 protected CurrentUserProvider _currentUserProvider; 151 152 /** url requires for authentication */ 153 protected Collection<Pattern> _acceptedUrlPatterns = Arrays.asList(new Pattern[]{Pattern.compile("^plugins/core/authenticate/[0-9]+$"), Pattern.compile("^plugins/core/reset-password.html$")}); 154 155 /** The authentication token manager */ 156 protected AuthenticationTokenManager _authenticateTokenManager; 157 /** The observation manager */ 158 protected ObservationManager _observationManager; 159 /** The user account manager */ 160 protected UserPasswordManager _userPasswordManager; 161 /** The user status manager */ 162 protected UserStatusManager _userStatusManager; 163 164 /** 165 * The token mode of this authentication action 166 */ 167 protected enum TOKEN_MODE 168 { 169 /** In this mode, only the token will be taken in account. If no token is found, authentication will not be considered done */ 170 TOKEN_ONLY, 171 /** 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 */ 172 ALLOW_ANONYMOUS, 173 /** In this default mode, the token will be taken in account, but if no token is found, the authentication process will continue */ 174 DEFAULT 175 } 176 177 @Override 178 public void initialize() throws Exception 179 { 180 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 181 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 182 _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE); 183 _userStatusManager = (UserStatusManager) manager.lookup(UserStatusManager.ROLE); 184 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 185 try 186 { 187 _userPasswordManager = (UserPasswordManager) manager.lookup(UserPasswordManager.ROLE); 188 _authenticateTokenManager = (AuthenticationTokenManager) manager.lookup(AuthenticationTokenManager.ROLE); 189 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 190 } 191 catch (ServiceException e) 192 { 193 // nothing... we are in safe mode, but this is not safe 194 } 195 } 196 197 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 198 { 199 Request request = ObjectModelHelper.getRequest(objectModel); 200 201 if (_preFlightCheck(redirector, resolver, objectModel, source, parameters) || _handleAuthenticationToken(request, parameters)) 202 { 203 // We passed the authentication, let's mark it now 204 request.setAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED, "true"); 205 206 // We passed the authentication (with a user) 207 return EMPTY_MAP; 208 } 209 210 // At this point, the user is still anonymous 211 212 // If only token are authorized for authentication, stop authentication process. There is no user authenticated here. 213 if (_getTokenMode(parameters) != TOKEN_MODE.DEFAULT) 214 { 215 if (_getTokenMode(parameters) == TOKEN_MODE.ALLOW_ANONYMOUS) 216 { 217 request.setAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED, "true"); 218 } 219 return null; 220 } 221 222 // At this point, we already know that the entire process will be executed, whatever the outcome 223 // Set the flag, so that the authentication process won't repeat 224 request.setAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED, "true"); 225 226 // Get population and if possible credential providers 227 List<UserPopulation> chosenUserPopulations = new ArrayList<>(); 228 List<CredentialProvider> credentialProviders = new ArrayList<>(); 229 if (!_prepareUserPopulationsAndCredentialProviders(request, parameters, redirector, chosenUserPopulations, credentialProviders)) 230 { 231 // Let's display the population screen 232 return EMPTY_MAP; 233 } 234 235 // Get the currently running credential provider 236 int runningCredentialProviderIndex = _getCurrentCredentialProviderIndex(request, credentialProviders); 237 request.setAttribute(REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_INDEX, runningCredentialProviderIndex); 238 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_URL, getLoginURL(request)); 239 240 // Let's process non-blocking 241 if (!_isCurrentCredentialProviderInBlockingMode(request)) 242 { 243 // if there was no one running, let's start with the first one 244 runningCredentialProviderIndex = Math.max(0, runningCredentialProviderIndex); 245 246 for (; runningCredentialProviderIndex < credentialProviders.size(); runningCredentialProviderIndex++) 247 { 248 CredentialProvider runningCredentialProvider = credentialProviders.get(runningCredentialProviderIndex); 249 if (_process(request, false, runningCredentialProvider, runningCredentialProviderIndex, redirector, chosenUserPopulations)) 250 { 251 // Whatever the user was correctly authenticated or he just required a redirect: let's stop here for the moment 252 return EMPTY_MAP; 253 } 254 } 255 256 // No one matches 257 runningCredentialProviderIndex = -1; 258 } 259 260 _saveLastKnownBlockingCredentialProvider(request, runningCredentialProviderIndex); 261 262 // Let's process the current blocking one or the only existing one 263 if (_shouldRunFirstBlockingCredentialProvider(runningCredentialProviderIndex, credentialProviders, request, chosenUserPopulations)) 264 { 265 CredentialProvider runningCredentialProvider = runningCredentialProviderIndex == -1 ? _getFirstBlockingCredentialProvider(credentialProviders) : credentialProviders.get(runningCredentialProviderIndex); 266 if (_process(request, true, runningCredentialProvider, runningCredentialProviderIndex, redirector, chosenUserPopulations)) 267 { 268 // Whatever the user was correctly authenticated or he just required a redirect: let's stop here for the moment 269 return EMPTY_MAP; 270 } 271 272 throw new AuthorizationRequiredException(); 273 } 274 275 // At this step we have two kind off requests 276 // 1) A secondary request of a blocking cp (such as captcha image...) 277 Integer formerRunningCredentialProviderIndex = (Integer) request.getSession(true).getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN); 278 if (formerRunningCredentialProviderIndex != null && credentialProviders.get(formerRunningCredentialProviderIndex).grantAnonymousRequest(true)) 279 { 280 // Anonymous request 281 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 282 _saveConnectingStateToSession(request, -1, true); 283 return EMPTY_MAP; 284 } 285 286 // 2) Or a main stream request that should display the list of available blocking cp 287 return _displayBlockingList(redirector, request, credentialProviders); 288 } 289 290 /** 291 * Prepare authentication 292 * @param redirector The redirector 293 * @param resolver The source resolver 294 * @param objectModel The object model 295 * @param source The source 296 * @param parameters The action parameters 297 * @return <code>true</code> if a user was authenticated, <code>false</code> otherwise 298 * @throws Exception if failed to prepare the authentication 299 */ 300 protected boolean _preFlightCheck(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 301 { 302 Request request = ObjectModelHelper.getRequest(objectModel); 303 304 return _handleLogout(redirector, objectModel, source, parameters) // Test if user wants to logout 305 || _internalRequest(request) // Test if this request was already authenticated or it the request is marked as an internal one 306 || _acceptedUrl(request) // Test if the url is used for authentication 307 || _validateCurrentlyConnectedUser(request, redirector, parameters) // Test if the currently connected user is still valid 308 || redirector.hasRedirected(); 309 } 310 311 /** 312 * Authenticate a user using the token in request (if configured so) 313 * @param request The request 314 * @param parameters The action parameters 315 * @return true if the user was authenticated 316 */ 317 protected boolean _handleAuthenticationToken(Request request, Parameters parameters) 318 { 319 String token = request.getHeader(HEADER_TOKEN); 320 if (StringUtils.isBlank(token)) 321 { 322 token = parameters.getParameter(PARAMETERS_PARAMETER_TOKEN, _getTokenFromRequest(request)); 323 } 324 325 if (StringUtils.isNotBlank(token)) 326 { 327 String context = parameters.getParameter(PARAMETERS_PARAMETER_TOKEN_CONTEXT, null); 328 UserIdentity userIdentity = _validateToken(token, context); 329 if (userIdentity != null) 330 { 331 // Save user identity 332 _setUserIdentityInSession(request, userIdentity, new UserDAO.ImpersonateCredentialProvider(), true); 333 _validateCurrentlyConnectedUserIsInAuthorizedPopulation(userIdentity, request, parameters); 334 335 _userStatusManager.updateConnectionDate(userIdentity); 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 _userStatusManager.updateConnectionDate(userIdentity); 733 734 _logLoginEvent(runningCredentialProvider, userIdentity); 735 736 return true; 737 } 738 739 /** 740 * Handle weak password exception 741 * @param request the request 742 * @param runningCredentialProvider the credential provider that detected the weak password 743 * @param redirector the redirector 744 * @param userIdentity the user identity with a weak password 745 * @throws Exception if an error occurred 746 */ 747 protected void _handleWeakPassword(Request request, CredentialProvider runningCredentialProvider, Redirector redirector, UserIdentity userIdentity) throws Exception 748 { 749 ForensicLogger.info("authentication.form.weak.password", Map.of("userIdentity", userIdentity), userIdentity); 750 Optional<String> resetPasswordURI = _getWeakPasswordURI(request, userIdentity); 751 if (resetPasswordURI.isPresent()) 752 { 753 // Force redirect to weak password url to force change password 754 getLogger().info("Password of user " + userIdentity + " does not meet the security requirements. Force to change password."); 755 redirector.redirect(false, resetPasswordURI.get()); 756 return; 757 } 758 // Defaut implementation only log that user has a weak password. User will be authenticated 759 getLogger().warn("Password of user " + userIdentity + " does not meet the security requirements. User is authenticated despite the risk for security."); 760 } 761 762 /** 763 * Get the URI where the user should be redirected after a weak password is detected 764 * @param request the current request 765 * @param userIdentity the user identity with a weak password 766 * @return the absolute uri 767 */ 768 protected Optional<String> _getWeakPasswordURI(Request request, UserIdentity userIdentity) 769 { 770 // do not force password change in safe or maintenance mode 771 if (_userPasswordManager != null && RuntimeServlet.getRunMode() == RunMode.NORMAL) 772 { 773 return _userPasswordManager.getChangePasswordURI(request, userIdentity, true); 774 } 775 return Optional.empty(); 776 } 777 778 /** 779 * Log login event 780 * @param credentialProvider the running credential provider 781 * @param userIdentity the user identity 782 */ 783 protected void _logLoginEvent(CredentialProvider credentialProvider, UserIdentity userIdentity) 784 { 785 Map<String, Object> loginArgs = Map.of("credential-provider", credentialProvider.getCredentialProviderModelId(), "user", userIdentity); 786 ForensicLogger.info("authentication.login", loginArgs, userIdentity); 787 } 788 789 /** 790 * Log logout event 791 * @param userIdentity the user identity 792 */ 793 protected void _logLogoutEvent(UserIdentity userIdentity) 794 { 795 Map<String, Object> logoutArgs = Map.of("user", userIdentity); 796 ForensicLogger.info("authentication.logout", logoutArgs, userIdentity); 797 } 798 799 /** 800 * Reset the connecting information in session 801 * @param request The request 802 */ 803 protected static void _resetConnectingStateToSession(Request request) 804 { 805 Session session = request.getSession(false); 806 if (session != null) 807 { 808 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 809 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 810 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN); 811 session.removeAttribute(SESSION_CONNECTING_USERPOPULATION_ID); 812 } 813 } 814 815 /** 816 * When the process end successfully, save the state 817 * @param request The request 818 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 819 * @param runningCredentialProviderIndex the currently tested credential provider 820 */ 821 protected void _saveConnectingStateToSession(Request request, int runningCredentialProviderIndex, boolean runningBlockingkMode) 822 { 823 Session session = request.getSession(true); 824 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX, runningCredentialProviderIndex); 825 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE, runningBlockingkMode); 826 session.setAttribute(SESSION_CONNECTING_USERPOPULATION_ID, request.getAttribute(REQUEST_ATTRIBUTE_USER_POPULATION_ID)); 827 } 828 829 /** 830 * Save user identity in request 831 * @param request The request 832 * @param userIdentity The useridentity to save 833 * @param credentialProvider The credential provider used to connect 834 * @param blockingMode The mode used for the credential provider 835 */ 836 protected void _setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 837 { 838 setUserIdentityInSession(request, userIdentity, credentialProvider, blockingMode); 839 if (_observationManager != null) 840 { 841 Map<String, Object> eventParams = new HashMap<>(); 842 eventParams.put(ObservationConstants.ARGS_USER, userIdentity); 843 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_AUTHENTICATED, UserPopulationDAO.SYSTEM_USER_IDENTITY, eventParams)); 844 } 845 } 846 847 /** 848 * Save user identity in request 849 * @param request The request 850 * @param userIdentity The useridentity to save 851 * @param credentialProvider The credential provider used to connect 852 * @param blockingMode The mode used for the credential provider 853 */ 854 public static void setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 855 { 856 Session session = renewSession(request); 857 _resetConnectingStateToSession(request); 858 session.setAttribute(SESSION_USERIDENTITY, userIdentity); 859 session.setAttribute(SESSION_CREDENTIALPROVIDER, credentialProvider); 860 session.setAttribute(SESSION_CREDENTIALPROVIDER_MODE, blockingMode); 861 } 862 863 /** 864 * Change the session id (for security purposes) 865 * @param request The current request 866 * @return The new session 867 */ 868 public static Session renewSession(Request request) 869 { 870 Session session = request.getSession(true); 871 872 Map<String, Object> attributesCopy = new HashMap<>(); 873 874 Enumeration<String> attributeNames = session.getAttributeNames(); 875 while (attributeNames.hasMoreElements()) 876 { 877 String attributeName = attributeNames.nextElement(); 878 attributesCopy.put(attributeName, session.getAttribute(attributeName)); 879 } 880 881 session.invalidate(); 882 883 session = request.getSession(true); 884 885 for (Entry<String, Object> attribute : attributesCopy.entrySet()) 886 { 887 session.setAttribute(attribute.getKey(), attribute.getValue()); 888 } 889 890 return session; 891 } 892 893 /** 894 * Get the user identity of the connected user from the session 895 * @param request The request 896 * @return The connected useridentity or null 897 */ 898 protected UserIdentity _getUserIdentityFromSession(Request request) 899 { 900 return getUserIdentityFromSession(request); 901 } 902 903 /** 904 * Get the user identity of the connected user from the session 905 * @param request The request 906 * @return The connected useridentity or null 907 */ 908 public static UserIdentity getUserIdentityFromSession(Request request) 909 { 910 Session session = request.getSession(false); 911 if (session != null) 912 { 913 return (UserIdentity) session.getAttribute(SESSION_USERIDENTITY); 914 } 915 return null; 916 } 917 918 /** 919 * Get the credential provider used for the current connection 920 * @param request The request 921 * @return The credential provider used or null 922 */ 923 protected CredentialProvider _getCredentialProviderFromSession(Request request) 924 { 925 return getCredentialProviderFromSession(request); 926 } 927 928 /** 929 * Get the credential provider used for the current connection 930 * @param request The request 931 * @return The credential provider used or null 932 */ 933 public static CredentialProvider getCredentialProviderFromSession(Request request) 934 { 935 Session session = request.getSession(false); 936 if (session != null) 937 { 938 return (CredentialProvider) session.getAttribute(SESSION_CREDENTIALPROVIDER); 939 } 940 return null; 941 } 942 943 /** 944 * Get the credential provider mode used for the current connection 945 * @param request The request 946 * @return The credential provider mode used or null 947 */ 948 protected Boolean _getCredentialProviderModeFromSession(Request request) 949 { 950 return getCredentialProviderModeFromSession(request); 951 } 952 953 /** 954 * Get the credential provider mode used for the current connection 955 * @param request The request 956 * @return The credential provider mode used or null 957 */ 958 public static Boolean getCredentialProviderModeFromSession(Request request) 959 { 960 Session session = request.getSession(false); 961 if (session != null) 962 { 963 return (Boolean) session.getAttribute(SESSION_CREDENTIALPROVIDER_MODE); 964 } 965 return null; 966 } 967 968 /** 969 * If there is a running credential provider, was it in non-blocking or blocking mode? 970 * @param request The request 971 * @return false if non-blocking, true if blocking 972 */ 973 protected boolean _isCurrentCredentialProviderInBlockingMode(Request request) 974 { 975 if (StringUtils.equals(request.getParameter(REQUEST_PARAMETER_NONBLOCING), "force")) 976 { 977 return false; 978 } 979 980 Integer requestedCredentialParameterIndex = _getCurrentCredentialProviderIndexFromParameter(request); 981 if (requestedCredentialParameterIndex != null && requestedCredentialParameterIndex != -1) 982 { 983 return true; 984 } 985 986 Session session = request.getSession(false); 987 if (session != null) 988 { 989 Boolean mode = (Boolean) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 990 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 991 if (mode != null) 992 { 993 return mode.booleanValue(); 994 } 995 } 996 return false; 997 } 998 999 /** 1000 * Call this to skip the currently used credential provider and proceed to the next one. 1001 * Useful for non blocking 1002 * @param request The request 1003 */ 1004 public static void skipCurrentCredentialProvider(Request request) 1005 { 1006 Session session = request.getSession(); 1007 if (session != null) 1008 { 1009 Integer cpIndex = (Integer) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1010 if (cpIndex != null) 1011 { 1012 cpIndex++; 1013 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX, cpIndex); 1014 } 1015 } 1016 } 1017 1018 /** 1019 * Get the current credential provider index or -1 if there no running provider FROM REQUEST PARAMETER 1020 * @param request The request 1021 * @return The credential provider index to use in the availablesCredentialProviders list or -1 or null 1022 */ 1023 protected Integer _getCurrentCredentialProviderIndexFromParameter(Request request) 1024 { 1025 // Is the CP requested? 1026 String requestedCredentialParameterIndex = request.getParameter(REQUEST_PARAMETER_CREDENTIALPROVIDER_INDEX); 1027 if (StringUtils.isNotBlank(requestedCredentialParameterIndex)) 1028 { 1029 int index = Integer.parseInt(requestedCredentialParameterIndex); 1030 return index; 1031 } 1032 return null; 1033 } 1034 1035 /** 1036 * Get the current credential provider index or -1 if there no running provider 1037 * @param request The request 1038 * @param availableCredentialProviders The list of available credential provider 1039 * @return The credential provider index to use in the availablesCredentialProviders list or -1 1040 */ 1041 protected int _getCurrentCredentialProviderIndex(Request request, List<CredentialProvider> availableCredentialProviders) 1042 { 1043 // Is the CP requested? 1044 Integer requestedCredentialParameterIndex = _getCurrentCredentialProviderIndexFromParameter(request); 1045 if (requestedCredentialParameterIndex != null) 1046 { 1047 if (requestedCredentialParameterIndex < availableCredentialProviders.size()) 1048 { 1049 return requestedCredentialParameterIndex; 1050 } 1051 else 1052 { 1053 return -1; 1054 } 1055 } 1056 1057 // Was the CP memorized? 1058 Session session = request.getSession(false); 1059 if (session != null) 1060 { 1061 Integer cpIndex = (Integer) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1062 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1063 1064 if (cpIndex != null) 1065 { 1066 return cpIndex; 1067 } 1068 } 1069 1070 // Default value 1071 return -1; 1072 } 1073 1074 /** 1075 * Get the authentication context 1076 * @param request The request 1077 * @param parameters The action parameters 1078 * @return The context 1079 * @throws IllegalArgumentException If there is no context set 1080 */ 1081 protected List<String> _getContexts(Request request, Parameters parameters) 1082 { 1083 String context = parameters.getParameter("context", null); 1084 if (context == null) 1085 { 1086 throw new IllegalArgumentException("The authentication is not parameterized correctly: an authentication context must be specified"); 1087 } 1088 return Collections.singletonList(context); 1089 } 1090 1091 /** 1092 * Determine if the request is internal and do not need authentication 1093 * @param request The request 1094 * @return true to bypass this authentication 1095 */ 1096 protected boolean _internalRequest(Request request) 1097 { 1098 return "true".equals(request.getAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED)) || request.getAttribute(REQUEST_ATTRIBUTE_INTERNAL_ALLOWED) != null; 1099 } 1100 1101 /** 1102 * Determine if the request is one of the authentication process (except the credential providers) 1103 * @param request The request 1104 * @return true to bypass this authentication 1105 */ 1106 protected boolean _acceptedUrl(Request request) 1107 { 1108 // URL without server context and leading slash. 1109 String url = (String) request.getAttribute(WorkspaceMatcher.IN_WORKSPACE_URL); 1110 for (Pattern pattern : _acceptedUrlPatterns) 1111 { 1112 if (pattern.matcher(url).matches()) 1113 { 1114 // Anonymous request 1115 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 1116 1117 return true; 1118 } 1119 } 1120 1121 return false; 1122 } 1123 1124 /** 1125 * This method ensure that there is a currently connected user and that it is still valid 1126 * @param request The request 1127 * @param redirector The cocoon redirector 1128 * @param parameters The action parameters 1129 * @return true if the user is connected and valid 1130 * @throws Exception if an error occurred 1131 */ 1132 protected boolean _validateCurrentlyConnectedUser(Request request, Redirector redirector, Parameters parameters) throws Exception 1133 { 1134 Session session = request.getSession(false); 1135 UserIdentity userCurrentlyConnected = _getUserIdentityFromSession(request); 1136 CredentialProvider runningCredentialProvider = _getCredentialProviderFromSession(request); 1137 Boolean runningBlockingkMode = _getCredentialProviderModeFromSession(request); 1138 1139 if (runningCredentialProvider == null || userCurrentlyConnected == null || runningBlockingkMode == null || !runningCredentialProvider.isStillConnected(runningBlockingkMode, userCurrentlyConnected, redirector)) 1140 { 1141 if (redirector.hasRedirected()) 1142 { 1143 return true; 1144 } 1145 1146 // There is an invalid connected user 1147 if (session != null && userCurrentlyConnected != null) 1148 { 1149 session.invalidate(); 1150 } 1151 return false; 1152 } 1153 1154 // let us make an exception for the user image url since we need it on the 403 page 1155 if (RuntimeServlet.getRunMode() == RunMode.MAINTENANCE && MaintenanceAction.acceptedUrl(request)) 1156 { 1157 return true; 1158 } 1159 1160 _validateCurrentlyConnectedUserIsInAuthorizedPopulation(userCurrentlyConnected, request, parameters); 1161 1162 return true; 1163 } 1164 1165 /** 1166 * This method is the second part of the process that ensure that there is a currently connected user and that it is still valid 1167 * @param userCurrentlyConnected The user to test 1168 * @param request The request 1169 * @param parameters The action parameters 1170 */ 1171 protected void _validateCurrentlyConnectedUserIsInAuthorizedPopulation(UserIdentity userCurrentlyConnected, Request request, Parameters parameters) 1172 { 1173 if (_getTokenMode(parameters) == TOKEN_MODE.DEFAULT) 1174 { 1175 // we know this is a valid user, but we need to check if the context is correct 1176 List<String> contexts = _getContexts(request, parameters); 1177 // All user populations for this context 1178 Set<String> availableUserPopulationsIds = _getAvailableUserPopulationsIds(request, contexts); 1179 1180 if (!availableUserPopulationsIds.contains(userCurrentlyConnected.getPopulationId())) 1181 { 1182 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."); 1183 } 1184 } 1185 else 1186 { 1187 // In 'token only' mode, check if user is part of the active populations (regardless of the context) 1188 List<String> availableUserPopulationsIds = _userPopulationDAO.getEnabledUserPopulations(false).stream().map(UserPopulation::getId).collect(Collectors.toList()); 1189 1190 if (!availableUserPopulationsIds.contains(userCurrentlyConnected.getPopulationId())) 1191 { 1192 throw new AccessDeniedException("The user " + userCurrentlyConnected + " cannot be authenticated because its populations does not exist or it is disabled."); 1193 } 1194 } 1195 } 1196 1197 /** 1198 * Test if user wants to logout and handle it 1199 * @param redirector The cocoon redirector 1200 * @param objectModel The cocoon object model 1201 * @param source The sitemap source 1202 * @param parameters The sitemap parameters 1203 * @return true if the user was logged out 1204 * @throws Exception if an error occurred 1205 */ 1206 protected boolean _handleLogout(Redirector redirector, Map objectModel, String source, Parameters parameters) throws Exception 1207 { 1208 Request request = ObjectModelHelper.getRequest(objectModel); 1209 if (StringUtils.equals(request.getContextPath() + request.getAttribute(WorkspaceMatcher.WORKSPACE_URI) + "/logout.html", request.getRequestURI()) 1210 || StringUtils.equals("true", parameters.getParameter("logout", "false"))) 1211 { 1212 // The user logs out 1213 UserIdentity currentUser = _currentUserProvider.getUser(); 1214 if (currentUser != null) // user can be null when calling the url with an expired session 1215 { 1216 _currentUserProvider.logout(redirector); 1217 _logLogoutEvent(currentUser); 1218 } 1219 1220 if (!redirector.hasRedirected()) 1221 { 1222 redirector.redirect(false, getLogoutURL(request)); 1223 } 1224 1225 return true; 1226 } 1227 return false; 1228 } 1229 1230 /** 1231 * Check the authentications of the authentication manager 1232 * @param userPopulations The list of available matching populations 1233 * @param redirector The cocoon redirector 1234 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 1235 * @param runningCredentialProvider The Credential provider to test 1236 * @param potentialUserIdentity A possible user identity. Population can be null. User may not exist either. 1237 * @return The user population matching credentials or null 1238 * @throws Exception If an error occurred 1239 * @throws AccessDeniedException If the user is rejected 1240 */ 1241 protected UserIdentity _getUserIdentity(List<UserPopulation> userPopulations, UserIdentity potentialUserIdentity, Redirector redirector, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider) throws Exception 1242 { 1243 if (potentialUserIdentity.getPopulationId() == null) 1244 { 1245 for (UserPopulation up : userPopulations) 1246 { 1247 User user = _userManager.getUser(up, potentialUserIdentity.getLogin()); 1248 if (_isLoginCaseExact(user, potentialUserIdentity)) 1249 { 1250 return user.getIdentity(); 1251 } 1252 } 1253 } 1254 else 1255 { 1256 User user = _userManager.getUser(potentialUserIdentity.getPopulationId(), potentialUserIdentity.getLogin()); 1257 if (_isLoginCaseExact(user, potentialUserIdentity)) 1258 { 1259 return user.getIdentity(); 1260 } 1261 } 1262 1263 runningCredentialProvider.userNotAllowed(runningBlockingkMode, redirector); 1264 1265 if (getLogger().isWarnEnabled()) 1266 { 1267 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."); 1268 } 1269 1270 return null; 1271 } 1272 1273 private boolean _isLoginCaseExact(User user, UserIdentity potentialUserIdentity) 1274 { 1275 return user != null 1276 && (user.getUserDirectory().isCaseSensitive() && StringUtils.equals(user.getIdentity().getLogin(), potentialUserIdentity.getLogin()) 1277 || !user.getUserDirectory().isCaseSensitive() && StringUtils.equalsIgnoreCase(user.getIdentity().getLogin(), potentialUserIdentity.getLogin())); 1278 } 1279}