beginning of client registration refactor to track IETF dynreg spec

pull/263/head
Justin Richer 2012-12-10 17:36:33 -05:00
parent 0659432561
commit e2bc15c2b2
5 changed files with 126 additions and 72 deletions

View File

@ -63,7 +63,6 @@ public class ClientDetailsEntity implements ClientDetails {
/** Our own fields **/
private String clientDescription = ""; // human-readable description
private boolean allowRefresh = false; // do we allow refresh tokens for this client?
private boolean allowMultipleAccessTokens = false; // do we allow multiple access tokens, or not?
private boolean reuseRefreshToken = false; // do we let someone reuse a refresh token?
private boolean dynamicallyRegistered = false; // was this client dynamically registered?
@ -83,7 +82,7 @@ public class ClientDetailsEntity implements ClientDetails {
/** Fields from Client Registration Specification **/
private AppType applicationType;
private String applicationName;
private String clientName;
private AuthType tokenEndpointAuthType = AuthType.SECRET_BASIC;
private UserIdType userIdType;
@ -91,6 +90,8 @@ public class ClientDetailsEntity implements ClientDetails {
private String logoUrl;
private String policyUrl;
private String clientUrl;
private String tosUrl;
private String jwkUrl;
private String jwkEncryptionUrl;
private String x509Url;
@ -239,19 +240,11 @@ public class ClientDetailsEntity implements ClientDetails {
/**
* @return the allowRefresh
*/
@Basic
@Column(name="allow_refresh")
@Transient
public boolean isAllowRefresh() {
return allowRefresh;
return getAuthorizedGrantTypes().contains("refresh_token");
}
/**
* @param allowRefresh Whether to allow for issuance of refresh tokens or not (defaults to false)
*/
public void setAllowRefresh(boolean allowRefresh) {
this.allowRefresh = allowRefresh;
}
@Basic
@Column(name="allow_multiple_access_tokens")
public boolean isAllowMultipleAccessTokens() {
@ -517,13 +510,13 @@ public class ClientDetailsEntity implements ClientDetails {
}
@Basic
@Column(name="application_name")
public String getApplicationName() {
return applicationName;
@Column(name="client_name")
public String getClientName() {
return clientName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
public void setClientName(String clientName) {
this.clientName = clientName;
}
@Enumerated(EnumType.STRING)
@ -580,6 +573,38 @@ public class ClientDetailsEntity implements ClientDetails {
this.policyUrl = policyUrl;
}
/**
* @return the clientUrl
*/
@Basic
@Column(name="client_url")
public String getClientUrl() {
return clientUrl;
}
/**
* @param clientUrl the clientUrl to set
*/
public void setClientUrl(String clientUrl) {
this.clientUrl = clientUrl;
}
/**
* @return the tosUrl
*/
@Basic
@Column(name="tos_url")
public String getTosUrl() {
return tosUrl;
}
/**
* @param tosUrl the tosUrl to set
*/
public void setTosUrl(String tosUrl) {
this.tosUrl = tosUrl;
}
@Basic
@Column(name="jwk_url")
public String getJwkUrl() {
@ -766,8 +791,6 @@ public class ClientDetailsEntity implements ClientDetails {
+ (id != null ? "id=" + id + ", " : "")
+ (clientDescription != null ? "clientDescription="
+ clientDescription + ", " : "")
+ "allowRefresh="
+ allowRefresh
+ ", allowMultipleAccessTokens="
+ allowMultipleAccessTokens
+ ", reuseRefreshToken="
@ -799,8 +822,8 @@ public class ClientDetailsEntity implements ClientDetails {
+ additionalInformation + ", " : "")
+ (applicationType != null ? "applicationType="
+ applicationType + ", " : "")
+ (applicationName != null ? "applicationName="
+ applicationName + ", " : "")
+ (clientName != null ? "applicationName="
+ clientName + ", " : "")
+ (tokenEndpointAuthType != null ? "tokenEndpointAuthType="
+ tokenEndpointAuthType + ", " : "")
+ (userIdType != null ? "userIdType=" + userIdType + ", " : "")
@ -864,9 +887,8 @@ public class ClientDetailsEntity implements ClientDetails {
+ ((additionalInformation == null) ? 0 : additionalInformation
.hashCode());
result = prime * result + (allowMultipleAccessTokens ? 1231 : 1237);
result = prime * result + (allowRefresh ? 1231 : 1237);
result = prime * result
+ ((applicationName == null) ? 0 : applicationName.hashCode());
+ ((clientName == null) ? 0 : clientName.hashCode());
result = prime * result
+ ((applicationType == null) ? 0 : applicationType.hashCode());
result = prime * result
@ -1002,14 +1024,11 @@ public class ClientDetailsEntity implements ClientDetails {
if (allowMultipleAccessTokens != other.allowMultipleAccessTokens) {
return false;
}
if (allowRefresh != other.allowRefresh) {
return false;
}
if (applicationName == null) {
if (other.applicationName != null) {
if (clientName == null) {
if (other.clientName != null) {
return false;
}
} else if (!applicationName.equals(other.applicationName)) {
} else if (!clientName.equals(other.clientName)) {
return false;
}
if (applicationType != other.applicationType) {

View File

@ -20,11 +20,15 @@ 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("clientAssociate")
public class ClientAssociateView extends AbstractView {
@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)
@ -40,17 +44,26 @@ public class ClientAssociateView extends AbstractView {
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());
}
obj.addProperty("registration_access_token", token.getValue());
if (token.getExpiration() != null) {
obj.addProperty("expires_at", token.getExpiration().getTime()); // TODO: make sure this makes sense?
} else {
obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire
if (fullClient) {
// TODO: display the rest of the client fields, for now just this to mark changes
obj.addProperty("client_name", client.getClientName());
}
if (token != null) {
obj.addProperty("registration_access_token", token.getValue());
if (token.getExpiration() != null) {
obj.addProperty("expires_at", token.getExpiration().getTime()); // TODO: make sure this makes sense?
} else {
obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire
}
}
Writer out = response.getWriter();

View File

@ -19,6 +19,9 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
/**
*
* Provides a minimal display response for a dynamic client registration's client_update operation.
*
* @author jricher
*
*/

View File

@ -183,23 +183,29 @@ public class ClientDynamicRegistrationEndpoint {
});
}
@RequestMapping(params = "type=client_associate", produces = "application/json")
public String clientAssociate(
@RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "application_type", required = false) AppType applicationType,
@RequestParam(value = "application_name", required = false) String applicationName,
@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 = "redirect_uris", required = false) Set<String> redirectUris,
@RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "tos_url", required = false) String tosUrl,
@RequestParam(value = "token_endpoint_auth_type", required = false) AuthType tokenEndpointAuthType,
@RequestParam(value = "policy_url", required = false) String policyUrl,
@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 = "user_id_type", required = false) UserIdType userIdType,
@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,
@ -212,9 +218,7 @@ public class ClientDynamicRegistrationEndpoint {
@RequestParam(value = "idtoken_encrypted_response_int", required = false) String idtokenEncryptedResponseInt,
*/
@RequestParam(value = "default_max_age", required = false) Integer defaultMaxAge,
@RequestParam(value = "require_auth_time", required = false, defaultValue = "true") Boolean requireAuthTime,
@RequestParam(value = "default_acr", required = false) String defaultAcr,
ModelMap model
) {
@ -223,12 +227,16 @@ public class ClientDynamicRegistrationEndpoint {
ClientDetailsEntity client = new ClientDetailsEntity();
// always generate a secret for this client
client = clientService.generateClientSecret(client);
// if it's not using a private key or no auth, then generate a secret
if (tokenEndpointAuthType != AuthType.PRIVATE_KEY && tokenEndpointAuthType != AuthType.NONE) {
client = clientService.generateClientSecret(client);
}
client.setContacts(contacts);
client.setApplicationType(applicationType);
client.setApplicationName(applicationName);
client.setClientName(clientName);
client.setClientUrl(clientUrl);
client.setTosUrl(tosUrl);
client.setLogoUrl(logoUrl);
client.setRegisteredRedirectUri(redirectUris);
client.setTokenEndpointAuthType(tokenEndpointAuthType);
@ -247,11 +255,10 @@ public class ClientDynamicRegistrationEndpoint {
// defaults for SECOAUTH functionality
// TODO: extensions to request, or configuration?
client.setScope(Sets.newHashSet("openid", "phone", "address", "profile", "email")); // provision all scopes
client.setAllowRefresh(true); // by default allow refresh tokens on dynamic clients
client.setAccessTokenValiditySeconds(3600); // access tokens good for 1hr
client.setIdTokenValiditySeconds(600); // id tokens good for 10min
client.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked
client.setAuthorizedGrantTypes(Sets.newHashSet("authorization_code"));
client.setAuthorizedGrantTypes(Sets.newHashSet("authorization_code", "refresh_token")); // allow authoirzation code and refresh token grant types
client.setDynamicallyRegistered(true);
@ -259,10 +266,11 @@ public class ClientDynamicRegistrationEndpoint {
OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client);
model.put("fullClient", Boolean.TRUE);
model.put("client", saved);
model.put("token", registrationAccessToken);
return "clientAssociate";
return "clientRegistration";
}
/**
@ -282,7 +290,7 @@ public class ClientDynamicRegistrationEndpoint {
}
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')")
@RequestMapping(params = "type=rotate_secret", produces = "application/json")
@RequestMapping(params = "operation=rotate_secret", produces = "application/json")
public String rotateSecret(OAuth2Authentication auth, ModelMap model) {
@ -316,30 +324,38 @@ public class ClientDynamicRegistrationEndpoint {
// save the client
ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("fullClient", Boolean.FALSE);
model.put("client", saved);
model.put("token", registrationAccessToken);
return "clientAssociate";
return "clientRegistration";
}
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')")
@RequestMapping(params = "type=client_update", produces = "application/json")
@RequestMapping(params = "operation=client_update", produces = "application/json")
public String clientUpdate(
@RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "application_type", required = false) AppType applicationType,
@RequestParam(value = "application_name", required = false) String applicationName,
@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 = "redirect_uris", required = false) Set<String> redirectUris,
@RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "tos_url", required = false) String tosUrl,
@RequestParam(value = "token_endpoint_auth_type", required = false) AuthType tokenEndpointAuthType,
@RequestParam(value = "policy_url", required = false) String policyUrl,
@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 = "user_id_type", required = false) UserIdType userIdType,
@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,
@ -352,9 +368,6 @@ public class ClientDynamicRegistrationEndpoint {
@RequestParam(value = "idtoken_encrypted_response_int", required = false) String idtokenEncryptedResponseInt,
*/
@RequestParam(value = "default_max_age", required = false) Integer defaultMaxAge,
@RequestParam(value = "require_auth_time", required = false) Boolean requireAuthTime,
@RequestParam(value = "default_acr", required = false) String defaultAcr,
OAuth2Authentication auth,
ModelMap model
@ -369,7 +382,9 @@ public class ClientDynamicRegistrationEndpoint {
client.setContacts(contacts);
client.setApplicationType(applicationType);
client.setApplicationName(applicationName);
client.setClientName(clientName);
client.setClientUrl(clientUrl);
client.setTosUrl(tosUrl);
client.setLogoUrl(logoUrl);
client.setRegisteredRedirectUri(redirectUris);
client.setTokenEndpointAuthType(tokenEndpointAuthType);
@ -387,8 +402,9 @@ public class ClientDynamicRegistrationEndpoint {
ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("fullClient", Boolean.TRUE);
model.put("client", saved);
return "clientUpdate";
return "clientRegister";
}
}

View File

@ -67,11 +67,11 @@ CREATE TABLE IF NOT EXISTS blacklisted_site (
CREATE TABLE IF NOT EXISTS client_details (
id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY,
client_description VARCHAR(256),
allow_refresh BOOLEAN,
allow_multiple_access_tokens BOOLEAN,
reuse_refresh_tokens BOOLEAN,
dynamically_registered BOOLEAN,
client_description VARCHAR(1024),
allow_multiple_access_tokens BOOLEAN NOT NULL DEFAULT true,
reuse_refresh_tokens BOOLEAN NOT NULL DEFAULT true,
dynamically_registered BOOLEAN NOT NULL DEFAULT false,
id_token_validity_seconds BIGINT,
client_id VARCHAR(256),
@ -80,12 +80,15 @@ CREATE TABLE IF NOT EXISTS client_details (
refresh_token_validity_seconds BIGINT,
application_type VARCHAR(256),
application_name VARCHAR(256),
client_name VARCHAR(256),
token_endpoint_auth_type VARCHAR(256),
user_id_type VARCHAR(256),
logo_url VARCHAR(2048),
policy_url VARCHAR(2048),
client_url VARCHAR(2048),
tos_url VARCHAR(2048),
jwk_url VARCHAR(2048),
jwk_encryption_url VARCHAR(2048),
x509_url VARCHAR(2048),
@ -105,7 +108,7 @@ CREATE TABLE IF NOT EXISTS client_details (
id_token_encrypted_response_int VARCHAR(256),
default_max_age BIGINT,
require_auth_time BOOLEAN,
require_auth_time BOOLEAN NOT NULL DEFAULT FALSE,
default_acr VARCHAR(256)
);