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.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 161 /** 162 * The token mode of this authentication action 163 */ 164 protected enum TOKEN_MODE 165 { 166 /** In this mode, only the token will be taken in account. If no token is found, authentication will not be considered done */ 167 TOKEN_ONLY, 168 /** 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 */ 169 ALLOW_ANONYMOUS, 170 /** In this default mode, the token will be taken in account, but if no token is found, the authentication process will continue */ 171 DEFAULT 172 } 173 174 @Override 175 public void initialize() throws Exception 176 { 177 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 178 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 179 _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE); 180 181 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 182 try 183 { 184 _userPasswordManager = (UserPasswordManager) manager.lookup(UserPasswordManager.ROLE); 185 _authenticateTokenManager = (AuthenticationTokenManager) manager.lookup(AuthenticationTokenManager.ROLE); 186 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 187 } 188 catch (ServiceException e) 189 { 190 // nothing... we are in safe mode, but this is not safe 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 Map<String, Object> tokenArgs = Map.of("user", userIdentity); 333 ForensicLogger.info("authentication.token", tokenArgs, userIdentity); 334 335 return true; 336 } 337 } 338 339 return false; 340 } 341 342 /** 343 * Get the token from the request 344 * @param request The request 345 * @return The token from the request or null 346 */ 347 protected String _getTokenFromRequest(Request request) 348 { 349 // FIXME RUNTIME-2501 check the parameter is provided in POST, e.g. by seeking if '?token=' and '&token=' are not used in request uri... 350 return request.getParameter(REQUEST_PARAMETER_TOKEN); 351 } 352 353 /** 354 * Validate the given token 355 * @param token The non empty token to validate 356 * @param context the context on which the token should be validated 357 * @return The corresponding user identity or null 358 */ 359 protected UserIdentity _validateToken(String token, String context) 360 { 361 return _authenticateTokenManager != null ? _authenticateTokenManager.validateToken(token, context) : null; 362 } 363 364 private TOKEN_MODE _getTokenMode(Parameters parameters) 365 { 366 return TOKEN_MODE.valueOf(parameters.getParameter(SITEMAP_PARAMETER_TOKEN_MODE, TOKEN_MODE.DEFAULT.toString()).toUpperCase()); 367 } 368 369 private void _saveLastKnownBlockingCredentialProvider(Request request, int runningCredentialProviderIndex) 370 { 371 if (runningCredentialProviderIndex != -1) 372 { 373 request.getSession(true).setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN, runningCredentialProviderIndex); 374 } 375 } 376 377 private Map _displayBlockingList(Redirector redirector, Request request, List<CredentialProvider> credentialProviders) throws IOException, ProcessingException, AuthorizationRequiredException 378 { 379 if (credentialProviders.stream().filter(cp -> cp instanceof BlockingCredentialProvider).findFirst().isPresent()) 380 { 381 _saveConnectingStateToSession(request, -1, true); 382 redirector.redirect(false, getLoginURL(request)); 383 return EMPTY_MAP; 384 } 385 else 386 { 387 // No way to login 388 throw new AuthorizationRequiredException(); 389 } 390 } 391 392 @SuppressWarnings("unchecked") 393 private boolean _shouldRunFirstBlockingCredentialProvider(int runningCredentialProviderIndex, List<CredentialProvider> credentialProviders, Request request, List<UserPopulation> chosenUserPopulations) 394 { 395 return runningCredentialProviderIndex >= 0 // There is a running credential provider 396 || credentialProviders.stream().filter(cp -> cp instanceof BlockingCredentialProvider).count() == 1 // There is a single blocking credential provider AND 397 && ( 398 ((List<UserPopulation>) request.getAttribute(REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST)).size() == chosenUserPopulations.size() // no population choice screen 399 || _getFirstBlockingCredentialProvider(credentialProviders).requiresNewWindow() // it does not requires a window opening 400 ); 401 } 402 403 private BlockingCredentialProvider _getFirstBlockingCredentialProvider(List<CredentialProvider> credentialProviders) 404 { 405 Optional<CredentialProvider> findFirst = credentialProviders.stream().filter(cp -> cp instanceof BlockingCredentialProvider).findFirst(); 406 if (findFirst.isPresent()) 407 { 408 return (BlockingCredentialProvider) findFirst.get(); 409 } 410 else 411 { 412 return null; 413 } 414 } 415 416 /** 417 * Fill the list of available users populations and credential providers 418 * @param request The request 419 * @param parameters The action parameters 420 * @param redirector The cocoon redirector 421 * @param chosenUserPopulations An empty non-null list to fill with with chosen populations 422 * @param credentialProviders An empty non-null list to fill with chosen credential providers 423 * @return true, if the population was determined, false if a redirection was required to choose 424 * @throws IOException If an error occurred 425 * @throws ProcessingException If an error occurred 426 */ 427 protected boolean _prepareUserPopulationsAndCredentialProviders(Request request, Parameters parameters, Redirector redirector, List<UserPopulation> chosenUserPopulations, List<CredentialProvider> credentialProviders) throws ProcessingException, IOException 428 { 429 // Get contexts 430 List<String> contexts = _getContexts(request, parameters); 431 request.setAttribute(REQUEST_ATTRIBUTE_CONTEXTS, contexts); 432 433 // All user populations for this context 434 List<UserPopulation> availableUserPopulations = _getAvailableUserPopulationsIds(request, contexts).stream().map(_userPopulationDAO::getUserPopulation).collect(Collectors.toList()); 435 request.setAttribute(REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST, availableUserPopulations); 436 437 // Chosen population 438 String userPopulationId = _getChosenUserPopulationId(request, availableUserPopulations); 439 request.setAttribute(REQUEST_ATTRIBUTE_USER_POPULATION_ID, userPopulationId); 440 441 chosenUserPopulations.addAll(userPopulationId == null ? availableUserPopulations : Collections.singletonList(_userPopulationDAO.getUserPopulation(userPopulationId))); 442 if (chosenUserPopulations.size() == 0) 443 { 444 String redirection = parameters.getParameter("nocontext-redirection", null); 445 if (redirection == null) 446 { 447 throw new IllegalStateException("There is no populations available for contexts '" + StringUtils.join(contexts, "', '") + "'"); 448 } 449 else 450 { 451 redirector.redirect(false, redirection); 452 return false; 453 } 454 } 455 456 // Get possible credential providers 457 boolean availableCredentialProviders = _hasCredentialProviders(chosenUserPopulations); 458 request.setAttribute(REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_LIST, availableCredentialProviders); 459 460 // null means the credential providers cannot be determine without knowing population first 461 if (!availableCredentialProviders) 462 { 463 request.setAttribute(REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST, true); 464 465 // if we are on this screen after a 'back' button hit, we need to reset connecting information 466 _resetConnectingStateToSession(request); 467 468 // Screen "Where Are You From?" with the list of populations to select 469 if (redirector != null) 470 { 471 redirector.redirect(false, getLoginURL(request)); 472 } 473 return false; 474 } 475 else 476 { 477 credentialProviders.addAll(chosenUserPopulations.get(0).getCredentialProviders()); 478 if (credentialProviders.size() == 0) 479 { 480 throw new IllegalStateException("There is no populations credential provider available for contexts '" + StringUtils.join(contexts, "', '") + "'"); 481 } 482 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()); 483 return true; 484 } 485 } 486 487 /** 488 * Get the url for the redirector to display the login screen 489 * @param request The request 490 * @return The url. Cannot be null or empty 491 */ 492 protected String getLoginURL(Request request) 493 { 494 return getLoginURLParameters(request, "cocoon://_plugins/core/login.html"); 495 } 496 497 498 /** 499 * Get the url for the redirector to display the login screen 500 * @param request The request 501 * @param baseURL The url to complete with parameters 502 * @return The url. Cannot be null or empty 503 */ 504 @SuppressWarnings("unchecked") 505 protected String getLoginURLParameters(Request request, String baseURL) 506 { 507 List<String> parameters = new ArrayList<>(); 508 509 Boolean invalidPopulationIds = (Boolean) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INVALID_POPULATION); 510 parameters.add("invalidPopulationIds=" + (invalidPopulationIds == Boolean.TRUE ? "true" : "false")); 511 512 boolean shouldDisplayUserPopulationsList = (boolean) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_SHOULD_DISPLAY_USER_POPULATIONS_LIST); 513 parameters.add("shouldDisplayUserPopulationsList=" + (shouldDisplayUserPopulationsList ? "true" : "false")); 514 515 List<UserPopulation> usersPopulations = (List<UserPopulation>) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST); 516 if (usersPopulations != null) 517 { 518 parameters.add("usersPopulations=" + URIUtils.encodeParameter(usersPopulations.stream().map(UserPopulation::getId).collect(Collectors.joining(",")))); 519 } 520 521 String chosenPopulationId = (String) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_USER_POPULATION_ID); 522 if (chosenPopulationId != null) 523 { 524 parameters.add("chosenPopulationId=" + URIUtils.encodeParameter(chosenPopulationId)); 525 } 526 527 boolean availableCredentialProviders = (boolean) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_LIST); 528 parameters.add("availableCredentialProviders=" + (availableCredentialProviders ? "true" : "false")); 529 530 Integer credentialProviderIndex = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_CREDENTIAL_PROVIDER_INDEX); 531 parameters.add("credentialProviderIndex=" + String.valueOf(credentialProviderIndex != null ? credentialProviderIndex : -1)); 532 533 List<String> contexts = (List<String>) request.getAttribute(REQUEST_ATTRIBUTE_CONTEXTS); 534 parameters.add("contexts=" + URIUtils.encodeParameter(StringUtils.join(contexts, ","))); 535 536 return baseURL + (baseURL.contains("?") ? "&" : "?") + StringUtils.join(parameters, "&"); 537 } 538 539 /** 540 * Get the url for the redirector to display the logout screen 541 * @param request The request 542 * @return The url. Cannot be null or empty 543 */ 544 protected String getLogoutURL(Request request) 545 { 546 return "cocoon://_plugins/core/logout.html"; 547 } 548 549 /** 550 * Determine if there is a list of credential providers to use 551 * @param userPopulations The list of applicable user populations 552 * @return true if credentialproviders can be used 553 */ 554 protected boolean _hasCredentialProviders(List<UserPopulation> userPopulations) 555 { 556 // Is there only one population or all populations have the same credential provider list as the first one? 557 if (userPopulations.size() == 1 558 || userPopulations.stream().map(UserPopulation::getCredentialProviders).distinct().count() == 1 559 && userPopulations.stream().map(this::_needsResetLinkOnFormCredential).distinct().count() == 1) 560 { 561 return true; 562 } 563 564 // Cannot determine the list 565 return false; 566 } 567 568 /** 569 * Check if the user population include a {@link FormCredentialProvider} 570 * that requires display of a reset password link 571 * @param userPopulation the user population 572 * @return true if a reset password link needs to be displayed 573 */ 574 private boolean _needsResetLinkOnFormCredential(UserPopulation userPopulation) 575 { 576 if (userPopulation.getCredentialProviders().stream() 577 .filter(FormCredentialProvider.class::isInstance) 578 .map(FormCredentialProvider.class::cast) 579 .map(FormCredentialProvider::displayResetLink) 580 .findAny().orElse(false)) 581 { 582 return userPopulation.getUserDirectories().stream() 583 .anyMatch(ModifiableUserDirectory.class::isInstance); 584 } 585 return false; 586 } 587 588 /** 589 * Get the available populations for the given contexts 590 * @param request The request 591 * @param contexts The contexts 592 * @return The non-null list of populations 593 */ 594 protected Set<String> _getAvailableUserPopulationsIds(Request request, List<String> contexts) 595 { 596 return _populationContextHelper.getUserPopulationsOnContexts(contexts, false, false); 597 } 598 599 /** 600 * Get the population for the given context 601 * @param request The request 602 * @param availableUserPopulations The available users populations 603 * @return The chosen population id. Can be null. 604 */ 605 protected String _getChosenUserPopulationId(Request request, List<UserPopulation> availableUserPopulations) 606 { 607 // Get request population choice 608 String userPopulationId = request.getParameter(REQUEST_PARAMETER_POPULATION_NAME); 609 if (userPopulationId == null) 610 { 611 // Get memorized population choice 612 Session session = request.getSession(false); 613 if (session != null) 614 { 615 userPopulationId = (String) session.getAttribute(SESSION_CONNECTING_USERPOPULATION_ID); 616 } 617 } 618 619 // A population choice was already made 620 if (StringUtils.isNotBlank(userPopulationId)) 621 { 622 final String finalUserPopulationId = userPopulationId; 623 if (availableUserPopulations.stream().anyMatch(userPopulation -> userPopulation.getId().equals(finalUserPopulationId))) 624 { 625 return userPopulationId; 626 } 627 else 628 { 629 // Wrong submitted population id 630 request.setAttribute(REQUEST_ATTRIBUTE_INVALID_POPULATION, true); 631 } 632 } 633 634 return null; 635 } 636 637 /** 638 * Try to authenticate with this credential provider in this mode. Delegates to _doProcess 639 * @param request The request 640 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 641 * @param runningCredentialProvider the Credential provider to test 642 * @param runningCredentialProviderIndex The index of the currently tested credential provider 643 * @param redirector The cocoon redirector 644 * @param userPopulations The list of possible user populations 645 * @return false if we should try with another Credential provider, true otherwise 646 * @throws Exception If an error occurred 647 */ 648 protected boolean _process(Request request, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider, int runningCredentialProviderIndex, Redirector redirector, List<UserPopulation> userPopulations) throws Exception 649 { 650 boolean existingSession = request.getSession(false) != null; 651 _saveConnectingStateToSession(request, runningBlockingkMode ? -1 : runningCredentialProviderIndex, runningBlockingkMode); 652 if (_doProcess(request, runningBlockingkMode, runningCredentialProvider, redirector, userPopulations)) 653 { 654 return true; 655 } 656 if (existingSession) 657 { 658 // A session was created but finally we do not need it 659 request.getSession().invalidate(); 660 } 661 return false; 662 } 663 664 /** 665 * Try to authenticate with this credential provider in this mode 666 * @param request The request 667 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 668 * @param runningCredentialProvider the Credential provider to test 669 * @param redirector The cocoon redirector 670 * @param userPopulations The list of possible user populations 671 * @return false if we should try with another Credential provider, true otherwise 672 * @throws Exception If an error occurred 673 */ 674 675 protected boolean _doProcess(Request request, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider, Redirector redirector, List<UserPopulation> userPopulations) throws Exception 676 { 677 if (runningCredentialProvider.grantAnonymousRequest(runningBlockingkMode)) 678 { 679 // Anonymous request 680 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 681 return true; 682 } 683 684 UserIdentity potentialUserIdentity = null; 685 try 686 { 687 688 potentialUserIdentity = runningCredentialProvider.getUserIdentity(runningBlockingkMode, redirector); 689 } 690 catch (WeakPasswordException e) 691 { 692 // User credentials are ok but user use a weak password 693 potentialUserIdentity = e.getUserIdentity(); 694 _handleWeakPassword(request, runningCredentialProvider, redirector, potentialUserIdentity); 695 } 696 697 if (redirector.hasRedirected()) 698 { 699 // getCredentials require a redirection, save state and proceed 700 return true; 701 } 702 else if (potentialUserIdentity == null) 703 { 704 // Let us try another credential provider 705 return false; 706 } 707 708 // Check if user exists 709 UserIdentity userIdentity = _getUserIdentity(userPopulations, potentialUserIdentity, redirector, runningBlockingkMode, runningCredentialProvider); 710 if (redirector.hasRedirected()) 711 { 712 // getCredentials require a redirection, save state and proceed 713 return true; 714 } 715 else if (userIdentity == null) 716 { 717 // Let us try another credential provider 718 return false; 719 } 720 721 // Save user identity 722 _setUserIdentityInSession(request, userIdentity, runningCredentialProvider, runningBlockingkMode); 723 724 // Authentication succeeded 725 runningCredentialProvider.userAllowed(runningBlockingkMode, userIdentity, redirector); 726 727 _logLoginEvent(runningCredentialProvider, userIdentity); 728 729 return true; 730 } 731 732 /** 733 * Handle weak password exception 734 * @param request the request 735 * @param runningCredentialProvider the credential provider that detected the weak password 736 * @param redirector the redirector 737 * @param userIdentity the user identity with a weak password 738 * @throws Exception if an error occurred 739 */ 740 protected void _handleWeakPassword(Request request, CredentialProvider runningCredentialProvider, Redirector redirector, UserIdentity userIdentity) throws Exception 741 { 742 ForensicLogger.info("authentication.form.weak.password", Map.of("userIdentity", userIdentity), userIdentity); 743 Optional<String> resetPasswordURI = _getWeakPasswordURI(request, userIdentity); 744 if (resetPasswordURI.isPresent()) 745 { 746 // Force redirect to weak password url to force change password 747 getLogger().info("Password of user " + userIdentity + " does not meet the security requirements. Force to change password."); 748 redirector.redirect(false, resetPasswordURI.get()); 749 return; 750 } 751 // Defaut implementation only log that user has a weak password. User will be authenticated 752 getLogger().warn("Password of user " + userIdentity + " does not meet the security requirements. User is authenticated despite the risk for security."); 753 } 754 755 /** 756 * Get the URI where the user should be redirected after a weak password is detected 757 * @param request the current request 758 * @param userIdentity the user identity with a weak password 759 * @return the absolute uri 760 */ 761 protected Optional<String> _getWeakPasswordURI(Request request, UserIdentity userIdentity) 762 { 763 // do not force password change in safe or maintenance mode 764 if (_userPasswordManager != null && RuntimeServlet.getRunMode() == RunMode.NORMAL) 765 { 766 return _userPasswordManager.getChangePasswordURI(request, userIdentity, true); 767 } 768 return Optional.empty(); 769 } 770 771 /** 772 * Log login event 773 * @param credentialProvider the running credential provider 774 * @param userIdentity the user identity 775 */ 776 protected void _logLoginEvent(CredentialProvider credentialProvider, UserIdentity userIdentity) 777 { 778 Map<String, Object> loginArgs = Map.of("credential-provider", credentialProvider.getCredentialProviderModelId(), "user", userIdentity); 779 ForensicLogger.info("authentication.login", loginArgs, userIdentity); 780 } 781 782 /** 783 * Log logout event 784 * @param userIdentity the user identity 785 */ 786 protected void _logLogoutEvent(UserIdentity userIdentity) 787 { 788 Map<String, Object> logoutArgs = Map.of("user", userIdentity); 789 ForensicLogger.info("authentication.logout", logoutArgs, userIdentity); 790 } 791 792 /** 793 * Reset the connecting information in session 794 * @param request The request 795 */ 796 protected static void _resetConnectingStateToSession(Request request) 797 { 798 Session session = request.getSession(false); 799 if (session != null) 800 { 801 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 802 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 803 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX_LASTBLOCKINGKNOWN); 804 session.removeAttribute(SESSION_CONNECTING_USERPOPULATION_ID); 805 } 806 } 807 808 /** 809 * When the process end successfully, save the state 810 * @param request The request 811 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 812 * @param runningCredentialProviderIndex the currently tested credential provider 813 */ 814 protected void _saveConnectingStateToSession(Request request, int runningCredentialProviderIndex, boolean runningBlockingkMode) 815 { 816 Session session = request.getSession(true); 817 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX, runningCredentialProviderIndex); 818 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE, runningBlockingkMode); 819 session.setAttribute(SESSION_CONNECTING_USERPOPULATION_ID, request.getAttribute(REQUEST_ATTRIBUTE_USER_POPULATION_ID)); 820 } 821 822 /** 823 * Save user identity in request 824 * @param request The request 825 * @param userIdentity The useridentity to save 826 * @param credentialProvider The credential provider used to connect 827 * @param blockingMode The mode used for the credential provider 828 */ 829 protected void _setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 830 { 831 setUserIdentityInSession(request, userIdentity, credentialProvider, blockingMode); 832 if (_observationManager != null) 833 { 834 Map<String, Object> eventParams = new HashMap<>(); 835 eventParams.put(ObservationConstants.ARGS_USER, userIdentity); 836 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_AUTHENTICATED, UserPopulationDAO.SYSTEM_USER_IDENTITY, eventParams)); 837 } 838 } 839 840 /** 841 * Save user identity in request 842 * @param request The request 843 * @param userIdentity The useridentity to save 844 * @param credentialProvider The credential provider used to connect 845 * @param blockingMode The mode used for the credential provider 846 */ 847 public static void setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 848 { 849 Session session = renewSession(request); 850 _resetConnectingStateToSession(request); 851 session.setAttribute(SESSION_USERIDENTITY, userIdentity); 852 session.setAttribute(SESSION_CREDENTIALPROVIDER, credentialProvider); 853 session.setAttribute(SESSION_CREDENTIALPROVIDER_MODE, blockingMode); 854 } 855 856 /** 857 * Change the session id (for security purposes) 858 * @param request The current request 859 * @return The new session 860 */ 861 public static Session renewSession(Request request) 862 { 863 Session session = request.getSession(true); 864 865 Map<String, Object> attributesCopy = new HashMap<>(); 866 867 Enumeration<String> attributeNames = session.getAttributeNames(); 868 while (attributeNames.hasMoreElements()) 869 { 870 String attributeName = attributeNames.nextElement(); 871 attributesCopy.put(attributeName, session.getAttribute(attributeName)); 872 } 873 874 session.invalidate(); 875 876 session = request.getSession(true); 877 878 for (Entry<String, Object> attribute : attributesCopy.entrySet()) 879 { 880 session.setAttribute(attribute.getKey(), attribute.getValue()); 881 } 882 883 return session; 884 } 885 886 /** 887 * Get the user identity of the connected user from the session 888 * @param request The request 889 * @return The connected useridentity or null 890 */ 891 protected UserIdentity _getUserIdentityFromSession(Request request) 892 { 893 return getUserIdentityFromSession(request); 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 public static UserIdentity getUserIdentityFromSession(Request request) 902 { 903 Session session = request.getSession(false); 904 if (session != null) 905 { 906 return (UserIdentity) session.getAttribute(SESSION_USERIDENTITY); 907 } 908 return null; 909 } 910 911 /** 912 * Get the credential provider used for the current connection 913 * @param request The request 914 * @return The credential provider used or null 915 */ 916 protected CredentialProvider _getCredentialProviderFromSession(Request request) 917 { 918 return getCredentialProviderFromSession(request); 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 public static CredentialProvider getCredentialProviderFromSession(Request request) 927 { 928 Session session = request.getSession(false); 929 if (session != null) 930 { 931 return (CredentialProvider) session.getAttribute(SESSION_CREDENTIALPROVIDER); 932 } 933 return null; 934 } 935 936 /** 937 * Get the credential provider mode used for the current connection 938 * @param request The request 939 * @return The credential provider mode used or null 940 */ 941 protected Boolean _getCredentialProviderModeFromSession(Request request) 942 { 943 return getCredentialProviderModeFromSession(request); 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 public static Boolean getCredentialProviderModeFromSession(Request request) 952 { 953 Session session = request.getSession(false); 954 if (session != null) 955 { 956 return (Boolean) session.getAttribute(SESSION_CREDENTIALPROVIDER_MODE); 957 } 958 return null; 959 } 960 961 /** 962 * If there is a running credential provider, was it in non-blocking or blocking mode? 963 * @param request The request 964 * @return false if non-blocking, true if blocking 965 */ 966 protected boolean _isCurrentCredentialProviderInBlockingMode(Request request) 967 { 968 if (StringUtils.equals(request.getParameter(REQUEST_PARAMETER_NONBLOCING), "force")) 969 { 970 return false; 971 } 972 973 Integer requestedCredentialParameterIndex = _getCurrentCredentialProviderIndexFromParameter(request); 974 if (requestedCredentialParameterIndex != null && requestedCredentialParameterIndex != -1) 975 { 976 return true; 977 } 978 979 Session session = request.getSession(false); 980 if (session != null) 981 { 982 Boolean mode = (Boolean) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 983 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_MODE); 984 if (mode != null) 985 { 986 return mode.booleanValue(); 987 } 988 } 989 return false; 990 } 991 992 /** 993 * Call this to skip the currently used credential provider and proceed to the next one. 994 * Useful for non blocking 995 * @param request The request 996 */ 997 public static void skipCurrentCredentialProvider(Request request) 998 { 999 Session session = request.getSession(); 1000 if (session != null) 1001 { 1002 Integer cpIndex = (Integer) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1003 if (cpIndex != null) 1004 { 1005 cpIndex++; 1006 session.setAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX, cpIndex); 1007 } 1008 } 1009 } 1010 1011 /** 1012 * Get the current credential provider index or -1 if there no running provider FROM REQUEST PARAMETER 1013 * @param request The request 1014 * @return The credential provider index to use in the availablesCredentialProviders list or -1 or null 1015 */ 1016 protected Integer _getCurrentCredentialProviderIndexFromParameter(Request request) 1017 { 1018 // Is the CP requested? 1019 String requestedCredentialParameterIndex = request.getParameter(REQUEST_PARAMETER_CREDENTIALPROVIDER_INDEX); 1020 if (StringUtils.isNotBlank(requestedCredentialParameterIndex)) 1021 { 1022 int index = Integer.parseInt(requestedCredentialParameterIndex); 1023 return index; 1024 } 1025 return null; 1026 } 1027 1028 /** 1029 * Get the current credential provider index or -1 if there no running provider 1030 * @param request The request 1031 * @param availableCredentialProviders The list of available credential provider 1032 * @return The credential provider index to use in the availablesCredentialProviders list or -1 1033 */ 1034 protected int _getCurrentCredentialProviderIndex(Request request, List<CredentialProvider> availableCredentialProviders) 1035 { 1036 // Is the CP requested? 1037 Integer requestedCredentialParameterIndex = _getCurrentCredentialProviderIndexFromParameter(request); 1038 if (requestedCredentialParameterIndex != null) 1039 { 1040 if (requestedCredentialParameterIndex < availableCredentialProviders.size()) 1041 { 1042 return requestedCredentialParameterIndex; 1043 } 1044 else 1045 { 1046 return -1; 1047 } 1048 } 1049 1050 // Was the CP memorized? 1051 Session session = request.getSession(false); 1052 if (session != null) 1053 { 1054 Integer cpIndex = (Integer) session.getAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1055 session.removeAttribute(SESSION_CONNECTING_CREDENTIALPROVIDER_INDEX); 1056 1057 if (cpIndex != null) 1058 { 1059 return cpIndex; 1060 } 1061 } 1062 1063 // Default value 1064 return -1; 1065 } 1066 1067 /** 1068 * Get the authentication context 1069 * @param request The request 1070 * @param parameters The action parameters 1071 * @return The context 1072 * @throws IllegalArgumentException If there is no context set 1073 */ 1074 protected List<String> _getContexts(Request request, Parameters parameters) 1075 { 1076 String context = parameters.getParameter("context", null); 1077 if (context == null) 1078 { 1079 throw new IllegalArgumentException("The authentication is not parameterized correctly: an authentication context must be specified"); 1080 } 1081 return Collections.singletonList(context); 1082 } 1083 1084 /** 1085 * Determine if the request is internal and do not need authentication 1086 * @param request The request 1087 * @return true to bypass this authentication 1088 */ 1089 protected boolean _internalRequest(Request request) 1090 { 1091 return "true".equals(request.getAttribute(REQUEST_ATTRIBUTE_AUTHENTICATED)) || request.getAttribute(REQUEST_ATTRIBUTE_INTERNAL_ALLOWED) != null; 1092 } 1093 1094 /** 1095 * Determine if the request is one of the authentication process (except the credential providers) 1096 * @param request The request 1097 * @return true to bypass this authentication 1098 */ 1099 protected boolean _acceptedUrl(Request request) 1100 { 1101 // URL without server context and leading slash. 1102 String url = (String) request.getAttribute(WorkspaceMatcher.IN_WORKSPACE_URL); 1103 for (Pattern pattern : _acceptedUrlPatterns) 1104 { 1105 if (pattern.matcher(url).matches()) 1106 { 1107 // Anonymous request 1108 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 1109 1110 return true; 1111 } 1112 } 1113 1114 return false; 1115 } 1116 1117 /** 1118 * This method ensure that there is a currently connected user and that it is still valid 1119 * @param request The request 1120 * @param redirector The cocoon redirector 1121 * @param parameters The action parameters 1122 * @return true if the user is connected and valid 1123 * @throws Exception if an error occurred 1124 */ 1125 protected boolean _validateCurrentlyConnectedUser(Request request, Redirector redirector, Parameters parameters) throws Exception 1126 { 1127 Session session = request.getSession(false); 1128 UserIdentity userCurrentlyConnected = _getUserIdentityFromSession(request); 1129 CredentialProvider runningCredentialProvider = _getCredentialProviderFromSession(request); 1130 Boolean runningBlockingkMode = _getCredentialProviderModeFromSession(request); 1131 1132 if (runningCredentialProvider == null || userCurrentlyConnected == null || runningBlockingkMode == null || !runningCredentialProvider.isStillConnected(runningBlockingkMode, userCurrentlyConnected, redirector)) 1133 { 1134 if (redirector.hasRedirected()) 1135 { 1136 return true; 1137 } 1138 1139 // There is an invalid connected user 1140 if (session != null && userCurrentlyConnected != null) 1141 { 1142 session.invalidate(); 1143 } 1144 return false; 1145 } 1146 1147 // let us make an exception for the user image url since we need it on the 403 page 1148 if (RuntimeServlet.getRunMode() == RunMode.MAINTENANCE && MaintenanceAction.acceptedUrl(request)) 1149 { 1150 return true; 1151 } 1152 1153 _validateCurrentlyConnectedUserIsInAuthorizedPopulation(userCurrentlyConnected, request, parameters); 1154 1155 return true; 1156 } 1157 1158 /** 1159 * This method is the second part of the process that ensure that there is a currently connected user and that it is still valid 1160 * @param userCurrentlyConnected The user to test 1161 * @param request The request 1162 * @param parameters The action parameters 1163 */ 1164 protected void _validateCurrentlyConnectedUserIsInAuthorizedPopulation(UserIdentity userCurrentlyConnected, Request request, Parameters parameters) 1165 { 1166 if (_getTokenMode(parameters) == TOKEN_MODE.DEFAULT) 1167 { 1168 // we know this is a valid user, but we need to check if the context is correct 1169 List<String> contexts = _getContexts(request, parameters); 1170 // All user populations for this context 1171 Set<String> availableUserPopulationsIds = _getAvailableUserPopulationsIds(request, contexts); 1172 1173 if (!availableUserPopulationsIds.contains(userCurrentlyConnected.getPopulationId())) 1174 { 1175 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."); 1176 } 1177 } 1178 else 1179 { 1180 // In 'token only' mode, check if user is part of the active populations (regardless of the context) 1181 List<String> availableUserPopulationsIds = _userPopulationDAO.getEnabledUserPopulations(false).stream().map(UserPopulation::getId).collect(Collectors.toList()); 1182 1183 if (!availableUserPopulationsIds.contains(userCurrentlyConnected.getPopulationId())) 1184 { 1185 throw new AccessDeniedException("The user " + userCurrentlyConnected + " cannot be authenticated because its populations does not exist or it is disabled."); 1186 } 1187 } 1188 } 1189 1190 /** 1191 * Test if user wants to logout and handle it 1192 * @param redirector The cocoon redirector 1193 * @param objectModel The cocoon object model 1194 * @param source The sitemap source 1195 * @param parameters The sitemap parameters 1196 * @return true if the user was logged out 1197 * @throws Exception if an error occurred 1198 */ 1199 protected boolean _handleLogout(Redirector redirector, Map objectModel, String source, Parameters parameters) throws Exception 1200 { 1201 Request request = ObjectModelHelper.getRequest(objectModel); 1202 if (StringUtils.equals(request.getContextPath() + request.getAttribute(WorkspaceMatcher.WORKSPACE_URI) + "/logout.html", request.getRequestURI()) 1203 || StringUtils.equals("true", parameters.getParameter("logout", "false"))) 1204 { 1205 // The user logs out 1206 UserIdentity currentUser = _currentUserProvider.getUser(); 1207 if (currentUser != null) // user can be null when calling the url with an expired session 1208 { 1209 _currentUserProvider.logout(redirector); 1210 _logLogoutEvent(currentUser); 1211 } 1212 1213 if (!redirector.hasRedirected()) 1214 { 1215 redirector.redirect(false, getLogoutURL(request)); 1216 } 1217 1218 return true; 1219 } 1220 return false; 1221 } 1222 1223 /** 1224 * Check the authentications of the authentication manager 1225 * @param userPopulations The list of available matching populations 1226 * @param redirector The cocoon redirector 1227 * @param runningBlockingkMode false for non-blocking mode, true for blocking mode 1228 * @param runningCredentialProvider The Credential provider to test 1229 * @param potentialUserIdentity A possible user identity. Population can be null. User may not exist either. 1230 * @return The user population matching credentials or null 1231 * @throws Exception If an error occurred 1232 * @throws AccessDeniedException If the user is rejected 1233 */ 1234 protected UserIdentity _getUserIdentity(List<UserPopulation> userPopulations, UserIdentity potentialUserIdentity, Redirector redirector, boolean runningBlockingkMode, CredentialProvider runningCredentialProvider) throws Exception 1235 { 1236 if (potentialUserIdentity.getPopulationId() == null) 1237 { 1238 for (UserPopulation up : userPopulations) 1239 { 1240 User user = _userManager.getUser(up, potentialUserIdentity.getLogin()); 1241 if (_isLoginCaseExact(user, potentialUserIdentity)) 1242 { 1243 return user.getIdentity(); 1244 } 1245 } 1246 } 1247 else 1248 { 1249 User user = _userManager.getUser(potentialUserIdentity.getPopulationId(), potentialUserIdentity.getLogin()); 1250 if (_isLoginCaseExact(user, potentialUserIdentity)) 1251 { 1252 return user.getIdentity(); 1253 } 1254 } 1255 1256 runningCredentialProvider.userNotAllowed(runningBlockingkMode, redirector); 1257 1258 if (getLogger().isWarnEnabled()) 1259 { 1260 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."); 1261 } 1262 1263 return null; 1264 } 1265 1266 private boolean _isLoginCaseExact(User user, UserIdentity potentialUserIdentity) 1267 { 1268 return user != null 1269 && (user.getUserDirectory().isCaseSensitive() && StringUtils.equals(user.getIdentity().getLogin(), potentialUserIdentity.getLogin()) 1270 || !user.getUserDirectory().isCaseSensitive() && StringUtils.equalsIgnoreCase(user.getIdentity().getLogin(), potentialUserIdentity.getLogin())); 1271 } 1272}