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 @@ -41,8 +41,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

Expand Down Expand Up @@ -174,6 +173,9 @@
.cacheMode(CacheMode.REPL_SYNC)
.hash().numSegments(numSegments);
return builder;

//performance nemusi byt persistence, clustering, REPL, index?,

}

@Bean(destroyMethod = "stop")
Expand All @@ -192,14 +194,31 @@
System.setProperty("infinispan.ssl.trustStore", trustStore);
System.setProperty("infinispan.ssl.trustStorePassword", trustStorePass);

List<String> caches;
//List<String> caches;

Check warning on line 197 in caching-service/src/main/java/org/zowe/apiml/caching/service/infinispan/config/InfinispanConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=zowe_api-layer&issues=AZ1ychRf2ZiTuE1IhvmF&open=AZ1ychRf2ZiTuE1IhvmF&pullRequest=4548

Map<String, ConfigurationBuilder> caches;
if (applicationInfo.isModulith()) {
caches = Arrays.asList(CACHE_ZOWE, CACHE_ZOWE_INVALIDATED_TOKEN, "zosmfAuthenticationEndpoint", "invalidatedJwtTokens", "validationJwtToken", "zosmfInfo", "zosmfJwtEndpoint", "trustedCertificates", "parseOIDCToken", "validationOIDCToken");
caches = new HashMap<>();
var defaultCacheconfig = getCacheConfig();
Arrays.asList(CACHE_ZOWE, CACHE_ZOWE_INVALIDATED_TOKEN, "invalidatedJwtTokens", "validationJwtToken", "zosmfAuthenticationEndpoint", "zosmfInfo", "zosmfJwtEndpoint", "trustedCertificates", "parseOIDCToken", "validationOIDCToken").forEach( c -> caches.put(c,defaultCacheconfig));
} else {
caches = Arrays.asList(CACHE_ZOWE, CACHE_ZOWE_INVALIDATED_TOKEN);
caches = new HashMap<>();
var defaultCacheconfig = getCacheConfig();
Arrays.asList(CACHE_ZOWE, CACHE_ZOWE_INVALIDATED_TOKEN).forEach( c -> caches.put(c,defaultCacheconfig));


//CACHE_ZOWE - genericke cache, napr. routy
//CACHE_ZOWE_INVALIDATED_TOKEN pro PAT
//invalidatedJwtTokens je v zaas
//validationJwtToken je zavisla na invalidatedJwtTokens

// jen pro infinispan
//pro modulith odstranime provolani pres edpoint pro invalidaci //invalidace pres distribuovanou cache probehne kaskadovite

//muzou byt lokalni: "zosmfAuthenticationEndpoint", "zosmfInfo", "zosmfJwtEndpoint", "trustedCertificates", "parseOIDCToken", "validationOIDCToken"
}

return new LazyCacheManager(getCacheManagerConfig(resourceLoader), getCacheConfig(), caches);
return new LazyCacheManager(getCacheManagerConfig(resourceLoader), caches);
}

private ClusteredLock lock(CacheContainer cacheManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ public class LazyCacheManager extends DefaultCacheManager {

public LazyCacheManager(
ConfigurationBuilderHolder cacheManagerConfig,
ConfigurationBuilder cacheConfig,
Collection<String> cacheNames
Map<String, ConfigurationBuilder> caches
) {
super(cacheManagerConfig, false);
cacheInitializer = new CacheInitializer(cacheManagerConfig, cacheConfig, new ArrayList<>(cacheNames));
cacheInitializer = new CacheInitializer(cacheManagerConfig, caches);
}

private DefaultCacheManager getCacheManager() {
Expand Down Expand Up @@ -257,8 +256,7 @@ class CacheInitializer {
private DefaultCacheManager underInit;

private final ConfigurationBuilderHolder cacheManagerConfig;
private final ConfigurationBuilder cacheConfig;
private final Collection<String> cacheNames;
private final Map<String, ConfigurationBuilder> caches;

private DefaultCacheManager startDefaultCacheManager() {
var defaultCacheManager = new DefaultCacheManager(cacheManagerConfig, false);
Expand Down Expand Up @@ -298,24 +296,25 @@ public DefaultCacheManager getDefaultCacheManager() {
}
}

if (cacheNames.isEmpty()) {
if (caches.isEmpty()) {
cacheManager.set(underInit);
}

return underInit;
}

private boolean createCaches() {
for (Iterator<String> i = cacheNames.iterator(); i.hasNext();) {
for (Iterator<String> i = caches.keySet().iterator(); i.hasNext();) {
String cacheName = i.next();
if (createCache(cacheName)) {
i.remove();
}
}
return cacheNames.isEmpty();
return caches.isEmpty();
}

private boolean createCache(String cacheName) {
var cacheConfig = caches.get(cacheName);
try {
underInit.administration()
.withFlags(CacheContainerAdmin.AdminFlag.VOLATILE)
Expand All @@ -331,7 +330,7 @@ private boolean createCache(String cacheName) {
}

public boolean isInitialized() {
return underInit != null && cacheNames.isEmpty();
return underInit != null && caches.isEmpty();
}

}
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 @@ -99,12 +100,14 @@ public class AuthenticationService {
private final CacheUtils cacheUtils;
private final Clock clock;
private boolean isModulithMode;
private boolean isInfinispanEnabled;
private Cache validationJwtTokenCache;
private Cache invalidatedJwtTokensCache;

@PostConstruct
public void afterPropertiesSet() {
isModulithMode = applicationContext.containsBean("modulithConfig");
isInfinispanEnabled = applicationContext.containsBean("infinispanConfig");
validationJwtTokenCache = cacheManager.getCache(CACHE_VALIDATION_JWT_TOKEN);
invalidatedJwtTokensCache = cacheManager.getCache(CACHE_INVALIDATED_JWT_TOKENS);
}
Expand Down Expand Up @@ -181,7 +184,6 @@ public QueryResponse parseJwtWithSignature(String jwt) {
* - on logout phase (distribute = true)
* - from another ZAAS instance to notify about change (distribute = false)
* <p>
* Note: This method should not be called from modulith-mode
*
* @param jwtToken token to invalidate
* @param distribute distribute invalidation to another instances?
Expand All @@ -197,6 +199,9 @@ public Boolean invalidateJwtToken(String jwtToken, boolean distribute) {
? eurekaClient.getApplication(CoreService.GATEWAY.getServiceId())
: eurekaClient.getApplication(CoreService.ZAAS.getServiceId());

//modulith and infinispan ensures distribution via replication
if (isModulithMode && isInfinispanEnabled) distribute = false;

return doInvalidateAndUpdateCaches(jwtToken, distribute, app);
}

Expand Down Expand Up @@ -375,26 +380,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 +470,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 +485,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 +621,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