private ErrorObject validateIdTokenClaims(
      ReadOnlyJWTClaimsSet claimsSet, SessionManager.Entry entry) {
    ErrorObject error = CommonValidator.validateBaseJwtClaims(claimsSet, TokenClass.ID_TOKEN);

    if (error == null
        && !Objects.equals(this.tenantInfo.getIssuer().getValue(), claimsSet.getIssuer())) {
      error = OAuth2Error.INVALID_REQUEST.setDescription("id_token has incorrect issuer");
    }

    if (error == null
        && entry != null
        && !Objects.equals(entry.getPersonUser().getSubject().getValue(), claimsSet.getSubject())) {
      error =
          OAuth2Error.INVALID_REQUEST.setDescription(
              "id_token subject does not match the session user");
    }

    if (error == null && claimsSet.getAudience().size() != 1) {
      error =
          OAuth2Error.INVALID_REQUEST.setDescription(
              "id_token must have a single audience value containing the client_id");
    }

    return error;
  }
  @Test
  public void testCreateFromIdTokenClaims_EmptyClaims() throws ParseException {

    final ReadOnlyJWTClaimsSet claimSet = PowerMock.createMock(ReadOnlyJWTClaimsSet.class);
    EasyMock.expect(claimSet.getAllClaims()).andReturn(new HashMap<String, Object>()).times(1);
    EasyMock.replay(claimSet);
    Assert.assertNull(UserInfo.createFromIdTokenClaims(claimSet));
    PowerMock.verifyAll();
  }
  /**
   * @param request The request from which to extract parameters and perform the authentication
   * @return The authenticated user token, or null if authentication is incomplete.
   */
  protected Authentication handleAuthorizationCodeResponse(
      HttpServletRequest request, HttpServletResponse response) {

    String authorizationCode = request.getParameter("code");

    HttpSession session = request.getSession();

    // check for state, if it doesn't match we bail early
    String storedState = getStoredState(session);
    if (!Strings.isNullOrEmpty(storedState)) {
      String state = request.getParameter("state");
      if (!storedState.equals(state)) {
        throw new AuthenticationServiceException(
            "State parameter mismatch on return. Expected " + storedState + " got " + state);
      }
    }

    // look up the issuer that we set out to talk to
    String issuer = getStoredSessionString(session, ISSUER_SESSION_VARIABLE);

    // pull the configurations based on that issuer
    ServerConfiguration serverConfig = servers.getServerConfiguration(issuer);
    final RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig);

    MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
    form.add("grant_type", "authorization_code");
    form.add("code", authorizationCode);
    form.setAll(authOptions.getTokenOptions(serverConfig, clientConfig, request));

    String redirectUri = getStoredSessionString(session, REDIRECT_URI_SESION_VARIABLE);
    if (redirectUri != null) {
      form.add("redirect_uri", redirectUri);
    }

    // Handle Token Endpoint interaction

    HttpClient httpClient =
        HttpClientBuilder.create()
            .useSystemProperties()
            .setDefaultRequestConfig(
                RequestConfig.custom().setSocketTimeout(httpSocketTimeout).build())
            .build();

    HttpComponentsClientHttpRequestFactory factory =
        new HttpComponentsClientHttpRequestFactory(httpClient);

    RestTemplate restTemplate;

    if (SECRET_BASIC.equals(clientConfig.getTokenEndpointAuthMethod())) {
      // use BASIC auth if configured to do so
      restTemplate =
          new RestTemplate(factory) {

            @Override
            protected ClientHttpRequest createRequest(URI url, HttpMethod method)
                throws IOException {
              ClientHttpRequest httpRequest = super.createRequest(url, method);
              httpRequest
                  .getHeaders()
                  .add(
                      "Authorization",
                      String.format(
                          "Basic %s",
                          Base64.encode(
                              String.format(
                                  "%s:%s",
                                  UriUtils.encodePathSegment(clientConfig.getClientId(), "UTF-8"),
                                  UriUtils.encodePathSegment(
                                      clientConfig.getClientSecret(), "UTF-8")))));

              return httpRequest;
            }
          };
    } else {
      // we're not doing basic auth, figure out what other flavor we have
      restTemplate = new RestTemplate(factory);

      if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod())
          || PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) {
        // do a symmetric secret signed JWT for auth

        JWTSigningAndValidationService signer = null;
        JWSAlgorithm alg = clientConfig.getTokenEndpointAuthSigningAlg();

        if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod())
            && (alg.equals(JWSAlgorithm.HS256)
                || alg.equals(JWSAlgorithm.HS384)
                || alg.equals(JWSAlgorithm.HS512))) {

          // generate one based on client secret
          signer = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient());

        } else if (PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) {

          // needs to be wired in to the bean
          signer = authenticationSignerService;

          if (alg == null) {
            alg = authenticationSignerService.getDefaultSigningAlgorithm();
          }
        }

        if (signer == null) {
          throw new AuthenticationServiceException(
              "Couldn't find required signer service for use with private key auth.");
        }

        JWTClaimsSet claimsSet = new JWTClaimsSet();

        claimsSet.setIssuer(clientConfig.getClientId());
        claimsSet.setSubject(clientConfig.getClientId());
        claimsSet.setAudience(Lists.newArrayList(serverConfig.getTokenEndpointUri()));
        claimsSet.setJWTID(UUID.randomUUID().toString());

        // TODO: make this configurable
        Date exp = new Date(System.currentTimeMillis() + (60 * 1000)); // auth good for 60 seconds
        claimsSet.setExpirationTime(exp);

        Date now = new Date(System.currentTimeMillis());
        claimsSet.setIssueTime(now);
        claimsSet.setNotBeforeTime(now);

        JWSHeader header =
            new JWSHeader(
                alg,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                signer.getDefaultSignerKeyId(),
                null,
                null);
        SignedJWT jwt = new SignedJWT(header, claimsSet);

        signer.signJwt(jwt, alg);

        form.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
        form.add("client_assertion", jwt.serialize());
      } else {
        // Alternatively use form based auth
        form.add("client_id", clientConfig.getClientId());
        form.add("client_secret", clientConfig.getClientSecret());
      }
    }

    logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointUri());
    logger.debug("form = " + form);

    String jsonString = null;

    try {
      jsonString =
          restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class);
    } catch (RestClientException e) {

      // Handle error

      logger.error("Token Endpoint error response:  " + e.getMessage());

      throw new AuthenticationServiceException("Unable to obtain Access Token: " + e.getMessage());
    }

    logger.debug("from TokenEndpoint jsonString = " + jsonString);

    JsonElement jsonRoot = new JsonParser().parse(jsonString);
    if (!jsonRoot.isJsonObject()) {
      throw new AuthenticationServiceException(
          "Token Endpoint did not return a JSON object: " + jsonRoot);
    }

    JsonObject tokenResponse = jsonRoot.getAsJsonObject();

    if (tokenResponse.get("error") != null) {

      // Handle error

      String error = tokenResponse.get("error").getAsString();

      logger.error("Token Endpoint returned: " + error);

      throw new AuthenticationServiceException(
          "Unable to obtain Access Token.  Token Endpoint returned: " + error);

    } else {

      // Extract the id_token to insert into the
      // OIDCAuthenticationToken

      // get out all the token strings
      String accessTokenValue = null;
      String idTokenValue = null;
      String refreshTokenValue = null;

      if (tokenResponse.has("access_token")) {
        accessTokenValue = tokenResponse.get("access_token").getAsString();
      } else {
        throw new AuthenticationServiceException(
            "Token Endpoint did not return an access_token: " + jsonString);
      }

      if (tokenResponse.has("id_token")) {
        idTokenValue = tokenResponse.get("id_token").getAsString();
      } else {
        logger.error("Token Endpoint did not return an id_token");
        throw new AuthenticationServiceException("Token Endpoint did not return an id_token");
      }

      if (tokenResponse.has("refresh_token")) {
        refreshTokenValue = tokenResponse.get("refresh_token").getAsString();
      }

      try {
        JWT idToken = JWTParser.parse(idTokenValue);

        // validate our ID Token over a number of tests
        ReadOnlyJWTClaimsSet idClaims = idToken.getJWTClaimsSet();

        // check the signature
        JWTSigningAndValidationService jwtValidator = null;

        Algorithm tokenAlg = idToken.getHeader().getAlgorithm();

        Algorithm clientAlg = clientConfig.getIdTokenSignedResponseAlg();

        if (clientAlg != null) {
          if (!clientAlg.equals(tokenAlg)) {
            throw new AuthenticationServiceException(
                "Token algorithm " + tokenAlg + " does not match expected algorithm " + clientAlg);
          }
        }

        if (idToken instanceof PlainJWT) {

          if (clientAlg == null) {
            throw new AuthenticationServiceException(
                "Unsigned ID tokens can only be used if explicitly configured in client.");
          }

          if (tokenAlg != null && !tokenAlg.equals(Algorithm.NONE)) {
            throw new AuthenticationServiceException(
                "Unsigned token received, expected signature with " + tokenAlg);
          }
        } else if (idToken instanceof SignedJWT) {

          SignedJWT signedIdToken = (SignedJWT) idToken;

          if (tokenAlg.equals(JWSAlgorithm.HS256)
              || tokenAlg.equals(JWSAlgorithm.HS384)
              || tokenAlg.equals(JWSAlgorithm.HS512)) {

            // generate one based on client secret
            jwtValidator = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient());
          } else {
            // otherwise load from the server's public key
            jwtValidator = validationServices.getValidator(serverConfig.getJwksUri());
          }

          if (jwtValidator != null) {
            if (!jwtValidator.validateSignature(signedIdToken)) {
              throw new AuthenticationServiceException("Signature validation failed");
            }
          } else {
            logger.error("No validation service found. Skipping signature validation");
            throw new AuthenticationServiceException(
                "Unable to find an appropriate signature validator for ID Token.");
          }
        } // TODO: encrypted id tokens

        // check the issuer
        if (idClaims.getIssuer() == null) {
          throw new AuthenticationServiceException("Id Token Issuer is null");
        } else if (!idClaims.getIssuer().equals(serverConfig.getIssuer())) {
          throw new AuthenticationServiceException(
              "Issuers do not match, expected "
                  + serverConfig.getIssuer()
                  + " got "
                  + idClaims.getIssuer());
        }

        // check expiration
        if (idClaims.getExpirationTime() == null) {
          throw new AuthenticationServiceException(
              "Id Token does not have required expiration claim");
        } else {
          // it's not null, see if it's expired
          Date now = new Date(System.currentTimeMillis() - (timeSkewAllowance * 1000));
          if (now.after(idClaims.getExpirationTime())) {
            throw new AuthenticationServiceException(
                "Id Token is expired: " + idClaims.getExpirationTime());
          }
        }

        // check not before
        if (idClaims.getNotBeforeTime() != null) {
          Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
          if (now.before(idClaims.getNotBeforeTime())) {
            throw new AuthenticationServiceException(
                "Id Token not valid untill: " + idClaims.getNotBeforeTime());
          }
        }

        // check issued at
        if (idClaims.getIssueTime() == null) {
          throw new AuthenticationServiceException(
              "Id Token does not have required issued-at claim");
        } else {
          // since it's not null, see if it was issued in the future
          Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
          if (now.before(idClaims.getIssueTime())) {
            throw new AuthenticationServiceException(
                "Id Token was issued in the future: " + idClaims.getIssueTime());
          }
        }

        // check audience
        if (idClaims.getAudience() == null) {
          throw new AuthenticationServiceException("Id token audience is null");
        } else if (!idClaims.getAudience().contains(clientConfig.getClientId())) {
          throw new AuthenticationServiceException(
              "Audience does not match, expected "
                  + clientConfig.getClientId()
                  + " got "
                  + idClaims.getAudience());
        }

        // compare the nonce to our stored claim
        String nonce = idClaims.getStringClaim("nonce");
        if (Strings.isNullOrEmpty(nonce)) {

          logger.error("ID token did not contain a nonce claim.");

          throw new AuthenticationServiceException("ID token did not contain a nonce claim.");
        }

        String storedNonce = getStoredNonce(session);
        if (!nonce.equals(storedNonce)) {
          logger.error(
              "Possible replay attack detected! The comparison of the nonce in the returned "
                  + "ID Token to the session "
                  + NONCE_SESSION_VARIABLE
                  + " failed. Expected "
                  + storedNonce
                  + " got "
                  + nonce
                  + ".");

          throw new AuthenticationServiceException(
              "Possible replay attack detected! The comparison of the nonce in the returned "
                  + "ID Token to the session "
                  + NONCE_SESSION_VARIABLE
                  + " failed. Expected "
                  + storedNonce
                  + " got "
                  + nonce
                  + ".");
        }

        // construct an PendingOIDCAuthenticationToken and return a Authentication object w/the
        // userId and the idToken

        PendingOIDCAuthenticationToken token =
            new PendingOIDCAuthenticationToken(
                idClaims.getSubject(),
                idClaims.getIssuer(),
                serverConfig,
                idToken,
                accessTokenValue,
                refreshTokenValue);

        Authentication authentication = this.getAuthenticationManager().authenticate(token);

        return authentication;
      } catch (ParseException e) {
        throw new AuthenticationServiceException("Couldn't parse idToken: ", e);
      }
    }
  }
  private Pair<LogoutSuccessResponse, Cookie> processInternal() throws ServerException {
    String sessionIdString =
        this.httpRequest.getCookieValue(Shared.getSessionCookieName(this.tenantInfo.getName()));
    SessionID sessionId = null;
    SessionManager.Entry entry = null;
    if (sessionIdString != null) {
      sessionId = new SessionID(sessionIdString);
      entry = this.sessionManager.get(sessionId);
    }

    SignedJWT idTokenJwt = this.logoutRequest.getIDTokenHint().getSignedJWT();

    boolean validSignature;
    try {
      validSignature = idTokenJwt.verify(new RSASSAVerifier(this.tenantInfo.getPublicKey()));
    } catch (JOSEException e) {
      throw new ServerException(
          OAuth2Error.SERVER_ERROR.setDescription("error while verifying id_token signature"), e);
    }
    if (!validSignature) {
      throw new ServerException(
          OAuth2Error.INVALID_REQUEST.setDescription("id_token has an invalid signature"));
    }

    ReadOnlyJWTClaimsSet idTokenClaimsSet;
    try {
      idTokenClaimsSet = idTokenJwt.getJWTClaimsSet();
    } catch (ParseException e) {
      throw new ServerException(
          OAuth2Error.INVALID_REQUEST.setDescription("failed to parse claims out of id_token"), e);
    }

    ErrorObject error = validateIdTokenClaims(idTokenClaimsSet, entry);
    if (error != null) {
      throw new ServerException(error);
    }

    ClientID clientId = new ClientID(idTokenClaimsSet.getAudience().get(0));
    ClientInfo clientInfo =
        this.clientInfoRetriever.retrieveClientInfo(this.tenantInfo.getName(), clientId);
    if (clientInfo.getCertSubjectDn() != null) {
      if (this.logoutRequest.getClientAssertion() != null) {
        this.solutionUserAuthenticator.authenticateByClientAssertion(
            this.logoutRequest.getClientAssertion(),
            REQUEST_LIFETIME_MS,
            this.httpRequest.getRequestUrl(),
            this.tenantInfo,
            clientInfo);
      } else {
        throw new ServerException(
            OAuth2Error.INVALID_CLIENT.setDescription(
                "client_assertion parameter is required since client has registered a cert"));
      }
    }

    if (this.logoutRequest.getPostLogoutRedirectionURI() != null) {
      if (!clientInfo
          .getPostLogoutRedirectUris()
          .contains(this.logoutRequest.getPostLogoutRedirectionURI())) {
        throw new ServerException(
            OAuth2Error.INVALID_REQUEST.setDescription("unregistered post_logout_redirect_uri"));
      }
    }

    // SLO using OpenID Connect HTTP-Based Logout 1.0 - draft 03
    // construct iframe links containing logout_uri requests, the browser will send these to other
    // participating clients
    // do not include the client that initiated this logout request as that client has already
    // logged out before sending us this request
    Set<URI> logoutUris = new HashSet<URI>();
    if (entry != null) {
      for (ClientInfo client : entry.getClients()) {
        if (client.getLogoutUri() != null && !client.getID().equals(clientId)) {
          logoutUris.add(client.getLogoutUri());
        }
      }
      this.sessionManager.remove(sessionId);
    }

    return Pair.of(
        new LogoutSuccessResponse(
            this.logoutRequest.getPostLogoutRedirectionURI(),
            this.logoutRequest.getState(),
            sessionId,
            logoutUris),
        (sessionId == null) ? null : wipeOutSessionCookie());
  }
  @Test
  public void testCreateFromIdTokenClaims_HasEmailSubjectPasswordClaims() throws ParseException {

    final ReadOnlyJWTClaimsSet claimSet = PowerMock.createMock(ReadOnlyJWTClaimsSet.class);
    final Map<String, Object> map = new HashMap<String, Object>();
    map.put("", "");
    EasyMock.expect(claimSet.getAllClaims()).andReturn(map).times(1);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_OBJECT_ID))
        .andReturn(null)
        .times(1);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_SUBJECT))
        .andReturn("sub")
        .times(2);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_UPN))
        .andReturn(null)
        .times(1);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_EMAIL))
        .andReturn("*****@*****.**")
        .times(2);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_GIVEN_NAME))
        .andReturn("test")
        .times(1);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_FAMILY_NAME))
        .andReturn("value")
        .times(1);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_IDENTITY_PROVIDER))
        .andReturn("idp")
        .times(1);
    EasyMock.expect(claimSet.getStringClaim(AuthenticationConstants.ID_TOKEN_PASSWORD_CHANGE_URL))
        .andReturn("url")
        .times(2);
    EasyMock.expect(claimSet.getClaim(AuthenticationConstants.ID_TOKEN_PASSWORD_EXPIRES_ON))
        .andReturn("5000")
        .times(2);

    EasyMock.replay(claimSet);
    final UserInfo ui = UserInfo.createFromIdTokenClaims(claimSet);
    Assert.assertNotNull(ui);
    Assert.assertEquals("*****@*****.**", ui.getDispayableId());
    Assert.assertEquals("sub", ui.getUniqueId());
    Assert.assertEquals("test", ui.getGivenName());
    Assert.assertEquals("value", ui.getFamilyName());
    Assert.assertEquals("idp", ui.getIdentityProvider());
    Assert.assertEquals("url", ui.getPasswordChangeUrl());
    Assert.assertNotNull(ui.getPasswordExpiresOn());
    PowerMock.verifyAll();
  }