@RequestMapping(
      value = "/access/{id}",
      method = RequestMethod.GET,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public String getAccessTokenById(@PathVariable("id") Long id, ModelMap m, Principal p) {

    OAuth2AccessTokenEntity token = tokenService.getAccessTokenById(id);

    if (token == null) {
      logger.error("getToken failed; token not found: " + id);
      m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
      m.put(
          JsonErrorView.ERROR_MESSAGE,
          "The requested token with id " + id + " could not be found.");
      return JsonErrorView.VIEWNAME;
    } else if (!token.getAuthenticationHolder().getAuthentication().getName().equals(p.getName())) {
      logger.error("getToken failed; token does not belong to principal " + p.getName());
      m.put(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
      m.put(JsonErrorView.ERROR_MESSAGE, "You do not have permission to view this token");
      return JsonErrorView.VIEWNAME;
    } else {
      m.put(JsonEntityView.ENTITY, token);
      return TokenApiView.VIEWNAME;
    }
  }
  private OAuth2AccessTokenEntity fetchValidRegistrationToken(
      OAuth2Authentication auth, ClientDetailsEntity client) {

    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
    OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());

    if (config.getRegTokenLifeTime() != null) {

      try {
        // Re-issue the token if it has been issued before [currentTime - validity]
        Date validToDate =
            new Date(System.currentTimeMillis() - config.getRegTokenLifeTime() * 1000);
        if (token.getJwt().getJWTClaimsSet().getIssueTime().before(validToDate)) {
          logger.info("Rotating the registration access token for " + client.getClientId());
          tokenService.revokeAccessToken(token);
          OAuth2AccessTokenEntity newToken = connectTokenService.createResourceAccessToken(client);
          tokenService.saveAccessToken(newToken);
          return newToken;
        } else {
          // it's not expired, keep going
          return token;
        }
      } catch (ParseException e) {
        logger.error("Couldn't parse a known-valid token?", e);
        return token;
      }
    } else {
      // tokens don't expire, just return it
      return token;
    }
  }
 @Override
 public OAuth2AccessTokenEntity rotateRegistrationAccessTokenForClient(
     ClientDetailsEntity client) {
   // revoke any previous tokens
   OAuth2AccessTokenEntity oldToken = tokenService.getRegistrationAccessTokenForClient(client);
   if (oldToken != null) {
     Set<String> scope = oldToken.getScope();
     tokenService.revokeAccessToken(oldToken);
     return createAssociatedToken(client, scope);
   } else {
     return null;
   }
 }
  /**
   * Get the meta information for a client.
   *
   * @param clientId
   * @param m
   * @param auth
   * @return
   */
  @PreAuthorize(
      "hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
          + SystemScopeService.RESOURCE_TOKEN_SCOPE
          + "')")
  @RequestMapping(
      value = "/{id}",
      method = RequestMethod.GET,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public String readResourceConfiguration(
      @PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {

    ClientDetailsEntity client = clientService.loadClientByClientId(clientId);

    if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {

      try {
        // possibly update the token
        OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client);

        RegisteredClient registered =
            new RegisteredClient(
                client,
                token.getValue(),
                config.getIssuer()
                    + "resource/"
                    + UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));

        // send it all out to the view
        m.addAttribute("client", registered);
        m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200

        return ClientInformationResponseView.VIEWNAME;
      } catch (UnsupportedEncodingException e) {
        logger.error("Unsupported encoding", e);
        m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
        return HttpCodeView.VIEWNAME;
      }
    } else {
      // client mismatch
      logger.error(
          "readResourceConfiguration failed, client ID mismatch: "
              + clientId
              + " and "
              + auth.getOAuth2Request().getClientId()
              + " do not match.");
      m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403

      return HttpCodeView.VIEWNAME;
    }
  }
  @Test
  public void getAccessTokenHash256() {

    mockToken256.getJwt().serialize();
    Base64URL expectedHash = new Base64URL("EP1gXNeESRH-n57baopfTQ");

    Base64URL resultHash = IdTokenHashUtils.getAccessTokenHash(JWSAlgorithm.HS256, mockToken256);

    assertEquals(expectedHash, resultHash);
  }
  @Before
  public void prepare() throws ParseException {

    /*
    Claims for first token:

    claims.setType("JWT");
    claims.setIssuer("www.example.com");
    claims.setSubject("example_user");
    claims.setClaim("alg", "HS256");
     */
    Mockito.when(mockToken256.getJwt())
        .thenReturn(
            JWTParser.parse(
                "eyJhbGciOiJub25lIn0.eyJhbGciOiJIUzI1NiIsInN1YiI6ImV4YW1wbGVfdXNlciIsImlzcyI6Ind3dy5leGFtcGxlLmNvbSIsInR5cCI6IkpXVCJ9."));

    /*
     * Claims for second token
    claims = new JWTClaimsSet();
    claims.setType("JWT");
    claims.setIssuer("www.another-example.net");
    claims.setSubject("another_user");
    claims.setClaim("alg", "ES384");
     */
    Mockito.when(mockToken384.getJwt())
        .thenReturn(
            JWTParser.parse(
                "eyJhbGciOiJub25lIn0.eyJhbGciOiJFUzM4NCIsInN1YiI6ImFub3RoZXJfdXNlciIsImlzcyI6Ind3dy5hbm90aGVyLWV4YW1wbGUubmV0IiwidHlwIjoiSldUIn0."));

    /*
     * Claims for third token:
    claims = new JWTClaimsSet();
    claims.setType("JWT");
    claims.setIssuer("www.different.com");
    claims.setSubject("different_user");
    claims.setClaim("alg", "RS512");
     */
    Mockito.when(mockToken512.getJwt())
        .thenReturn(
            JWTParser.parse(
                "eyJhbGciOiJub25lIn0.eyJhbGciOiJSUzUxMiIsInN1YiI6ImRpZmZlcmVudF91c2VyIiwiaXNzIjoid3d3LmRpZmZlcmVudC5jb20iLCJ0eXAiOiJKV1QifQ."));
  }
  /**
   * Get the meta information for a client.
   *
   * @param clientId
   * @param m
   * @param auth
   * @return
   */
  @PreAuthorize(
      "hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
          + OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE
          + "')")
  @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json")
  public String readClientConfiguration(
      @PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {

    ClientDetailsEntity client = clientService.loadClientByClientId(clientId);

    if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {

      // we return the token that we got in
      OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
      OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());

      // TODO: urlencode the client id for safety?
      RegisteredClient registered =
          new RegisteredClient(
              client, token.getValue(), config.getIssuer() + "register/" + client.getClientId());

      // send it all out to the view
      m.addAttribute("client", registered);
      m.addAttribute("code", HttpStatus.OK); // http 200

      return "clientInformationResponseView";
    } else {
      // client mismatch
      logger.error(
          "readClientConfiguration failed, client ID mismatch: "
              + clientId
              + " and "
              + auth.getOAuth2Request().getClientId()
              + " do not match.");
      m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403

      return "httpCodeView";
    }
  }
  @Test
  public void getAccessTokenHash384() {

    /*
    * independently generate hash
    ascii of token = eyJhbGciOiJub25lIn0.eyJhbGciOiJFUzM4NCIsInN1YiI6ImFub3RoZXJfdXNlciIsImlzcyI6Ind3dy5hbm90aGVyLWV4YW1wbGUubmV0IiwidHlwIjoiSldUIn0.
    base64url of hash = BWfFK73PQI36M1rg9R6VjMyWOE0-XvBK
    */

    mockToken384.getJwt().serialize();
    Base64URL expectedHash = new Base64URL("BWfFK73PQI36M1rg9R6VjMyWOE0-XvBK");

    Base64URL resultHash = IdTokenHashUtils.getAccessTokenHash(JWSAlgorithm.ES384, mockToken384);

    assertEquals(expectedHash, resultHash);
  }
  @Test
  public void getAccessTokenHash512() {

    /*
    * independently generate hash
    ascii of token = eyJhbGciOiJub25lIn0.eyJhbGciOiJSUzUxMiIsInN1YiI6ImRpZmZlcmVudF91c2VyIiwiaXNzIjoid3d3LmRpZmZlcmVudC5jb20iLCJ0eXAiOiJKV1QifQ.
    base64url of hash = vGH3QMY-knpACkLgzdkTqu3C9jtvbf2Wk_RSu2vAx8k
    */

    mockToken512.getJwt().serialize();
    Base64URL expectedHash = new Base64URL("vGH3QMY-knpACkLgzdkTqu3C9jtvbf2Wk_RSu2vAx8k");

    Base64URL resultHash = IdTokenHashUtils.getAccessTokenHash(JWSAlgorithm.RS512, mockToken512);

    assertEquals(expectedHash, resultHash);
  }
  /**
   * Create a new Client, issue a client ID, and create a registration access token.
   *
   * @param jsonString
   * @param m
   * @param p
   * @return
   */
  @RequestMapping(
      method = RequestMethod.POST,
      consumes = "application/json",
      produces = "application/json")
  public String registerNewClient(@RequestBody String jsonString, Model m) {

    ClientDetailsEntity newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);

    if (newClient != null) {
      // it parsed!

      //
      // Now do some post-processing consistency checks on it
      //

      // clear out any spurious id/secret (clients don't get to pick)
      newClient.setClientId(null);
      newClient.setClientSecret(null);

      // set of scopes that are OK for clients to dynamically register for
      Set<SystemScope> dynScopes = scopeService.getDynReg();

      // scopes that the client is asking for
      Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());

      // the scopes that the client can have must be a subset of the dynamically allowed scopes
      Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes);

      // if the client didn't ask for any, give them the defaults
      if (allowedScopes == null || allowedScopes.isEmpty()) {
        allowedScopes = scopeService.getDefaults();
      }

      newClient.setScope(scopeService.toStrings(allowedScopes));

      // set default grant types if needed
      if (newClient.getGrantTypes() == null || newClient.getGrantTypes().isEmpty()) {
        if (newClient.getScope().contains("offline_access")) { // client asked for offline access
          newClient.setGrantTypes(
              Sets.newHashSet(
                  "authorization_code",
                  "refresh_token")); // allow authorization code and refresh token grant types by
          // default
        } else {
          newClient.setGrantTypes(
              Sets.newHashSet(
                  "authorization_code")); // allow authorization code grant type by default
        }
      }

      // set default response types if needed
      // TODO: these aren't checked by SECOAUTH
      // TODO: the consistency between the response_type and grant_type needs to be checked by the
      // client service, most likely
      if (newClient.getResponseTypes() == null || newClient.getResponseTypes().isEmpty()) {
        newClient.setResponseTypes(
            Sets.newHashSet("code")); // default to allowing only the auth code flow
      }

      if (newClient.getTokenEndpointAuthMethod() == null) {
        newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
      }

      if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC
          || newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT
          || newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) {

        // we need to generate a secret
        newClient = clientService.generateClientSecret(newClient);
      }

      // set some defaults for token timeouts
      newClient.setAccessTokenValiditySeconds(
          (int) TimeUnit.HOURS.toSeconds(1)); // access tokens good for 1hr
      newClient.setIdTokenValiditySeconds(
          (int) TimeUnit.MINUTES.toSeconds(10)); // id tokens good for 10min
      newClient.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked

      // this client has been dynamically registered (obviously)
      newClient.setDynamicallyRegistered(true);

      // TODO: check and enforce the sector URI if it's not null (#504)

      // now save it
      try {
        ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);

        // generate the registration access token
        OAuth2AccessTokenEntity token =
            connectTokenService.createRegistrationAccessToken(savedClient);
        tokenService.saveAccessToken(token);

        // send it all out to the view

        // TODO: urlencode the client id for safety?
        RegisteredClient registered =
            new RegisteredClient(
                savedClient,
                token.getValue(),
                config.getIssuer() + "register/" + savedClient.getClientId());

        m.addAttribute("client", registered);
        m.addAttribute("code", HttpStatus.CREATED); // http 201

        return "clientInformationResponseView";
      } catch (IllegalArgumentException e) {
        logger.error("Couldn't save client", e);
        m.addAttribute("code", HttpStatus.BAD_REQUEST);

        return "httpCodeView";
      }
    } else {
      // didn't parse, this is a bad request
      logger.error("registerNewClient failed; submitted JSON is malformed");
      m.addAttribute("code", HttpStatus.BAD_REQUEST); // http 400

      return "httpCodeView";
    }
  }
  /**
   * Update the metainformation for a given client.
   *
   * @param clientId
   * @param jsonString
   * @param m
   * @param auth
   * @return
   */
  @PreAuthorize(
      "hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
          + OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE
          + "')")
  @RequestMapping(
      value = "/{id}",
      method = RequestMethod.PUT,
      produces = "application/json",
      consumes = "application/json")
  public String updateClient(
      @PathVariable("id") String clientId,
      @RequestBody String jsonString,
      Model m,
      OAuth2Authentication auth) {

    ClientDetailsEntity newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
    ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);

    if (newClient != null
        && oldClient != null // we have an existing client and the new one parsed
        && oldClient
            .getClientId()
            .equals(
                auth.getOAuth2Request()
                    .getClientId()) // the client passed in the URI matches the one in the auth
        && oldClient
            .getClientId()
            .equals(
                newClient.getClientId()) // the client passed in the body matches the one in the URI
    ) {

      // a client can't ask to update its own client secret to any particular value
      newClient.setClientSecret(oldClient.getClientSecret());

      // we need to copy over all of the local and SECOAUTH fields
      newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds());
      newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds());
      newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds());
      newClient.setDynamicallyRegistered(true); // it's still dynamically registered
      newClient.setAllowIntrospection(oldClient.isAllowIntrospection());
      newClient.setAuthorities(oldClient.getAuthorities());
      newClient.setClientDescription(oldClient.getClientDescription());
      newClient.setCreatedAt(oldClient.getCreatedAt());
      newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken());

      // set of scopes that are OK for clients to dynamically register for
      Set<SystemScope> dynScopes = scopeService.getDynReg();

      // scopes that the client is asking for
      Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());

      // the scopes that the client can have must be a subset of the dynamically allowed scopes
      Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes);

      // make sure that the client doesn't ask for scopes it can't have
      newClient.setScope(scopeService.toStrings(allowedScopes));

      try {
        // save the client
        ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);

        // we return the token that we got in
        // TODO: rotate this after some set amount of time
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
        OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());

        // TODO: urlencode the client id for safety?
        RegisteredClient registered =
            new RegisteredClient(
                savedClient,
                token.getValue(),
                config.getIssuer() + "register/" + savedClient.getClientId());

        // send it all out to the view
        m.addAttribute("client", registered);
        m.addAttribute("code", HttpStatus.OK); // http 200

        return "clientInformationResponseView";
      } catch (IllegalArgumentException e) {
        logger.error("Couldn't save client", e);
        m.addAttribute("code", HttpStatus.BAD_REQUEST);

        return "httpCodeView";
      }
    } else {
      // client mismatch
      logger.error(
          "readClientConfiguration failed, client ID mismatch: "
              + clientId
              + " and "
              + auth.getOAuth2Request().getClientId()
              + " do not match.");
      m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403

      return "httpCodeView";
    }
  }
  /**
   * Create a new Client, issue a client ID, and create a registration access token.
   *
   * @param jsonString
   * @param m
   * @param p
   * @return
   */
  @RequestMapping(
      method = RequestMethod.POST,
      consumes = MediaType.APPLICATION_JSON_VALUE,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public String registerNewProtectedResource(@RequestBody String jsonString, Model m) {

    ClientDetailsEntity newClient = null;
    try {
      newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
    } catch (JsonSyntaxException e) {
      // bad parse
      // didn't parse, this is a bad request
      logger.error("registerNewProtectedResource failed; submitted JSON is malformed");
      m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
      return HttpCodeView.VIEWNAME;
    }

    if (newClient != null) {
      // it parsed!

      //
      // Now do some post-processing consistency checks on it
      //

      // clear out any spurious id/secret (clients don't get to pick)
      newClient.setClientId(null);
      newClient.setClientSecret(null);

      // do validation on the fields
      try {
        newClient = validateScopes(newClient);
        newClient = validateAuth(newClient);
      } catch (ValidationException ve) {
        // validation failed, return an error
        m.addAttribute(JsonErrorView.ERROR, ve.getError());
        m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
        m.addAttribute(HttpCodeView.CODE, ve.getStatus());
        return JsonErrorView.VIEWNAME;
      }

      // no grant types are allowed
      newClient.setGrantTypes(new HashSet<String>());
      newClient.setResponseTypes(new HashSet<String>());
      newClient.setRedirectUris(new HashSet<String>());

      // don't issue tokens to this client
      newClient.setAccessTokenValiditySeconds(0);
      newClient.setIdTokenValiditySeconds(0);
      newClient.setRefreshTokenValiditySeconds(0);

      // clear out unused fields
      newClient.setDefaultACRvalues(new HashSet<String>());
      newClient.setDefaultMaxAge(null);
      newClient.setIdTokenEncryptedResponseAlg(null);
      newClient.setIdTokenEncryptedResponseEnc(null);
      newClient.setIdTokenSignedResponseAlg(null);
      newClient.setInitiateLoginUri(null);
      newClient.setPostLogoutRedirectUris(null);
      newClient.setRequestObjectSigningAlg(null);
      newClient.setRequireAuthTime(null);
      newClient.setReuseRefreshToken(false);
      newClient.setSectorIdentifierUri(null);
      newClient.setSubjectType(null);
      newClient.setUserInfoEncryptedResponseAlg(null);
      newClient.setUserInfoEncryptedResponseEnc(null);
      newClient.setUserInfoSignedResponseAlg(null);

      // this client has been dynamically registered (obviously)
      newClient.setDynamicallyRegistered(true);

      // this client has access to the introspection endpoint
      newClient.setAllowIntrospection(true);

      // now save it
      try {
        ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);

        // generate the registration access token
        OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient);
        tokenService.saveAccessToken(token);

        // send it all out to the view

        RegisteredClient registered =
            new RegisteredClient(
                savedClient,
                token.getValue(),
                config.getIssuer()
                    + "resource/"
                    + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
        m.addAttribute("client", registered);
        m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201

        return ClientInformationResponseView.VIEWNAME;
      } catch (UnsupportedEncodingException e) {
        logger.error("Unsupported encoding", e);
        m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
        return HttpCodeView.VIEWNAME;
      } catch (IllegalArgumentException e) {
        logger.error("Couldn't save client", e);

        m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
        m.addAttribute(
            JsonErrorView.ERROR_MESSAGE,
            "Unable to save client due to invalid or inconsistent metadata.");
        m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400

        return JsonErrorView.VIEWNAME;
      }
    } else {
      // didn't parse, this is a bad request
      logger.error("registerNewClient failed; submitted JSON is malformed");
      m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400

      return HttpCodeView.VIEWNAME;
    }
  }
  /**
   * Update the metainformation for a given client.
   *
   * @param clientId
   * @param jsonString
   * @param m
   * @param auth
   * @return
   */
  @PreAuthorize(
      "hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
          + SystemScopeService.RESOURCE_TOKEN_SCOPE
          + "')")
  @RequestMapping(
      value = "/{id}",
      method = RequestMethod.PUT,
      produces = MediaType.APPLICATION_JSON_VALUE,
      consumes = MediaType.APPLICATION_JSON_VALUE)
  public String updateProtectedResource(
      @PathVariable("id") String clientId,
      @RequestBody String jsonString,
      Model m,
      OAuth2Authentication auth) {

    ClientDetailsEntity newClient = null;
    try {
      newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
    } catch (JsonSyntaxException e) {
      // bad parse
      // didn't parse, this is a bad request
      logger.error("updateProtectedResource failed; submitted JSON is malformed");
      m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
      return HttpCodeView.VIEWNAME;
    }

    ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);

    if (newClient != null
        && oldClient != null // we have an existing client and the new one parsed
        && oldClient
            .getClientId()
            .equals(
                auth.getOAuth2Request()
                    .getClientId()) // the client passed in the URI matches the one in the auth
        && oldClient
            .getClientId()
            .equals(
                newClient.getClientId()) // the client passed in the body matches the one in the URI
    ) {

      // a client can't ask to update its own client secret to any particular value
      newClient.setClientSecret(oldClient.getClientSecret());

      newClient.setCreatedAt(oldClient.getCreatedAt());

      // no grant types are allowed
      newClient.setGrantTypes(new HashSet<String>());
      newClient.setResponseTypes(new HashSet<String>());
      newClient.setRedirectUris(new HashSet<String>());

      // don't issue tokens to this client
      newClient.setAccessTokenValiditySeconds(0);
      newClient.setIdTokenValiditySeconds(0);
      newClient.setRefreshTokenValiditySeconds(0);

      // clear out unused fields
      newClient.setDefaultACRvalues(new HashSet<String>());
      newClient.setDefaultMaxAge(null);
      newClient.setIdTokenEncryptedResponseAlg(null);
      newClient.setIdTokenEncryptedResponseEnc(null);
      newClient.setIdTokenSignedResponseAlg(null);
      newClient.setInitiateLoginUri(null);
      newClient.setPostLogoutRedirectUris(null);
      newClient.setRequestObjectSigningAlg(null);
      newClient.setRequireAuthTime(null);
      newClient.setReuseRefreshToken(false);
      newClient.setSectorIdentifierUri(null);
      newClient.setSubjectType(null);
      newClient.setUserInfoEncryptedResponseAlg(null);
      newClient.setUserInfoEncryptedResponseEnc(null);
      newClient.setUserInfoSignedResponseAlg(null);

      // this client has been dynamically registered (obviously)
      newClient.setDynamicallyRegistered(true);

      // this client has access to the introspection endpoint
      newClient.setAllowIntrospection(true);

      // do validation on the fields
      try {
        newClient = validateScopes(newClient);
        newClient = validateAuth(newClient);
      } catch (ValidationException ve) {
        // validation failed, return an error
        m.addAttribute(JsonErrorView.ERROR, ve.getError());
        m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
        m.addAttribute(HttpCodeView.CODE, ve.getStatus());
        return JsonErrorView.VIEWNAME;
      }

      try {
        // save the client
        ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);

        // possibly update the token
        OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient);

        RegisteredClient registered =
            new RegisteredClient(
                savedClient,
                token.getValue(),
                config.getIssuer()
                    + "resource/"
                    + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));

        // send it all out to the view
        m.addAttribute("client", registered);
        m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200

        return ClientInformationResponseView.VIEWNAME;
      } catch (UnsupportedEncodingException e) {
        logger.error("Unsupported encoding", e);
        m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
        return HttpCodeView.VIEWNAME;
      } catch (IllegalArgumentException e) {
        logger.error("Couldn't save client", e);

        m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
        m.addAttribute(
            JsonErrorView.ERROR_MESSAGE,
            "Unable to save client due to invalid or inconsistent metadata.");
        m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400

        return JsonErrorView.VIEWNAME;
      }
    } else {
      // client mismatch
      logger.error(
          "updateProtectedResource"
              + " failed, client ID mismatch: "
              + clientId
              + " and "
              + auth.getOAuth2Request().getClientId()
              + " do not match.");
      m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403

      return HttpCodeView.VIEWNAME;
    }
  }
  @Override
  public OAuth2AccessTokenEntity createIdToken(
      ClientDetailsEntity client,
      OAuth2Request request,
      Date issueTime,
      String sub,
      OAuth2AccessTokenEntity accessToken) {

    JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();

    if (client.getIdTokenSignedResponseAlg() != null) {
      signingAlg = client.getIdTokenSignedResponseAlg();
    }

    OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity();
    JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder();

    // if the auth time claim was explicitly requested OR if the client always wants the auth time,
    // put it in
    if (request.getExtensions().containsKey("max_age")
        || (request
            .getExtensions()
            .containsKey(
                "idtoken")) // TODO: parse the ID Token claims (#473) -- for now assume it could be
                            // in there
        || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) {

      if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) {

        Long authTimestamp =
            Long.parseLong(
                (String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP));
        if (authTimestamp != null) {
          idClaims.claim("auth_time", authTimestamp / 1000L);
        }
      } else {
        // we couldn't find the timestamp!
        logger.warn(
            "Unable to find authentication timestamp! There is likely something wrong with the configuration.");
      }
    }

    idClaims.issueTime(issueTime);

    if (client.getIdTokenValiditySeconds() != null) {
      Date expiration =
          new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L));
      idClaims.expirationTime(expiration);
      idTokenEntity.setExpiration(expiration);
    }

    idClaims.issuer(configBean.getIssuer());
    idClaims.subject(sub);
    idClaims.audience(Lists.newArrayList(client.getClientId()));
    idClaims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it

    String nonce = (String) request.getExtensions().get("nonce");
    if (!Strings.isNullOrEmpty(nonce)) {
      idClaims.claim("nonce", nonce);
    }

    Set<String> responseTypes = request.getResponseTypes();

    if (responseTypes.contains("token")) {
      // calculate the token hash
      Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken);
      idClaims.claim("at_hash", at_hash);
    }

    if (client.getIdTokenEncryptedResponseAlg() != null
        && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
        && client.getIdTokenEncryptedResponseEnc() != null
        && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
        && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) {

      JWTEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client);

      if (encrypter != null) {

        EncryptedJWT idToken =
            new EncryptedJWT(
                new JWEHeader(
                    client.getIdTokenEncryptedResponseAlg(),
                    client.getIdTokenEncryptedResponseEnc()),
                idClaims.build());

        encrypter.encryptJwt(idToken);

        idTokenEntity.setJwt(idToken);

      } else {
        logger.error("Couldn't find encrypter for client: " + client.getClientId());
      }

    } else {

      JWT idToken;

      if (signingAlg.equals(Algorithm.NONE)) {
        // unsigned ID token
        idToken = new PlainJWT(idClaims.build());

      } else {

        // signed ID token

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

          JWSHeader header =
              new JWSHeader(
                  signingAlg,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  jwtService.getDefaultSignerKeyId(),
                  null,
                  null);
          idToken = new SignedJWT(header, idClaims.build());

          JWTSigningAndValidationService signer =
              symmetricCacheService.getSymmetricValidtor(client);

          // sign it with the client's secret
          signer.signJwt((SignedJWT) idToken);
        } else {
          idClaims.claim("kid", jwtService.getDefaultSignerKeyId());

          JWSHeader header =
              new JWSHeader(
                  signingAlg,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  jwtService.getDefaultSignerKeyId(),
                  null,
                  null);

          idToken = new SignedJWT(header, idClaims.build());

          // sign it with the server's key
          jwtService.signJwt((SignedJWT) idToken);
        }
      }

      idTokenEntity.setJwt(idToken);
    }

    idTokenEntity.setAuthenticationHolder(accessToken.getAuthenticationHolder());

    // create a scope set with just the special "id-token" scope
    // Set<String> idScopes = new HashSet<String>(token.getScope()); // this would copy the original
    // token's scopes in, we don't really want that
    Set<String> idScopes = Sets.newHashSet(SystemScopeService.ID_TOKEN_SCOPE);
    idTokenEntity.setScope(idScopes);

    idTokenEntity.setClient(accessToken.getClient());

    return idTokenEntity;
  }
  private OAuth2AccessTokenEntity createAssociatedToken(
      ClientDetailsEntity client, Set<String> scope) {

    // revoke any previous tokens that might exist, just to be sure
    OAuth2AccessTokenEntity oldToken = tokenService.getRegistrationAccessTokenForClient(client);
    if (oldToken != null) {
      tokenService.revokeAccessToken(oldToken);
    }

    // create a new token

    Map<String, String> authorizationParameters = Maps.newHashMap();
    OAuth2Request clientAuth =
        new OAuth2Request(
            authorizationParameters,
            client.getClientId(),
            Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")),
            true,
            scope,
            null,
            null,
            null,
            null);
    OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null);

    OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
    token.setClient(client);
    token.setScope(scope);

    AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity();
    authHolder.setAuthentication(authentication);
    authHolder = authenticationHolderRepository.save(authHolder);
    token.setAuthenticationHolder(authHolder);

    JWTClaimsSet claims =
        new JWTClaimsSet.Builder()
            .audience(Lists.newArrayList(client.getClientId()))
            .issuer(configBean.getIssuer())
            .issueTime(new Date())
            .expirationTime(token.getExpiration())
            .jwtID(UUID.randomUUID().toString()) // set a random NONCE in the middle of it
            .build();

    JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
    JWSHeader header =
        new JWSHeader(
            signingAlg,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            jwtService.getDefaultSignerKeyId(),
            null,
            null);
    SignedJWT signed = new SignedJWT(header, claims);

    jwtService.signJwt(signed);

    token.setJwt(signed);

    return token;
  }