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 OIDCBasedCredentialProvider, 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 public String getClientId() 159 { 160 return _clientID.getValue(); 161 } 162 163 public String getIssuer() 164 { 165 return _iss.getValue(); 166 } 167 168 public URL getJwkSetURL() 169 { 170 return _jwkSetURL; 171 } 172 173 /** 174 * get the client authentication info for the token end point 175 * @return the client authentication 176 */ 177 protected ClientAuthentication getClientAuthentication() 178 { 179 return new ClientSecretBasic(_clientID, _clientSecret); 180 } 181 182 public boolean blockingGrantAnonymousRequest() 183 { 184 return false; 185 } 186 187 @Override 188 public boolean nonBlockingGrantAnonymousRequest() 189 { 190 return false; 191 } 192 193 public boolean blockingIsStillConnected(UserIdentity userIdentity, Redirector redirector) throws Exception 194 { 195 Request request = ContextHelper.getRequest(_context); 196 Session session = request.getSession(true); 197 198 Date expDat = (Date) session.getAttribute(EXPDATE_SESSION_ATTRIBUTE); 199 if (new Date().before(expDat)) 200 { 201 return true; 202 } 203 204 RefreshToken refreshToken = (RefreshToken) session.getAttribute(REFRESH_TOKEN_SESSION_ATTRIBUTE); 205 AuthorizationGrant refreshTokenGrant = new RefreshTokenGrant(refreshToken); 206 207 // The credentials to authenticate the client at the token endpoint 208 ClientAuthentication clientAuth = getClientAuthentication(); 209 210 // Make the token request 211 OIDCTokens tokens = requestToken(clientAuth, refreshTokenGrant); 212 213 // idToken to validate the token 214 JWT idToken = tokens.getIDToken(); 215 // accessToken to be able to access the user info 216 AccessToken accessToken = tokens.getAccessToken(); 217 IDTokenClaimsSet claims = validateIdToken(idToken); 218 session.setAttribute(EXPDATE_SESSION_ATTRIBUTE, claims.getExpirationTime()); 219 session.setAttribute(TOKEN_SESSION_ATTRIBUTE, accessToken); 220 221 return true; 222 } 223 224 @Override 225 public boolean nonBlockingIsStillConnected(UserIdentity userIdentity, Redirector redirector) throws Exception 226 { 227 return blockingIsStillConnected(userIdentity, redirector); 228 } 229 230 private UserIdentity _login(boolean silent, Redirector redirector) throws Exception 231 { 232 Request request = ContextHelper.getRequest(_context); 233 Session session = request.getSession(true); 234 235 URI redirectUri = _buildRedirectUri(); 236 237 getLogger().debug("OIDCCredentialProvider callback URI: {}", redirectUri); 238 239 boolean wasSilent = false; 240 if (silent) 241 { 242 wasSilent = "true".equals(session.getAttribute(__ATTRIBUTE_SILENT)); 243 } 244 245 String code = request.getParameter("code"); 246 // if the code is null, then this is the first time the user sign-in 247 // if no state are stored in session, then the code belongs to a previous session. Restart 248 if (code == null || session.getAttribute(STATE_SESSION_ATTRIBUTE) == null) 249 { 250 signIn(redirector, redirectUri, silent, wasSilent, session); 251 return null; 252 } 253 254 // we got an authorization code 255 // but first, check the state to prevent CSRF attacks 256 checkState(); 257 AuthorizationCode authCode = new AuthorizationCode(code); 258 // get the tokens (id token and access token) 259 OIDCTokens tokens = requestToken(authCode, redirectUri); 260 261 // idToken to validate the token 262 JWT idToken = tokens.getIDToken(); 263 // accessToken to be able to access the user info 264 AccessToken accessToken = tokens.getAccessToken(); 265 RefreshToken refreshToken = tokens.getRefreshToken(); 266 267 session.setAttribute(REFRESH_TOKEN_SESSION_ATTRIBUTE, refreshToken); 268 269 // validate id token 270 IDTokenClaimsSet claims = validateIdToken(idToken); 271 272 // set expirationTime 273 claims.getExpirationTime(); 274 session.setAttribute(EXPDATE_SESSION_ATTRIBUTE, claims.getExpirationTime()); 275 276 UserInfo userInfo = getUserInfo(accessToken); 277 278 // then the user is finally logged in 279 return getUserIdentity(userInfo, request, redirector); 280 } 281 282 public UserIdentity blockingGetUserIdentity(Redirector redirector) throws Exception 283 { 284 return _login(false, redirector); 285 } 286 287 public UserIdentity nonBlockingGetUserIdentity(Redirector redirector) throws Exception 288 { 289 if (!_silent) 290 { 291 return null; 292 } 293 294 return _login(true, redirector); 295 } 296 297 public void blockingUserNotAllowed(Redirector redirector) throws Exception 298 { 299 // Nothing to do. 300 } 301 302 @Override 303 public void nonBlockingUserNotAllowed(Redirector redirector) throws Exception 304 { 305 // Nothing to do. 306 } 307 308 public void blockingUserAllowed(UserIdentity userIdentity, Redirector redirector) throws Exception 309 { 310 Request request = ContextHelper.getRequest(_context); 311 Session session = request.getSession(true); 312 String redirectUri = (String) session.getAttribute(AbstractOIDCCredentialProvider.REDIRECT_URI_SESSION_ATTRIBUTE); 313 redirector.redirect(true, redirectUri); 314 } 315 316 @Override 317 public void nonBlockingUserAllowed(UserIdentity userIdentity, Redirector redirector) throws Exception 318 { 319 blockingUserAllowed(userIdentity, redirector); 320 } 321 322 public boolean requiresNewWindow() 323 { 324 return true; 325 } 326 327 private UserPopulation _getPopulation(Request request) 328 { 329 @SuppressWarnings("unchecked") 330 List<UserPopulation> userPopulations = (List<UserPopulation>) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST); 331 332 // If the list has only one element 333 if (userPopulations.size() == 1) 334 { 335 return userPopulations.get(0); 336 } 337 338 // In this list a population was maybe chosen? 339 final String chosenUserPopulationId = (String) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_USER_POPULATION_ID); 340 if (StringUtils.isNotBlank(chosenUserPopulationId)) 341 { 342 return userPopulations.stream() 343 .filter(userPopulation -> StringUtils.equals(userPopulation.getId(), chosenUserPopulationId)) 344 .findFirst() 345 .get(); 346 } 347 348 // Cannot work here... 349 throw new IllegalStateException("The " + this.getClass().getName() + " does not work when population is not known"); 350 } 351 352 /** 353 * Initialize the URIs 354 * @throws AccessDeniedException If an error occurs 355 */ 356 protected abstract void initUrisScope() throws AccessDeniedException; 357 358 /** 359 * Builds the redirect URI and the actual redirect URI 360 * @return The redirect <code>URI</code> and saves the actual redirect <code>URI</code> 361 * @throws URISyntaxException If an error occurs 362 */ 363 private URI _buildRedirectUri() throws URISyntaxException 364 { 365 Request request = ContextHelper.getRequest(_context); 366 367 // creation of the actual redirect URI (The one we actually want to go back to) 368 StringBuilder actualRedirectUri = new StringBuilder(request.getRequestURI()); 369 String queryString = request.getQueryString(); 370 if (queryString != null) 371 { 372 // remove any existing code or state from the request param if any exist, they are outdated 373 queryString = Stream.of(StringUtils.split(queryString, "&")) 374 .filter(str -> !StringUtils.startsWithAny(str, "code=", "state=")) 375 .collect(Collectors.joining("&")); 376 if (StringUtils.isNotEmpty(queryString)) 377 { 378 actualRedirectUri.append("?"); 379 actualRedirectUri.append(queryString); 380 } 381 } 382 383 // saving the actualRedirectUri to enable its use in "OIDCCallbackAction" 384 Session session = request.getSession(true); 385 session.setAttribute(REDIRECT_URI_SESSION_ATTRIBUTE, actualRedirectUri.toString()); 386 387 // creation of redirect URI (the issuer (google, facebook, etc.) is going to redirect to) 388 return buildAbsoluteURI(request, OIDCCallbackAction.CALLBACK_URL); 389 } 390 391 /** 392 * Computes the callback uri 393 * @param request the current request 394 * @param path the callback path 395 * @return the callback uri 396 */ 397 protected URI buildAbsoluteURI(Request request, String path) 398 { 399 StringBuilder uriBuilder = new StringBuilder() 400 .append(request.getScheme()) 401 .append("://") 402 .append(request.getServerName()); 403 404 if (request.isSecure()) 405 { 406 if (request.getServerPort() != 443) 407 { 408 uriBuilder.append(":"); 409 uriBuilder.append(request.getServerPort()); 410 } 411 } 412 else 413 { 414 if (request.getServerPort() != 80) 415 { 416 uriBuilder.append(":"); 417 uriBuilder.append(request.getServerPort()); 418 } 419 } 420 421 uriBuilder.append(request.getContextPath()); 422 uriBuilder.append(path); 423 424 return URI.create(uriBuilder.toString()); 425 } 426 427 /** 428 * Sign the user in by sending an authentication request to the issuer 429 * @param redirector The redirector 430 * @param redirectUri The redirect URI 431 * @param silent if the user should be silently signed in 432 * @param wasSilent indicates that we already passed through this, to prevent infinite loops 433 * @param session The current session 434 * @throws ProcessingException If an error occurs 435 * @throws IOException If an error occurs 436 */ 437 protected void signIn(Redirector redirector, URI redirectUri, boolean silent, boolean wasSilent, Session session) throws ProcessingException, IOException 438 { 439 // sign-in request: redirect the client through the actual authentication process 440 441 if (wasSilent) 442 { 443 // already passed through this, there should have been some error somewhere 444 return; 445 } 446 447 if (silent) 448 { 449 session.setAttribute(__ATTRIBUTE_SILENT, "true"); 450 } 451 452 // creation of the state used to secure the process 453 State state = new State(); 454 session.setAttribute(STATE_SESSION_ATTRIBUTE, state); 455 456 // compose the request 457 AuthenticationRequest authenticationRequest = new AuthenticationRequest.Builder(new ResponseType(ResponseType.Value.CODE), _scope, _clientID, redirectUri) 458 .endpointURI(_authUri) 459 .state(state) 460 .prompt(silent ? Prompt.Type.NONE : null) 461 .build(); 462 463 String authReqURI = authenticationRequest.toURI().toString() + "&access_type=offline"; 464 465 redirector.redirect(false, authReqURI); 466 } 467 468 /** 469 * Checks the State parameter of the request to prevent CSRF attacks 470 * @throws AccessDeniedException If an error occurs 471 */ 472 protected void checkState() throws AccessDeniedException 473 { 474 Request request = ContextHelper.getRequest(_context); 475 Session session = request.getSession(true); 476 String storedState = session.getAttribute(STATE_SESSION_ATTRIBUTE).toString(); 477 String stateRequest = request.getParameter("state"); 478 479 if (!storedState.equals(stateRequest)) 480 { 481 getLogger().error("OIDC state mismatch. Method checkState of AbstractOIDCCredentialProvider"); 482 throw new AccessDeniedException("OIDC state mismatch"); 483 } 484 485 session.setAttribute(STATE_SESSION_ATTRIBUTE, null); 486 } 487 488 /** 489 * Request the tokens (ID token and Access token) 490 * @param authCode The authorization code from the authentication request 491 * @param redirectUri The redirect URI 492 * @return The <code>OIDCTokens</code> that contains the access token and the id token 493 * @throws AccessDeniedException If an error occurs 494 */ 495 protected OIDCTokens requestToken(AuthorizationCode authCode, URI redirectUri) throws AccessDeniedException 496 { 497 // token request: checking if the user is known 498 TokenRequest tokenReq = new TokenRequest(_tokenEndpointUri, getClientAuthentication(), new AuthorizationCodeGrant(authCode, redirectUri), null); 499 // sending request 500 HTTPResponse tokenHTTPResp = null; 501 try 502 { 503 tokenHTTPResp = tokenReq.toHTTPRequest().send(); 504 } 505 catch (SerializeException | IOException e) 506 { 507 getLogger().error("OIDC token request failed ", e); 508 throw new AccessDeniedException("OIDC token request failed"); 509 } 510 511 // cast the HTTPResponse to TokenResponse 512 TokenResponse tokenResponse = null; 513 try 514 { 515 tokenResponse = OIDCTokenResponseParser.parse(tokenHTTPResp); 516 } 517 catch (ParseException e) 518 { 519 getLogger().error("OIDC token request result invalid ", e); 520 throw new AccessDeniedException("OIDC token request result invalid"); 521 } 522 523 if (tokenResponse instanceof TokenErrorResponse) 524 { 525 getLogger().error("OIDC token request invalid token response instance of TokenErrorResponse in method requestToken from AbstractOIDCCredentialProvider"); 526 throw new AccessDeniedException("OIDC token request result invalid"); 527 } 528 529 // get the tokens 530 OIDCTokenResponse accessTokenResponse = (OIDCTokenResponse) tokenResponse; 531 532 return accessTokenResponse.getOIDCTokens(); 533 } 534 535 /** 536 * Request the tokens using a refresh token 537 * @param clientAuth The client authentication 538 * @param refreshTokenGrant The refreshtokenGrant 539 * @return The <code>OIDCTokens</code> that contains the access token and the id token 540 * @throws AccessDeniedException If an error occurs 541 * @throws URISyntaxException If an error occurs 542 */ 543 protected OIDCTokens requestToken(ClientAuthentication clientAuth, AuthorizationGrant refreshTokenGrant) throws AccessDeniedException, URISyntaxException 544 { 545 // token request: checking if the user is known 546 TokenRequest tokenReq = new TokenRequest(_tokenEndpointUri, clientAuth, refreshTokenGrant, null); 547 // sending request 548 549 HTTPResponse tokenHTTPResp = null; 550 try 551 { 552 tokenHTTPResp = tokenReq.toHTTPRequest().send(); 553 } 554 catch (SerializeException | IOException e) 555 { 556 getLogger().error("OIDC token request failed ", e); 557 throw new AccessDeniedException("OIDC token request failed"); 558 } 559 560 // cast the HTTPResponse to TokenResponse 561 TokenResponse tokenResponse = null; 562 try 563 { 564 tokenResponse = OIDCTokenResponseParser.parse(tokenHTTPResp); 565 } 566 catch (ParseException e) 567 { 568 getLogger().error("OIDC token request result invalid ", e); 569 throw new AccessDeniedException("OIDC token request result invalid"); 570 } 571 572 if (tokenResponse instanceof TokenErrorResponse) 573 { 574 getLogger().error("OIDC token request result invalid: tokenResponse instance of TokenErrorResponse in method requestToken from AbstractOIDCCredentialProvider"); 575 throw new AccessDeniedException("OIDC token request result invalid"); 576 } 577 578 // get the tokens 579 OIDCTokenResponse accessTokenResponse = (OIDCTokenResponse) tokenResponse; 580 581 return accessTokenResponse.getOIDCTokens(); 582 } 583 584 /** 585 * Validate the id token from the token request 586 * @param idToken The id token from the token request 587 * @return The <code>IDTokenClaimsSet</code> that contains information on the connection such as the expiration time 588 * @throws AccessDeniedException If an error occurs 589 */ 590 protected IDTokenClaimsSet validateIdToken(JWT idToken) throws AccessDeniedException 591 { 592 JWSAlgorithm jwsAlg = JWSAlgorithm.RS256; 593 // create validator for signed ID tokens 594 IDTokenValidator validator = new IDTokenValidator(_iss, _clientID, jwsAlg, _jwkSetURL); 595 IDTokenClaimsSet claims; 596 597 try 598 { 599 claims = validator.validate(idToken, null); 600 } 601 catch (BadJOSEException e) 602 { 603 getLogger().error("OIDC invalid : issuer, clientId, jwsAlg or jwkSetURL", e); 604 throw new AccessDeniedException("OIDC invalid signature issuer, clientId, jwsAlg or jwkSetURL"); 605 } 606 catch (JOSEException e) 607 { 608 getLogger().error("OIDC error while validating token", e); 609 throw new AccessDeniedException("OIDC error while validating token"); 610 } 611 612 return claims; 613 } 614 615 /** 616 * Request the userInfo using the user info end point and an access token 617 * @param accessToken the access token to retrieve the user info 618 * @return a representation of the user info from the scope requested with the token 619 * @throws IOException if an error occurred while contacting the end point 620 * @throws ParseException if an error occurred while parsing the end point answer 621 */ 622 protected UserInfo getUserInfo(AccessToken accessToken) throws IOException, ParseException 623 { 624 HTTPResponse httpResponse = new UserInfoRequest(_userInfoEndpoint, accessToken).toHTTPRequest().send(); 625 UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse); 626 627 if (userInfoResponse.indicatesSuccess()) 628 { 629 return userInfoResponse.toSuccessResponse().getUserInfo(); 630 } 631 else 632 { 633 String error = userInfoResponse.toErrorResponse().getErrorObject().toJSONObject().toJSONString(); 634 getLogger().error("Failed to retrieve the user info. The server indicate the following error :\n" + error); 635 throw new AccessDeniedException("Failed to retrieve the user info. The server indicate the following error :\n" + error); 636 } 637 } 638 639 /** 640 * Compute a user identity based on the user info 641 * @param userInfo the user info 642 * @param request the original request 643 * @param redirector the redirector to use if need be 644 * @return the identified user info or null if no matching user were found 645 * @throws NotUniqueUserException if multiple user matched 646 */ 647 protected UserIdentity getUserIdentity(UserInfo userInfo, Request request, Redirector redirector) throws NotUniqueUserException 648 { 649 // get the user email 650 String login = userInfo.getEmailAddress(); 651 if (login == null) 652 { 653 getLogger().error("Email not found, connection canceled "); 654 throw new AccessDeniedException("Email not found, connection canceled"); 655 } 656 657 // create a UserIdentity from the email 658 UserPopulation userPopulation = _getPopulation(request); 659 UserIdentity user = _getUserIdentity(login, userPopulation); 660 661 // If we found a UserIdentity, we return it 662 if (user != null) 663 { 664 return user; 665 } 666 667 // If not, we are going to pre-sign-up the user with its email, firstname and lastname 668 String firstName = userInfo.getGivenName(); 669 String lastName = userInfo.getFamilyName(); 670 if (firstName == null || lastName == null) 671 { 672 getLogger().info("The fields could not be pre-filled"); 673 } 674 675 // 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 676 _endOfAuthenticationProcess.unexistingUser(login, firstName, lastName, userPopulation, redirector, request); 677 678 return null; 679 } 680 681 private UserIdentity _getUserIdentity(String login, UserPopulation userPopulation) throws NotUniqueUserException 682 { 683 StoredUser storedUser = null; 684 685 for (UserDirectory userDirectory : userPopulation.getUserDirectories()) 686 { 687 storedUser = userDirectory.getStoredUser(login); 688 689 if (storedUser == null) 690 { 691 // Try to get user by email 692 storedUser = userDirectory.getStoredUserByEmail(login); 693 } 694 695 if (storedUser != null) 696 { 697 return userDirectory.getUserIdentity(storedUser); 698 } 699 } 700 701 return null; 702 } 703}