Skip to content

Commit

Permalink
Refactored token validation to support separate refresh token validation
Browse files Browse the repository at this point in the history
[#153566482] https://www.pivotaltracker.com/story/show/153566482

Signed-off-by: Jennifer Hamon <jhamon@pivotal.io>
  • Loading branch information
Jaskanwal Pawar authored and jhamon committed Jul 18, 2018
1 parent 5e06713 commit b52626c
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 42 deletions.
Expand Up @@ -127,7 +127,7 @@
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REFRESH_TOKEN_SUFFIX;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_TOKEN_FORMAT;
import static org.cloudfoundry.identity.uaa.util.TokenValidation.validate;
import static org.cloudfoundry.identity.uaa.util.TokenValidation.validateAccessToken;
import static org.springframework.util.StringUtils.hasText;


Expand Down Expand Up @@ -1069,7 +1069,7 @@ protected TokenValidation validateToken(String token) {
token = revocableToken.getValue();
}

tokenValidation = validate(token)
tokenValidation = validateAccessToken(token)
.checkRevocableTokenStore(tokenProvisioning)
.throwIfInvalid();
Jwt tokenJwt = tokenValidation.getJwt();
Expand Down
Expand Up @@ -100,7 +100,7 @@
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.util.TokenValidation.validate;
import static org.cloudfoundry.identity.uaa.util.TokenValidation.validateAccessToken;
import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.isAcceptedInvitationAuthentication;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.isEmpty;
Expand Down Expand Up @@ -511,11 +511,11 @@ private TokenValidation validateToken(String idToken, AbstractXOAuthIdentityProv
TokenValidation validation;
if (tokenServices.getTokenEndpoint().equals(config.getIssuer())) {
tokenKey = getTokenKeyForUaaOrigin();
validation = validate(idToken)
validation = validateAccessToken(idToken)
.checkSignature(new ChainedSignatureVerifier(tokenKey));
} else {
tokenKey = getTokenKeyFromOAuth(config);
validation = validate(idToken)
validation = validateAccessToken(idToken)
.checkSignature(new ChainedSignatureVerifier(tokenKey))
.checkIssuer((isEmpty(config.getIssuer()) ? config.getTokenUrl().toString() : config.getIssuer()))
.checkAudience(config.getRelyingPartyId());
Expand Down
Expand Up @@ -15,13 +15,16 @@
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.oauth.KeyInfo;
import org.cloudfoundry.identity.uaa.oauth.TokenRevokedException;
import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt;
import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper;
import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.zone.ClientServicesExtension;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.flywaydb.core.internal.util.StringUtils;
import org.springframework.dao.EmptyResultDataAccessException;
Expand Down Expand Up @@ -73,7 +76,11 @@ public class TokenValidation {
private final boolean decoded; // this is used to avoid checking claims on tokens that had errors when decoding
private final List<RuntimeException> validationErrors = new ArrayList<>();

public static TokenValidation validate(String tokenJwtValue) {
public static TokenValidation validateAccessToken(String tokenJwtValue) {
return new TokenValidation(tokenJwtValue);
}

public static TokenValidation validateRefreshToken(String tokenJwtValue) {
return new TokenValidation(tokenJwtValue);
}

Expand Down Expand Up @@ -104,9 +111,36 @@ private TokenValidation(String token) {
this.claims = new HashMap<>();
}

if (tokenJwt != null) {
validateHeader(tokenJwt);
}

this.decoded = isValid();
}

public TokenValidation validateHeader(Jwt tokenJwt) {
String kid = tokenJwt.getHeader().getKid();
if (kid == null) {
validationErrors.add(
new InvalidTokenException("kid claim not found in JWT token header")
);
return this;
}

KeyInfo signingKey = KeyInfo.getKey(kid);
if (signingKey == null) {
validationErrors.add(
new InvalidTokenException(String.format(
"Token header claim [kid] references unknown signing key : [%s]", kid
))
);
return this;
}

SignatureVerifier signatureVerifier = signingKey.getVerifier();
return checkSignature(signatureVerifier);
}

public boolean isValid() {
return validationErrors.size() == 0;
}
Expand Down Expand Up @@ -509,4 +543,25 @@ public TokenValidation checkAccessToken() {
return this;
}

public ClientDetails getClientDetails(ClientServicesExtension clientDetailsService) {
String clientId = (String) claims.get(CID);
try {
return clientDetailsService.loadClientByClientId(clientId, IdentityZoneHolder.get().getId());
} catch (NoSuchClientException x) {
//happens if the client is deleted and token exist
throw new InvalidTokenException("Invalid client ID "+clientId);
}
}

public UaaUser getUserDetails(UaaUserDatabase userDatabase) {
String userId = (String) claims.get(USER_ID);
if( UaaTokenUtils.isUserToken(claims)) {
try {
return userDatabase.retrieveUserById(userId);
} catch (UsernameNotFoundException e) {
throw new InvalidTokenException("Token bears a non-existent user ID: " + userId);
}
}
return null;
}
}
Expand Up @@ -99,6 +99,7 @@
import static org.cloudfoundry.identity.uaa.oauth.UaaTokenServices.UAA_REFRESH_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.client.ClientConstants.REQUIRED_USER_GROUPS;
import static org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification.SECRET;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SCOPE;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.OPAQUE;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_TOKEN_FORMAT;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.JWT;
Expand Down Expand Up @@ -141,6 +142,8 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyString;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
Expand Down Expand Up @@ -1941,7 +1944,7 @@ public void validate_token_client_gone() throws Exception {
@Test
public void opaque_tokens_validate_signature() throws Exception {
expectedEx.expect(InvalidTokenException.class);
expectedEx.expectMessage("Invalid key ID: testKey");
expectedEx.expectMessage("Token header claim [kid] references unknown signing key : [testKey]");

Consumer<Void> setup = (ignore) -> {
Map < String, String > keys = new HashMap<>();
Expand Down

0 comments on commit b52626c

Please sign in to comment.