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