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