001/*
002 *  Copyright 2022 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.plugins.extrausermgt.authentication.oidc;
017
018import java.io.IOException;
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.net.URL;
022import java.util.Date;
023import java.util.List;
024import java.util.Map;
025import java.util.stream.Collectors;
026import java.util.stream.Stream;
027
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.cocoon.ProcessingException;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Redirector;
037import org.apache.cocoon.environment.Request;
038import org.apache.cocoon.environment.Session;
039import org.apache.commons.lang3.StringUtils;
040
041import org.ametys.core.authentication.AbstractCredentialProvider;
042import org.ametys.core.authentication.AuthenticateAction;
043import org.ametys.core.authentication.BlockingCredentialProvider;
044import org.ametys.core.authentication.NonBlockingCredentialProvider;
045import org.ametys.core.user.UserIdentity;
046import org.ametys.core.user.directory.NotUniqueUserException;
047import org.ametys.core.user.directory.StoredUser;
048import org.ametys.core.user.directory.UserDirectory;
049import org.ametys.core.user.population.UserPopulation;
050import org.ametys.plugins.extrausermgt.authentication.oidc.endofauthenticationprocess.EndOfAuthenticationProcess;
051import org.ametys.runtime.authentication.AccessDeniedException;
052import org.ametys.workspaces.extrausermgt.authentication.oidc.OIDCCallbackAction;
053
054import com.nimbusds.jose.JOSEException;
055import com.nimbusds.jose.JWSAlgorithm;
056import com.nimbusds.jose.proc.BadJOSEException;
057import com.nimbusds.jwt.JWT;
058import com.nimbusds.oauth2.sdk.AuthorizationCode;
059import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
060import com.nimbusds.oauth2.sdk.AuthorizationGrant;
061import com.nimbusds.oauth2.sdk.ParseException;
062import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
063import com.nimbusds.oauth2.sdk.ResponseType;
064import com.nimbusds.oauth2.sdk.Scope;
065import com.nimbusds.oauth2.sdk.SerializeException;
066import com.nimbusds.oauth2.sdk.TokenErrorResponse;
067import com.nimbusds.oauth2.sdk.TokenRequest;
068import com.nimbusds.oauth2.sdk.TokenResponse;
069import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
070import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
071import com.nimbusds.oauth2.sdk.auth.Secret;
072import com.nimbusds.oauth2.sdk.http.HTTPResponse;
073import com.nimbusds.oauth2.sdk.id.ClientID;
074import com.nimbusds.oauth2.sdk.id.Issuer;
075import com.nimbusds.oauth2.sdk.id.State;
076import com.nimbusds.oauth2.sdk.token.AccessToken;
077import com.nimbusds.oauth2.sdk.token.RefreshToken;
078import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
079import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
080import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
081import com.nimbusds.openid.connect.sdk.Prompt;
082import com.nimbusds.openid.connect.sdk.UserInfoRequest;
083import com.nimbusds.openid.connect.sdk.UserInfoResponse;
084import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
085import com.nimbusds.openid.connect.sdk.claims.UserInfo;
086import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
087import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
088
089/**
090 * Sign in (through Google, facebook...) using the OpenId Connect (OIDC) protocol.
091 */
092public abstract class AbstractOIDCCredentialProvider extends AbstractCredentialProvider implements BlockingCredentialProvider, NonBlockingCredentialProvider, Contextualizable, Serviceable
093{
094    /** Session attribute for OIDC */
095    public static final String REDIRECT_URI_SESSION_ATTRIBUTE = "oidc_actualRedirectUri";
096    /** Session attribute for OIDC*/
097    public static final String TOKEN_SESSION_ATTRIBUTE = "oidc_token";
098    /** Session date attribute for OIDC*/
099    public static final String EXPDATE_SESSION_ATTRIBUTE = "oidc_expirationDate";
100    /** Session attribute for OIDC*/
101    public static final String REFRESH_TOKEN_SESSION_ATTRIBUTE = "oidc_refreshToken";
102    /** Session attribute for OIDC*/
103    public static final String STATE_SESSION_ATTRIBUTE = "oidc_state";
104
105    private static final String __ATTRIBUTE_SILENT = "oidc_silent";
106    
107    /** Scope  for the authentication request */
108    protected Scope _scope;
109    
110    /** URI for the authentication request */
111    protected URI _authUri;
112    
113    /** URI for the token request */
114    protected URI _tokenEndpointUri;
115    
116    /** URI for the user info request */
117    protected URI _userInfoEndpoint;
118    
119    /** jwk URL for the validation of the token */
120    protected URL _jwkSetURL;
121    
122    /** Issuer  for the validation of the token */
123    protected Issuer _iss;
124    
125    /** Ametys context */
126    protected Context _context;
127    /** Client ID */
128    protected ClientID _clientID;
129    /** Client secret */
130    protected Secret _clientSecret;
131    
132    /** If we should try to authenticate silently */
133    protected boolean _silent;
134    
135    private EndOfAuthenticationProcess _endOfAuthenticationProcess;
136    
137    public void contextualize(Context context) throws ContextException
138    {
139        _context = context;
140    }
141    
142    public void service(ServiceManager manager) throws ServiceException
143    {
144        _endOfAuthenticationProcess = (EndOfAuthenticationProcess) manager.lookup(EndOfAuthenticationProcess.ROLE);
145    }
146    
147    @Override
148    public void init(String id, String cpModelId, Map<String, Object> paramValues, String label) throws Exception
149    {
150        super.init(id, cpModelId, paramValues, label);
151        _clientID = new ClientID(paramValues.get("authentication.oidc.idclient").toString());
152        _clientSecret = new Secret(paramValues.get("authentication.oidc.clientsecret").toString());
153        _silent = (boolean) paramValues.get("authentication.oidc.silent");
154
155        initUrisScope();
156    }
157    
158    /**
159     * get the client authentication info for the token end point
160     * @return the client authentication
161     */
162    protected ClientAuthentication getClientAuthentication()
163    {
164        return new ClientSecretBasic(_clientID, _clientSecret);
165    }
166    
167    public boolean blockingGrantAnonymousRequest()
168    {
169        return false;
170    }
171    
172    @Override
173    public boolean nonBlockingGrantAnonymousRequest()
174    {
175        return false;
176    }
177    
178    public boolean blockingIsStillConnected(UserIdentity userIdentity, Redirector redirector) throws Exception
179    {
180        Request request = ContextHelper.getRequest(_context);
181        Session session = request.getSession(true);
182   
183        Date expDat = (Date) session.getAttribute(EXPDATE_SESSION_ATTRIBUTE);
184        if (new Date().before(expDat))
185        {
186            return true;
187        }
188        
189        RefreshToken refreshToken = (RefreshToken) session.getAttribute(REFRESH_TOKEN_SESSION_ATTRIBUTE);
190        AuthorizationGrant refreshTokenGrant = new RefreshTokenGrant(refreshToken);
191        
192        // The credentials to authenticate the client at the token endpoint
193        ClientAuthentication clientAuth = getClientAuthentication();
194        
195        // Make the token request
196        OIDCTokens tokens = requestToken(clientAuth, refreshTokenGrant);
197
198        // idToken to validate the token
199        JWT idToken = tokens.getIDToken();
200        // accessToken to be able to access the user info
201        AccessToken accessToken = tokens.getAccessToken();
202        IDTokenClaimsSet claims = validateIdToken(idToken);
203        session.setAttribute(EXPDATE_SESSION_ATTRIBUTE, claims.getExpirationTime());
204        session.setAttribute(TOKEN_SESSION_ATTRIBUTE, accessToken);
205        
206        return true;
207    }
208    
209    @Override
210    public boolean nonBlockingIsStillConnected(UserIdentity userIdentity, Redirector redirector) throws Exception
211    {
212        return blockingIsStillConnected(userIdentity, redirector);
213    }
214    
215    private UserIdentity _login(boolean silent, Redirector redirector) throws Exception
216    {
217        Request request = ContextHelper.getRequest(_context);
218        Session session = request.getSession(true);
219        
220        URI redirectUri = _buildRedirectUri();
221
222        getLogger().debug("OIDCCredentialProvider callback URI: {}", redirectUri);
223        
224        boolean wasSilent = false;
225        if (silent)
226        {
227            wasSilent = "true".equals(session.getAttribute(__ATTRIBUTE_SILENT));
228        }
229   
230        String code = request.getParameter("code");
231        // if the code is null, then this is the first time the user sign-in
232        // if no state are stored in session, then the code belongs to a previous session. Restart
233        if (code == null || session.getAttribute(STATE_SESSION_ATTRIBUTE) == null)
234        {
235            signIn(redirector, redirectUri, silent, wasSilent, session);
236            return null;
237        }
238
239        // we got an authorization code
240        // but first, check the state to prevent CSRF attacks
241        checkState();
242        AuthorizationCode authCode = new AuthorizationCode(code);
243        // get the tokens (id token and access token)
244        OIDCTokens tokens = requestToken(authCode, redirectUri);
245
246        // idToken to validate the token
247        JWT idToken = tokens.getIDToken();
248        // accessToken to be able to access the user info
249        AccessToken accessToken = tokens.getAccessToken();
250        RefreshToken refreshToken = tokens.getRefreshToken();
251        
252        session.setAttribute(REFRESH_TOKEN_SESSION_ATTRIBUTE, refreshToken);
253        
254        // validate id token
255        IDTokenClaimsSet claims = validateIdToken(idToken);
256
257        // set expirationTime
258        claims.getExpirationTime();
259        session.setAttribute(EXPDATE_SESSION_ATTRIBUTE, claims.getExpirationTime());
260        
261        UserInfo userInfo = getUserInfo(accessToken);
262        
263        // then the user is finally logged in
264        return getUserIdentity(userInfo, request, redirector);
265    }
266
267    public UserIdentity blockingGetUserIdentity(Redirector redirector) throws Exception
268    {
269        return _login(false, redirector);
270    }
271    
272    public UserIdentity nonBlockingGetUserIdentity(Redirector redirector) throws Exception
273    {
274        if (!_silent)
275        {
276            return null;
277        }
278        
279        return _login(true, redirector);
280    }
281    
282    public void blockingUserNotAllowed(Redirector redirector) throws Exception
283    {
284        // Nothing to do.
285    }
286
287    @Override
288    public void nonBlockingUserNotAllowed(Redirector redirector) throws Exception
289    {
290        // Nothing to do.
291    }
292
293    public void blockingUserAllowed(UserIdentity userIdentity, Redirector redirector) throws Exception
294    {
295        Request request = ContextHelper.getRequest(_context);
296        Session session = request.getSession(true);
297        String redirectUri = (String) session.getAttribute(AbstractOIDCCredentialProvider.REDIRECT_URI_SESSION_ATTRIBUTE);
298        redirector.redirect(true, redirectUri);
299    }
300    
301    @Override
302    public void nonBlockingUserAllowed(UserIdentity userIdentity, Redirector redirector) throws Exception
303    {
304        blockingUserAllowed(userIdentity, redirector);
305    }
306
307    public boolean requiresNewWindow()
308    {
309        return true;
310    }
311    
312    private UserPopulation _getPopulation(Request request)
313    {
314        @SuppressWarnings("unchecked")
315        List<UserPopulation> userPopulations = (List<UserPopulation>) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST);
316
317        // If the list has only one element
318        if (userPopulations.size() == 1)
319        {
320            return userPopulations.get(0);
321        }
322
323        // In this list a population was maybe chosen?
324        final String chosenUserPopulationId = (String) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_USER_POPULATION_ID);
325        if (StringUtils.isNotBlank(chosenUserPopulationId))
326        {
327            return userPopulations.stream()
328                    .filter(userPopulation -> StringUtils.equals(userPopulation.getId(), chosenUserPopulationId))
329                    .findFirst()
330                    .get();
331        }
332
333        // Cannot work here...
334        throw new IllegalStateException("The " + this.getClass().getName() + " does not work when population is not known");
335    }
336    
337    /**
338     * Initialize the URIs
339     * @throws AccessDeniedException If an error occurs
340     */
341    protected abstract void initUrisScope() throws AccessDeniedException;
342    
343    /**
344     * Builds the redirect URI and the actual redirect URI
345     * @return The redirect <code>URI</code> and saves the actual redirect <code>URI</code>
346     * @throws URISyntaxException If an error occurs
347     */
348    private URI _buildRedirectUri() throws URISyntaxException
349    {
350        Request request = ContextHelper.getRequest(_context);
351        
352        // creation of the actual redirect URI (The one we actually want to go back to)
353        StringBuilder actualRedirectUri = new StringBuilder(request.getRequestURI());
354        String queryString = request.getQueryString();
355        if (queryString != null)
356        {
357            // remove any existing code or state from the request param if any exist, they are outdated
358            queryString = Stream.of(StringUtils.split(queryString, "&"))
359                    .filter(str -> !StringUtils.startsWithAny(str, "code=", "state="))
360                    .collect(Collectors.joining("&"));
361            if (StringUtils.isNotEmpty(queryString))
362            {
363                actualRedirectUri.append("?");
364                actualRedirectUri.append(queryString);
365            }
366        }
367        
368        // saving the actualRedirectUri to enable its use in "OIDCCallbackAction"
369        Session session = request.getSession(true);
370        session.setAttribute(REDIRECT_URI_SESSION_ATTRIBUTE, actualRedirectUri.toString());
371        
372        // creation of redirect URI (the issuer (google, facebook, etc.) is going to redirect to)
373        return buildAbsoluteURI(request, OIDCCallbackAction.CALLBACK_URL);
374    }
375
376    /**
377     * Computes the callback uri
378     * @param request the current request
379     * @param path the callback path
380     * @return the callback uri
381     */
382    protected URI buildAbsoluteURI(Request request, String path)
383    {
384        StringBuilder uriBuilder = new StringBuilder()
385            .append(request.getScheme())
386            .append("://")
387            .append(request.getServerName());
388        
389        if (request.isSecure())
390        {
391            if (request.getServerPort() != 443)
392            {
393                uriBuilder.append(":");
394                uriBuilder.append(request.getServerPort());
395            }
396        }
397        else
398        {
399            if (request.getServerPort() != 80)
400            {
401                uriBuilder.append(":");
402                uriBuilder.append(request.getServerPort());
403            }
404        }
405
406        uriBuilder.append(request.getContextPath());
407        uriBuilder.append(path);
408        
409        return URI.create(uriBuilder.toString());
410    }
411    
412    /**
413     * Sign the user in by sending an authentication request to the issuer
414     * @param redirector The redirector
415     * @param redirectUri The redirect URI
416     * @param silent if the user should be silently signed in 
417     * @param wasSilent indicates that we already passed through this, to prevent infinite loops
418     * @param session The current session
419     * @throws ProcessingException If an error occurs
420     * @throws IOException If an error occurs
421     */
422    protected void signIn(Redirector redirector, URI redirectUri, boolean silent, boolean wasSilent, Session session) throws ProcessingException, IOException
423    {
424        // sign-in request: redirect the client through the actual authentication process
425
426        if (wasSilent)
427        {
428            // already passed through this, there should have been some error somewhere
429            return;
430        }
431        
432        if (silent)
433        {
434            session.setAttribute(__ATTRIBUTE_SILENT, "true");
435        }
436        
437        // creation of the state used to secure the process
438        State state = new State();
439        session.setAttribute(STATE_SESSION_ATTRIBUTE, state);
440
441        // compose the request
442        AuthenticationRequest authenticationRequest = new AuthenticationRequest.Builder(new ResponseType(ResponseType.Value.CODE), _scope, _clientID, redirectUri)
443                                                                               .endpointURI(_authUri)
444                                                                               .state(state)
445                                                                               .prompt(silent ? Prompt.Type.NONE : null)
446                                                                               .build();
447        
448        String authReqURI = authenticationRequest.toURI().toString() + "&access_type=offline";
449        
450        redirector.redirect(false, authReqURI);
451    }
452    
453    /**
454     * Checks the State parameter of the request to prevent CSRF attacks
455     * @throws AccessDeniedException If an error occurs
456     */
457    protected void checkState() throws AccessDeniedException
458    {
459        Request request = ContextHelper.getRequest(_context);
460        Session session = request.getSession(true);
461        String storedState = session.getAttribute(STATE_SESSION_ATTRIBUTE).toString();
462        String stateRequest = request.getParameter("state");
463        
464        if (!storedState.equals(stateRequest))
465        {
466            getLogger().error("OIDC state mismatch. Method checkState of AbstractOIDCCredentialProvider");
467            throw new AccessDeniedException("OIDC state mismatch");
468        }
469        
470        session.setAttribute(STATE_SESSION_ATTRIBUTE, null);
471    }
472    
473    /**
474     * Request the tokens (ID token and Access token)
475     * @param authCode The authorization code from the authentication request
476     * @param redirectUri The redirect URI
477     * @return The <code>OIDCTokens</code> that contains the access token and the id token
478     * @throws AccessDeniedException If an error occurs
479     */
480    protected OIDCTokens requestToken(AuthorizationCode authCode, URI redirectUri) throws AccessDeniedException
481    {
482        // token request: checking if the user is known
483        TokenRequest tokenReq = new TokenRequest(_tokenEndpointUri, getClientAuthentication(), new AuthorizationCodeGrant(authCode, redirectUri), null);
484        // sending request
485        HTTPResponse tokenHTTPResp = null;
486        try
487        {
488            tokenHTTPResp = tokenReq.toHTTPRequest().send();
489        }
490        catch (SerializeException | IOException e)
491        {
492            getLogger().error("OIDC token request failed ", e);
493            throw new AccessDeniedException("OIDC token request failed");
494        }
495
496        // cast the HTTPResponse to TokenResponse
497        TokenResponse tokenResponse = null;
498        try
499        {
500            tokenResponse = OIDCTokenResponseParser.parse(tokenHTTPResp);
501        }
502        catch (ParseException e)
503        {
504            getLogger().error("OIDC token request result invalid ", e);
505            throw new AccessDeniedException("OIDC token request result invalid");
506        }
507
508        if (tokenResponse instanceof TokenErrorResponse)
509        {
510            getLogger().error("OIDC token request invalid token response instance of TokenErrorResponse in method requestToken from AbstractOIDCCredentialProvider");
511            throw new AccessDeniedException("OIDC token request result invalid");
512        }
513
514        // get the tokens
515        OIDCTokenResponse  accessTokenResponse = (OIDCTokenResponse) tokenResponse;
516        
517        return accessTokenResponse.getOIDCTokens();
518    }
519    
520    /**
521     * Request the tokens using a refresh token
522     * @param clientAuth The client authentication
523     * @param refreshTokenGrant The refreshtokenGrant
524     * @return The <code>OIDCTokens</code> that contains the access token and the id token
525     * @throws AccessDeniedException If an error occurs
526     * @throws URISyntaxException If an error occurs
527     */
528    protected OIDCTokens requestToken(ClientAuthentication clientAuth, AuthorizationGrant refreshTokenGrant) throws AccessDeniedException, URISyntaxException
529    {
530        // token request: checking if the user is known
531        TokenRequest tokenReq = new TokenRequest(_tokenEndpointUri, clientAuth, refreshTokenGrant, null);
532        // sending request
533       
534        HTTPResponse tokenHTTPResp = null;
535        try
536        {
537            tokenHTTPResp = tokenReq.toHTTPRequest().send();
538        }
539        catch (SerializeException | IOException e)
540        {
541            getLogger().error("OIDC token request failed ", e);
542            throw new AccessDeniedException("OIDC token request failed");
543        }
544
545        // cast the HTTPResponse to TokenResponse
546        TokenResponse tokenResponse = null;
547        try
548        {
549            tokenResponse = OIDCTokenResponseParser.parse(tokenHTTPResp);
550        }
551        catch (ParseException e)
552        {
553            getLogger().error("OIDC token request result invalid ", e);
554            throw new AccessDeniedException("OIDC token request result invalid");
555        }
556
557        if (tokenResponse instanceof TokenErrorResponse)
558        {
559            getLogger().error("OIDC token request result invalid: tokenResponse instance of TokenErrorResponse in method requestToken from AbstractOIDCCredentialProvider");
560            throw new AccessDeniedException("OIDC token request result invalid");
561        }
562
563        // get the tokens
564        OIDCTokenResponse  accessTokenResponse = (OIDCTokenResponse) tokenResponse;
565        
566        return accessTokenResponse.getOIDCTokens();
567    }
568    
569    /**
570     * Validate the id token from the token request
571     * @param idToken The id token from the token request
572     * @return The <code>IDTokenClaimsSet</code> that contains information on the connection such as the expiration time
573     * @throws AccessDeniedException If an error occurs
574     */
575    protected IDTokenClaimsSet validateIdToken(JWT idToken) throws AccessDeniedException
576    {
577        JWSAlgorithm jwsAlg = JWSAlgorithm.RS256;
578        // create validator for signed ID tokens
579        IDTokenValidator validator = new IDTokenValidator(_iss, _clientID, jwsAlg, _jwkSetURL);
580        IDTokenClaimsSet claims;
581        
582        try
583        {
584            claims = validator.validate(idToken, null);
585        }
586        catch (BadJOSEException e)
587        {
588            getLogger().error("OIDC invalid : issuer, clientId, jwsAlg or jwkSetURL", e);
589            throw new AccessDeniedException("OIDC invalid signature issuer, clientId, jwsAlg or jwkSetURL");
590        }
591        catch (JOSEException e)
592        {
593            getLogger().error("OIDC error while validating token", e);
594            throw new AccessDeniedException("OIDC error while validating token");
595        }
596        
597        return claims;
598    }
599    
600    /**
601     * Request the userInfo using the user info end point and an access token
602     * @param accessToken the access token to retrieve the user info
603     * @return a representation of the user info from the scope requested with the token
604     * @throws IOException if an error occurred while contacting the end point
605     * @throws ParseException if an error occurred while parsing the end point answer
606     */
607    protected UserInfo getUserInfo(AccessToken accessToken) throws IOException, ParseException
608    {
609        HTTPResponse httpResponse = new UserInfoRequest(_userInfoEndpoint, accessToken).toHTTPRequest().send();
610        UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
611        
612        if (userInfoResponse.indicatesSuccess())
613        {
614            return userInfoResponse.toSuccessResponse().getUserInfo();
615        }
616        else
617        {
618            String error = userInfoResponse.toErrorResponse().getErrorObject().toJSONObject().toJSONString();
619            getLogger().error("Failed to retrieve the user info. The server indicate the following error :\n" + error);
620            throw new AccessDeniedException("Failed to retrieve the user info. The server indicate the following error :\n" + error);
621        }
622    }
623
624    /**
625     * Compute a user identity based on the user info
626     * @param userInfo the user info
627     * @param request the original request
628     * @param redirector the redirector to use if need be
629     * @return the identified user info or null if no matching user were found
630     * @throws NotUniqueUserException if multiple user matched
631     */
632    protected UserIdentity getUserIdentity(UserInfo userInfo, Request request, Redirector redirector) throws NotUniqueUserException
633    {
634        // get the user email
635        String login = userInfo.getEmailAddress();
636        if (login == null)
637        {
638            getLogger().error("Email not found, connection canceled ");
639            throw new AccessDeniedException("Email not found, connection canceled");
640        }
641        
642        // create a UserIdentity from the email
643        UserPopulation userPopulation = _getPopulation(request);
644        UserIdentity user = _getUserIdentity(login, userPopulation);
645    
646        // If we found a UserIdentity, we return it
647        if (user != null)
648        {
649            return user;
650        }
651    
652        // If not, we are going to pre-sign-up the user with its email, firstname and lastname
653        String firstName = userInfo.getGivenName();
654        String lastName = userInfo.getFamilyName();
655        if (firstName == null || lastName == null)
656        {
657            getLogger().info("The fields could not be pre-filled");
658        }
659        
660        // We call the temporarySignup method from the endOfSignupProcess, which will do nothing if it is in the CMS and temporary sign the user up if it is the site
661        _endOfAuthenticationProcess.unexistingUser(login, firstName, lastName, userPopulation, redirector, request);
662        
663        return null;
664    }
665
666    private UserIdentity _getUserIdentity(String login, UserPopulation userPopulation) throws NotUniqueUserException
667    {
668        StoredUser storedUser = null;
669        
670        for (UserDirectory userDirectory : userPopulation.getUserDirectories())
671        {
672            storedUser = userDirectory.getStoredUser(login);
673
674            if (storedUser == null)
675            {
676                // Try to get user by email
677                storedUser = userDirectory.getStoredUserByEmail(login);
678            }
679            
680            if (storedUser != null)
681            {
682                return userDirectory.getUserIdentity(storedUser);
683            }
684        }
685        
686        return null;
687    }
688}