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