MitreID code compiles against latest Spring libraries

pull/1611/head
Andrea Ceccanti 2021-10-25 17:13:07 +02:00
parent 711a2e7eab
commit 12bfab4f55
5 changed files with 441 additions and 443 deletions

View File

@ -50,8 +50,6 @@ import org.mitre.oauth2.model.convert.JWTStringConverter;
import org.mitre.openid.connect.model.ApprovedSite; import org.mitre.openid.connect.model.ApprovedSite;
import org.mitre.uma.model.Permission; import org.mitre.uma.model.Permission;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer;
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Serializer;
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer; import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer;
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer; import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer;
import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken;
@ -74,8 +72,6 @@ import com.nimbusds.jwt.JWT;
@NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_RESOURCE_SET, query = "select a from OAuth2AccessTokenEntity a join a.permissions p where p.resourceSet.id = :" + OAuth2AccessTokenEntity.PARAM_RESOURCE_SET_ID), @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_RESOURCE_SET, query = "select a from OAuth2AccessTokenEntity a join a.permissions p where p.resourceSet.id = :" + OAuth2AccessTokenEntity.PARAM_RESOURCE_SET_ID),
@NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_NAME, query = "select r from OAuth2AccessTokenEntity r where r.authenticationHolder.userAuth.name = :" + OAuth2AccessTokenEntity.PARAM_NAME) @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_NAME, query = "select r from OAuth2AccessTokenEntity r where r.authenticationHolder.userAuth.name = :" + OAuth2AccessTokenEntity.PARAM_NAME)
}) })
@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)
@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class) @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
public class OAuth2AccessTokenEntity implements OAuth2AccessToken { public class OAuth2AccessTokenEntity implements OAuth2AccessToken {

View File

@ -15,7 +15,6 @@
*******************************************************************************/ *******************************************************************************/
package org.mitre.oauth2.service.impl; package org.mitre.oauth2.service.impl;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collection; import java.util.Collection;
@ -91,7 +90,7 @@ public class UriEncodedClientUserDetailsService implements UserDetailsService {
} else { } else {
throw new UsernameNotFoundException("Client not found: " + clientId); throw new UsernameNotFoundException("Client not found: " + clientId);
} }
} catch (UnsupportedEncodingException | InvalidClientException e) { } catch (InvalidClientException e) {
throw new UsernameNotFoundException("Client not found: " + clientId); throw new UsernameNotFoundException("Client not found: " + clientId);
} }

View File

