Merged ClientAPI and ClientDynamicRegistrationEndpoitn by hand

pull/306/merge
Amanda Anganes 12 years ago
parent 5cac7055a9
commit f9b0670ae9

@ -18,6 +18,9 @@ package org.mitre.openid.connect.web;
import java.lang.reflect.Type;
import java.util.Collection;
import org.mitre.jose.JWEAlgorithmEmbed;
import org.mitre.jose.JWEEncryptionMethodEmbed;
import org.mitre.jose.JWSAlgorithmEmbed;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.slf4j.Logger;

@ -2,12 +2,12 @@
package org.mitre.openid.connect.web;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.mitre.jose.JWSAlgorithmEntity;
import org.mitre.oauth2.exception.ClientNotFoundException;
import org.mitre.jose.JWEAlgorithmEmbed;
import org.mitre.jose.JWEEncryptionMethodEmbed;
import org.mitre.jose.JWSAlgorithmEmbed;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
@ -24,23 +24,27 @@ import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
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.RequestParam;
import org.springframework.web.bind.annotation.RequestMethod;
import com.google.common.base.Strings;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import com.nimbusds.jose.JWSAlgorithm;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
@Controller
@RequestMapping(value = "register"/*, method = RequestMethod.POST*/)
@RequestMapping(value = "register")
public class ClientDynamicRegistrationEndpoint {
@Autowired
@ -53,354 +57,417 @@ public class ClientDynamicRegistrationEndpoint {
private SystemScopeService scopeService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
private JsonParser parser = new JsonParser();
private Gson gson = new Gson();
@RequestMapping(params = "operation=client_register", produces = "application/json")
public String clientRegister(
@RequestParam(value = "redirect_uris", required = true) Set<String> redirectUris,
@RequestParam(value = "client_name", required = false) String clientName,
@RequestParam(value = "client_url", required = false) String clientUrl,
@RequestParam(value = "logo_url", required = false) String logoUrl,
@RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "tos_url", required = false) String tosUrl,
@RequestParam(value = "token_endpoint_auth_method", required = false) AuthMethod tokenEndpointAuthMethod,
@RequestParam(value = "policy_url", required = false) String policyUrl,
@RequestParam(value = "scope", required = false) Set<String> scope,
@RequestParam(value = "grant_type", required = false) Set<String> grantType,
@RequestParam(value = "jwk_url", required = false) String jwkUrl,
@RequestParam(value = "jwk_encryption_url", required = false) String jwkEncryptionUrl,
@RequestParam(value = "x509_url", required = false) String x509Url,
@RequestParam(value = "x509_encryption_url", required = false) String x509EncryptionUrl,
@RequestParam(value = "default_max_age", required = false) Integer defaultMaxAge,
@RequestParam(value = "default_acr", required = false) String defaultAcr,
// OPENID CONNECT EXTENSIONS BELOW
@RequestParam(value = "application_type", required = false) AppType applicationType,
@RequestParam(value = "sector_identifier_url", required = false) String sectorIdentifierUrl,
@RequestParam(value = "subject_type", required = false) SubjectType subjectType,
@RequestParam(value = "require_signed_request_object", required = false) JWSAlgorithm requireSignedRequestObject,
// TODO: JWE needs to be handled properly, see @InitBinder above -- we'll ignore these right now
/*
@RequestParam(value = "userinfo_signed_response_alg", required = false) String userinfoSignedResponseAlg,
@RequestParam(value = "userinfo_encrypted_response_alg", required = false) String userinfoEncryptedResponseAlg,
@RequestParam(value = "userinfo_encrypted_response_enc", required = false) String userinfoEncryptedResponseEnc,
@RequestParam(value = "userinfo_encrypted_response_int", required = false) String userinfoEncryptedResponseInt,
@RequestParam(value = "idtoken_signed_response_alg", required = false) String idtokenSignedResponseAlg,
@RequestParam(value = "idtoken_encrypted_response_alg", required = false) String idtokenEncryptedResponseAlg,
@RequestParam(value = "idtoken_encrypted_response_enc", required = false) String idtokenEncryptedResponseEnc,
@RequestParam(value = "idtoken_encrypted_response_int", required = false) String idtokenEncryptedResponseInt,
*/
@RequestParam(value = "require_auth_time", required = false, defaultValue = "true") Boolean requireAuthTime,
ModelMap model
) {
/**
* 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) {
// Create a new Client
ClientDetailsEntity newClient = parse(jsonString);
ClientDetailsEntity client = new ClientDetailsEntity();
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();
// if it's not using a private key or no auth, then generate a secret
if (tokenEndpointAuthMethod != AuthMethod.PRIVATE_KEY && tokenEndpointAuthMethod != AuthMethod.NONE) {
client = clientService.generateClientSecret(client);
}
client.setContacts(contacts);
client.setApplicationType(applicationType);
client.setClientName(clientName);
client.setClientUrl(clientUrl);
client.setTosUrl(tosUrl);
client.setLogoUrl(logoUrl);
client.setRegisteredRedirectUri(redirectUris);
client.setTokenEndpointAuthMethod(tokenEndpointAuthMethod);
client.setPolicyUrl(policyUrl);
client.setJwkUrl(jwkUrl);
client.setJwkEncryptionUrl(jwkEncryptionUrl);
client.setX509Url(x509Url);
client.setX509EncryptionUrl(x509EncryptionUrl);
client.setSectorIdentifierUrl(sectorIdentifierUrl);
client.setSubjectType(subjectType);
client.setRequireSignedRequestObject(new JWSAlgorithmEntity(requireSignedRequestObject));
client.setDefaultMaxAge(defaultMaxAge);
client.setRequireAuthTime(requireAuthTime == null ? false : requireAuthTime.booleanValue());
client.setDefaultACR(defaultAcr);
// scopes that the client is asking for
Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());
// 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(scope);
if (requestedScopes == null) {
requestedScopes = scopeService.getDefaults();
}
// 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 (requestedScopes == null || requestedScopes.isEmpty()) {
requestedScopes = scopeService.getDefaults();
}
client.setScope(scopeService.toStrings(allowedScopes));
if (grantType != null) {
// TODO: check against some kind of grant type service for validity
client.setAuthorizedGrantTypes(grantType);
// the scopes that the client can have must be a subset of the dynamically allowed scopes
Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes);
newClient.setScope(scopeService.toStrings(allowedScopes));
// set default grant types if needed
if (newClient.getGrantTypes() == null || newClient.getGrantTypes().isEmpty()) {
newClient.setGrantTypes(Sets.newHashSet("authorization_code", "refresh_token")); // allow authorization code and refresh token grant types 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);
// now save it
ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);
// generate the registration access token
OAuth2AccessTokenEntity token = createRegistrationAccessToken(savedClient);
// send it all out to the view
m.addAttribute("client", savedClient);
m.addAttribute("code", HttpStatus.CREATED); // http 201
m.addAttribute("token", token);
return "clientInformationResponseView";
} else {
client.setAuthorizedGrantTypes(Sets.newHashSet("authorization_code", "refresh_token")); // allow authorization code and refresh token grant types
// didn't parse, this is a bad request
logger.error("ClientDynamicRegistrationEndpoint: registerNewClient failed; submitted JSON is malformed");
m.addAttribute("code", HttpStatus.BAD_REQUEST); // http 400
return "httpCodeView";
}
// defaults for SECOAUTH functionality
// TODO: extensions to request, or configuration?
client.setAccessTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(1)); // access tokens good for 1hr
client.setIdTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(10)); // id tokens good for 10min
client.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked
client.setDynamicallyRegistered(true);
ClientDetailsEntity saved = clientService.saveNewClient(client);
OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client);
model.put("fullClient", Boolean.TRUE);
model.put("client", saved);
model.put("token", registrationAccessToken);
return "clientRegistration";
}
/**
* @param client
* @return
* @throws AuthenticationException
*/
private OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) throws AuthenticationException, InvalidClientException {
// create a registration access token, treat it like a client credentials flow
// I can't use the auth request interface here because it has no setters and bad constructors -- THIS IS BAD API DESIGN
DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(client.getClientId(), Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE));
authorizationRequest.setApproved(true);
authorizationRequest.setAuthorities(Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")));
OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest, null);
OAuth2AccessTokenEntity registrationAccessToken = (OAuth2AccessTokenEntity) tokenService.createAccessToken(authentication);
return registrationAccessToken;
}
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')")
@RequestMapping(params = "operation=rotate_secret", produces = "application/json")
public String rotateSecret(OAuth2Authentication auth, ModelMap model) {
* 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) {
String clientId = auth.getAuthorizationRequest().getClientId();
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
if (client == null) {
logger.error("ClientDynamicRegistrationEndpoint: rotateSecret failed, could not find client " + clientId);
model.addAttribute("code", HttpStatus.NOT_FOUND);
return "httpCodeView";
}
// rotate the secret, if available
if (client.isSecretRequired()) {
client = clientService.generateClientSecret(client);
}
OAuth2AccessTokenEntity registrationAccessToken = null;
try {
// mint a new access token
registrationAccessToken = createRegistrationAccessToken(client);
} catch (AuthenticationException e) {
logger.error("ClientDynamicRegistrationEndpoint: rotateSecret failed; AuthenticationException: CLient " + clientId
+ " attempted to rotate secret and failed with the following stack trace: "
+ e.getStackTrace().toString());
model.addAttribute("code", HttpStatus.FORBIDDEN);
return "httpCodeView";
}
if (client != null && client.getClientId().equals(auth.getAuthorizationRequest().getClientId())) {
// revoke the old one
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
if (details != null) {
OAuth2AccessTokenEntity oldAccessToken = null;
try {
oldAccessToken = tokenService.readAccessToken(details.getTokenValue());
} catch (AuthenticationException e) {
logger.error("ClientDynamicRegistrationEndpoint: rotateSecret failed; AuthenticationException: CLient " + clientId
+ " attempted to rotate secret and failed with the following stack trace: "
+ e.getStackTrace().toString());
model.addAttribute("code", HttpStatus.FORBIDDEN);
return "httpCodeView";
} catch (InvalidTokenException e) {
logger.error("ClientDynamicRegistrationEndpoint: rotateSecret failed; InvalidTokenException: CLient " + clientId
+ " attempted to rotate secret with an invalid token."
+ e.getStackTrace().toString());
model.addAttribute("code", HttpStatus.BAD_REQUEST);
return "httpCodeView";
}
if (oldAccessToken != null) {
tokenService.revokeAccessToken(oldAccessToken);
} else {
// This is a severe error
logger.error("SEVERE: ClientDynamicRegistrationEndpoint: rotateSecret failed; Revocation of access token for client " + clientId
+ " failed. Original token can not be found.");
throw OAuth2Exception.create(OAuth2Exception.INVALID_TOKEN, "SEVERE: token not found, something is fishy");
}
// we return the token that we got in
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
// send it all out to the view
m.addAttribute("client", client);
m.addAttribute("code", HttpStatus.OK); // http 200
m.addAttribute("token", token);
return "clientInformationResponseView";
} else {
// client mismatch
logger.error("ClientDynamicRegistrationEndpoint: readClientConfiguration failed, client ID mismatch: "
+ clientId + " and " + auth.getAuthorizationRequest().getClientId() + " do not match.");
m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403
return "httpCodeView";
}
// save the client
ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("fullClient", Boolean.FALSE);
model.put("client", saved);
model.put("token", registrationAccessToken);
return "clientRegistration";
}
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')")
@RequestMapping(params = "operation=client_update", produces = "application/json")
public String clientUpdate(
@RequestParam(value = "redirect_uris", required = true) Set<String> redirectUris,
@RequestParam(value = "client_name", required = false) String clientName,
@RequestParam(value = "client_url", required = false) String clientUrl,
@RequestParam(value = "logo_url", required = false) String logoUrl,
@RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "tos_url", required = false) String tosUrl,
@RequestParam(value = "token_endpoint_auth_method", required = false) AuthMethod tokenEndpointAuthMethod,
@RequestParam(value = "policy_url", required = false) String policyUrl,
@RequestParam(value = "scope", required = false) Set<String> scope,
@RequestParam(value = "grant_type", required = false) Set<String> grantType,
@RequestParam(value = "jwk_url", required = false) String jwkUrl,
@RequestParam(value = "jwk_encryption_url", required = false) String jwkEncryptionUrl,
@RequestParam(value = "x509_url", required = false) String x509Url,
@RequestParam(value = "x509_encryption_url", required = false) String x509EncryptionUrl,
@RequestParam(value = "default_max_age", required = false) Integer defaultMaxAge,
@RequestParam(value = "default_acr", required = false) String defaultAcr,
// OPENID CONNECT EXTENSIONS BELOW
@RequestParam(value = "application_type", required = false) AppType applicationType,
@RequestParam(value = "sector_identifier_url", required = false) String sectorIdentifierUrl,
@RequestParam(value = "subject_type", required = false) SubjectType subjectType,
@RequestParam(value = "require_signed_request_object", required = false) JWSAlgorithm requireSignedRequestObject,
@RequestParam(value = "require_auth_time", required = false, defaultValue = "true") Boolean requireAuthTime,
// TODO: JWE needs to be handled properly, see @InitBinder above -- we'll ignore these right now
/*
@RequestParam(value = "userinfo_signed_response_alg", required = false) String userinfoSignedResponseAlg,
@RequestParam(value = "userinfo_encrypted_response_alg", required = false) String userinfoEncryptedResponseAlg,
@RequestParam(value = "userinfo_encrypted_response_enc", required = false) String userinfoEncryptedResponseEnc,
@RequestParam(value = "userinfo_encrypted_response_int", required = false) String userinfoEncryptedResponseInt,
@RequestParam(value = "idtoken_signed_response_alg", required = false) String idtokenSignedResponseAlg,
@RequestParam(value = "idtoken_encrypted_response_alg", required = false) String idtokenEncryptedResponseAlg,
@RequestParam(value = "idtoken_encrypted_response_enc", required = false) String idtokenEncryptedResponseEnc,
@RequestParam(value = "idtoken_encrypted_response_int", required = false) String idtokenEncryptedResponseInt,
*/
@RequestParam Map<String, String> params,
OAuth2Authentication auth,
ModelMap model
) {
/**
* 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) {
String clientId = auth.getAuthorizationRequest().getClientId();
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
ClientDetailsEntity newClient = parse(jsonString);
ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
if (client == null) {
logger.error("ClientDynamicRegistrationEndpoint: clientUpdate failed; Client with id " + clientId + " does not exist or cannot be found.");
model.addAttribute("code", HttpStatus.NOT_FOUND);
return "httpCodeView";
}
/*
* now process each field:
* 1) If input is not provided (null, not in map), keep existing value
* 2) If input is provided (in map) but null or blank, remove existing value
* 3) If input is not null and not blank, replace existing value
*/
if (params.containsKey("contacts")) {
client.setContacts(contacts);
}
if (params.containsKey("application_type")) {
client.setApplicationType(applicationType);
}
if (params.containsKey("client_name")) {
client.setClientName(Strings.emptyToNull(clientName));
}
if (params.containsKey("client_url")) {
client.setClientUrl(Strings.emptyToNull(clientUrl));
}
if (params.containsKey("tos_url")) {
client.setTosUrl(Strings.emptyToNull(tosUrl));
}
if (params.containsKey("logo_url")) {
client.setLogoUrl(Strings.emptyToNull(logoUrl));
}
if (params.containsKey("redirect_uris")) {
client.setRegisteredRedirectUri(redirectUris);
}
if (params.containsKey("token_endpoint_auth_method")) {
client.setTokenEndpointAuthMethod(tokenEndpointAuthMethod);
}
if (params.containsKey("policy_url")) {
client.setPolicyUrl(Strings.emptyToNull(policyUrl));
}
if (params.containsKey("jwk_url")) {
client.setJwkUrl(Strings.emptyToNull(jwkUrl));
}
if (params.containsKey("jwk_encryption_url")) {
client.setJwkEncryptionUrl(Strings.emptyToNull(jwkEncryptionUrl));
}
if (params.containsKey("x509_url")) {
client.setX509Url(Strings.emptyToNull(x509Url));
}
if (params.containsKey("x509_encryption_url")) {
client.setX509EncryptionUrl(Strings.emptyToNull(x509EncryptionUrl));
}
if (params.containsKey("default_max_age")) {
client.setDefaultMaxAge(defaultMaxAge);
}
if (params.containsKey("default_acr")) {
client.setDefaultACR(Strings.emptyToNull(defaultAcr));
}
if (params.containsKey("scope")) {
if (newClient != null && oldClient != null // we have an existing client and the new one parsed
&& oldClient.getClientId().equals(auth.getAuthorizationRequest().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(scope);
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);
client.setScope(scopeService.toStrings(allowedScopes));
}
if (params.containsKey("grant_type")) {
// TODO: check against some kind of grant type service for validity
client.setAuthorizedGrantTypes(grantType);
// make sure that the client doesn't ask for scopes it can't have
newClient.setScope(scopeService.toStrings(allowedScopes));
// 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());
// send it all out to the view
m.addAttribute("client", savedClient);
m.addAttribute("code", HttpStatus.OK); // http 200
m.addAttribute("token", token);
return "clientInformationResponseView";
} else {
// client mismatch
logger.error("ClientDynamicRegistrationEndpoint: readClientConfiguration failed, client ID mismatch: "
+ clientId + " and " + auth.getAuthorizationRequest().getClientId() + " do not match.");
m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403
return "httpCodeView";
}
}
/**
* Delete the indicated client from the system.
* @param clientId
* @param m
* @param auth
* @return
*/
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE + "')")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json")
public String deleteClient(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
// OIDC
if (params.containsKey("sector_identifier_url")) {
client.setSectorIdentifierUrl(Strings.emptyToNull(sectorIdentifierUrl));
}
if (params.containsKey("subject_type")) {
client.setSubjectType(subjectType);
}
if (params.containsKey("require_signed_request_object")) { // TODO: rename field
client.setRequireSignedRequestObject(new JWSAlgorithmEntity(requireSignedRequestObject));
if (client != null && client.getClientId().equals(auth.getAuthorizationRequest().getClientId())) {
clientService.deleteClient(client);
// we return the token that we got in
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
// send it all out to the view
m.addAttribute("client", client);
m.addAttribute("code", HttpStatus.OK); // http 200
m.addAttribute("token", token);
return "clientInformationResponseView";
} else {
// client mismatch
logger.error("ClientDynamicRegistrationEndpoint: readClientConfiguration failed, client ID mismatch: "
+ clientId + " and " + auth.getAuthorizationRequest().getClientId() + " do not match.");
m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403
return "httpCodeView";
}
if (params.containsKey("require_auth_time")) {
client.setRequireAuthTime(requireAuthTime == null ? false : requireAuthTime.booleanValue()); // watch out for autoboxing
}
/**
*
* Create an unbound ClientDetailsEntity from the given JSON string.
*
* @param jsonString
* @return the entity if successful, null otherwise
*/
private ClientDetailsEntity parse(String jsonString) {
JsonElement jsonEl = parser.parse(jsonString);
if (jsonEl.isJsonObject()) {
JsonObject o = jsonEl.getAsJsonObject();
ClientDetailsEntity c = new ClientDetailsEntity();
// TODO: make these field names into constants
// these two fields should only be sent in the update request, and MUST match existing values
c.setClientId(getAsString(o, "client_id"));
c.setClientSecret(getAsString(o, "client_secret"));
// OAuth DynReg
c.setRedirectUris(getAsStringSet(o, "redirect_uris"));
c.setClientName(getAsString(o, "client_name"));
c.setClientUri(getAsString(o, "client_uri"));
c.setLogoUri(getAsString(o, "logo_uri"));
c.setContacts(getAsStringSet(o, "contacts"));
c.setTosUri(getAsString(o, "tos_uri"));
String authMethod = getAsString(o, "token_endpoint_auth_method");
if (authMethod != null) {
c.setTokenEndpointAuthMethod(AuthMethod.getByValue(authMethod));
}
// scope is a space-separated string
String scope = getAsString(o, "scope");
if (scope != null) {
c.setScope(Sets.newHashSet(Splitter.on(" ").split(scope)));
}
c.setGrantTypes(getAsStringSet(o, "grant_types"));
c.setPolicyUri(getAsString(o, "policy_uri"));
c.setJwksUri(getAsString(o, "jwks_uri"));
// OIDC Additions
String appType = getAsString(o, "application_type");
if (appType != null) {
c.setApplicationType(AppType.getByValue(appType));
}
c.setSectorIdentifierUri(getAsString(o, "sector_identifier_uri"));
String subjectType = getAsString(o, "subject_type");
if (subjectType != null) {
c.setSubjectType(SubjectType.getByValue(subjectType));
}
c.setRequestObjectSigningAlg(getAsJwsAlgorithm(o, "request_object_signing_alg"));
c.setUserInfoSignedResponseAlg(getAsJwsAlgorithm(o, "userinfo_signed_response_alg"));
c.setUserInfoEncryptedResponseAlg(getAsJweAlgorithm(o, "userinfo_encrypted_response_alg"));
c.setUserInfoEncryptedResponseEnc(getAsJweEncryptionMethod(o, "userinfo_encrypted_response_enc"));
c.setIdTokenSignedResponseAlg(getAsJwsAlgorithm(o, "id_token_signed_response_alg"));
c.setIdTokenEncryptedResponseAlg(getAsJweAlgorithm(o, "id_token_encrypted_response_alg"));
c.setIdTokenEncryptedResponseEnc(getAsJweEncryptionMethod(o, "id_token_encrypted_response_enc"));
if (o.has("default_max_age")) {
if (o.get("default_max_age").isJsonPrimitive()) {
c.setDefaultMaxAge(o.get("default_max_age").getAsInt());
}
}
if (o.has("require_auth_time")) {
if (o.get("require_auth_time").isJsonPrimitive()) {
c.setRequireAuthTime(o.get("require_auth_time").getAsBoolean());
}
}
c.setDefaultACRvalues(getAsStringSet(o, "default_acr_values"));
c.setInitiateLoginUri(getAsString(o, "initiate_login_uri"));
c.setPostLogoutRedirectUri(getAsString(o, "post_logout_redirect_uri"));
c.setRequestUris(getAsStringSet(o, "request_uris"));
return c;
} else {
return null;
}
}
ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("fullClient", Boolean.TRUE);
model.put("client", saved);
return "clientRegister";
}
/**
* Gets the value of the given given member as a set of strings, null if it doesn't exist
*/
private Set<String> getAsStringSet(JsonObject o, String member) throws JsonSyntaxException {
if (o.has(member)) {
return gson.fromJson(o.get(member), new TypeToken<Set<String>>(){}.getType());
} else {
return null;
}
}
/**
* Gets the value of the given member as a string, null if it doesn't exist
*/
private String getAsString(JsonObject o, String member) {
if (o.has(member)) {
JsonElement e = o.get(member);
if (e != null && e.isJsonPrimitive()) {
return e.getAsString();
} else {
return null;
}
} else {
return null;
}
}
/**
* Gets the value of the given member as a JWS Algorithm, null if it doesn't exist
*/
private JWSAlgorithmEmbed getAsJwsAlgorithm(JsonObject o, String member) {
String s = getAsString(o, member);
if (s != null) {
return JWSAlgorithmEmbed.getForAlgorithmName(s);
} else {
return null;
}
}
/**
* Gets the value of the given member as a JWE Algorithm, null if it doesn't exist
*/
private JWEAlgorithmEmbed getAsJweAlgorithm(JsonObject o, String member) {
String s = getAsString(o, member);
if (s != null) {
return JWEAlgorithmEmbed.getForAlgorithmName(s);
} else {
return null;
}
}
/**
* Gets the value of the given member as a JWE Encryption Method, null if it doesn't exist
*/
private JWEEncryptionMethodEmbed getAsJweEncryptionMethod(JsonObject o, String member) {
String s = getAsString(o, member);
if (s != null) {
return JWEEncryptionMethodEmbed.getForAlgorithmName(s);
} else {
return null;
}
}
/**
* @param client
* @return
* @throws AuthenticationException
*/
private OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) throws AuthenticationException {
// create a registration access token, treat it like a client credentials flow
// I can't use the auth request interface here because it has no setters and bad constructors -- THIS IS BAD API DESIGN
DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(client.getClientId(), Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE));
authorizationRequest.setApproved(true);
authorizationRequest.setAuthorities(Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")));
OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest, null);
OAuth2AccessTokenEntity registrationAccessToken = (OAuth2AccessTokenEntity) tokenService.createAccessToken(authentication);
return registrationAccessToken;
}
}
}
Loading…
Cancel
Save