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