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