Skip to content
Open
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 @@ -22,6 +22,7 @@
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
Expand All @@ -34,20 +35,26 @@ public static String createZoweJwtToken(String username, String domain, String l
return createToken(username, domain, ltpaToken, config, "APIML");
}

public static String createExpiredZoweJwtToken(String username, String domain, String ltpaToken, HttpsConfig config) {
return createToken(username, domain, ltpaToken, System.currentTimeMillis() - Duration.ofDays(1).toMillis(), config, "APIML");
}

public static String createZosmfJwtToken(String username, String domain, String ltpaToken, HttpsConfig config) {
return createToken(username, domain, ltpaToken, config, "zOSMF");
}

public static String createToken(String username, String domain, String ltpaToken, HttpsConfig config, String issuer) {
long now = System.currentTimeMillis();
long expiration = now + 100_000L;
return createToken(username, domain, ltpaToken, System.currentTimeMillis() + 100_000L, config, issuer);
}

public static String createToken(String username, String domain, String ltpaToken, long expiration, HttpsConfig config, String issuer) {
Key jwtSecret = SecurityUtils.loadKey(config);

return Jwts.builder()
.subject(username)
.claim("dom", domain)
.claim("ltpa", ltpaToken)
.issuedAt(new Date(now))
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(expiration))
.issuer(issuer)
.id(UUID.randomUUID().toString())
Expand Down
1 change: 1 addition & 0 deletions apiml/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ dependencies {
testImplementation(testFixtures(project(":apiml-common")))
testImplementation(testFixtures(project(":apiml-security-common")))
testImplementation(testFixtures(project(":gateway-service")))
testImplementation(testFixtures(project(":apiml-security-common")))
testImplementation libs.spring.boot.starter.test
testImplementation libs.spring.mock.mvc
testImplementation libs.modulith.test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.acceptance;

import lombok.SneakyThrows;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.gateway.MockService;
import org.zowe.apiml.security.common.token.TokenAuthentication;
import org.zowe.apiml.security.common.util.JWTTestUtils;
import org.zowe.apiml.util.config.SslContext;
import org.zowe.apiml.util.config.SslContextConfigurer;

import java.util.stream.Stream;

import static io.restassured.RestAssured.given;
import static org.apache.http.HttpStatus.SC_OK;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.junit.jupiter.api.Assertions.*;

@AcceptanceTest
class ValidationJwtCacheRoutingTest extends AcceptanceTestWithMockServices {

private static final String COOKIE = "apimlAuthenticationToken";

@Autowired
CacheManager cacheManager;

@LocalServerPort
private int port;

@Value("${apiml.service.hostname:localhost}")
private String hostname;

private String expiredJwtToken;

private Cache validationJwtTokenCache;

@Value("${server.ssl.keyPassword}")
char[] password;
@Value("${server.ssl.keyStore}")
String client_cert_keystore;
@Value("${server.ssl.keyStore}")
String keystore;

@BeforeAll
void initMockServices() {
getSchemes().forEach(scheme ->
mockService("%s-service".formatted(scheme.toLowerCase())).scope(MockService.Scope.CLASS)
.authenticationScheme(AuthenticationScheme.fromString(scheme))
.applid("dummy")
.addEndpoint("/%s-service/foo".formatted(scheme.toLowerCase()))
.assertion(exchange -> assertFalse(exchange.getRequestHeaders().containsKey("Authorization")))
.assertion(exchange -> assertFalse(exchange.getRequestHeaders().containsKey("X-SAF-Token")))
.assertion(exchange -> assertTrue(exchange.getRequestHeaders().containsKey("X-zowe-auth-failure")))
Comment thread
richard-salac marked this conversation as resolved.
.responseCode(200)
.and()
.start()
);
}

@BeforeEach
@SneakyThrows
void setUp() {
mockZosmfSuccess();
SslContextConfigurer configurer = new SslContextConfigurer(password, client_cert_keystore, keystore);
SslContext.prepareSslAuthentication(configurer);
expiredJwtToken = JWTTestUtils.createExpiredZoweJwtToken("user", "z/OS", "Ltpa", httpConfig.getHttpsConfig());
validationJwtTokenCache = cacheManager.getCache("validatedJwtTokens");
validationJwtTokenCache.put(expiredJwtToken, new TokenAuthentication(expiredJwtToken));
}

@AfterAll
void stop() {
SslContext.reset();
}

Stream<String> getSchemes() {
return Stream.of("httpBasicPassTicket", "zosmf", "zoweJwt", "safIdt");
}

@ParameterizedTest
@MethodSource("getSchemes")
void whenExpiredTokenInValidationCacheRouting_thenAuthorizationFails(String scheme) {
//@formatter:off
var response = given()
.cookie(COOKIE, expiredJwtToken)
.when()
.get("https://%s:%d/%s-service/api/v1/foo".formatted(hostname, port, scheme.toLowerCase()));
//@formatter:on

assertEquals(SC_OK, response.getStatusCode());
assertNotNull(response.getHeader("X-zowe-auth-failure"));
}

@Test
void whenExpiredTokenInValidationCacheQuery_thenAuthorizationFails() {
//@formatter:off
given()
.cookie(COOKIE, expiredJwtToken)
.when()
.get("https://%s:%d/gateway/api/v1/auth/query".formatted(hostname, port))
.then()
.statusCode(SC_UNAUTHORIZED);
//@formatter:on
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.acceptance;

import io.restassured.config.SSLConfig;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.zowe.apiml.product.web.HttpConfig;
import org.zowe.apiml.security.common.token.TokenAuthentication;
import org.zowe.apiml.security.common.util.JWTTestUtils;
import org.zowe.apiml.util.config.SslContext;
import org.zowe.apiml.util.config.SslContextConfigurer;
import org.zowe.apiml.zaas.ZaasApplication;

import static io.restassured.RestAssured.config;
import static io.restassured.RestAssured.given;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest(classes = ZaasApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class JwtValidationCacheTest {

private static final String COOKIE = "apimlAuthenticationToken";

@Autowired
private HttpConfig httpConfig;

@Autowired
private CacheManager cacheManager;

@LocalServerPort
private int port;

@Value("${apiml.service.hostname:localhost}")
private String hostname;

private String expiredJwtToken;

private Cache validationJwtTokenCache;

@Value("${server.ssl.keyPassword}")
private char[] password;

@Value("${server.ssl.keyStore}")
private String clientCertKeystore;

@Value("${server.ssl.keyStore}")
private String keystore;

@BeforeEach
void setUp() throws Exception {
SslContextConfigurer configurer = new SslContextConfigurer(password, clientCertKeystore, keystore);
SslContext.prepareSslAuthentication(configurer);
expiredJwtToken = JWTTestUtils.createExpiredZoweJwtToken("user", "z/OS", "Ltpa", httpConfig.getHttpsConfig());
validationJwtTokenCache = cacheManager.getCache("validatedJwtTokens");
validationJwtTokenCache.put(expiredJwtToken, new TokenAuthentication(expiredJwtToken));
}

@Nested
class ExpiredJwtTokenInValidationCache {
@ParameterizedTest
@ValueSource(strings = {"ticket", "zosmf","zoweJwt","safIdt"})
void whenZoweJwtSchemeCalled_thenUnauthorized(String scheme) {
//@formatter:off
given().config(config().sslConfig(new SSLConfig().sslSocketFactory(
new SSLSocketFactory(httpConfig.getSecureSslContextWithoutKeystore(), ALLOW_ALL_HOSTNAME_VERIFIER)))
)
.cookie(COOKIE, expiredJwtToken)
.when()
.post(String.format("https://%s:%d/zaas/scheme/%s", hostname, port, scheme))
.then()
.statusCode(SC_UNAUTHORIZED);
//@formatter:on
}
}

@Test
void whenQueryCalledWithExpiredJwtToken_thenUnauthorized() {
Comment thread
richard-salac marked this conversation as resolved.
//@formatter:off
var response = given().config(config().sslConfig(new SSLConfig().sslSocketFactory(
new SSLSocketFactory(httpConfig.getSecureSslContextWithoutKeystore(), ALLOW_ALL_HOSTNAME_VERIFIER)))
)
.cookie(COOKIE, expiredJwtToken)
.when()
.get(String.format("https://%s:%d/zaas/api/v1/auth/query", hostname, port))
.then()
.statusCode(SC_UNAUTHORIZED)
.extract().body().asString();
//@formatter:on

assertTrue(response.contains("The validity of the token is expired."));
}

}
Loading