Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ public class JwtUtils {
* @throws TokenNotValidException in case of invalid input, or TokenExpireException if JWT is expired
*/
public JWTClaimsSet getJwtClaims(String jwt) {
return getJwtClaimsInternal(jwt, true);
}

/**
* This method reads the claims without validating the token signature. It should be used only if the validity was checked in the calling code. Ignores token expiration.
*
* @param jwt token to be parsed
* @return parsed claims
* @throws TokenNotValidException in case of invalid input
*/
public JWTClaimsSet getJwtClaimsIgnoreExpiration(String jwt) {
return getJwtClaimsInternal(jwt, false);
}

private JWTClaimsSet getJwtClaimsInternal(String jwt, boolean validateExpiration) {
/*
* Removes signature, because we don't have key to verify z/OS tokens, and we just need to read claim.
* Verification is done by SAF itself. JWT library doesn't parse signed key without verification.
Expand All @@ -54,9 +69,10 @@ public JWTClaimsSet getJwtClaims(String jwt) {
var token = JWTParser.parse(jwtWithoutSignature);
var claims = token.getJWTClaimsSet();

if (claims.getExpirationTime().toInstant().isBefore(Instant.now())) {
if (validateExpiration && claims.getExpirationTime().toInstant().isBefore(Instant.now())) {
throw new ExpiredJWTException("JWT token is expired");
}

return token.getJWTClaimsSet();
} catch (RuntimeException | ParseException | BadJWTException exception) {
throw handleJwtParserException(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ public class PassticketSchemeTest implements TestWithStartedInstances {
public static Stream<Arguments> getTokens() {
return Stream.of(
Arguments.of("validJwt", SC_OK, Matchers.blankOrNullString()),
Arguments.of("noSignJwt", SC_OK, startsWith("ZWEAG160E")),
Arguments.of("publicKeySignedJwt", SC_OK, startsWith("ZWEAG160E")),
Arguments.of("noSignJwt", SC_OK, startsWith("ZWEAO402E")),
Arguments.of("publicKeySignedJwt", SC_OK, startsWith("ZWEAO402E")),
Arguments.of("changedRealmJwt", SC_OK, startsWith("ZWEAO402E")),
Arguments.of("changedUserJwt", SC_OK, startsWith("ZWEAG160E")),
Arguments.of("changedUserJwt", SC_OK, startsWith("ZWEAO402E")),
Arguments.of("personalAccessToken", SC_OK, Matchers.blankOrNullString())
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import java.util.Set;

import static org.zowe.apiml.security.common.util.JwtUtils.getJwtClaims;
import static org.zowe.apiml.security.common.util.JwtUtils.getJwtClaimsIgnoreExpiration;
import static org.zowe.apiml.security.common.util.JwtUtils.handleJwtParserException;
import static org.zowe.apiml.zaas.security.service.zosmf.ZosmfService.TokenType.JWT;
import static org.zowe.apiml.zaas.security.service.zosmf.ZosmfService.TokenType.LTPA;
Expand Down Expand Up @@ -375,26 +376,35 @@ public TokenAuthentication validateJwtToken(String jwtToken) {
if (jwtToken != null && validationJwtTokenCache != null) {
Cache.ValueWrapper cached = validationJwtTokenCache.get(jwtToken);
if (cached != null) {
log.debug("JWT found in the cache.");
return (TokenAuthentication) cached.get();
var tokenAuthentication = (TokenAuthentication) cached.get();
log.debug("JWT found in the cache. Is authenticated: {}", tokenAuthentication.isAuthenticated());
return tokenAuthentication;
}
}

QueryResponse queryResponse = parseJwtToken(jwtToken);
boolean isValid = switch (queryResponse.getSource()) {
case ZOWE -> {
validateAndParseLocalJwtToken(jwtToken);
yield true;
try {
QueryResponse queryResponse = parseJwtToken(jwtToken);
switch (queryResponse.getSource()) {
case ZOWE -> validateAndParseLocalJwtToken(jwtToken);
case ZOSMF -> zosmfService.validate(jwtToken);
default -> throw new TokenNotValidException("Unknown token type.");
}
case ZOSMF -> zosmfService.validate(jwtToken);
default -> throw new TokenNotValidException("Unknown token type.");
};
boolean notInvalidated = !isInvalidated(jwtToken);
boolean notInvalidated = !isInvalidated(jwtToken);
return processJWTvalidationResult(queryResponse, jwtToken, notInvalidated);
} catch (TokenNotValidException | TokenExpireException ex) {
log.debug("JWT token is not valid", ex);
processJWTvalidationResult(parseJwtTokenIgnoreExpiration(jwtToken), jwtToken, false);
throw ex;
}
}

private TokenAuthentication processJWTvalidationResult(QueryResponse queryResponse, String jwtToken, Boolean tokenValid) {
TokenAuthentication tokenAuthentication = new TokenAuthentication(queryResponse.getUserId(), jwtToken, TokenAuthentication.Type.JWT);
tokenAuthentication.setAuthenticated(notInvalidated && isValid);
tokenAuthentication.setAuthenticated(tokenValid);

putValidationCache(jwtToken, tokenAuthentication);
log.debug("JWT validation result: {}", tokenAuthentication.isAuthenticated());

return tokenAuthentication;
}

Expand Down Expand Up @@ -456,10 +466,9 @@ public TokenAuthentication validateJwtToken(TokenAuthentication token) {
if (token == null) {
throw new TokenNotValidException("Null token.");
}
parseJwtToken(token.getCredentials()); // throws on expired token, this needs to happen before cache


var tokenAuth = validateJwtToken(token.getCredentials());

return tokenAuth;
}

Expand All @@ -472,10 +481,22 @@ public TokenAuthentication validateJwtToken(TokenAuthentication token) {
*/
public QueryResponse parseJwtToken(String jwtToken) {
log.debug("Parsing JWT: ...{}", StringUtils.right(jwtToken, 15));
var claims = getJwtClaims(jwtToken);
return parseQueryResponse(claims);
return parseQueryResponse(getJwtClaims(jwtToken));
}

/**
* Parses the JWT token and return a {@link QueryResponse} object containing the domain, user id, type (Zowe / z/OSMF),
* date of creation and date of expiration. Does not validate expiration.
*
* @param jwtToken the JWT token
* @return the query response
*/
public QueryResponse parseJwtTokenIgnoreExpiration(String jwtToken) {
log.debug("Parsing JWT: ...{}", StringUtils.right(jwtToken, 15));
return parseQueryResponse(getJwtClaimsIgnoreExpiration(jwtToken));
}


public QueryResponse parseQueryResponse(JWTClaimsSet claims) {
Object scopesObject = claims.getClaim(SCOPES);
List<String> scopes = Collections.emptyList();
Expand Down Expand Up @@ -596,7 +617,6 @@ private Optional<String> extractJwtTokenFromAuthorizationHeader(String header) {
return Optional.empty();
}


/**
* Calculate the expiration time
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import static org.zowe.apiml.zaas.security.service.zosmf.ZosmfService.TokenType.JWT;
import static org.zowe.apiml.zaas.security.service.zosmf.ZosmfService.TokenType.LTPA;
Expand Down Expand Up @@ -487,24 +483,47 @@ public boolean jwtBuilderEndpointExists() {
return meAsProxy.jwtEndpointExists(headers);
}

/**
* Validates jwt token with all available strategies.
*
* @param token
* @return true if at least one validation strategy evaluates token as valid
* @throws ServiceNotAccessibleException if all validation strategies failed because of an error
* @throws TokenNotValidException if all token validation strategies evaluate token as invalid
*/
public boolean validate(String token) {
log.debug("ZosmfService validating token: ....{}", StringUtils.right(token, 15));
TokenValidationRequest request = new TokenValidationRequest(TokenType.JWT, token, getURI(getZosmfServiceId()), getEndpointMap());

var isTokenValid = Optional.<Boolean>empty();

for (TokenValidationStrategy s : tokenValidationStrategy) {
log.debug("Trying to validate token with strategy: {}", s.toString());
try {
s.validate(request);
if (requestIsAuthenticated(request)) {
log.debug("Token validity has been successfully determined: {}", request.getAuthenticated());
return true;
isTokenValid = Optional.of(true);
break;
} else {
isTokenValid = Optional.of(false);
}
} catch (RuntimeException re) {
log.debug("Exception during token validation:", re);
}
}

log.debug("Token validation strategies exhausted, final validation status: {}", request.getAuthenticated());
return false;

if (isTokenValid.isPresent()) {
if (isTokenValid.get()) {
return true;
} else {
throw new TokenNotValidException("Token is not valid by any of zosmf validation strategies");
}
}

throw new ServiceNotAccessibleException("All token validation strategies has failed with " + request.getZosmfBaseUrl());
}

private boolean requestIsAuthenticated(TokenValidationRequest request) {
Expand Down
Loading
Loading