added parser to client registration endpoint
parent
5c044b9eff
commit
db24c203ec
|
@ -78,11 +78,11 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
private AuthMethod tokenEndpointAuthMethod = AuthMethod.SECRET_BASIC; // token_endpoint_auth_method
|
||||
private Set<String> scope = new HashSet<String>(); // scope
|
||||
private Set<String> grantTypes = new HashSet<String>(); // grant_types
|
||||
private Set<String> responseTypes = new HashSet<String>(); // response_types
|
||||
private String policyUri;
|
||||
private String jwksUri;
|
||||
|
||||
/** Fields from OIDC Client Registration Specification **/
|
||||
private Set<String> responseTypes = new HashSet<String>(); // response_types
|
||||
private AppType applicationType; // application_type
|
||||
private String sectorIdentifierUri; // sector_identifier_uri
|
||||
private SubjectType subjectType; // subject_type
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.view;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides a minimal representation of a client's registration information, to be shown from the dynamic registration endpoint
|
||||
* on the client_register and rotate_secret operations.
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
@Component("clientInformationResponse")
|
||||
public class ClientInformationResponseView extends AbstractView {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
response.setContentType("application/json");
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
|
||||
OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) model.get("token");
|
||||
HttpStatus code = (HttpStatus) model.get("code");
|
||||
if (code == null) {
|
||||
code = HttpStatus.OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.view;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides a minimal representation of a client's registration information, to be shown from the dynamic registration endpoint
|
||||
* on the client_register and rotate_secret operations.
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
@Component("clientRegistration")
|
||||
public class ClientRegistrationView extends AbstractView {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
response.setContentType("application/json");
|
||||
|
||||
try {
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
|
||||
OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) model.get("token");
|
||||
Boolean fullClient = (Boolean) model.get("fullClient"); // do we display the full client or not?
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("client_id", client.getClientId());
|
||||
if (client.isSecretRequired()) {
|
||||
obj.addProperty("client_secret", client.getClientSecret());
|
||||
}
|
||||
|
||||
if (fullClient) {
|
||||
// TODO: display the rest of the client fields, for now just this to mark changes
|
||||
obj.addProperty("client_name", client.getClientName());
|
||||
if (client.getScope() != null) {
|
||||
obj.addProperty("scope", Joiner.on(" ").join(client.getScope()));
|
||||
}
|
||||
if (client.getRegisteredRedirectUri() != null) {
|
||||
obj.addProperty("redirect_uri", Joiner.on(" ").join(client.getRegisteredRedirectUri()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (token != null) {
|
||||
obj.addProperty("registration_access_token", token.getValue());
|
||||
obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire
|
||||
}
|
||||
|
||||
Writer out = response.getWriter();
|
||||
gson.toJson(obj, out);
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
logger.error("IOException ", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
package org.mitre.openid.connect.web;
|
||||
|
||||
import java.util.Map;
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mitre.jose.JWEAlgorithmEntity;
|
||||
import org.mitre.jose.JWEEncryptionMethodEntity;
|
||||
import org.mitre.jose.JWSAlgorithmEntity;
|
||||
import org.mitre.oauth2.exception.ClientNotFoundException;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||
|
@ -18,21 +19,26 @@ import org.mitre.oauth2.service.ClientDetailsEntityService;
|
|||
import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
||||
import org.mitre.oauth2.service.SystemScopeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
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.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.JsonPrimitive;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(value = "register"/*, method = RequestMethod.POST*/)
|
||||
|
@ -47,122 +53,232 @@ public class ClientDynamicRegistrationEndpoint {
|
|||
@Autowired
|
||||
private SystemScopeService scopeService;
|
||||
|
||||
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
|
||||
) {
|
||||
@RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
|
||||
public String registerNewClient(@RequestBody String jsonString, Model m, Principal p) {
|
||||
|
||||
ClientDetailsEntity newClient = parse(jsonString);
|
||||
|
||||
// Create a new Client
|
||||
|
||||
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());
|
||||
if (requestedScopes == null) {
|
||||
requestedScopes = scopeService.getDefaults();
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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 (grantType != null) {
|
||||
// TODO: check against some kind of grant type service for validity
|
||||
client.setAuthorizedGrantTypes(grantType);
|
||||
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
|
||||
}
|
||||
|
||||
// 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 "clientInformationView";
|
||||
} 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
|
||||
|
||||
m.addAttribute("code", HttpStatus.BAD_REQUEST);
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 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
|
||||
|
||||
// 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_type"));
|
||||
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, "user_info_encrypted_response_alg"));
|
||||
c.setUserInfoEncryptedResponseEnc(getAsJweEncryptionMethod(o, "user_info_encrypted_response_enc"));
|
||||
|
||||
c.setIdTokenSignedResponseAlg(getAsJwsAlgorithm(o, "id_token_signed_response_alg"));
|
||||
c.setIdTokenEncryptedResponseAlg(getAsJweAlgorithm(o, "id_token_encrypted_response_alg"));
|
||||
c.setIdTokenEncryptedReponseEnc(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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 JWSAlgorithmEntity getAsJwsAlgorithm(JsonObject o, String member) {
|
||||
String s = getAsString(o, member);
|
||||
if (s != null) {
|
||||
return new JWSAlgorithmEntity(s);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the given member as a JWE Algorithm, null if it doesn't exist
|
||||
*/
|
||||
private JWEAlgorithmEntity getAsJweAlgorithm(JsonObject o, String member) {
|
||||
String s = getAsString(o, member);
|
||||
if (s != null) {
|
||||
return new JWEAlgorithmEntity(s);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of the given member as a JWE Encryption Method, null if it doesn't exist
|
||||
*/
|
||||
private JWEEncryptionMethodEntity getAsJweEncryptionMethod(JsonObject o, String member) {
|
||||
String s = getAsString(o, member);
|
||||
if (s != null) {
|
||||
return new JWEEncryptionMethodEntity(s);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param client
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
|
@ -178,191 +294,4 @@ public class ClientDynamicRegistrationEndpoint {
|
|||
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) {
|
||||
|
||||
|
||||
String clientId = auth.getAuthorizationRequest().getClientId();
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client == null) {
|
||||
throw new ClientNotFoundException("Could not find client: " + clientId);
|
||||
}
|
||||
|
||||
// rotate the secret, if available
|
||||
if (client.isSecretRequired()) {
|
||||
client = clientService.generateClientSecret(client);
|
||||
}
|
||||
|
||||
// mint a new access token
|
||||
OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client);
|
||||
|
||||
// revoke the old one
|
||||
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
|
||||
if (details != null) {
|
||||
OAuth2AccessTokenEntity oldAccessToken = tokenService.readAccessToken(details.getTokenValue());
|
||||
if (oldAccessToken != null) {
|
||||
tokenService.revokeAccessToken(oldAccessToken);
|
||||
} else {
|
||||
// serious error here -- how'd we get this far without a valid token?!
|
||||
throw new OAuth2Exception("SEVERE: token not found, something is fishy");
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
) {
|
||||
|
||||
String clientId = auth.getAuthorizationRequest().getClientId();
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client == null) {
|
||||
throw new ClientNotFoundException("Could not find client: " + clientId);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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")) {
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
// 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 (params.containsKey("require_auth_time")) {
|
||||
client.setRequireAuthTime(requireAuthTime == null ? false : requireAuthTime.booleanValue()); // watch out for autoboxing
|
||||
}
|
||||
|
||||
|
||||
ClientDetailsEntity saved = clientService.updateClient(client, client);
|
||||
|
||||
model.put("fullClient", Boolean.TRUE);
|
||||
model.put("client", saved);
|
||||
return "clientRegister";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue