/**
   * Make sure the resource set doesn't have any restricted or reserved scopes.
   *
   * @param rs
   */
  private ResourceSet validateScopes(ResourceSet rs) {
    // scopes that the client is asking for
    Set<SystemScope> requestedScopes = scopeService.fromStrings(rs.getScopes());

    // the scopes that the resource set can have must be a subset of the dynamically allowed scopes
    Set<SystemScope> allowedScopes =
        scopeService.removeRestrictedAndReservedScopes(requestedScopes);

    rs.setScopes(scopeService.toStrings(allowedScopes));

    return rs;
  }
  private ClientDetailsEntity validateScopes(ClientDetailsEntity newClient)
      throws ValidationException {
    // 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 =
        scopeService.removeRestrictedAndReservedScopes(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));

    return newClient;
  }
  /**
   * 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";
    }
  }