Package org.mitre.openid.connect.web

Source Code of org.mitre.openid.connect.web.ProtectedResourceRegistrationEndpoint

/*******************************************************************************
* Copyright 2014 The MITRE Corporation
*   and the MIT Kerberos and Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.openid.connect.web;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.model.RegisteredClient;
import org.mitre.oauth2.model.SystemScope;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.service.OAuth2TokenEntityService;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.exception.ValidationException;
import org.mitre.openid.connect.service.BlacklistedSiteService;
import org.mitre.openid.connect.service.OIDCTokenService;
import org.mitre.openid.connect.view.ClientInformationResponseView;
import org.mitre.openid.connect.view.HttpCodeView;
import org.mitre.openid.connect.view.JsonErrorView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.UriUtils;

import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gson.JsonSyntaxException;

@Controller
@RequestMapping(value = "resource")
public class ProtectedResourceRegistrationEndpoint {

  @Autowired
  private ClientDetailsEntityService clientService;

  @Autowired
  private OAuth2TokenEntityService tokenService;

  @Autowired
  private JwtSigningAndValidationService jwtService;

  @Autowired
  private SystemScopeService scopeService;

  @Autowired
  private BlacklistedSiteService blacklistService;

  @Autowired
  private ConfigurationPropertiesBean config;

  @Autowired
  private OIDCTokenService connectTokenService;

  private static Logger logger = LoggerFactory.getLogger(ProtectedResourceRegistrationEndpoint.class);

  /**
   * 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 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("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("error", ve.getError());
        m.addAttribute("errorMessage", ve.getErrorDescription());
        m.addAttribute("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.setPostLogoutRedirectUri(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("code", HttpStatus.CREATED); // http 201

        return ClientInformationResponseView.VIEWNAME;
      } catch (UnsupportedEncodingException e) {
        logger.error("Unsupported encoding", e);
        m.addAttribute("code", HttpStatus.INTERNAL_SERVER_ERROR);
        return HttpCodeView.VIEWNAME;
      } catch (IllegalArgumentException e) {
        logger.error("Couldn't save client", e);
       
        m.addAttribute("error", "invalid_client_metadata");
        m.addAttribute("errorMessage", "Unable to save client due to invalid or inconsistent metadata.");
        m.addAttribute("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("code", HttpStatus.BAD_REQUEST); // http 400

      return HttpCodeView.VIEWNAME;
    }

  }

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

  /**
   * 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 = "application/json")
  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("code", HttpStatus.OK); // http 200

        return ClientInformationResponseView.VIEWNAME;
      } catch (UnsupportedEncodingException e) {
        logger.error("Unsupported encoding", e);
        m.addAttribute("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("code", HttpStatus.FORBIDDEN); // http 403

      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 = "application/json", consumes = "application/json")
  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("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.setPostLogoutRedirectUri(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("error", ve.getError());
        m.addAttribute("errorMessage", ve.getErrorDescription());
        m.addAttribute("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("code", HttpStatus.OK); // http 200

        return ClientInformationResponseView.VIEWNAME;
      } catch (UnsupportedEncodingException e) {
        logger.error("Unsupported encoding", e);
        m.addAttribute("code", HttpStatus.INTERNAL_SERVER_ERROR);
        return HttpCodeView.VIEWNAME;
      } catch (IllegalArgumentException e) {
        logger.error("Couldn't save client", e);
       
        m.addAttribute("error", "invalid_client_metadata");
        m.addAttribute("errorMessage", "Unable to save client due to invalid or inconsistent metadata.");
        m.addAttribute("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("code", HttpStatus.FORBIDDEN); // http 403

      return HttpCodeView.VIEWNAME;
    }
  }

  /**
   * Delete the indicated client from the system.
   * @param clientId
   * @param m
   * @param auth
   * @return
   */
  @PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
  @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json")
  public String deleteResource(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {

    ClientDetailsEntity client = clientService.loadClientByClientId(clientId);

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

      clientService.deleteClient(client);

      m.addAttribute("code", HttpStatus.NO_CONTENT); // http 204

      return HttpCodeView.VIEWNAME;
    } 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.VIEWNAME;
    }
  }

  private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient) throws ValidationException {
    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) {

      if (Strings.isNullOrEmpty(newClient.getClientSecret())) {
        // no secret yet, we need to generate a secret
        newClient = clientService.generateClientSecret(newClient);
      }
    } else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.PRIVATE_KEY) {
      if (Strings.isNullOrEmpty(newClient.getJwksUri())) {
        throw new ValidationException("invalid_client_metadata", "JWK Set URI required when using private key authentication", HttpStatus.BAD_REQUEST);
      }
     
      newClient.setClientSecret(null);
    } else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.NONE) {
      newClient.setClientSecret(null);
    } else {
      throw new ValidationException("invalid_client_metadata", "Unknown authentication method", HttpStatus.BAD_REQUEST);
    }
    return newClient;
  }
 
  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;
    }
  }
}
TOP

Related Classes of org.mitre.openid.connect.web.ProtectedResourceRegistrationEndpoint

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.