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