Skip to content

Commit

Permalink
Refactoring to simplify what we need to achieve in terms of validatin…
Browse files Browse the repository at this point in the history
…g a combination of client and user attributes
  • Loading branch information
fhanik committed Mar 4, 2017
1 parent 597c664 commit e794fc8
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 44 deletions.
Expand Up @@ -49,9 +49,6 @@
/** /**
* Controller which decodes access tokens for clients who are not able to do so * Controller which decodes access tokens for clients who are not able to do so
* (or where opaque token values are used). * (or where opaque token values are used).
*
* @author Luke Taylor
* @author Joel D'sa
*/ */
@Controller @Controller
public class CheckTokenEndpoint implements InitializingBean { public class CheckTokenEndpoint implements InitializingBean {
Expand Down
Expand Up @@ -49,6 +49,7 @@
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier; import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
Expand All @@ -59,7 +60,6 @@
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException;
import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
Expand Down Expand Up @@ -1164,22 +1164,23 @@ protected TokenValidation validateToken(String token) {


String clientId = (String) claims.get(CID); String clientId = (String) claims.get(CID);
String userId = (String) claims.get(USER_ID); String userId = (String) claims.get(USER_ID);
UaaUser user = null;
ClientDetails client; ClientDetails client;
try { try {
client = clientDetailsService.loadClientByClientId(clientId); client = clientDetailsService.loadClientByClientId(clientId);
} catch (NoSuchClientException x) { } catch (NoSuchClientException x) {
//happens if the client is deleted and token exist //happens if the client is deleted and token exist
throw new UnauthorizedClientException("Invalid client ID "+clientId); throw new InvalidTokenException("Invalid client ID "+clientId);
} }
tokenValidation.checkClient(client).throwIfInvalid(); UaaUser user = null;

if( UaaTokenUtils.isUserToken(claims)) { if( UaaTokenUtils.isUserToken(claims)) {
tokenValidation.checkUser(userDatabase).throwIfInvalid(); try {
user = userDatabase.retrieveUserById(userId); user = userDatabase.retrieveUserById(userId);
} catch (UsernameNotFoundException e) {
throw new InvalidTokenException("Token bears a non-existent user ID: " + userId);
}
} }

tokenValidation.checkClientAndUser(client, user).throwIfInvalid();
tokenValidation.checkRevocableTokenStore(tokenProvisioning).throwIfInvalid();


List<String> clientSecrets = new ArrayList<>(); List<String> clientSecrets = new ArrayList<>();
List<String> revocationSignatureList = new ArrayList<>(); List<String> revocationSignatureList = new ArrayList<>();
Expand Down
Expand Up @@ -22,7 +22,6 @@
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken; import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning; import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning;
import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.flywaydb.core.internal.util.StringUtils; import org.flywaydb.core.internal.util.StringUtils;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
Expand All @@ -32,7 +31,6 @@
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.util.Assert; import org.springframework.util.Assert;


Expand All @@ -58,6 +56,7 @@
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCATION_SIGNATURE; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCATION_SIGNATURE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SCOPE; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SCOPE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID;
import static org.cloudfoundry.identity.uaa.util.UaaTokenUtils.isUserToken;


public class TokenValidation { public class TokenValidation {
private static final Log logger = LogFactory.getLog(TokenValidation.class); private static final Log logger = LogFactory.getLog(TokenValidation.class);
Expand Down Expand Up @@ -171,21 +170,8 @@ public TokenValidation checkExpiry() {
return checkExpiry(Instant.now()); return checkExpiry(Instant.now());
} }


public TokenValidation checkUser(UaaUser user) { protected TokenValidation checkUser(Function<String, UaaUser> getUser) {
return checkUser(uid -> { if(!decoded || !isUserToken(claims)) {
if (!equals(uid, user.getId())) {
throw new InvalidTokenException("Token does not have expected user ID.");
}
return user;
});
}

public TokenValidation checkUser(UaaUserDatabase userDb) {
return checkUser(userDb::retrieveUserById);
}

public TokenValidation checkUser(Function<String, UaaUser> getUser) {
if(!decoded || !UaaTokenUtils.isUserToken(claims)) {
addError("Token is not a user token."); addError("Token is not a user token.");
return this; return this;
} }
Expand Down Expand Up @@ -265,14 +251,33 @@ public TokenValidation checkScopesWithin(Collection<String> scopes) {
return this; return this;
} }


public TokenValidation checkClient(ClientDetails client) { public TokenValidation checkClientAndUser(ClientDetails client, UaaUser user) {
return checkClient(cid -> { TokenValidation validation =
if (!equals(cid, client.getClientId())) { throw new InvalidTokenException("Token's client ID does not match expected value: " + client.getClientId()); } checkClient(
return client; cid -> {
}); if (!equals(cid, client.getClientId())) {
throw new InvalidTokenException("Token's client ID does not match expected value: " + client.getClientId());
}
return client;
});
if (isUserToken(claims)) {
return validation.checkUser(uid -> {
if (user == null) {
throw new InvalidTokenException("Unable to validate user, no user found.");
} else {
if (!equals(uid, user.getId())) {
throw new InvalidTokenException("Token does not have expected user ID.");
}
return user;
}
});
} else {
return validation;
}

} }


public TokenValidation checkClient(ClientDetailsService clientDetailsService) { protected TokenValidation checkClient(Function<String, ClientDetails> getClient) {
if(!decoded || !claims.containsKey(CID)) { if(!decoded || !claims.containsKey(CID)) {
addError("Token bears no client ID."); addError("Token bears no client ID.");
return this; return this;
Expand All @@ -292,7 +297,7 @@ public TokenValidation checkClient(ClientDetailsService clientDetailsService) {
} }


try { try {
ClientDetails client = clientDetailsService.loadClientByClientId(clientId); ClientDetails client = getClient.apply(clientId);


Collection<String> clientScopes; Collection<String> clientScopes;
if (null == claims.get(USER_ID)) { if (null == claims.get(USER_ID)) {
Expand Down
Expand Up @@ -129,9 +129,9 @@ public void validateToken() throws Exception {
TokenValidation validation = validate(getToken()) TokenValidation validation = validate(getToken())
.checkSignature(verifier) .checkSignature(verifier)
.checkIssuer("http://localhost:8080/uaa/oauth/token") .checkIssuer("http://localhost:8080/uaa/oauth/token")
.checkClient(clientDetailsService) .checkClient((clientId) -> clientDetailsService.loadClientByClientId(clientId))
.checkExpiry(oneSecondBeforeTheTokenExpires) .checkExpiry(oneSecondBeforeTheTokenExpires)
.checkUser(userDb) .checkUser((uid) -> userDb.retrieveUserById(uid))
.checkScopesInclude("acme.dev") .checkScopesInclude("acme.dev")
.checkScopesWithin("acme.dev", "another.scope") .checkScopesWithin("acme.dev", "another.scope")
.checkRevocationSignature(Collections.singletonList("fa1c787d")) .checkRevocationSignature(Collections.singletonList("fa1c787d"))
Expand All @@ -148,9 +148,9 @@ public void validateToken_Without_Email_And_Username() throws Exception {
TokenValidation validation = validate(getToken(Arrays.asList(EMAIL, USER_NAME))) TokenValidation validation = validate(getToken(Arrays.asList(EMAIL, USER_NAME)))
.checkSignature(verifier) .checkSignature(verifier)
.checkIssuer("http://localhost:8080/uaa/oauth/token") .checkIssuer("http://localhost:8080/uaa/oauth/token")
.checkClient(clientDetailsService) .checkClient((clientId) -> clientDetailsService.loadClientByClientId(clientId))
.checkExpiry(oneSecondBeforeTheTokenExpires) .checkExpiry(oneSecondBeforeTheTokenExpires)
.checkUser(userDb) .checkUser((uid) -> userDb.retrieveUserById(uid))
.checkScopesInclude("acme.dev") .checkScopesInclude("acme.dev")
.checkScopesWithin("acme.dev", "another.scope") .checkScopesWithin("acme.dev", "another.scope")
.checkRevocationSignature(Collections.singletonList("fa1c787d")) .checkRevocationSignature(Collections.singletonList("fa1c787d"))
Expand Down Expand Up @@ -212,7 +212,7 @@ public void nonExistentUser() {
UaaUserDatabase userDb = new InMemoryUaaUserDatabase(Collections.emptySet()); UaaUserDatabase userDb = new InMemoryUaaUserDatabase(Collections.emptySet());


TokenValidation validation = validate(getToken()) TokenValidation validation = validate(getToken())
.checkUser(userDb); .checkUser((uid) -> userDb.retrieveUserById(uid));
assertFalse(validation.isValid()); assertFalse(validation.isValid());
assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class))); assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class)));
} }
Expand All @@ -226,7 +226,7 @@ public void userHadScopeRevoked() {
.withAuthorities(Collections.singletonList(new SimpleGrantedAuthority("a.different.scope")))); .withAuthorities(Collections.singletonList(new SimpleGrantedAuthority("a.different.scope"))));


TokenValidation validation = validate(getToken()) TokenValidation validation = validate(getToken())
.checkUser(userDb); .checkUser((uid) -> userDb.retrieveUserById(uid));
assertFalse(validation.isValid()); assertFalse(validation.isValid());
assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class))); assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class)));
} }
Expand All @@ -252,7 +252,7 @@ public void nonExistentClient() {
InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService(); InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService();
clientDetailsService.setClientDetailsStore(Collections.emptyMap()); clientDetailsService.setClientDetailsStore(Collections.emptyMap());
TokenValidation validation = validate(getToken()) TokenValidation validation = validate(getToken())
.checkClient(clientDetailsService); .checkClient((clientId) -> clientDetailsService.loadClientByClientId(clientId));
assertFalse(validation.isValid()); assertFalse(validation.isValid());
assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class))); assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class)));
} }
Expand All @@ -263,7 +263,7 @@ public void clientHasScopeRevoked() {
clientDetailsService.setClientDetailsStore(Collections.singletonMap("app", new BaseClientDetails("app", "acme", "a.different.scope", "authorization_code", ""))); clientDetailsService.setClientDetailsStore(Collections.singletonMap("app", new BaseClientDetails("app", "acme", "a.different.scope", "authorization_code", "")));


TokenValidation validation = validate(getToken()) TokenValidation validation = validate(getToken())
.checkClient(clientDetailsService); .checkClient((clientId) -> clientDetailsService.loadClientByClientId(clientId));
assertFalse(validation.isValid()); assertFalse(validation.isValid());
assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class))); assertThat(validation.getValidationErrors(), hasItem(instanceOf(InvalidTokenException.class)));
} }
Expand Down

0 comments on commit e794fc8

Please sign in to comment.