@ -17,7 +17,6 @@
*******************************************************************************/ *******************************************************************************/
package org.mitre.openid.connect.web; package org.mitre.openid.connect.web;
import java.io.UnsupportedEncodingException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Date; import java.util.Date;
import java.util.Set; import java.util.Set;
@ -218,10 +217,6 @@ public class DynamicClientRegistrationEndpoint {
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201 m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
return ClientInformationResponseView.VIEWNAME; return ClientInformationResponseView.VIEWNAME;
} catch (UnsupportedEncodingException e) {
logger.error("Unsupported encoding", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
return HttpCodeView.VIEWNAME;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.error("Couldn't save client", e); logger.error("Couldn't save client", e);
@ -260,23 +255,16 @@ public class DynamicClientRegistrationEndpoint {
ClientDetailsEntity client = clientService.loadClientByClientId(clientId); ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, client);
RegisteredClient registered =
new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/"
+ UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
try { // send it all out to the view
OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, client); m.addAttribute("client", registered);
RegisteredClient registered = m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/"
+ UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
// send it all out to the view return ClientInformationResponseView.VIEWNAME;
m.addAttribute("client", registered);
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
return ClientInformationResponseView.VIEWNAME;
} catch (UnsupportedEncodingException e) {
logger.error("Unsupported encoding", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
return HttpCodeView.VIEWNAME;
}
} else { } else {
// client mismatch // client mismatch
@ -380,10 +368,6 @@ public class DynamicClientRegistrationEndpoint {
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
return ClientInformationResponseView.VIEWNAME; return ClientInformationResponseView.VIEWNAME;
} catch (UnsupportedEncodingException e) {
logger.error("Unsupported encoding", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
return HttpCodeView.VIEWNAME;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.error("Couldn't save client", e); logger.error("Couldn't save client", e);

View File

@ -15,7 +15,6 @@
*******************************************************************************/ *******************************************************************************/
package org.mitre.openid.connect.web; package org.mitre.openid.connect.web;
import java.io.UnsupportedEncodingException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
@ -59,412 +58,431 @@ import com.google.gson.JsonSyntaxException;
@RequestMapping(value = ProtectedResourceRegistrationEndpoint.URL) @RequestMapping(value = ProtectedResourceRegistrationEndpoint.URL)
public class ProtectedResourceRegistrationEndpoint { public class ProtectedResourceRegistrationEndpoint {
/** /**
* *
*/ */
public static final String URL = "resource"; public static final String URL = "resource";
@Autowired @Autowired
private ClientDetailsEntityService clientService; private ClientDetailsEntityService clientService;
@Autowired @Autowired
private OAuth2TokenEntityService tokenService; private OAuth2TokenEntityService tokenService;
@Autowired @Autowired
private SystemScopeService scopeService; private SystemScopeService scopeService;
@Autowired @Autowired
private ConfigurationPropertiesBean config; private ConfigurationPropertiesBean config;
@Autowired @Autowired
private OIDCTokenService connectTokenService; private OIDCTokenService connectTokenService;
/** /**
* Logger for this class * Logger for this class
*/ */
private static final Logger logger = LoggerFactory.getLogger(ProtectedResourceRegistrationEndpoint.class); private static final Logger logger =
LoggerFactory.getLogger(ProtectedResourceRegistrationEndpoint.class);
/**
* Create a new Client, issue a client ID, and create a registration access token. /**
* @param jsonString * Create a new Client, issue a client ID, and create a registration access token.
* @param m *
* @param p * @param jsonString
* @return * @param m
*/ * @param p
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) * @return
public String registerNewProtectedResource(@RequestBody String jsonString, Model m) { */
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
ClientDetailsEntity newClient = null; produces = MediaType.APPLICATION_JSON_VALUE)
try { public String registerNewProtectedResource(@RequestBody String jsonString, Model m) {
newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
} catch (JsonSyntaxException e) { ClientDetailsEntity newClient = null;
// bad parse try {
// didn't parse, this is a bad request newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
logger.error("registerNewProtectedResource failed; submitted JSON is malformed"); } catch (JsonSyntaxException e) {
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 // bad parse
return HttpCodeView.VIEWNAME; // didn't parse, this is a bad request
} logger.error("registerNewProtectedResource failed; submitted JSON is malformed");
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
if (newClient != null) { return HttpCodeView.VIEWNAME;
// it parsed! }
// if (newClient != null) {
// Now do some post-processing consistency checks on it // it parsed!
//
//
// clear out any spurious id/secret (clients don't get to pick) // Now do some post-processing consistency checks on it
newClient.setClientId(null); //
newClient.setClientSecret(null);
// clear out any spurious id/secret (clients don't get to pick)
// do validation on the fields newClient.setClientId(null);
try { newClient.setClientSecret(null);
newClient = validateScopes(newClient);
newClient = validateAuth(newClient); // do validation on the fields
} catch (ValidationException ve) { try {
// validation failed, return an error newClient = validateScopes(newClient);
m.addAttribute(JsonErrorView.ERROR, ve.getError()); newClient = validateAuth(newClient);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription()); } catch (ValidationException ve) {
m.addAttribute(HttpCodeView.CODE, ve.getStatus()); // validation failed, return an error
return JsonErrorView.VIEWNAME; m.addAttribute(JsonErrorView.ERROR, ve.getError());
} m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
m.addAttribute(HttpCodeView.CODE, ve.getStatus());
return JsonErrorView.VIEWNAME;
// no grant types are allowed }
newClient.setGrantTypes(new HashSet<String>());
newClient.setResponseTypes(new HashSet<String>());
newClient.setRedirectUris(new HashSet<String>()); // no grant types are allowed
newClient.setGrantTypes(new HashSet<String>());
// don't issue tokens to this client newClient.setResponseTypes(new HashSet<String>());
newClient.setAccessTokenValiditySeconds(0); newClient.setRedirectUris(new HashSet<String>());
newClient.setIdTokenValiditySeconds(0);
newClient.setRefreshTokenValiditySeconds(0); // don't issue tokens to this client
newClient.setAccessTokenValiditySeconds(0);
// clear out unused fields newClient.setIdTokenValiditySeconds(0);
newClient.setDefaultACRvalues(new HashSet<String>()); newClient.setRefreshTokenValiditySeconds(0);
newClient.setDefaultMaxAge(null);
newClient.setIdTokenEncryptedResponseAlg(null); // clear out unused fields
newClient.setIdTokenEncryptedResponseEnc(null); newClient.setDefaultACRvalues(new HashSet<String>());
newClient.setIdTokenSignedResponseAlg(null); newClient.setDefaultMaxAge(null);
newClient.setInitiateLoginUri(null); newClient.setIdTokenEncryptedResponseAlg(null);
newClient.setPostLogoutRedirectUris(null); newClient.setIdTokenEncryptedResponseEnc(null);
newClient.setRequestObjectSigningAlg(null); newClient.setIdTokenSignedResponseAlg(null);
newClient.setRequireAuthTime(null); newClient.setInitiateLoginUri(null);
newClient.setReuseRefreshToken(false); newClient.setPostLogoutRedirectUris(null);
newClient.setSectorIdentifierUri(null); newClient.setRequestObjectSigningAlg(null);
newClient.setSubjectType(null); newClient.setRequireAuthTime(null);
newClient.setUserInfoEncryptedResponseAlg(null); newClient.setReuseRefreshToken(false);
newClient.setUserInfoEncryptedResponseEnc(null); newClient.setSectorIdentifierUri(null);
newClient.setUserInfoSignedResponseAlg(null); newClient.setSubjectType(null);
newClient.setUserInfoEncryptedResponseAlg(null);
// this client has been dynamically registered (obviously) newClient.setUserInfoEncryptedResponseEnc(null);
newClient.setDynamicallyRegistered(true); newClient.setUserInfoSignedResponseAlg(null);
// this client has access to the introspection endpoint // this client has been dynamically registered (obviously)
newClient.setAllowIntrospection(true); newClient.setDynamicallyRegistered(true);
// now save it // this client has access to the introspection endpoint
try { newClient.setAllowIntrospection(true);
ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);
// now save it
// generate the registration access token try {
OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient); ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);
tokenService.saveAccessToken(token);
// generate the registration access token
// send it all out to the view OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient);
tokenService.saveAccessToken(token);
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
m.addAttribute("client", registered); // send it all out to the view
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
RegisteredClient registered =
return ClientInformationResponseView.VIEWNAME; new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/"
} catch (UnsupportedEncodingException e) { + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
logger.error("Unsupported encoding", e); m.addAttribute("client", registered);
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
return HttpCodeView.VIEWNAME;
} catch (IllegalArgumentException e) { return ClientInformationResponseView.VIEWNAME;
logger.error("Couldn't save client", e); } catch (IllegalArgumentException e) {
logger.error("Couldn't save client", e);
m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client due to invalid or inconsistent metadata."); m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 m.addAttribute(JsonErrorView.ERROR_MESSAGE,
"Unable to save client due to invalid or inconsistent metadata.");
return JsonErrorView.VIEWNAME; m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
}
} else { return JsonErrorView.VIEWNAME;
// didn't parse, this is a bad request }
logger.error("registerNewClient failed; submitted JSON is malformed"); } else {
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 // didn't parse, this is a bad request
logger.error("registerNewClient failed; submitted JSON is malformed");
return HttpCodeView.VIEWNAME; m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
}
return HttpCodeView.VIEWNAME;
} }
private ClientDetailsEntity validateScopes(ClientDetailsEntity newClient) throws ValidationException { }
// scopes that the client is asking for
Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope()); private ClientDetailsEntity validateScopes(ClientDetailsEntity newClient)
throws ValidationException {
// the scopes that the client can have must be a subset of the dynamically allowed scopes // scopes that the client is asking for
Set<SystemScope> allowedScopes = scopeService.removeRestrictedAndReservedScopes(requestedScopes); Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());
// if the client didn't ask for any, give them the defaults // the scopes that the client can have must be a subset of the dynamically allowed scopes
if (allowedScopes == null || allowedScopes.isEmpty()) { Set<SystemScope> allowedScopes =
allowedScopes = scopeService.getDefaults(); scopeService.removeRestrictedAndReservedScopes(requestedScopes);
}
// if the client didn't ask for any, give them the defaults
newClient.setScope(scopeService.toStrings(allowedScopes)); if (allowedScopes == null || allowedScopes.isEmpty()) {
allowedScopes = scopeService.getDefaults();
return newClient; }
}
newClient.setScope(scopeService.toStrings(allowedScopes));
/**
* Get the meta information for a client. return newClient;
* @param clientId }
* @param m
* @param auth /**
* @return * Get the meta information for a client.
*/ *
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") * @param clientId
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) * @param m
public String readResourceConfiguration(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) { * @param auth
* @return
ClientDetailsEntity client = clientService.loadClientByClientId(clientId); */
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
@RequestMapping(value = "/{id}", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String readResourceConfiguration(@PathVariable("id") String clientId, Model m,
try { OAuth2Authentication auth) {
// possibly update the token
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client); ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8")); if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
// send it all out to the view // possibly update the token
m.addAttribute("client", registered); OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client);
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
RegisteredClient registered =
return ClientInformationResponseView.VIEWNAME; new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/"
} catch (UnsupportedEncodingException e) { + UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
logger.error("Unsupported encoding", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); // send it all out to the view
return HttpCodeView.VIEWNAME; m.addAttribute("client", registered);
} m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
} else {
// client mismatch return ClientInformationResponseView.VIEWNAME;
logger.error("readResourceConfiguration failed, client ID mismatch: "
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); } else {
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403 // client mismatch
logger.error("readResourceConfiguration failed, client ID mismatch: " + clientId + " and "
return HttpCodeView.VIEWNAME; + auth.getOAuth2Request().getClientId() + " do not match.");
} m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
}
return HttpCodeView.VIEWNAME;
/** }
* Update the metainformation for a given client. }
* @param clientId
* @param jsonString /**
* @param m * Update the metainformation for a given client.
* @param auth *
* @return * @param clientId
*/ * @param jsonString
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") * @param m
@RequestMapping(value = "/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) * @param auth
public String updateProtectedResource(@PathVariable("id") String clientId, @RequestBody String jsonString, Model m, OAuth2Authentication auth) { * @return
*/
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
ClientDetailsEntity newClient = null; + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
try { @RequestMapping(value = "/{id}", method = RequestMethod.PUT,
newClient = ClientDetailsEntityJsonProcessor.parse(jsonString); produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
} catch (JsonSyntaxException e) { public String updateProtectedResource(@PathVariable("id") String clientId,
// bad parse @RequestBody String jsonString, Model m, OAuth2Authentication auth) {
// didn't parse, this is a bad request
logger.error("updateProtectedResource failed; submitted JSON is malformed");
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 ClientDetailsEntity newClient = null;
return HttpCodeView.VIEWNAME; try {
} newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
} catch (JsonSyntaxException e) {
ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId); // bad parse
// didn't parse, this is a bad request
if (newClient != null && oldClient != null // we have an existing client and the new one parsed logger.error("updateProtectedResource failed; submitted JSON is malformed");
&& oldClient.getClientId().equals(auth.getOAuth2Request().getClientId()) // the client passed in the URI matches the one in the auth m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
&& oldClient.getClientId().equals(newClient.getClientId()) // the client passed in the body matches the one in the URI return HttpCodeView.VIEWNAME;
) { }
// a client can't ask to update its own client secret to any particular value ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
newClient.setClientSecret(oldClient.getClientSecret());
if (newClient != null && oldClient != null // we have an existing client and the new one parsed
newClient.setCreatedAt(oldClient.getCreatedAt()); && oldClient.getClientId().equals(auth.getOAuth2Request().getClientId()) // the client
// passed in the
// no grant types are allowed // URI matches the
newClient.setGrantTypes(new HashSet<String>()); // one in the auth
newClient.setResponseTypes(new HashSet<String>()); && oldClient.getClientId().equals(newClient.getClientId()) // the client passed in the body
newClient.setRedirectUris(new HashSet<String>()); // matches the one in the URI
) {
// don't issue tokens to this client
newClient.setAccessTokenValiditySeconds(0); // a client can't ask to update its own client secret to any particular value
newClient.setIdTokenValiditySeconds(0); newClient.setClientSecret(oldClient.getClientSecret());
newClient.setRefreshTokenValiditySeconds(0);
newClient.setCreatedAt(oldClient.getCreatedAt());
// clear out unused fields
newClient.setDefaultACRvalues(new HashSet<String>()); // no grant types are allowed
newClient.setDefaultMaxAge(null); newClient.setGrantTypes(new HashSet<String>());
newClient.setIdTokenEncryptedResponseAlg(null); newClient.setResponseTypes(new HashSet<String>());
newClient.setIdTokenEncryptedResponseEnc(null); newClient.setRedirectUris(new HashSet<String>());
newClient.setIdTokenSignedResponseAlg(null);
newClient.setInitiateLoginUri(null); // don't issue tokens to this client
newClient.setPostLogoutRedirectUris(null); newClient.setAccessTokenValiditySeconds(0);
newClient.setRequestObjectSigningAlg(null); newClient.setIdTokenValiditySeconds(0);
newClient.setRequireAuthTime(null); newClient.setRefreshTokenValiditySeconds(0);
newClient.setReuseRefreshToken(false);
newClient.setSectorIdentifierUri(null); // clear out unused fields
newClient.setSubjectType(null); newClient.setDefaultACRvalues(new HashSet<String>());
newClient.setUserInfoEncryptedResponseAlg(null); newClient.setDefaultMaxAge(null);
newClient.setUserInfoEncryptedResponseEnc(null); newClient.setIdTokenEncryptedResponseAlg(null);
newClient.setUserInfoSignedResponseAlg(null); newClient.setIdTokenEncryptedResponseEnc(null);
newClient.setIdTokenSignedResponseAlg(null);
// this client has been dynamically registered (obviously) newClient.setInitiateLoginUri(null);
newClient.setDynamicallyRegistered(true); newClient.setPostLogoutRedirectUris(null);
newClient.setRequestObjectSigningAlg(null);
// this client has access to the introspection endpoint newClient.setRequireAuthTime(null);
newClient.setAllowIntrospection(true); newClient.setReuseRefreshToken(false);
newClient.setSectorIdentifierUri(null);
// do validation on the fields newClient.setSubjectType(null);
try { newClient.setUserInfoEncryptedResponseAlg(null);
newClient = validateScopes(newClient); newClient.setUserInfoEncryptedResponseEnc(null);
newClient = validateAuth(newClient); newClient.setUserInfoSignedResponseAlg(null);
} catch (ValidationException ve) {
// validation failed, return an error // this client has been dynamically registered (obviously)
m.addAttribute(JsonErrorView.ERROR, ve.getError()); newClient.setDynamicallyRegistered(true);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
m.addAttribute(HttpCodeView.CODE, ve.getStatus()); // this client has access to the introspection endpoint
return JsonErrorView.VIEWNAME; newClient.setAllowIntrospection(true);
}
// do validation on the fields
try {
try { newClient = validateScopes(newClient);
// save the client newClient = validateAuth(newClient);
ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient); } catch (ValidationException ve) {
// validation failed, return an error
// possibly update the token m.addAttribute(JsonErrorView.ERROR, ve.getError());
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient); m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
m.addAttribute(HttpCodeView.CODE, ve.getStatus());
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8")); return JsonErrorView.VIEWNAME;
}
// send it all out to the view
m.addAttribute("client", registered);
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 try {
// save the client
return ClientInformationResponseView.VIEWNAME; ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);
} catch (UnsupportedEncodingException e) {
logger.error("Unsupported encoding", e); // possibly update the token
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient);
return HttpCodeView.VIEWNAME;
} catch (IllegalArgumentException e) { RegisteredClient registered =
logger.error("Couldn't save client", e); new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/"
+ UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client due to invalid or inconsistent metadata."); // send it all out to the view
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400 m.addAttribute("client", registered);
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
return JsonErrorView.VIEWNAME;
} return ClientInformationResponseView.VIEWNAME;
} else {
// client mismatch } catch (IllegalArgumentException e) {
logger.error("updateProtectedResource" + logger.error("Couldn't save client", e);
" failed, client ID mismatch: "
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403 m.addAttribute(JsonErrorView.ERROR_MESSAGE,
"Unable to save client due to invalid or inconsistent metadata.");
return HttpCodeView.VIEWNAME; m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
}
} return JsonErrorView.VIEWNAME;
}
/** } else {
* Delete the indicated client from the system. // client mismatch
* @param clientId logger.error("updateProtectedResource" + " failed, client ID mismatch: " + clientId + " and "
* @param m + auth.getOAuth2Request().getClientId() + " do not match.");
* @param auth m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
* @return
*/ return HttpCodeView.VIEWNAME;
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") }
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) }
public String deleteResource(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {
/**
ClientDetailsEntity client = clientService.loadClientByClientId(clientId); * Delete the indicated client from the system.
*
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { * @param clientId
* @param m
clientService.deleteClient(client); * @param auth
* @return
m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); // http 204 */
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
return HttpCodeView.VIEWNAME; + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
} else { @RequestMapping(value = "/{id}", method = RequestMethod.DELETE,
// client mismatch produces = MediaType.APPLICATION_JSON_VALUE)
logger.error("readClientConfiguration failed, client ID mismatch: " public String deleteResource(@PathVariable("id") String clientId, Model m,
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match."); OAuth2Authentication auth) {
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
return HttpCodeView.VIEWNAME;
} if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
}
clientService.deleteClient(client);
private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient) throws ValidationException {
if (newClient.getTokenEndpointAuthMethod() == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); // http 204
newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
} return HttpCodeView.VIEWNAME;
} else {
if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC || // client mismatch
newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT || logger.error("readClientConfiguration failed, client ID mismatch: " + clientId + " and "
newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) { + auth.getOAuth2Request().getClientId() + " do not match.");
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
if (Strings.isNullOrEmpty(newClient.getClientSecret())) {
// no secret yet, we need to generate a secret return HttpCodeView.VIEWNAME;
newClient = clientService.generateClientSecret(newClient); }
} }
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.PRIVATE_KEY) {
if (Strings.isNullOrEmpty(newClient.getJwksUri()) && newClient.getJwks() == null) { private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient)
throw new ValidationException("invalid_client_metadata", "JWK Set URI required when using private key authentication", HttpStatus.BAD_REQUEST); throws ValidationException {
} if (newClient.getTokenEndpointAuthMethod() == null) {
newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
newClient.setClientSecret(null); }
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.NONE) {
newClient.setClientSecret(null); if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC
} else { || newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT
throw new ValidationException("invalid_client_metadata", "Unknown authentication method", HttpStatus.BAD_REQUEST); || newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) {
}
return newClient; if (Strings.isNullOrEmpty(newClient.getClientSecret())) {
} // no secret yet, we need to generate a secret
newClient = clientService.generateClientSecret(newClient);
private OAuth2AccessTokenEntity fetchValidRegistrationToken(OAuth2Authentication auth, ClientDetailsEntity client) { }
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.PRIVATE_KEY) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails(); if (Strings.isNullOrEmpty(newClient.getJwksUri()) && newClient.getJwks() == null) {
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue()); throw new ValidationException("invalid_client_metadata",
"JWK Set URI required when using private key authentication", HttpStatus.BAD_REQUEST);
if (config.getRegTokenLifeTime() != null) { }
try { newClient.setClientSecret(null);
// Re-issue the token if it has been issued before [currentTime - validity] } else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.NONE) {
Date validToDate = new Date(System.currentTimeMillis() - config.getRegTokenLifeTime() * 1000); newClient.setClientSecret(null);
if(token.getJwt().getJWTClaimsSet().getIssueTime().before(validToDate)) { } else {
logger.info("Rotating the registration access token for " + client.getClientId()); throw new ValidationException("invalid_client_metadata", "Unknown authentication method",
tokenService.revokeAccessToken(token); HttpStatus.BAD_REQUEST);
OAuth2AccessTokenEntity newToken = connectTokenService.createResourceAccessToken(client); }
tokenService.saveAccessToken(newToken); return newClient;
return newToken; }
} else {
// it's not expired, keep going private OAuth2AccessTokenEntity fetchValidRegistrationToken(OAuth2Authentication auth,
return token; ClientDetailsEntity client) {
}
} catch (ParseException e) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
logger.error("Couldn't parse a known-valid token?", e); OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
return token;
} if (config.getRegTokenLifeTime() != null) {
} else {
// tokens don't expire, just return it try {
return token; // Re-issue the token if it has been issued before [currentTime - validity]
} Date validToDate =
} new Date(System.currentTimeMillis() - config.getRegTokenLifeTime() * 1000);
if (token.getJwt().getJWTClaimsSet().getIssueTime().before(validToDate)) {
logger.info("Rotating the registration access token for " + client.getClientId());
tokenService.revokeAccessToken(token);
OAuth2AccessTokenEntity newToken = connectTokenService.createResourceAccessToken(client);
tokenService.saveAccessToken(newToken);
return newToken;
} else {
// it's not expired, keep going
return token;
}
} catch (ParseException e) {
logger.error("Couldn't parse a known-valid token?", e);
return token;
}
} else {
// tokens don't expire, just return it
return token;
}
}
} }

11
pom.xml
View File

@ -385,35 +385,36 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId> <artifactId>spring-framework-bom</artifactId>
<version>4.3.7.RELEASE</version> <version>5.3.12</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- Jackson --> <!-- Jackson -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.9.0.pr2</version> <version>2.12.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
<version>2.9.0.pr2</version> <version>2.12.5</version>
</dependency> </dependency>
<!-- Spring Security --> <!-- Spring Security -->
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId> <artifactId>spring-security-bom</artifactId>
<version>4.2.4.RELEASE</version> <version>5.5.3</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security.oauth</groupId> <groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId> <artifactId>spring-security-oauth2</artifactId>
<version>2.1.0.RELEASE</version> <version>2.5.1.RELEASE</version>
</dependency> </dependency>
<!-- Servlet --> <!-- Servlet -->