beginning of client registration refactor to track IETF dynreg spec
parent
0659432561
commit
e2bc15c2b2
|
@ -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,17 +240,9 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
/**
|
||||
* @return the allowRefresh
|
||||
*/
|
||||
@Basic
|
||||
@Column(name="allow_refresh")
|
||||
@Transient
|
||||
public boolean isAllowRefresh() {
|
||||
return allowRefresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowRefresh Whether to allow for issuance of refresh tokens or not (defaults to false)
|
||||
*/
|
||||
public void setAllowRefresh(boolean allowRefresh) {
|
||||
this.allowRefresh = allowRefresh;
|
||||
return getAuthorizedGrantTypes().contains("refresh_token");
|
||||
}
|
||||
|
||||
@Basic
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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();
|
|
@ -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
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue