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

View File

@ -20,11 +20,15 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject; 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 * @author jricher
* *
*/ */
@Component("clientAssociate") @Component("clientRegistration")
public class ClientAssociateView extends AbstractView { public class ClientRegistrationView extends AbstractView {
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) * @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
@ -40,18 +44,27 @@ public class ClientAssociateView extends AbstractView {
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client"); ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) model.get("token"); OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) model.get("token");
Boolean fullClient = (Boolean) model.get("fullClient"); // do we display the full client or not?
JsonObject obj = new JsonObject(); JsonObject obj = new JsonObject();
obj.addProperty("client_id", client.getClientId()); obj.addProperty("client_id", client.getClientId());
if (client.isSecretRequired()) { if (client.isSecretRequired()) {
obj.addProperty("client_secret", client.getClientSecret()); 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 (token != null) {
obj.addProperty("registration_access_token", token.getValue()); obj.addProperty("registration_access_token", token.getValue());
if (token.getExpiration() != null) { if (token.getExpiration() != null) {
obj.addProperty("expires_at", token.getExpiration().getTime()); // TODO: make sure this makes sense? obj.addProperty("expires_at", token.getExpiration().getTime()); // TODO: make sure this makes sense?
} else { } else {
obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire
} }
}
Writer out = response.getWriter(); Writer out = response.getWriter();
gson.toJson(obj, out); gson.toJson(obj, out);

View File

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

View File

@ -183,23 +183,29 @@ public class ClientDynamicRegistrationEndpoint {
}); });
} }
@RequestMapping(params = "type=client_associate", produces = "application/json") @RequestMapping(params = "operation=client_register", produces = "application/json")
public String clientAssociate( public String clientRegister(
@RequestParam(value = "contacts", required = false) Set<String> contacts, @RequestParam(value = "redirect_uris", required = true) Set<String> redirectUris,
@RequestParam(value = "application_type", required = false) AppType applicationType, @RequestParam(value = "client_name", required = false) String clientName,
@RequestParam(value = "application_name", required = false) String applicationName, @RequestParam(value = "client_url", required = false) String clientUrl,
@RequestParam(value = "logo_url", required = false) String logoUrl, @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 = "token_endpoint_auth_type", required = false) AuthType tokenEndpointAuthType,
@RequestParam(value = "policy_url", required = false) String policyUrl, @RequestParam(value = "policy_url", required = false) String policyUrl,
@RequestParam(value = "jwk_url", required = false) String jwkUrl, @RequestParam(value = "jwk_url", required = false) String jwkUrl,
@RequestParam(value = "jwk_encryption_url", required = false) String jwkEncryptionUrl, @RequestParam(value = "jwk_encryption_url", required = false) String jwkEncryptionUrl,
@RequestParam(value = "x509_url", required = false) String x509Url, @RequestParam(value = "x509_url", required = false) String x509Url,
@RequestParam(value = "x509_encryption_url", required = false) String x509EncryptionUrl, @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 = "sector_identifier_url", required = false) String sectorIdentifierUrl,
@RequestParam(value = "user_id_type", required = false) UserIdType userIdType, @RequestParam(value = "user_id_type", required = false) UserIdType userIdType,
@RequestParam(value = "require_signed_request_object", required = false) JwsAlgorithm requireSignedRequestObject, @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 // 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_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 = "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 = "require_auth_time", required = false, defaultValue = "true") Boolean requireAuthTime,
@RequestParam(value = "default_acr", required = false) String defaultAcr,
ModelMap model ModelMap model
) { ) {
@ -223,12 +227,16 @@ public class ClientDynamicRegistrationEndpoint {
ClientDetailsEntity client = new ClientDetailsEntity(); ClientDetailsEntity client = new ClientDetailsEntity();
// always generate a secret for this 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 = clientService.generateClientSecret(client);
}
client.setContacts(contacts); client.setContacts(contacts);
client.setApplicationType(applicationType); client.setApplicationType(applicationType);
client.setApplicationName(applicationName); client.setClientName(clientName);
client.setClientUrl(clientUrl);
client.setTosUrl(tosUrl);
client.setLogoUrl(logoUrl); client.setLogoUrl(logoUrl);
client.setRegisteredRedirectUri(redirectUris); client.setRegisteredRedirectUri(redirectUris);
client.setTokenEndpointAuthType(tokenEndpointAuthType); client.setTokenEndpointAuthType(tokenEndpointAuthType);
@ -247,11 +255,10 @@ public class ClientDynamicRegistrationEndpoint {
// defaults for SECOAUTH functionality // defaults for SECOAUTH functionality
// TODO: extensions to request, or configuration? // TODO: extensions to request, or configuration?
client.setScope(Sets.newHashSet("openid", "phone", "address", "profile", "email")); // provision all scopes 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.setAccessTokenValiditySeconds(3600); // access tokens good for 1hr
client.setIdTokenValiditySeconds(600); // id tokens good for 10min client.setIdTokenValiditySeconds(600); // id tokens good for 10min
client.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked 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); client.setDynamicallyRegistered(true);
@ -259,10 +266,11 @@ public class ClientDynamicRegistrationEndpoint {
OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client); OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client);
model.put("fullClient", Boolean.TRUE);
model.put("client", saved); model.put("client", saved);
model.put("token", registrationAccessToken); model.put("token", registrationAccessToken);
return "clientAssociate"; return "clientRegistration";
} }
/** /**
@ -282,7 +290,7 @@ public class ClientDynamicRegistrationEndpoint {
} }
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')") @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) { public String rotateSecret(OAuth2Authentication auth, ModelMap model) {
@ -316,30 +324,38 @@ public class ClientDynamicRegistrationEndpoint {
// save the client // save the client
ClientDetailsEntity saved = clientService.updateClient(client, client); ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("fullClient", Boolean.FALSE);
model.put("client", saved); model.put("client", saved);
model.put("token", registrationAccessToken); model.put("token", registrationAccessToken);
return "clientAssociate"; return "clientRegistration";
} }
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')") @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( public String clientUpdate(
@RequestParam(value = "contacts", required = false) Set<String> contacts, @RequestParam(value = "redirect_uris", required = true) Set<String> redirectUris,
@RequestParam(value = "application_type", required = false) AppType applicationType, @RequestParam(value = "client_name", required = false) String clientName,
@RequestParam(value = "application_name", required = false) String applicationName, @RequestParam(value = "client_url", required = false) String clientUrl,
@RequestParam(value = "logo_url", required = false) String logoUrl, @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 = "token_endpoint_auth_type", required = false) AuthType tokenEndpointAuthType,
@RequestParam(value = "policy_url", required = false) String policyUrl, @RequestParam(value = "policy_url", required = false) String policyUrl,
@RequestParam(value = "jwk_url", required = false) String jwkUrl, @RequestParam(value = "jwk_url", required = false) String jwkUrl,
@RequestParam(value = "jwk_encryption_url", required = false) String jwkEncryptionUrl, @RequestParam(value = "jwk_encryption_url", required = false) String jwkEncryptionUrl,
@RequestParam(value = "x509_url", required = false) String x509Url, @RequestParam(value = "x509_url", required = false) String x509Url,
@RequestParam(value = "x509_encryption_url", required = false) String x509EncryptionUrl, @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 = "sector_identifier_url", required = false) String sectorIdentifierUrl,
@RequestParam(value = "user_id_type", required = false) UserIdType userIdType, @RequestParam(value = "user_id_type", required = false) UserIdType userIdType,
@RequestParam(value = "require_signed_request_object", required = false) JwsAlgorithm requireSignedRequestObject, @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 // 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_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 = "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, OAuth2Authentication auth,
ModelMap model ModelMap model
@ -369,7 +382,9 @@ public class ClientDynamicRegistrationEndpoint {
client.setContacts(contacts); client.setContacts(contacts);
client.setApplicationType(applicationType); client.setApplicationType(applicationType);
client.setApplicationName(applicationName); client.setClientName(clientName);
client.setClientUrl(clientUrl);
client.setTosUrl(tosUrl);
client.setLogoUrl(logoUrl); client.setLogoUrl(logoUrl);
client.setRegisteredRedirectUri(redirectUris); client.setRegisteredRedirectUri(redirectUris);
client.setTokenEndpointAuthType(tokenEndpointAuthType); client.setTokenEndpointAuthType(tokenEndpointAuthType);
@ -387,8 +402,9 @@ public class ClientDynamicRegistrationEndpoint {
ClientDetailsEntity saved = clientService.updateClient(client, client); ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("fullClient", Boolean.TRUE);
model.put("client", saved); 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 ( CREATE TABLE IF NOT EXISTS client_details (
id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY,
client_description VARCHAR(256),
allow_refresh BOOLEAN, client_description VARCHAR(1024),
allow_multiple_access_tokens BOOLEAN, allow_multiple_access_tokens BOOLEAN NOT NULL DEFAULT true,
reuse_refresh_tokens BOOLEAN, reuse_refresh_tokens BOOLEAN NOT NULL DEFAULT true,
dynamically_registered BOOLEAN, dynamically_registered BOOLEAN NOT NULL DEFAULT false,
id_token_validity_seconds BIGINT, id_token_validity_seconds BIGINT,
client_id VARCHAR(256), client_id VARCHAR(256),
@ -80,12 +80,15 @@ CREATE TABLE IF NOT EXISTS client_details (
refresh_token_validity_seconds BIGINT, refresh_token_validity_seconds BIGINT,
application_type VARCHAR(256), application_type VARCHAR(256),
application_name VARCHAR(256), client_name VARCHAR(256),
token_endpoint_auth_type VARCHAR(256), token_endpoint_auth_type VARCHAR(256),
user_id_type VARCHAR(256), user_id_type VARCHAR(256),
logo_url VARCHAR(2048), logo_url VARCHAR(2048),
policy_url VARCHAR(2048), policy_url VARCHAR(2048),
client_url VARCHAR(2048),
tos_url VARCHAR(2048),
jwk_url VARCHAR(2048), jwk_url VARCHAR(2048),
jwk_encryption_url VARCHAR(2048), jwk_encryption_url VARCHAR(2048),
x509_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), id_token_encrypted_response_int VARCHAR(256),
default_max_age BIGINT, default_max_age BIGINT,
require_auth_time BOOLEAN, require_auth_time BOOLEAN NOT NULL DEFAULT FALSE,
default_acr VARCHAR(256) default_acr VARCHAR(256)
); );