@ -112,168 +112,20 @@ public class ClientDynamicRegistrationEndpoint {
newClient . setClientId ( null ) ;
newClient . setClientSecret ( null ) ;
// 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 ( newClient . getScope ( ) ) ;
// the scopes that the client can have must be a subset of the dynamically allowed scopes
Set < SystemScope > allowedScopes = Sets . intersection ( dynScopes , requestedScopes ) ;
// if the client didn't ask for any, give them the defaults
if ( allowedScopes = = null | | allowedScopes . isEmpty ( ) ) {
allowedScopes = scopeService . getDefaults ( ) ;
}
newClient . setScope ( scopeService . toStrings ( allowedScopes ) ) ;
// set default grant types if needed
if ( newClient . getGrantTypes ( ) = = null | | newClient . getGrantTypes ( ) . isEmpty ( ) ) {
if ( newClient . getScope ( ) . contains ( "offline_access" ) ) { // client asked for offline access
newClient . setGrantTypes ( Sets . newHashSet ( "authorization_code" , "refresh_token" ) ) ; // allow authorization code and refresh token grant types by default
} else {
newClient . setGrantTypes ( Sets . newHashSet ( "authorization_code" ) ) ; // allow authorization code grant type by default
}
}
if ( newClient . getResponseTypes ( ) = = null ) {
newClient . setResponseTypes ( new HashSet < String > ( ) ) ;
}
// filter out unknown grant types
// TODO: make this a pluggable service
Set < String > requestedGrantTypes = new HashSet < String > ( newClient . getGrantTypes ( ) ) ;
requestedGrantTypes . removeAll (
ImmutableSet . of ( "authorization_code" , "implicit" ,
"password" , "client_credentials" , "refresh_token" ,
"urn:ietf:params:oauth:grant_type:redelegate" ) ) ;
if ( ! requestedGrantTypes . isEmpty ( ) ) {
// return an error, there were unknown grant types requested
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Unknown grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
// don't allow "password" grant type for dynamic registration
if ( newClient . getGrantTypes ( ) . contains ( "password" ) ) {
// return an error, you can't dynamically register for the password grant
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "The password grant type is not allowed in dynamic registration on this server." ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
// don't allow clients to have multiple incompatible grant types and scopes
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) ) {
// check for incompatible grants
if ( newClient . getGrantTypes ( ) . contains ( "implicit" ) | |
newClient . getGrantTypes ( ) . contains ( "client_credentials" ) ) {
// return an error, you can't have these grant types together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
if ( newClient . getResponseTypes ( ) . contains ( "token" ) ) {
// return an error, you can't have this grant type and response type together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible response types requested: " + newClient . getGrantTypes ( ) + " / " + newClient . getResponseTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
newClient . getResponseTypes ( ) . add ( "code" ) ;
}
if ( newClient . getGrantTypes ( ) . contains ( "implicit" ) ) {
// check for incompatible grants
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) | |
newClient . getGrantTypes ( ) . contains ( "client_credentials" ) ) {
// return an error, you can't have these grant types together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
if ( newClient . getResponseTypes ( ) . contains ( "code" ) ) {
// return an error, you can't have this grant type and response type together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible response types requested: " + newClient . getGrantTypes ( ) + " / " + newClient . getResponseTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
newClient . getResponseTypes ( ) . add ( "token" ) ;
// don't allow refresh tokens in implicit clients
newClient . getGrantTypes ( ) . remove ( "refresh_token" ) ;
newClient . getScope ( ) . remove ( "offline_access" ) ;
}
if ( newClient . getGrantTypes ( ) . contains ( "client_credentials" ) ) {
// check for incompatible grants
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) | |
newClient . getGrantTypes ( ) . contains ( "implicit" ) ) {
// return an error, you can't have these grant types together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
if ( ! newClient . getResponseTypes ( ) . isEmpty ( ) ) {
// return an error, you can't have this grant type and response type together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible response types requested: " + newClient . getGrantTypes ( ) + " / " + newClient . getResponseTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
// don't allow refresh tokens or id tokens in client_credentials clients
newClient . getGrantTypes ( ) . remove ( "refresh_token" ) ;
newClient . getScope ( ) . remove ( "offline_access" ) ;
newClient . getScope ( ) . remove ( "openid" ) ;
}
if ( newClient . getGrantTypes ( ) . isEmpty ( ) ) {
// return an error, you need at least one grant type selected
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Clients must register at least one grant type." ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
// check to make sure this client registered a redirect URI if using a redirect flow
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) | | newClient . getGrantTypes ( ) . contains ( "implicit" ) ) {
if ( newClient . getRedirectUris ( ) = = null | | newClient . getRedirectUris ( ) . isEmpty ( ) ) {
// return an error
m . addAttribute ( "error" , "invalid_client_uri" ) ;
m . addAttribute ( "errorMessage" , "Clients using a redirect-based grant type must register at least one redirect URI." ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
}
for ( String uri : newClient . getRedirectUris ( ) ) {
if ( blacklistService . isBlacklisted ( uri ) ) {
// return an error
m . addAttribute ( "error" , "invalid_client_uri" ) ;
m . addAttribute ( "errorMessage" , "Redirect URI is not allowed: " + uri ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
// do validation on the fields
try {
newClient = validateScopes ( newClient ) ;
newClient = validateResponseTypes ( newClient ) ;
newClient = validateGrantTypes ( newClient ) ;
newClient = validateRedirectUris ( newClient ) ;
newClient = validateAuth ( newClient ) ;
} catch ( ValidationException ve ) {
// validation failed, return an error
m . addAttribute ( "error" , ve . getError ( ) ) ;
m . addAttribute ( "errorMessage" , ve . getErrorDescription ( ) ) ;
m . addAttribute ( "code" , ve . getStatus ( ) ) ;
return "jsonErrorView" ;
}
}
}
if ( newClient . getTokenEndpointAuthMethod ( ) = = null ) {
newClient . setTokenEndpointAuthMethod ( AuthMethod . SECRET_BASIC ) ;
@ -420,6 +272,88 @@ public class ClientDynamicRegistrationEndpoint {
newClient . setCreatedAt ( oldClient . getCreatedAt ( ) ) ;
newClient . setReuseRefreshToken ( oldClient . isReuseRefreshToken ( ) ) ;
// do validation on the fields
try {
newClient = validateScopes ( newClient ) ;
newClient = validateResponseTypes ( newClient ) ;
newClient = validateGrantTypes ( newClient ) ;
newClient = validateRedirectUris ( newClient ) ;
newClient = validateAuth ( newClient ) ;
} catch ( ValidationException ve ) {
// validation failed, return an error
m . addAttribute ( "error" , ve . getError ( ) ) ;
m . addAttribute ( "errorMessage" , ve . getErrorDescription ( ) ) ;
m . addAttribute ( "code" , ve . getStatus ( ) ) ;
return "jsonErrorView" ;
}
try {
// save the client
ClientDetailsEntity savedClient = clientService . updateClient ( oldClient , newClient ) ;
// we return the token that we got in
// TODO: rotate this after some set amount of time
OAuth2AuthenticationDetails details = ( OAuth2AuthenticationDetails ) auth . getDetails ( ) ;
OAuth2AccessTokenEntity token = tokenService . readAccessToken ( details . getTokenValue ( ) ) ;
RegisteredClient registered = new RegisteredClient ( savedClient , token . getValue ( ) , config . getIssuer ( ) + "register/" + UriUtils . encodePathSegment ( savedClient . getClientId ( ) , "UTF-8" ) ) ;
// send it all out to the view
m . addAttribute ( "client" , registered ) ;
m . addAttribute ( "code" , HttpStatus . OK ) ; // http 200
return "clientInformationResponseView" ;
} catch ( IllegalArgumentException e ) {
logger . error ( "Couldn't save client" , e ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "httpCodeView" ;
} catch ( UnsupportedEncodingException e ) {
logger . error ( "Unsupported encoding" , e ) ;
m . addAttribute ( "code" , HttpStatus . INTERNAL_SERVER_ERROR ) ;
return "httpCodeView" ;
}
} else {
// client mismatch
logger . error ( "updateClient failed, client ID mismatch: "
+ clientId + " and " + auth . getOAuth2Request ( ) . getClientId ( ) + " do not match." ) ;
m . addAttribute ( "code" , HttpStatus . FORBIDDEN ) ; // http 403
return "httpCodeView" ;
}
}
/ * *
* Delete the indicated client from the system .
* @param clientId
* @param m
* @param auth
* @return
* /
@PreAuthorize ( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService . REGISTRATION_TOKEN_SCOPE + "')" )
@RequestMapping ( value = "/{id}" , method = RequestMethod . DELETE , produces = "application/json" )
public String deleteClient ( @PathVariable ( "id" ) String clientId , Model m , OAuth2Authentication auth ) {
ClientDetailsEntity client = clientService . loadClientByClientId ( clientId ) ;
if ( client ! = null & & client . getClientId ( ) . equals ( auth . getOAuth2Request ( ) . getClientId ( ) ) ) {
clientService . deleteClient ( client ) ;
m . addAttribute ( "code" , HttpStatus . NO_CONTENT ) ; // http 204
return "httpCodeView" ;
} else {
// client mismatch
logger . error ( "readClientConfiguration failed, client ID mismatch: "
+ clientId + " and " + auth . getOAuth2Request ( ) . getClientId ( ) + " do not match." ) ;
m . addAttribute ( "code" , HttpStatus . FORBIDDEN ) ; // http 403
return "httpCodeView" ;
}
}
private ClientDetailsEntity validateScopes ( ClientDetailsEntity newClient ) throws ValidationException {
// set of scopes that are OK for clients to dynamically register for
Set < SystemScope > dynScopes = scopeService . getDynReg ( ) ;
@ -434,12 +368,27 @@ public class ClientDynamicRegistrationEndpoint {
allowedScopes = scopeService . getDefaults ( ) ;
}
// make sure that the client doesn't ask for scopes it can't have
newClient . setScope ( scopeService . toStrings ( allowedScopes ) ) ;
return newClient ;
}
private ClientDetailsEntity validateResponseTypes ( ClientDetailsEntity newClient ) throws ValidationException {
if ( newClient . getResponseTypes ( ) = = null ) {
newClient . setResponseTypes ( new HashSet < String > ( ) ) ;
}
return newClient ;
}
private ClientDetailsEntity validateGrantTypes ( ClientDetailsEntity newClient ) throws ValidationException {
// set default grant types if needed
if ( newClient . getGrantTypes ( ) = = null | | newClient . getGrantTypes ( ) . isEmpty ( ) ) {
if ( newClient . getScope ( ) . contains ( "offline_access" ) ) { // client asked for offline access
newClient . setGrantTypes ( Sets . newHashSet ( "authorization_code" , "refresh_token" ) ) ; // allow authorization code and refresh token grant types by default
} else {
newClient . setGrantTypes ( Sets . newHashSet ( "authorization_code" ) ) ; // allow authorization code grant type by default
}
}
// filter out unknown grant types
// TODO: make this a pluggable service
@ -450,19 +399,13 @@ public class ClientDynamicRegistrationEndpoint {
"urn:ietf:params:oauth:grant_type:redelegate" ) ) ;
if ( ! requestedGrantTypes . isEmpty ( ) ) {
// return an error, there were unknown grant types requested
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Unknown grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_metadata" , "Unknown grant types requested: " + newClient . getGrantTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
// don't allow "password" grant type for dynamic registration
if ( newClient . getGrantTypes ( ) . contains ( "password" ) ) {
// return an error, you can't dynamically register for the password grant
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "The password grant type is not allowed in dynamic registration on this server." ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_metadata" , "The password grant type is not allowed in dynamic registration on this server." , HttpStatus . BAD_REQUEST ) ;
}
// don't allow clients to have multiple incompatible grant types and scopes
@ -472,14 +415,17 @@ public class ClientDynamicRegistrationEndpoint {
if ( newClient . getGrantTypes ( ) . contains ( "implicit" ) | |
newClient . getGrantTypes ( ) . contains ( "client_credentials" ) ) {
// return an error, you can't have these grant types together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_metadata" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
if ( newClient . getResponseTypes ( ) . contains ( "token" ) ) {
// return an error, you can't have this grant type and response type together
throw new ValidationException ( "invalid_client_metadata" , "Incompatible response types requested: " + newClient . getGrantTypes ( ) + " / " + newClient . getResponseTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
newClient . getResponseTypes ( ) . add ( "code" ) ;
}
if ( newClient . getGrantTypes ( ) . contains ( "implicit" ) ) {
@ -488,10 +434,12 @@ public class ClientDynamicRegistrationEndpoint {
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) | |
newClient . getGrantTypes ( ) . contains ( "client_credentials" ) ) {
// return an error, you can't have these grant types together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_metadata" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
if ( newClient . getResponseTypes ( ) . contains ( "code" ) ) {
// return an error, you can't have this grant type and response type together
throw new ValidationException ( "invalid_client_metadata" , "Incompatible response types requested: " + newClient . getGrantTypes ( ) + " / " + newClient . getResponseTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
newClient . getResponseTypes ( ) . add ( "token" ) ;
@ -507,10 +455,12 @@ public class ClientDynamicRegistrationEndpoint {
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) | |
newClient . getGrantTypes ( ) . contains ( "implicit" ) ) {
// return an error, you can't have these grant types together
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_metadata" , "Incompatible grant types requested: " + newClient . getGrantTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
if ( ! newClient . getResponseTypes ( ) . isEmpty ( ) ) {
// return an error, you can't have this grant type and response type together
throw new ValidationException ( "invalid_client_metadata" , "Incompatible response types requested: " + newClient . getGrantTypes ( ) + " / " + newClient . getResponseTypes ( ) , HttpStatus . BAD_REQUEST ) ;
}
// don't allow refresh tokens or id tokens in client_credentials clients
@ -521,33 +471,31 @@ public class ClientDynamicRegistrationEndpoint {
if ( newClient . getGrantTypes ( ) . isEmpty ( ) ) {
// return an error, you need at least one grant type selected
m . addAttribute ( "error" , "invalid_client_metadata" ) ;
m . addAttribute ( "errorMessage" , "Clients must register at least one grant type." ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_metadata" , "Clients must register at least one grant type." , HttpStatus . BAD_REQUEST ) ;
}
return newClient ;
}
private ClientDetailsEntity validateRedirectUris ( ClientDetailsEntity newClient ) throws ValidationException {
// check to make sure this client registered a redirect URI if using a redirect flow
if ( newClient . getGrantTypes ( ) . contains ( "authorization_code" ) | | newClient . getGrantTypes ( ) . contains ( "implicit" ) ) {
if ( newClient . getRedirectUris ( ) = = null | | newClient . getRedirectUris ( ) . isEmpty ( ) ) {
// return an error
m . addAttribute ( "error" , "invalid_client_uri" ) ;
m . addAttribute ( "errorMessage" , "Clients using a redirect-based grant type must register at least one redirect URI." ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_uri" , "Clients using a redirect-based grant type must register at least one redirect URI." , HttpStatus . BAD_REQUEST ) ;
}
for ( String uri : newClient . getRedirectUris ( ) ) {
if ( blacklistService . isBlacklisted ( uri ) ) {
// return an error
m . addAttribute ( "error" , "invalid_client_uri" ) ;
m . addAttribute ( "errorMessage" , "Redirect URI is not allowed: " + uri ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "jsonErrorView" ;
throw new ValidationException ( "invalid_client_uri" , "Redirect URI is not allowed: " + uri , HttpStatus . BAD_REQUEST ) ;
}
}
}
return newClient ;
}
private ClientDetailsEntity validateAuth ( ClientDetailsEntity newClient ) throws ValidationException {
if ( newClient . getTokenEndpointAuthMethod ( ) = = null ) {
newClient . setTokenEndpointAuthMethod ( AuthMethod . SECRET_BASIC ) ;
}
@ -559,72 +507,44 @@ public class ClientDynamicRegistrationEndpoint {
// we need to generate a secret
newClient = clientService . generateClientSecret ( newClient ) ;
}
try {
// save the client
ClientDetailsEntity savedClient = clientService . updateClient ( oldClient , newClient ) ;
// we return the token that we got in
// TODO: rotate this after some set amount of time
OAuth2AuthenticationDetails details = ( OAuth2AuthenticationDetails ) auth . getDetails ( ) ;
OAuth2AccessTokenEntity token = tokenService . readAccessToken ( details . getTokenValue ( ) ) ;
RegisteredClient registered = new RegisteredClient ( savedClient , token . getValue ( ) , config . getIssuer ( ) + "register/" + UriUtils . encodePathSegment ( savedClient . getClientId ( ) , "UTF-8" ) ) ;
// send it all out to the view
m . addAttribute ( "client" , registered ) ;
m . addAttribute ( "code" , HttpStatus . OK ) ; // http 200
return "clientInformationResponseView" ;
} catch ( IllegalArgumentException e ) {
logger . error ( "Couldn't save client" , e ) ;
m . addAttribute ( "code" , HttpStatus . BAD_REQUEST ) ;
return "httpCodeView" ;
} catch ( UnsupportedEncodingException e ) {
logger . error ( "Unsupported encoding" , e ) ;
m . addAttribute ( "code" , HttpStatus . INTERNAL_SERVER_ERROR ) ;
return "httpCodeView" ;
}
} else {
// client mismatch
logger . error ( "updateClient failed, client ID mismatch: "
+ clientId + " and " + auth . getOAuth2Request ( ) . getClientId ( ) + " do not match." ) ;
m . addAttribute ( "code" , HttpStatus . FORBIDDEN ) ; // http 403
return "httpCodeView" ;
}
return newClient ;
}
/ * *
* Delete the indicated client from the system .
* @param clientId
* @param m
* @param auth
* @return
* Thrown by utility methods when a client fails to validate . Contains information
* to be returned .
* @author jricher
*
* /
@PreAuthorize ( "hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService . REGISTRATION_TOKEN_SCOPE + "')" )
@RequestMapping ( value = "/{id}" , method = RequestMethod . DELETE , produces = "application/json" )
public String deleteClient ( @PathVariable ( "id" ) String clientId , Model m , OAuth2Authentication auth ) {
ClientDetailsEntity client = clientService . loadClientByClientId ( clientId ) ;
if ( client ! = null & & client . getClientId ( ) . equals ( auth . getOAuth2Request ( ) . getClientId ( ) ) ) {
clientService . deleteClient ( client ) ;
m . addAttribute ( "code" , HttpStatus . NO_CONTENT ) ; // http 204
return "httpCodeView" ;
} else {
// client mismatch
logger . error ( "readClientConfiguration failed, client ID mismatch: "
+ clientId + " and " + auth . getOAuth2Request ( ) . getClientId ( ) + " do not match." ) ;
m . addAttribute ( "code" , HttpStatus . FORBIDDEN ) ; // http 403
return "httpCodeView" ;
private class ValidationException extends Exception {
private String error ;
private String errorDescription ;
private HttpStatus status ;
public ValidationException ( String error , String errorDescription ,
HttpStatus status ) {
this . error = error ;
this . errorDescription = errorDescription ;
this . status = status ;
}
public String getError ( ) {
return error ;
}
public void setError ( String error ) {
this . error = error ;
}
public String getErrorDescription ( ) {
return errorDescription ;
}
public void setErrorDescription ( String errorDescription ) {
this . errorDescription = errorDescription ;
}
public HttpStatus getStatus ( ) {
return status ;
}
public void setStatus ( HttpStatus status ) {
this . status = status ;
}
}
}