diff --git a/apiml-security-common/src/main/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSaf.java b/apiml-security-common/src/main/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSaf.java index 44b47d41eb..1f9116019f 100644 --- a/apiml-security-common/src/main/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSaf.java +++ b/apiml-security-common/src/main/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSaf.java @@ -11,6 +11,7 @@ package org.zowe.apiml.security.common.auth.saf; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.Authentication; import java.lang.invoke.MethodHandle; @@ -96,6 +97,9 @@ private boolean checkPermission(String userId, String resourceType, String resou @Override public boolean hasSafResourceAccess(Authentication authentication, String resourceClass, String resourceName, String accessLevel) { String userid = authentication.getName(); + if (StringUtils.isEmpty(userid)) { + return false; + } AccessLevel level = AccessLevel.valueOf(accessLevel); log.debug("Evaluating access of user {} to resource {} in class {} level {}", userid, resourceClass, resourceName, level); return checkPermission(userid, resourceClass, resourceName, level.getValue(), true); diff --git a/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSafTest.java b/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSafTest.java index 00d8bb5530..88fdc8f2c4 100644 --- a/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSafTest.java +++ b/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/saf/SafResourceAccessSafTest.java @@ -88,6 +88,11 @@ void testHasSafResourceAccess_whenNoResponse_thenTrue() { assertTrue(safResourceAccessVerifying.hasSafResourceAccess(authentication, CLASS, RESOURCE, LEVEL.name())); } + @Test + void testHasSafResourceAccess_whenUseridEmpty_thenFalse() { + assertFalse(safResourceAccessVerifying.hasSafResourceAccess(new UsernamePasswordAuthenticationToken("", "token"), CLASS, RESOURCE, LEVEL.name())); + } + @Builder public static class TestPlatformReturned { diff --git a/common-service-core/src/main/java/org/zowe/apiml/passticket/AbstractIRRPassTicketException.java b/common-service-core/src/main/java/org/zowe/apiml/passticket/AbstractIRRPassTicketException.java index 8775342d80..fe0d7ea3a4 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/passticket/AbstractIRRPassTicketException.java +++ b/common-service-core/src/main/java/org/zowe/apiml/passticket/AbstractIRRPassTicketException.java @@ -62,7 +62,7 @@ public enum ErrorCode { ERR_8_12_16(8, 12, 16, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Invocation of the Security Server Network Authentication Service Program Call (PC) interface failed with a 'local services are not available' return code. This indicates that the Security Server Network Authentication Service started task (SKRBKDC) address space has not been started or is terminating."), ERR_8_12_20(8, 12, 20, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Invocation of the Security Server Network Authentication Service Program Call (PC) interface failed with an 'abend in the PC service routine' return code. The symptom record associated with this abend can be found in the logrec data set."), ERR_8_12_24(8, 12, 24, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Invocation of the Security Server Network Authentication Service Program Call (PC) interface failed with an 'unable to obtain control lock' return code. This can occur if the task holding the lock is not being dispatched (for example, a dump is in progress)."), - ERR_8_16_28(8, 16, 28, HttpStatus.SC_BAD_REQUEST, "Unable to generate PassTicket. Verify that the secured signon (PassTicket) function and application ID is configured properly by referring to Using PassTickets in z/OS Security Server RACF Security Administrator's Guide."), + ERR_8_16_28(8, 16, 28, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Unable to generate PassTicket. Verify that the secured signon (PassTicket) function and application ID is configured properly by referring to Using PassTickets in z/OS Security Server RACF Security Administrator's Guide."), ERR_8_16_32(8, 16, 32, HttpStatus.SC_INTERNAL_SERVER_ERROR, "PassTicket evaluation failure. Possible reasons include: " + "PassTicket to be evaluated is not a successful PassTicket. " + "The PassTicket to be evaluated was already evaluated before and replay protection is in effect. " diff --git a/common-service-core/src/test/java/org/zowe/apiml/passticket/AbstractIRRPassTicketExceptionTest.java b/common-service-core/src/test/java/org/zowe/apiml/passticket/AbstractIRRPassTicketExceptionTest.java index 1a835292f8..45049d0127 100644 --- a/common-service-core/src/test/java/org/zowe/apiml/passticket/AbstractIRRPassTicketExceptionTest.java +++ b/common-service-core/src/test/java/org/zowe/apiml/passticket/AbstractIRRPassTicketExceptionTest.java @@ -34,7 +34,7 @@ void testErrorCode() { te = new TestException(8, 16, 28); assertSame(AbstractIRRPassTicketException.ErrorCode.ERR_8_16_28, te.getErrorCode()); - assertEquals(HttpStatus.SC_BAD_REQUEST, te.getHttpStatus()); + assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, te.getHttpStatus()); } class TestException extends AbstractIRRPassTicketException { diff --git a/config/local/gateway-service.yml b/config/local/gateway-service.yml index 75affbb305..319ca61eff 100644 --- a/config/local/gateway-service.yml +++ b/config/local/gateway-service.yml @@ -13,6 +13,10 @@ eureka: serviceUrl: defaultZone: https://localhost:10011/eureka/ server: + webSocket: + requestBufferSize: 16348 + max-http-request-header-size: 16348 + ssl: keyAlias: localhost keyPassword: password diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index c6c7f67edb..55e697e796 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -77,7 +77,7 @@ test { task startUpCheck(type: Test) { group 'integration tests' - description "Check that the API Mediation Layer is up and runnig" + description "Check that the API Mediation Layer is up and running" systemProperties System.properties useJUnitPlatform { @@ -97,7 +97,6 @@ task environmentCheck(type: Test) { outputs.upToDateWhen { false } } - task runStartUpCheck(type: Test) { group 'integration tests' description "Check that the API Mediation Layer is up and running" @@ -170,14 +169,14 @@ task runAllIntegrationTests(type: Test) { outputs.upToDateWhen { false } } -task runAllIntegrationTestsForZoweTestingOnZos(type: Test) { +task runAllIntegrationTestsForZoweNonHaTestingOnZos(type: Test) { // This task is intended to run on z/OS systems with some limitations: - // Only 1 Gateway + // Only 1 Gateway (Non-HA mode) // z/OSMF Authentication provider only // No support for SAF ID Tokens group "Integration tests" - description "Run all integration tests for Zowe testing on z/OS (limited)" + description "Run all integration tests for Zowe Non-HA testing on z/OS (limited)" def targetSystem = System.getenv("ZOS_TARGET_SYS") ? "-" + System.getenv("ZOS_TARGET_SYS") : "" systemProperty "environment.config", targetSystem @@ -203,9 +202,60 @@ task runAllIntegrationTestsForZoweTestingOnZos(type: Test) { 'SafIdTokenTest' ) } + + debugOptions { + port = 5005 + suspend = true + server = true + } outputs.upToDateWhen { false } } +task runAllIntegrationTestsForZoweHaTestingOnZos(type: Test) { + // This task is intended to run on z/OS systems with some limitations: + // More than 1 Gateway (HA mode) + // z/OSMF Authentication provider only + // No support for SAF ID Tokens + + group "Integration tests" + description "Run all integration tests for Zowe HA testing on z/OS (limited)" + + def targetSystem = System.getenv("ZOS_TARGET_SYS") ? "-" + System.getenv("ZOS_TARGET_SYS") : "" + systemProperty "environment.config", targetSystem + systemProperty "environment.zos.target", "true" + systemProperty "environment.ha", true + systemProperties System.properties + systemProperties.remove('java.endorsed.dirs') + + useJUnitPlatform { + excludeTags( + 'StartupCheck', + 'EnvironmentCheck', + 'AdditionalLocalTest', + 'TestsNotMeantForZowe', + 'DiscoverableClientDependentTest', + 'OktaOauth2Test', + 'MultipleRegistrationsTest', + 'NotForMainframeTest', + 'ApiCatalogStandaloneTest', + 'SAFProviderTest', + 'GatewayProxyTest', + 'SafIdTokenTest', + 'ChaoticHATest', + 'GraphQLTest' + ) + } + + debugOptions { + port = 5005 + suspend = true + server = true + } + + outputs.upToDateWhen { false } + outputs.cacheIf { false } +} + task runAllIntegrationTestsForZoweTesting(type: Test) { group "Integration tests" description "Run all integration tests for Zowe testing" @@ -350,6 +400,7 @@ task runGatewayProxyTest(type: Test) { ) } } + task runGatewayServiceRoutingTest(type: Test) { group "integration tests" description "Run tests verifying central gateway can locate service and translate auth scheme" @@ -521,9 +572,13 @@ task runChaoticHATests(type: Test) { group "Integration tests" description "Run Chaotic tests verifying High Availability" + def targetSystem = System.getenv("ZOS_TARGET_SYS") ? "-" + System.getenv("ZOS_TARGET_SYS") : "" + systemProperty "environment.config", targetSystem + systemProperty "environment.zos.target", "true" + systemProperty "environment.ha", true + outputs.cacheIf { false } - systemProperty "environment.ha", true systemProperties System.getProperties() useJUnitPlatform { includeTags( diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogEndpointIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogEndpointIntegrationTest.java index 2c69479d82..1f5d2b6ab8 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogEndpointIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogEndpointIntegrationTest.java @@ -21,6 +21,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.http.MediaType; import org.zowe.apiml.util.TestWithStartedInstances; import org.zowe.apiml.util.categories.CatalogTest; @@ -33,12 +34,16 @@ import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.UUID; +import java.util.stream.Stream; import static io.restassured.RestAssured.given; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -47,7 +52,9 @@ import static org.zowe.apiml.util.http.HttpRequestUtils.getUriFromGateway; @CatalogTest +@TestInstance(Lifecycle.PER_CLASS) class ApiCatalogEndpointIntegrationTest implements TestWithStartedInstances { + private static final String GET_ALL_CONTAINERS_ENDPOINT = "/apicatalog/api/v1/containers"; private static final String GET_CONTAINER_BY_ID_ENDPOINT = "/apicatalog/api/v1/containers/apimediationlayer"; private static final String GET_CONTAINER_BY_INVALID_ID_ENDPOINT = "/apicatalog/api/v1/containers/bad"; @@ -55,19 +62,24 @@ class ApiCatalogEndpointIntegrationTest implements TestWithStartedInstances { private static final String GET_API_CATALOG_API_DOC_ENDPOINT = "/apicatalog/api/v1/apidoc/apicatalog/zowe.apiml.apicatalog v1.0.0"; private static final String INVALID_API_CATALOG_API_DOC_ENDPOINT = "/apicatalog/api/v1/apidoc/apicatalog/zowe.apiml.apicatalog v18.0.0"; - private final static String UNAUTHORIZED_USERNAME = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-unauthorized").get(0).getUser(); - private final static String UNAUTHORIZED_PASSWORD = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-unauthorized").get(0).getPassword(); - private final static String USERNAME = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-authorized").get(0).getUser(); - private final static String PASSWORD = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-authorized").get(0).getPassword(); + private static final String UNAUTHORIZED_USERNAME = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-unauthorized").get(0).getUser(); + private static final String UNAUTHORIZED_PASSWORD = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-unauthorized").get(0).getPassword(); + private static final String USERNAME = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-authorized").get(0).getUser(); + private static final String PASSWORD = ConfigReader.environmentConfiguration().getAuxiliaryUserList().getCredentials("servicesinfo-authorized").get(0).getPassword(); + + private final List baseHosts = new ArrayList<>(); + private String validGatewayToken; + private String unauthorizedGatewayToken; - private String baseHost; + @BeforeAll + void init() { + validGatewayToken = gatewayToken(USERNAME, PASSWORD); + unauthorizedGatewayToken = gatewayToken(UNAUTHORIZED_USERNAME, UNAUTHORIZED_PASSWORD); - @BeforeEach - void setUp() { GatewayServiceConfiguration gatewayServiceConfiguration = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration(); - String host = gatewayServiceConfiguration.getHost(); int port = gatewayServiceConfiguration.getExternalPort(); - baseHost = host + ":" + port; + Stream.of(gatewayServiceConfiguration.getHost().split(",")) + .forEach(host -> baseHosts.add(host + ":" + port)); } @Nested @@ -138,7 +150,9 @@ void whenSpecificCatalogApiDoc() throws Exception { // Then assertFalse(paths.isEmpty(), apiCatalogSwagger); assertFalse(componentSchemas.isEmpty(), apiCatalogSwagger); - assertEquals("https://" + baseHost + "/apicatalog/api/v1", swaggerServer, apiCatalogSwagger); + assertThat(apiCatalogSwagger, baseHosts.stream() + .map(host -> "https://" + host + "/apicatalog/api/v1") + .toList(), hasItem(equalTo(swaggerServer))); assertNull(paths.get("/status/updates"), apiCatalogSwagger); assertNotNull(paths.get("/containers/{id}"), apiCatalogSwagger); assertNotNull(paths.get("/containers"), apiCatalogSwagger); @@ -171,7 +185,9 @@ void whenDefaultCatalogApiDoc() throws Exception { // Then assertFalse(paths.isEmpty(), apiCatalogSwagger); assertFalse(componentSchemas.isEmpty(), apiCatalogSwagger); - assertEquals("https://" + baseHost + "/apicatalog/api/v1", swaggerServer, apiCatalogSwagger); + assertThat(apiCatalogSwagger, baseHosts.stream() + .map(host -> "https://" + host + "/apicatalog/api/v1") + .toList(), hasItem(equalTo(swaggerServer))); assertNull(paths.get("/status/updates"), apiCatalogSwagger); assertNotNull(paths.get("/containers/{id}"), apiCatalogSwagger); assertNotNull(paths.get("/containers"), apiCatalogSwagger); @@ -197,35 +213,36 @@ class StaticApis { private static final String STATIC_DEFINITION_GENERATE_ENDPOINT = "/apicatalog/api/v1/static-api/generate"; private static final String STATIC_DEFINITION_DELETE_ENDPOINT = "/apicatalog/api/v1/static-api/delete"; private static final String REFRESH_STATIC_APIS_ENDPOINT = "/apicatalog/api/v1/static-api/refresh"; - private String staticDefinitionServiceId = "a" + UUID.randomUUID().toString().replace("-", "").substring(0, 10); + private final String staticDefinitionServiceId = "a" + UUID.randomUUID().toString().replace("-", "").substring(0, 10); + private final String staticDefinitionServiceIdUnauthorized = "una" + UUID.randomUUID().toString().replace("-", "").substring(0, 10); @AfterAll void cleanupStaticDefinition() { given().relaxedHTTPSValidation() .when() .header("Service-Id", staticDefinitionServiceId) - .cookie(COOKIE_NAME, gatewayToken()) + .cookie(COOKIE_NAME, validGatewayToken) .delete(getUriFromGateway(STATIC_DEFINITION_DELETE_ENDPOINT)); } @Test @Order(1) void whenCallStaticApiRefresh_thenResponseOk() throws IOException { - getStaticApiResponse(REFRESH_STATIC_APIS_ENDPOINT, null, HttpStatus.SC_OK, null, gatewayToken(USERNAME, PASSWORD)); + getStaticApiResponse(REFRESH_STATIC_APIS_ENDPOINT, null, HttpStatus.SC_OK, null, validGatewayToken); } @Test @Order(30) void whenCallStaticDefinitionGenerate_thenResponse201() throws IOException { String json = "# Dummy content"; - getStaticApiResponse(STATIC_DEFINITION_GENERATE_ENDPOINT, staticDefinitionServiceId, HttpStatus.SC_CREATED, json, gatewayToken(USERNAME, PASSWORD)); + getStaticApiResponse(STATIC_DEFINITION_GENERATE_ENDPOINT, staticDefinitionServiceId, HttpStatus.SC_CREATED, json, validGatewayToken); } @Test @Order(31) void whenCallStaticDefinitionGenerateWithUnauthorizedUser_thenResponse403() throws IOException { String json = "# Dummy content"; - getStaticApiResponse(STATIC_DEFINITION_GENERATE_ENDPOINT, staticDefinitionServiceId, HttpStatus.SC_FORBIDDEN, json, gatewayToken(UNAUTHORIZED_USERNAME, UNAUTHORIZED_PASSWORD)); + getStaticApiResponse(STATIC_DEFINITION_GENERATE_ENDPOINT, staticDefinitionServiceIdUnauthorized, HttpStatus.SC_FORBIDDEN, json, unauthorizedGatewayToken); } private Response getStaticApiResponse(String endpoint, String definitionFileName, int returnCode, String body, String JWT) throws IOException { diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogLoginIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogLoginIntegrationTest.java index abdac17569..1c84bb84e1 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogLoginIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogLoginIntegrationTest.java @@ -25,7 +25,7 @@ import static io.restassured.http.ContentType.JSON; import static org.apache.http.HttpStatus.*; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.zowe.apiml.security.SecurityUtils.readPassword; @@ -60,7 +60,7 @@ void doLoginWithValidBodyLoginRequest() { .post(LOGIN_ENDPOINT_URL) .then() .statusCode(is(SC_NO_CONTENT)) - .cookie(COOKIE_NAME, not(isEmptyString())) + .cookie(COOKIE_NAME, not(is(emptyString()))) .extract().detailedCookie(COOKIE_NAME); } diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/discovery/DiscoveryServiceAuthenticationTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/discovery/DiscoveryServiceAuthenticationTest.java index fa0dfe2534..452ae6eb95 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/discovery/DiscoveryServiceAuthenticationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/discovery/DiscoveryServiceAuthenticationTest.java @@ -18,6 +18,7 @@ import org.zowe.apiml.util.SecurityUtils; import org.zowe.apiml.util.categories.GeneralAuthenticationTest; +import org.zowe.apiml.util.categories.TestsNotMeantForZowe; import org.zowe.apiml.util.config.ConfigReader; import org.zowe.apiml.util.config.ItSslConfigFactory; import org.zowe.apiml.util.config.SslContext; @@ -45,8 +46,10 @@ static void setup() throws Exception { @Nested class GivenBearerAuthentication { + @Nested class WhenAccessingProtectedEndpoint { + @Test void thenAuthenticate() { String token = SecurityUtils.gatewayToken(USERNAME, PASSWORD); @@ -57,13 +60,17 @@ void thenAuthenticate() { .then() .statusCode(is(SC_OK)); } + } + } @Nested class GivenInvalidBearerAuthentication { + @Nested class WhenAccessingProtectedEndpoint { + @Test void thenReturnUnauthorized() { String expectedMessage = "Token is not valid for URL '" + ACTUATOR_ENDPOINT + "'"; @@ -77,10 +84,12 @@ void thenReturnUnauthorized() { "messages.find { it.messageNumber == 'ZWEAS130E' }.messageContent", equalTo(expectedMessage) ); } + } } @Test + @TestsNotMeantForZowe("Automation needs unprotected health endpoint") @DisplayName("This test needs to run against discovery service instance that has application/health endpoint authentication enabled.") void thenDoNotRequireAuthentication() { given() @@ -92,6 +101,7 @@ void thenDoNotRequireAuthentication() { } @Test + @TestsNotMeantForZowe("Automation needs unprotected health endpoint") @DisplayName("This test needs to run against discovery service instance that has application/health endpoint authentication enabled with authentication.") void thenDoNotAuthenticateTheRequest() { String token = SecurityUtils.gatewayToken(USERNAME, PASSWORD); diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayAuthenticationTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayAuthenticationTest.java index 48f9ef1f13..4a53fee75e 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayAuthenticationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayAuthenticationTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.zowe.apiml.util.SecurityUtils; import org.zowe.apiml.util.categories.GeneralAuthenticationTest; +import org.zowe.apiml.util.categories.TestsNotMeantForZowe; import org.zowe.apiml.util.config.ConfigReader; import org.zowe.apiml.util.http.HttpRequestUtils; @@ -42,8 +43,10 @@ void setUp() { @Nested class GivenBearerAuthentication { + @Nested class WhenAccessingProtectedEndpoint { + @ParameterizedTest @ValueSource(strings = {ACTUATOR_ENDPOINT, HEALTH_ENDPOINT}) void thenAuthenticate(String endpoint) { @@ -56,14 +59,19 @@ void thenAuthenticate(String endpoint) { .then() .statusCode(is(SC_OK)); } + } + } @Nested class GivenInvalidBearerAuthentication { + @Nested class WhenAccessingProtectedEndpoint { + @ParameterizedTest + @TestsNotMeantForZowe("Automation needs unprotected health endpoint") @ValueSource(strings = {ACTUATOR_ENDPOINT, HEALTH_ENDPOINT}) void thenReturnUnauthorized(String endpoint) { String expectedMessage = "The request has not been applied because it lacks valid authentication credentials."; @@ -78,6 +86,9 @@ void thenReturnUnauthorized(String endpoint) { "messages.find { it.messageNumber == 'ZWEAO402E' }.messageContent", equalTo(expectedMessage) ); } + } + } + } diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/InMemoryRateLimiterFilterFactoryIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/InMemoryRateLimiterFilterFactoryIntegrationTest.java index e252c53b21..384890a8dc 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/InMemoryRateLimiterFilterFactoryIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/InMemoryRateLimiterFilterFactoryIntegrationTest.java @@ -10,7 +10,6 @@ package org.zowe.apiml.functional.gateway; -import com.fasterxml.jackson.core.JsonProcessingException; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -24,13 +23,15 @@ import reactor.netty.http.client.HttpClient; import javax.net.ssl.SSLException; +import java.time.Duration; +import java.util.stream.IntStream; @RateLimitTest public class InMemoryRateLimiterFilterFactoryIntegrationTest { private static WebTestClient client; - final int bucketCapacity = 20; + final int bucketCapacity = 60; @BeforeAll static void setUpTester() { @@ -49,6 +50,7 @@ static void setUpTester() { client = WebTestClient.bindToServer().clientConnector(new ReactorClientHttpConnector(httpClient)) + .responseTimeout(Duration.ofSeconds(30L)) .baseUrl(baseUrl) .build(); } @@ -62,36 +64,32 @@ void testRateLimitingWhenAllowedWithCookie() { } @Test - void testRateLimitingWhenExceeded() throws JsonProcessingException { - for (int i = 0; i < bucketCapacity; i++) { - client.get() - .cookie("apimlAuthenticationToken", "validTokenValue") - .exchange(); - } + void testRateLimitingWhenExceeded() { + IntStream.range(0, bucketCapacity).parallel().forEach(i -> client.get() + .cookie("apimlAuthenticationToken", "validTokenValue") + .exchange()); client.get() .cookie("apimlAuthenticationToken", "validTokenValue") .exchange() .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) - .expectBody(String.class) - .value(body -> body.contains("Connections limit exceeded for service")); + .expectBody() + .jsonPath("$.messages[0].messageReason").isEqualTo("Connections limit exceeded.");; } @Test void testRateLimiterAllowsAccessToAnotherUser() { // the first user requires access - for (int i = 0; i < bucketCapacity; i++) { - client.get() - .cookie("apimlAuthenticationToken", "theFirstUser") - .exchange(); - } + IntStream.range(0, bucketCapacity).parallel().forEach(i -> client.get() + .cookie("apimlAuthenticationToken", "theFirstUser") + .exchange()); //access should be denied client.get() .cookie("apimlAuthenticationToken", "theFirstUser") .exchange() .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) - .expectBody(String.class) - .value(body -> body.contains("Connections limit exceeded for service")); + .expectBody() + .jsonPath("$.messages[0].messageReason").isEqualTo("Connections limit exceeded."); // the second user requires access client.get() diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java index f91606383d..725868aabb 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java @@ -14,6 +14,7 @@ import io.restassured.http.ContentType; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.zowe.apiml.util.SecurityUtils; @@ -28,6 +29,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.IntStream; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.containsString; @@ -63,31 +65,55 @@ void setup() throws Exception { @Test void givenValidToken_invalidateTheToken() { - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .delete(REVOKE_ENDPOINT) - .then().statusCode(204); - given().contentType(ContentType.JSON).body(bodyContent).when() - .post(VALIDATE_ENDPOINT) - .then().statusCode(401); + .then() + .statusCode(204); + IntStream.range(0, 3).forEach(x -> { + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() + .post(VALIDATE_ENDPOINT) + .then() + .statusCode(401); + }); } @Test void givenTokenInvalidated_returnUnauthorized() { - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .delete(REVOKE_ENDPOINT) - .then().statusCode(204); - given().contentType(ContentType.JSON).body(bodyContent).when() - .delete(REVOKE_ENDPOINT) - .then().statusCode(401); + .then() + .statusCode(204); + IntStream.range(0, 3).forEach(x -> { + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() + .delete(REVOKE_ENDPOINT) + .then() + .statusCode(401); + }); } @Test void givenMatchingScopes_validateTheToken() throws Exception { SslContext.prepareSslAuthentication(ItSslConfigFactory.integrationTests()); RestAssured.useRelaxedHTTPSValidation(); - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(204); + .then() + .statusCode(204); } @Test @@ -95,9 +121,13 @@ void givenInvalidScopes_returnUnauthorized() throws Exception { SslContext.prepareSslAuthentication(ItSslConfigFactory.integrationTests()); RestAssured.useRelaxedHTTPSValidation(); bodyContent.setServiceId("differentService"); - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(401); + .then() + .statusCode(401); } } @@ -120,19 +150,34 @@ void givenAuthorizedRequest_thenRevokeTokenForUser() { bodyContent.setServiceId("service"); bodyContent.setToken(pat); // validate before revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(204); + .then() + .statusCode(204); // revoke all tokens for USERNAME Map requestBody = new HashMap<>(); requestBody.put("userId", SecurityUtils.USERNAME); - given().contentType(ContentType.JSON).config(SslContext.clientCertUser).body(requestBody) - .when().delete(REVOKE_FOR_USER_ENDPOINT) - .then().statusCode(204); + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertUser) + .body(requestBody) + .when() + .delete(REVOKE_FOR_USER_ENDPOINT) + .then() + .statusCode(204); // validate after revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() - .post(VALIDATE_ENDPOINT) - .then().statusCode(401); + IntStream.range(0, 3).forEach(x -> { + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() + .post(VALIDATE_ENDPOINT) + .then() + .statusCode(401); + }); } @Test @@ -142,17 +187,29 @@ void givenAuthenticatedCall_thenRevokeUserToken() { bodyContent.setServiceId("service"); bodyContent.setToken(pat); // validate before revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(204); + .then() + .statusCode(204); // revoke all tokens for USERNAME - given().contentType(ContentType.JSON).config(SslContext.clientCertValid) - .when().delete(REVOKE_OWN_TOKENS_ENDPOINT) - .then().statusCode(204); + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertValid) + .when() + .delete(REVOKE_OWN_TOKENS_ENDPOINT) + .then() + .statusCode(204); // validate after revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(401); + .then() + .statusCode(401); } @Test @@ -165,19 +222,34 @@ void givenAuthorizedRequest_thenRevokeTokensForScope() { bodyContent.setServiceId("gateway"); bodyContent.setToken(pat); // validate before revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(204); + .then() + .statusCode(204); // revoke all tokens for USERNAME Map requestBody = new HashMap<>(); requestBody.put("serviceId", "api-catalog"); - given().contentType(ContentType.JSON).config(SslContext.clientCertUser).body(requestBody) - .when().delete(REVOKE_FOR_SCOPE_ENDPOINT) - .then().statusCode(204); + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertUser) + .body(requestBody) + .when() + .delete(REVOKE_FOR_SCOPE_ENDPOINT) + .then() + .statusCode(204); // validate after revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() - .post(VALIDATE_ENDPOINT) - .then().statusCode(401); + IntStream.range(0, 3).forEach(x -> { + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() + .post(VALIDATE_ENDPOINT) + .then() + .statusCode(401); + }); } @Test @@ -186,45 +258,71 @@ void givenAuthorizedRequest_thenEvictRules() { Map requestBody = new HashMap<>(); requestBody.put("userId", SecurityUtils.USERNAME); requestBody.put("timestamp", "1582239600000"); - given().contentType(ContentType.JSON).config(SslContext.clientCertUser).body(requestBody) - .when().delete(REVOKE_FOR_USER_ENDPOINT) - .then().statusCode(204); + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertUser) + .body(requestBody) + .when() + .delete(REVOKE_FOR_USER_ENDPOINT) + .then() + .statusCode(204); // evict the rule - given().contentType(ContentType.JSON).config(SslContext.clientCertUser) - .when() + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertUser) + .when() .delete(EVICT_ENDPOINT) - .then().statusCode(204); + .then() + .statusCode(204); // return all the items from the cache - given().contentType(ContentType.JSON).config(SslContext.clientCertUser) - .when() + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertUser) + .when() .get(CACHE_LIST_ENDPOINT) - .then() + .then() .statusCode(200) - .body("content", not(containsString("1582239600000"))).extract().asString(); + .body("content", not(containsString("1582239600000"))) + .extract() + .asString(); } @Test + @Disabled("Disable for now; fix is in progress") void givenNotAuthorizedCall_thenDontAllowToRevokeTokensForUser() { String pat = SecurityUtils.personalAccessTokenWithClientCert(SslContext.clientCertValid); bodyContent = new ValidateRequestModel(); bodyContent.setServiceId("service"); bodyContent.setToken(pat); // validate before revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(204); -// revoke all tokens for USERNAME - Map requestBody = new HashMap<>(); - requestBody.put("userId", SecurityUtils.USERNAME); - given().contentType(ContentType.JSON).config(SslContext.clientCertApiml).body(requestBody) - .when().delete(REVOKE_FOR_USER_ENDPOINT) - .then().statusCode(403); + .then() + .statusCode(204); // validate after revocation rule - given().contentType(ContentType.JSON).body(bodyContent).when() + given() + .contentType(ContentType.JSON) + .body(bodyContent) + .when() .post(VALIDATE_ENDPOINT) - .then().statusCode(204); + .then() + .statusCode(204); + // revoke all tokens for USERNAME + Map requestBody = new HashMap<>(); + requestBody.put("userId", SecurityUtils.USERNAME); + given() + .contentType(ContentType.JSON) + .config(SslContext.clientCertApiml) + .body(requestBody) + .when() + .delete(REVOKE_FOR_USER_ENDPOINT) + .then() + .statusCode(403); } - } + } } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/PATWithAllSchemesTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/PATWithAllSchemesTest.java index 62d7b7e4df..2840f84a0e 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/PATWithAllSchemesTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/PATWithAllSchemesTest.java @@ -22,12 +22,15 @@ import org.junit.jupiter.params.provider.MethodSource; import org.zowe.apiml.constants.ApimlConstants; import org.zowe.apiml.util.categories.InfinispanStorageTest; +import org.zowe.apiml.util.config.ConfigReader; import org.zowe.apiml.util.config.ItSslConfigFactory; +import org.zowe.apiml.util.config.SafIdtConfiguration; import org.zowe.apiml.util.config.SslContext; import org.zowe.apiml.util.http.HttpRequestUtils; import java.net.URI; import java.text.ParseException; +import java.util.ArrayList; import java.util.Collections; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -44,6 +47,8 @@ class PATWithAllSchemesTest { + private static SafIdtConfiguration safIdtConfig = ConfigReader.environmentConfiguration().getSafIdtConfiguration(); + static Stream authentication() { return Stream.of( Arguments.of("PAT header", (BiFunction) (rs, token) -> rs.header(ApimlConstants.PAT_HEADER_NAME, token)), @@ -54,29 +59,31 @@ static Stream authentication() { } static Stream schemas() { - return Stream.of( - Arguments.of("zowejwt", HttpRequestUtils.getUriFromGateway(ZOWE_JWT_REQUEST), (Consumer) r -> { - assertEquals(HttpStatus.SC_OK, r.getStatusCode()); - assertNull(r.getBody().path("headers.authorization")); - assertThat(r.getBody().path("headers.cookie"), containsString(COOKIE_NAME)); - String jwt = r.getBody().path("headers.cookie").toString(); - try { - String issuer = JWTParser.parse(jwt.substring(COOKIE_NAME.length()).trim()).getJWTClaimsSet().toJSONObject().get("iss").toString(); - assertEquals("zOSMF", issuer); - } catch (ParseException e) { - fail(e); - } - }), - Arguments.of("dcpassticket", HttpRequestUtils.getUriFromGateway(REQUEST_INFO_ENDPOINT), (Consumer) r -> { - assertEquals(HttpStatus.SC_OK, r.getStatusCode()); - assertThat(r.getBody().path("headers.authorization"), startsWith("Basic ")); - assertThat(r.getBody().path("cookies"), not(hasKey(COOKIE_NAME))); - }), + var schemasTest = new ArrayList(); + schemasTest.add(Arguments.of("zowejwt", HttpRequestUtils.getUriFromGateway(ZOWE_JWT_REQUEST), (Consumer) r -> { + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + assertNull(r.getBody().path("headers.authorization")); + assertThat(r.getBody().path("headers.cookie"), containsString(COOKIE_NAME)); + String jwt = r.getBody().path("headers.cookie").toString(); + try { + String issuer = JWTParser.parse(jwt.substring(COOKIE_NAME.length()).trim()).getJWTClaimsSet().toJSONObject().get("iss").toString(); + assertEquals("zOSMF", issuer); + } catch (ParseException e) { + fail(e); + } + })); + schemasTest.add(Arguments.of("dcpassticket", HttpRequestUtils.getUriFromGateway(REQUEST_INFO_ENDPOINT), (Consumer) r -> { + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + assertThat(r.getBody().path("headers.authorization"), startsWith("Basic ")); + assertThat(r.getBody().path("cookies"), not(hasKey(COOKIE_NAME))); + })); + if (safIdtConfig.isEnabled()) { Arguments.of("dcsafidt", HttpRequestUtils.getUriFromGateway(SAF_IDT_REQUEST), (Consumer) r -> { assertEquals(HttpStatus.SC_OK, r.getStatusCode()); assertThat(r.getBody().path("headers"), hasKey("x-saf-token")); - }) - ); + }); + } + return schemasTest.stream(); } static Stream authSchemas() { @@ -98,10 +105,13 @@ void requestWithPAT( String name, URI urlSpecification, Consumer validation) { String pat = personalAccessToken(Collections.singleton(name)); - validation.accept(authenticationAction.apply(given(), pat) + Response response = authenticationAction.apply(given(), pat) .config(SslContext.tlsWithoutCert) .when() - .get(urlSpecification) - ); + .get(urlSpecification); + + assertEquals(HttpStatus.SC_OK, response.getStatusCode(), "Expected HTTP 200 OK response"); + + validation.accept(response); } } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java index 397f61cbc3..e9b485c8d1 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java @@ -48,7 +48,7 @@ import static org.apache.http.HttpStatus.*; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.zowe.apiml.util.SecurityUtils.*; @@ -110,7 +110,7 @@ void givenValidCredentialsInBody(URI loginUrl) { .statusCode(is(SC_NO_CONTENT)) // RestAssured version in use doesn't have SameSite attribute in cookie so validate using the Set-Cookie header .header("Set-Cookie", containsString("SameSite=Strict")) - .cookie(COOKIE_NAME, not(isEmptyString())) + .cookie(COOKIE_NAME, not(is(emptyString()))) .extract().detailedCookie(COOKIE_NAME); assertValidAuthToken(cookie); @@ -128,7 +128,7 @@ void givenValidCredentialsInHeader(URI loginUrl) { .statusCode(is(SC_NO_CONTENT)) // RestAssured version in use doesn't have SameSite attribute in cookie so validate using the Set-Cookie header .header("Set-Cookie", containsString("SameSite=Strict")) - .cookie(COOKIE_NAME, not(isEmptyString())) + .cookie(COOKIE_NAME, not(is(emptyString()))) .extract().cookie(COOKIE_NAME); int i = token.lastIndexOf('.'); diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LogoutTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LogoutTest.java index c1c4144e81..20b36e2232 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LogoutTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LogoutTest.java @@ -45,8 +45,10 @@ void setUp() { @Nested class WhenUserLogOut { + @Nested class InvalidateTheToken { + @ParameterizedTest(name = "givenValidCredentials {index} {0} ") @MethodSource("org.zowe.apiml.integration.authentication.providers.LogoutTest#logoutUrlsSource") void givenValidCredentials(String logoutUrl) { @@ -61,6 +63,9 @@ void givenValidCredentials(String logoutUrl) { // check if it is logged out assertIfLogged(jwt, false); } + } + } + } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/QueryTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/QueryTest.java index 047e3d78b8..f39234a7db 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/QueryTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/QueryTest.java @@ -12,8 +12,10 @@ import io.restassured.RestAssured; import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.zowe.apiml.util.SecurityUtils; @@ -36,31 +38,31 @@ @GeneralAuthenticationTest @SAFAuthTest @zOSMFAuthTest +@TestInstance(Lifecycle.PER_CLASS) class QueryTest implements TestWithStartedInstances { - private final static String SCHEME = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getScheme(); - private final static String HOST = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getHost(); - private final static int PORT = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getPort(); - private final static String BASE_PATH = "/gateway/api/v1"; - private final static String QUERY_ENDPOINT = "/auth/query"; - private final static String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword(); - private final static String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); - private final static String COOKIE = "apimlAuthenticationToken"; + private static final String SCHEME = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getScheme(); + private static final String HOST = StringUtils.isBlank(ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getDvipaHost()) ? ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getHost() : ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getDvipaHost(); + private static final int PORT = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().getPort(); + private static final String BASE_PATH = "/gateway/api/v1"; + private static final String QUERY_ENDPOINT = "/auth/query"; + private static final String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword(); + private static final String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); + private static final String COOKIE = "apimlAuthenticationToken"; public static final String QUERY_ENDPOINT_URL = String.format("%s://%s:%d%s%s", SCHEME, HOST, PORT, BASE_PATH, QUERY_ENDPOINT); - private String token; + private String validToken; static String[] queryUrlsSource() { return new String[]{QUERY_ENDPOINT_URL}; } - @BeforeEach - void setUp() { + @BeforeAll + void init() { + this.validToken = SecurityUtils.gatewayToken(USERNAME, PASSWORD); RestAssured.port = PORT; RestAssured.basePath = BASE_PATH; RestAssured.useRelaxedHTTPSValidation(); - - token = SecurityUtils.gatewayToken(USERNAME, PASSWORD); } @Nested @@ -72,7 +74,7 @@ class ReturnInfo { @MethodSource("org.zowe.apiml.integration.authentication.providers.QueryTest#queryUrlsSource") void givenValidTokenInHeader(String queryUrl) { given() - .header("Authorization", "Bearer " + token) + .header("Authorization", "Bearer " + validToken) .when() .get(queryUrl) .then() @@ -84,7 +86,7 @@ void givenValidTokenInHeader(String queryUrl) { @MethodSource("org.zowe.apiml.integration.authentication.providers.QueryTest#queryUrlsSource") void givenValidTokenInCookie(String queryUrl) { given() - .cookie(COOKIE, token) + .cookie(COOKIE, validToken) .when() .get(queryUrl) .then() @@ -156,7 +158,7 @@ void givenValidTokenInWrongCookie(String queryUrl) { String expectedMessage = "No authorization token provided for URL '" + queryPath + "'"; given() - .cookie(invalidCookie, token) + .cookie(invalidCookie, validToken) .when() .get(queryUrl) .then() @@ -179,7 +181,7 @@ void givenValidToken(String queryUrl) { String expectedMessage = "Authentication method 'POST' is not supported for URL '" + queryPath + "'"; given() - .header("Authorization", "Bearer " + token) + .header("Authorization", "Bearer " + validToken) .when() .post(queryUrl) .then() diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/ZosmfLoginTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/ZosmfLoginTest.java index e8528b38ab..b178b170ea 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/ZosmfLoginTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/ZosmfLoginTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.util.StringUtils; import org.zowe.apiml.util.TestWithStartedInstances; import org.zowe.apiml.util.categories.zOSMFAuthTest; import org.zowe.apiml.util.config.ConfigReader; @@ -43,9 +44,10 @@ class ZosmfLoginTest implements TestWithStartedInstances { private final static boolean ZOS_TARGET = Boolean.parseBoolean(System.getProperty("environment.zos.target", "false")); private final static String ZOSMF_SERVICE_ID = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getServiceId(); + private static final String ZOSMF_CONTEXT_ROOT = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getContextRoot(); + private final static String ZOSMF_ENDPOINT_GW = "/" + ZOSMF_SERVICE_ID + "/api/v1/" + (StringUtils.hasText(ZOSMF_CONTEXT_ROOT) ? ZOSMF_CONTEXT_ROOT + "/" : "") + "restfiles/ds"; private final static String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getClientUser(); private final static String ZOSMF_ENDPOINT_MOCK = "/" + ZOSMF_SERVICE_ID + "/api/zosmf/restfiles/ds"; - private final static String ZOSMF_ENDPOINT_GW = "/" + ZOSMF_SERVICE_ID + "/api/v1/restfiles/ds"; private final static String ZOSMF_ENDPOINT = ZOS_TARGET ? ZOSMF_ENDPOINT_GW : ZOSMF_ENDPOINT_MOCK; @BeforeAll @@ -66,7 +68,7 @@ void givenValidCertificate_thenReturnExistingDatasets() { URI uri = HttpRequestUtils.getUriFromGateway(ZOSMF_ENDPOINT, new BasicNameValuePair("dslevel", "sys1.p*")); given() - .config(SslContext.clientCertValid) + .config(SslContext.clientCertUser) .header("X-CSRF-ZOSMF-HEADER", "") .when() .get(uri) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/GatewayAuthTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/GatewayAuthTest.java index ca48cfc29f..ad159eafbd 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/GatewayAuthTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/GatewayAuthTest.java @@ -48,18 +48,18 @@ public class GatewayAuthTest implements TestWithStartedInstances { static Stream validToBeTransformed() { List arguments = new ArrayList<>(Arrays.asList( Arguments.of("Zowe auth scheme", ZOWE_JWT_REQUEST, (Consumer) response -> { - assertNotNull(response.jsonPath().getString("cookies.apimlAuthenticationToken")); - assertNull(response.jsonPath().getString("headers.authorization")); - assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs"))); + assertNotNull(response.jsonPath().getString("cookies.apimlAuthenticationToken"), "Expected not null apimlAuthenticationToken. Response was: " + response.asPrettyString()); + assertNull(response.jsonPath().getString("headers.authorization"), "Expected null Authorization header. Response was: " + response.asPrettyString()); + assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs")), "Expected empty certs list. Response was: " + response.asPrettyString()); }), Arguments.of("z/OSMF auth scheme", ZOSMF_REQUEST, (Consumer) response -> { - assertNotNull(response.jsonPath().getString("cookies.jwtToken")); - assertNull(response.jsonPath().getString("headers.authorization")); - assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs"))); + assertNotNull(response.jsonPath().getString("cookies.jwtToken"), "Expected not null jwtToken cookie. Response was: " + response.asPrettyString()); + assertNull(response.jsonPath().getString("headers.authorization"), "Expected null Authorization header. Response was: " + response.asPrettyString()); + assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs")), "Expected empty certs list. Response was: " + response.asPrettyString()); }), Arguments.of("PassTicket auth scheme", REQUEST_INFO_ENDPOINT, (Consumer) response -> { - assertNotNull(response.jsonPath().getString("headers.authorization")); - assertTrue(response.jsonPath().getString("headers.authorization").startsWith("Basic ")); + assertNotNull(response.jsonPath().getString("headers.authorization"), "Expected not null Authorization header. Response was: " + response.asPrettyString()); + assertTrue(response.jsonPath().getString("headers.authorization").startsWith("Basic "), "Expected basic Authorization present. Response was: " + response.asPrettyString()); assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs"))); }) )); @@ -81,10 +81,12 @@ static Stream noAuthTransformation() { "cookies.jwtToken", "headers.authorization" }) { - if (path.equals(ignore)) continue; - assertNull(response.jsonPath().getString(path)); + if (path.equals(ignore)) { + continue; + } + assertNull(response.jsonPath().getString(path), "Expected " + path + " to be Null. Response is: " + response.asPrettyString()); } - assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs"))); + assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs")), "Expected empty certs. Response is: " + response.asPrettyString()); }; List arguments = new ArrayList<>(Arrays.asList( @@ -196,7 +198,7 @@ void givenInvalidPatRequest_thenPatIsNotTransformed(String title, String basePat .header(HttpHeaders.AUTHORIZATION, "Bearer " + pat) .when() .get(HttpRequestUtils.getUri(GATEWAY_CONF, basePath)); - assertEquals(200, response.getStatusCode()); + assertEquals(200, response.getStatusCode(), "Expected 200 while using token " + pat); assertions.accept("headers.authorization", response); assertEquals("Bearer " + pat, response.jsonPath().getString("headers.authorization")); } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/services/ServiceProtectedEndpointIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/services/ServiceProtectedEndpointIntegrationTest.java index 7f8ad7b497..1efaa7cf8b 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/services/ServiceProtectedEndpointIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/services/ServiceProtectedEndpointIntegrationTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.util.StringUtils; import org.zowe.apiml.util.SecurityUtils; import org.zowe.apiml.util.TestWithStartedInstances; import org.zowe.apiml.util.categories.zOSMFAuthTest; @@ -39,15 +40,16 @@ @zOSMFAuthTest class ServiceProtectedEndpointIntegrationTest implements TestWithStartedInstances { - private final static boolean ZOS_TARGET = Boolean.parseBoolean(System.getProperty("environment.zos.target", "false")); - private final static String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword(); - private final static String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); + private static final boolean ZOS_TARGET = Boolean.parseBoolean(System.getProperty("environment.zos.target", "false")); + private static final String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword(); + private static final String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); - private final static String ZOSMF_SERVICE_ID = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getServiceId(); + private static final String ZOSMF_SERVICE_ID = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getServiceId(); + private static final String ZOSMF_CONTEXT_ROOT = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getContextRoot(); - private final static String ZOSMF_ENDPOINT_MOCK = "/" + ZOSMF_SERVICE_ID + "/api/zosmf/restfiles/ds"; - private final static String ZOSMF_ENDPOINT_GW = "/" + ZOSMF_SERVICE_ID + "/api/v1/restfiles/ds"; - private final static String ZOSMF_ENDPOINT = ZOS_TARGET ? ZOSMF_ENDPOINT_GW : ZOSMF_ENDPOINT_MOCK; + private static final String ZOSMF_ENDPOINT_MOCK = "/" + ZOSMF_SERVICE_ID + "/api/zosmf/restfiles/ds"; + private static final String ZOSMF_ENDPOINT_GW = "/" + ZOSMF_SERVICE_ID + "/api/v1/" + (StringUtils.hasText(ZOSMF_CONTEXT_ROOT) ? ZOSMF_CONTEXT_ROOT + "/" : "") + "restfiles/ds"; + private static final String ZOSMF_ENDPOINT = ZOS_TARGET ? ZOSMF_ENDPOINT_GW : ZOSMF_ENDPOINT_MOCK; private static final NameValuePair ARGUMENT = new BasicNameValuePair("dslevel", "sys1.p*"); diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java index 1bc9aeb851..cf17ada819 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java @@ -31,8 +31,8 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.isEmptyString; import static org.junit.jupiter.api.Assertions.*; import static org.zowe.apiml.util.requests.Endpoints.*; @@ -132,7 +132,7 @@ void givenApis_whenGetContainer_thenApisReturned() throws IOException { LinkedHashMap codeSnippet = (LinkedHashMap) codeSnippets.get(0); assertEquals("/greeting", codeSnippet.get("endpoint")); assertNotNull(codeSnippet.get("codeBlock")); - assertThat(codeSnippet.get("codeBlock"), not(isEmptyString())); + assertThat(codeSnippet.get("codeBlock"), not(is(emptyString()))); assertEquals("java", codeSnippet.get("language")); } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/external/CachingStorageTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/external/CachingStorageTest.java index 63c3db7a22..8e1141479e 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/external/CachingStorageTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/external/CachingStorageTest.java @@ -33,8 +33,8 @@ import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; import static org.apache.http.HttpStatus.*; -import static org.hamcrest.Matchers.isEmptyOrNullString; -import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.zowe.apiml.util.requests.Endpoints.CACHING_CACHE; @@ -79,9 +79,9 @@ void givenMultipleConcurrentCalls_correctResponseInTheEnd() throws InterruptedEx given().config(SslContext.clientCertValid) .contentType(JSON) .when() - .get(CACHING_PATH).then().body("20", is(not(isEmptyString()))) - .body("21", is(not(isEmptyString()))) - .body("22", is(not(isEmptyString()))) + .get(CACHING_PATH).then().body("20", is(not(is(emptyString())))) + .body("21", is(not(is(emptyString())))) + .body("22", is(not(is(emptyString())))) .statusCode(200); ExecutorService deleteService = Executors.newFixedThreadPool(8); @@ -100,9 +100,9 @@ void givenMultipleConcurrentCalls_correctResponseInTheEnd() throws InterruptedEx given().config(SslContext.clientCertValid) .contentType(JSON) .when() - .get(CACHING_PATH).then().body("20", isEmptyOrNullString()) - .body("21", isEmptyOrNullString()) - .body("22", isEmptyOrNullString()) + .get(CACHING_PATH).then().body("20", is(emptyOrNullString())) + .body("21", is(emptyOrNullString())) + .body("22", is(emptyOrNullString())) .statusCode(200); } @@ -128,9 +128,9 @@ void givenMultipleUpdates_correctResultReturned() throws InterruptedException { .get(CACHING_INVALIDATE_TOKEN_PATH + "/invalidTokens") .then() .statusCode(200) - .body("testTokens0", is(not(isEmptyOrNullString()))) - .body("testTokens1", is(not(isEmptyOrNullString()))) - .body("testTokens2", is(not(isEmptyOrNullString()))); + .body("testTokens0", is(not(is(emptyOrNullString())))) + .body("testTokens1", is(not(is(emptyOrNullString())))) + .body("testTokens2", is(not(is(emptyOrNullString())))); } @Test @@ -188,14 +188,14 @@ void givenTokensAndRules_correctResultReturned() throws InterruptedException { .get(CACHING_INVALIDATE_TOKEN_PATH) .then() .statusCode(200) - .body("invalidTokens", is(not(isEmptyOrNullString()))) - .body("invalidTokenRules", is(not(isEmptyOrNullString()))) - .body("invalidTokens.hashed_token0", is(not(isEmptyOrNullString()))) - .body("invalidTokens.hashed_token1", is(not(isEmptyOrNullString()))) - .body("invalidTokens.hashed_token2", is(not(isEmptyOrNullString()))) - .body("invalidTokenRules.hashed_rule0", is(not(isEmptyOrNullString()))) - .body("invalidTokenRules.hashed_rule1", is(not(isEmptyOrNullString()))) - .body("invalidTokenRules.hashed_rule2", is(not(isEmptyOrNullString()))); + .body("invalidTokens", is(not(is(emptyOrNullString())))) + .body("invalidTokenRules", is(not(is(emptyOrNullString())))) + .body("invalidTokens.hashed_token0", is(not(is(emptyOrNullString())))) + .body("invalidTokens.hashed_token1", is(not(is(emptyOrNullString())))) + .body("invalidTokens.hashed_token2", is(not(is(emptyOrNullString())))) + .body("invalidTokenRules.hashed_rule0", is(not(is(emptyOrNullString())))) + .body("invalidTokenRules.hashed_rule1", is(not(is(emptyOrNullString())))) + .body("invalidTokenRules.hashed_rule2", is(not(is(emptyOrNullString())))); } @Nested @@ -250,7 +250,7 @@ void givenValidKeyParameter() { .when() .get(CACHING_PATH + "/testKey") .then() - .body(not(isEmptyString())) + .body(not(is(emptyString()))) .statusCode(is(SC_OK)); } finally { deleteValueUnderServiceIdWithoutValidation("testKey", SslContext.clientCertValid); @@ -267,7 +267,7 @@ void givenNonExistingKeyParameter() { .when() .get(CACHING_PATH + "/invalidKey") .then() - .body(not(isEmptyString())) + .body(not(is(emptyString()))) .statusCode(is(SC_NOT_FOUND)); } } @@ -306,10 +306,10 @@ void givenValidKeyAndCertificate() { .when() .get(CACHING_PATH) .then().log().all() - .body("testKey1", is(not(isEmptyString())), - "testKey2", is(not(isEmptyString())), - "testKey3", isEmptyOrNullString(), - "testKey4", isEmptyOrNullString()) + .body("testKey1", is(not(is(emptyString()))), + "testKey2", is(not(is(emptyString()))), + "testKey3", is(emptyOrNullString()), + "testKey4", is(emptyOrNullString())) .statusCode(is(SC_OK)); given().config(user2) @@ -318,10 +318,10 @@ void givenValidKeyAndCertificate() { .when() .get(CACHING_PATH) .then().log().all() - .body("testKey3", is(not(isEmptyString())), - "testKey4", is(not(isEmptyString())), - "testKey1", isEmptyOrNullString(), - "testKey2", isEmptyOrNullString()) + .body("testKey3", is(not(is(emptyString()))), + "testKey4", is(not(is(emptyString()))), + "testKey1", is(emptyOrNullString()), + "testKey2", is(emptyOrNullString())) .statusCode(is(SC_OK)); } finally { deleteValueUnderServiceIdWithoutValidation("testKey1", user1); @@ -358,10 +358,10 @@ void givenValidKeyCertificateAndServiceHeader() { .when() .get(CACHING_PATH) .then().log().all() - .body("testKey1", is(not(isEmptyString())), - "testKey2", isEmptyOrNullString(), - "testKey3", isEmptyOrNullString(), - "testKey4", isEmptyOrNullString()) + .body("testKey1", is(not(is(emptyString()))), + "testKey2", is(emptyOrNullString()), + "testKey3", is(emptyOrNullString()), + "testKey4", is(emptyOrNullString())) .statusCode(is(SC_OK)); given().config(user1) @@ -371,10 +371,10 @@ void givenValidKeyCertificateAndServiceHeader() { .when() .get(CACHING_PATH) .then().log().all() - .body("testKey2", is(not(isEmptyString())), - "testKey1", isEmptyOrNullString(), - "testKey3", isEmptyOrNullString(), - "testKey4", isEmptyOrNullString()) + .body("testKey2", is(not(is(emptyString()))), + "testKey1", is(emptyOrNullString()), + "testKey3", is(emptyOrNullString()), + "testKey4", is(emptyOrNullString())) .statusCode(is(SC_OK)); given().config(user2) @@ -384,10 +384,10 @@ void givenValidKeyCertificateAndServiceHeader() { .when() .get(CACHING_PATH) .then().log().all() - .body("testKey3", is(not(isEmptyString())), - "testKey4", is(not(isEmptyString())), - "testKey1", isEmptyOrNullString(), - "testKey2", isEmptyOrNullString()) + .body("testKey3", is(not(is(emptyString()))), + "testKey4", is(not(is(emptyString()))), + "testKey1", is(emptyOrNullString()), + "testKey2", is(emptyOrNullString())) .statusCode(is(SC_OK)); given().config(user2) @@ -397,10 +397,10 @@ void givenValidKeyCertificateAndServiceHeader() { .when() .get(CACHING_PATH) .then().log().all() - .body("testKey4", is(not(isEmptyString())), - "testKey3", isEmptyOrNullString(), - "testKey1", isEmptyOrNullString(), - "testKey2", isEmptyOrNullString()) + .body("testKey4", is(not(is(emptyString()))), + "testKey3", is(emptyOrNullString()), + "testKey1", is(emptyOrNullString()), + "testKey2", is(emptyOrNullString())) .statusCode(is(SC_OK)); } finally { deleteValueUnderServiceIdWithoutValidation("testKey1", user1, serviceSpecificId1); @@ -441,7 +441,7 @@ void givenValidServiceParameter() { .when() .get(CACHING_PATH + "/testKey1") .then() - .body(not(isEmptyString())) + .body(not(is(emptyString()))) .statusCode(is(SC_NOT_FOUND)); } } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/graphql/BookControllerTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/graphql/BookControllerTest.java index 052401db7c..6f75d20829 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/graphql/BookControllerTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/graphql/BookControllerTest.java @@ -21,17 +21,20 @@ import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.test.web.reactive.server.WebTestClient; +import org.zowe.apiml.util.categories.GraphQLTest; import org.zowe.apiml.util.http.HttpRequestUtils; import reactor.netty.http.client.HttpClient; import javax.net.ssl.SSLException; + +import java.time.Duration; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import static org.junit.jupiter.api.Assertions.*; -@org.zowe.apiml.util.categories.BookControllerTest +@GraphQLTest public class BookControllerTest { static HttpGraphQlTester tester; @@ -60,6 +63,7 @@ static void setUpTester() { WebTestClient client = WebTestClient.bindToServer().clientConnector(new ReactorClientHttpConnector(httpClient)) + .responseTimeout(Duration.ofSeconds(30)) .baseUrl(baseUrl) .build(); tester = HttpGraphQlTester.create(client); diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/ha/ApiCatalogMultipleInstancesTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/ha/ApiCatalogMultipleInstancesTest.java index b434245b6a..9794dfa880 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/ha/ApiCatalogMultipleInstancesTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/ha/ApiCatalogMultipleInstancesTest.java @@ -31,6 +31,7 @@ public class ApiCatalogMultipleInstancesTest { private final HAApiCatalogRequests haApiCatalogRequests = new HAApiCatalogRequests(); private final HADiscoveryRequests haDiscoveryRequests = new HADiscoveryRequests(); + @BeforeEach void setUp() { RestAssured.config = RestAssured.config().sslConfig(getConfiguredSslConfig()); diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/ha/DeterministicLoadBalancingTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/ha/DeterministicLoadBalancingTest.java index ad1aed7476..044cbb9e7a 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/ha/DeterministicLoadBalancingTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/ha/DeterministicLoadBalancingTest.java @@ -20,6 +20,7 @@ import org.zowe.apiml.util.requests.ha.HAGatewayRequests; import java.net.URISyntaxException; +import java.util.Optional; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.notNullValue; @@ -52,7 +53,7 @@ class GivenDeterministicRoutingViaHeader { void whenRoutedInstanceExists_thenReturn200() throws URISyntaxException { assumeTrue(haGatewayRequests.existing() > 1); assertThat(haDiscoveryRequests.getAmountOfRegisteredInstancesForService(0, Apps.DISCOVERABLE_CLIENT), is(2)); - var expectedInstance = "discoverable-client:discoverableclient:10012"; + var expectedInstance = Optional.ofNullable(System.getenv("DETERMINISTIC_ROUTING_SERVICE_ID")).orElse("discoverable-client:discoverableclient:10012"); String jwt = gatewayToken(); diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/penetration/JwtPenTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/penetration/JwtPenTest.java index f98851d549..346e395e30 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/penetration/JwtPenTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/penetration/JwtPenTest.java @@ -14,6 +14,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import io.restassured.RestAssured; +import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.Nested; @@ -71,7 +72,7 @@ public class JwtPenTest implements TestWithStartedInstances { private static final EnvironmentConfiguration ENV = ConfigReader.environmentConfiguration(); private static final String SCHEME = ENV.getGatewayServiceConfiguration().getScheme(); - private static final String HOST = ENV.getGatewayServiceConfiguration().getHost(); + private static final String HOST = StringUtils.isNotBlank(ENV.getGatewayServiceConfiguration().getDvipaHost()) ? ENV.getGatewayServiceConfiguration().getDvipaHost() : ENV.getGatewayServiceConfiguration().getHost(); private static final int PORT = ENV.getGatewayServiceConfiguration().getPort(); private static final String APPLICATION_NAME = ENV.getDiscoverableClientConfiguration().getApplId(); private static final String USERNAME = ENV.getCredentials().getUser(); diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/WebSocketProxyTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/WebSocketProxyTest.java index aeb8888b41..b4e1c89263 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/WebSocketProxyTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/proxy/WebSocketProxyTest.java @@ -12,11 +12,9 @@ import io.restassured.RestAssured; import jakarta.websocket.ContainerProvider; +import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHttpHeaders; @@ -48,7 +46,7 @@ @WebsocketTest class WebSocketProxyTest implements TestWithStartedInstances { - private final GatewayServiceConfiguration serviceConfiguration = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration(); + private final GatewayServiceConfiguration gatewayServiceConfiguration = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration(); private static final URI DC_WS_REST_ENDPOINT = HttpRequestUtils.getUriFromGateway("/discoverableclient/api/v1/ws"); private static final int WAIT_TIMEOUT_MS = 10000; @@ -70,6 +68,12 @@ static void setup() { } + @AfterAll + static void teardown() { + VALID_AUTH_HEADERS.clear(); + INVALID_AUTH_HEADERS.clear(); + } + private TextWebSocketHandler appendResponseHandler(StringBuilder target, int countToNotify) { final AtomicInteger counter = new AtomicInteger(countToNotify); return new TextWebSocketHandler() { @@ -101,9 +105,9 @@ public boolean supportsPartialMessages() { } private String discoverableClientGatewayUrl(String gatewayUrl) throws URISyntaxException { - String scheme = serviceConfiguration.getScheme().equals("http") ? "ws" : "wss"; - String host = serviceConfiguration.getHost(); - int port = serviceConfiguration.getPort(); + String scheme = gatewayServiceConfiguration.getScheme().equals("http") ? "ws" : "wss"; + String host = StringUtils.isNotBlank(gatewayServiceConfiguration.getDvipaHost()) ? gatewayServiceConfiguration.getDvipaHost() : gatewayServiceConfiguration.getHost(); + int port = gatewayServiceConfiguration.getPort(); return new URIBuilder().setScheme(scheme).setHost(host).setPort(port).setPath(gatewayUrl).build().toString(); } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/PassTicketTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/PassTicketTest.java index d7c231bf66..0b9d0a9ec8 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/PassTicketTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/PassTicketTest.java @@ -31,16 +31,27 @@ import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; -import static io.restassured.http.ContentType.XML; -import static org.apache.http.HttpStatus.*; +import static io.restassured.http.ContentType.TEXT; +import static org.apache.http.HttpStatus.SC_BAD_REQUEST; +import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED; +import static org.apache.http.HttpStatus.SC_OK; +import static org.apache.http.HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase; import static org.zowe.apiml.integration.zaas.ZaasTestUtil.COOKIE; import static org.zowe.apiml.integration.zaas.ZaasTestUtil.ZAAS_TICKET_URI; -import static org.zowe.apiml.util.SecurityUtils.*; +import static org.zowe.apiml.util.SecurityUtils.USERNAME; +import static org.zowe.apiml.util.SecurityUtils.generateZoweJwtWithLtpa; +import static org.zowe.apiml.util.SecurityUtils.getConfiguredSslConfig; +import static org.zowe.apiml.util.SecurityUtils.getZosmfJwtTokenFromGw; +import static org.zowe.apiml.util.SecurityUtils.getZosmfLtpaToken; +import static org.zowe.apiml.util.SecurityUtils.personalAccessToken; +import static org.zowe.apiml.util.SecurityUtils.validOktaAccessToken; /** * Verify integration of the API ML PassTicket support with the zOS provider of the PassTicket. @@ -152,7 +163,7 @@ void givenValidOAuthToken() { .then() .statusCode(SC_OK) .body("ticket", not(isEmptyOrNullString())) - .body("userId", is(USERNAME)) + .body("userId", equalToIgnoringCase(USERNAME)) .body("applicationName", is(APPLICATION_NAME)); //@formatter:on } @@ -264,7 +275,7 @@ void givenInvalidContentType() { given() .body(new TicketRequest(APPLICATION_NAME)) .cookie(COOKIE, jwt) - .contentType(XML) + .contentType(TEXT) .when() .post(ZAAS_TICKET_URI) .then() diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zos/PassTicketTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zos/PassTicketTest.java index 5bc83d2fee..a72e97914b 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zos/PassTicketTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zos/PassTicketTest.java @@ -27,14 +27,16 @@ import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; +import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static org.apache.http.HttpStatus.*; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.zowe.apiml.passticket.PassTicketService.DefaultPassTicketImpl.UNKNOWN_APPLID; import static org.zowe.apiml.util.SecurityUtils.gatewayToken; import static org.zowe.apiml.util.SecurityUtils.getConfiguredSslConfig; -import static org.zowe.apiml.util.requests.Endpoints.*; +import static org.zowe.apiml.util.requests.Endpoints.ROUTED_PASSTICKET; /** * Verify integration of the API ML Passticket support with the zOS provider of the Passticket. @@ -186,7 +188,7 @@ void givenNoApplicationName() { @Test void givenInvalidApplicationName() { - String expectedMessage = "The generation of the PassTicket failed. Reason: Unable to generate PassTicket. Verify that the secured signon (PassTicket) function and application ID is configured properly by referring to Using PassTickets in z/OS Security Server RACF Security Administrator's Guide."; + String expectedMessage = "The generation of the PassTicket failed. Reason:"; TicketRequest ticketRequest = new TicketRequest(UNKNOWN_APPLID); given() @@ -196,9 +198,8 @@ void givenInvalidApplicationName() { .when() .post(url) .then() - .statusCode(is(SC_BAD_REQUEST)) - .body("messages.find { it.messageNumber == 'ZWEAG141E' }.messageContent", equalTo(expectedMessage)); - + .statusCode(is(SC_INTERNAL_SERVER_ERROR)) + .body("messages.find { it.messageNumber == 'ZWEAG141E' }.messageContent", containsString(expectedMessage)); } } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zos/ServicesInfoTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zos/ServicesInfoTest.java index af3de2a475..5afb24c9b1 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zos/ServicesInfoTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zos/ServicesInfoTest.java @@ -158,9 +158,10 @@ void givenValidToken() { @Nested class ReturnForbidden { + @Test @SuppressWarnings({"squid:S2699", "Assets are after then()"}) - void givenInvalidCredentials() { + void givenValidUnauthorizedCredentials() { String expectedMessage = "The user is not authorized to the target resource:"; //@formatter:off diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/SecurityUtils.java b/integration-tests/src/test/java/org/zowe/apiml/util/SecurityUtils.java index 0243d51d4c..7be5d738ee 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/SecurityUtils.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/SecurityUtils.java @@ -16,26 +16,30 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import io.restassured.RestAssured; -import io.restassured.config.HttpClientConfig; import io.restassured.config.RestAssuredConfig; import io.restassured.config.SSLConfig; +import io.restassured.filter.log.LogDetail; import io.restassured.http.Cookie; -import io.restassured.response.Response; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHeaders; +import org.apache.http.ParseException; import org.apache.http.client.CookieStore; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.utils.URIBuilder; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; +import org.apache.http.util.EntityUtils; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -56,12 +60,14 @@ import org.zowe.apiml.util.http.HttpRequestUtils; import javax.net.ssl.SSLContext; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.net.URI; +import java.net.URISyntaxException; import java.security.Key; import java.security.KeyManagementException; import java.security.KeyPair; @@ -72,14 +78,11 @@ import java.security.NoSuchProviderException; import java.security.Security; import java.security.UnrecoverableKeyException; -import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -100,30 +103,30 @@ import static org.zowe.apiml.util.requests.Endpoints.ZOSMF_AUTH_ENDPOINT; public class SecurityUtils { - public final static String GATEWAY_TOKEN_COOKIE_NAME = "apimlAuthenticationToken"; + public static final String GATEWAY_TOKEN_COOKIE_NAME = "apimlAuthenticationToken"; - private final static GatewayServiceConfiguration serviceConfiguration = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration(); - private final static TlsConfiguration tlsConfiguration = ConfigReader.environmentConfiguration().getTlsConfiguration(); + private static final GatewayServiceConfiguration serviceConfiguration = ConfigReader.environmentConfiguration().getGatewayServiceConfiguration(); + private static final TlsConfiguration tlsConfiguration = ConfigReader.environmentConfiguration().getTlsConfiguration(); - private final static String gatewayScheme = serviceConfiguration.getScheme(); - private final static String gatewayHost = serviceConfiguration.getHost(); - private final static int gatewayPort = serviceConfiguration.getPort(); + private static final String GATEWAY_SCHEME = serviceConfiguration.getScheme(); + private static final String GATEWAY_HOST = StringUtils.isBlank(serviceConfiguration.getDvipaHost()) ? serviceConfiguration.getHost() : serviceConfiguration.getDvipaHost(); + private static final int GATEWAY_PORT = serviceConfiguration.getPort(); - private final static String zosmfScheme = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getScheme(); - private final static String zosmfHost = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getHost(); - private final static int zosmfPort = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getPort(); + private static final String ZOSMF_SCHEME = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getScheme(); + private static final String ZOSMF_HOST = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getHost(); + private static final int ZOSMF_PORT = ConfigReader.environmentConfiguration().getZosmfServiceConfiguration().getPort(); - public final static String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); - public final static String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword(); + public static final String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); + public static final String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword(); - public final static String OKTA_HOSTNAME = ConfigReader.environmentConfiguration().getIdpConfiguration().getHost(); - public final static String OKTA_CLIENT_ID = ConfigReader.environmentConfiguration().getOidcConfiguration().getClientId(); - public final static String OKTA_USER = ConfigReader.environmentConfiguration().getIdpConfiguration().getUser(); - public final static String OKTA_PASSWORD = ConfigReader.environmentConfiguration().getIdpConfiguration().getPassword(); - public final static String OKTA_ALT_USER = ConfigReader.environmentConfiguration().getIdpConfiguration().getAlternateUser(); - public final static String OKTA_ALT_PASSWORD = ConfigReader.environmentConfiguration().getIdpConfiguration().getAlternatePassword(); + public static final String OKTA_HOSTNAME = ConfigReader.environmentConfiguration().getIdpConfiguration().getHost(); + public static final String OKTA_CLIENT_ID = ConfigReader.environmentConfiguration().getOidcConfiguration().getClientId(); + public static final String OKTA_USER = ConfigReader.environmentConfiguration().getIdpConfiguration().getUser(); + public static final String OKTA_PASSWORD = ConfigReader.environmentConfiguration().getIdpConfiguration().getPassword(); + public static final String OKTA_ALT_USER = ConfigReader.environmentConfiguration().getIdpConfiguration().getAlternateUser(); + public static final String OKTA_ALT_PASSWORD = ConfigReader.environmentConfiguration().getIdpConfiguration().getAlternatePassword(); - public final static String COOKIE_NAME = "apimlAuthenticationToken"; + public static final String COOKIE_NAME = "apimlAuthenticationToken"; public static final String PAT_COOKIE_AUTH_NAME = "personalAccessToken"; protected static String getUsername() { @@ -133,11 +136,11 @@ protected static String getUsername() { //@formatter:off public static String getGatewayUrl(String path) { - return getGatewayUrl(path, gatewayPort); + return getGatewayUrl(path, GATEWAY_PORT); } public static String getGatewayUrl(String path, int port) { - return String.format("%s://%s:%d%s", gatewayScheme, gatewayHost, port, path); + return String.format("%s://%s:%d%s", GATEWAY_SCHEME, GATEWAY_HOST, port, path); } public static String getGatewayLogoutUrl(String path) { @@ -162,18 +165,20 @@ public static String gatewayToken(URI gatewayLoginEndpoint, String username, Str SSLConfig originalConfig = RestAssured.config().getSSLConfig(); RestAssured.config = RestAssured.config().sslConfig(getConfiguredSslConfig()); - String cookie = given() - .contentType(JSON) - .body(loginRequest) - .when() - .post(gatewayLoginEndpoint) - .then() - .statusCode(is(SC_NO_CONTENT)) - .cookie(GATEWAY_TOKEN_COOKIE_NAME, not(isEmptyString())) - .extract().cookie(GATEWAY_TOKEN_COOKIE_NAME); - - RestAssured.config = RestAssured.config().sslConfig(originalConfig); - return cookie; + try { + return given() + .contentType(JSON) + .body(loginRequest) + .when() + .post(gatewayLoginEndpoint) + .then() + .log().ifValidationFails(LogDetail.ALL) + .statusCode(is(SC_NO_CONTENT)) + .cookie(GATEWAY_TOKEN_COOKIE_NAME, not(isEmptyString())) + .extract().cookie(GATEWAY_TOKEN_COOKIE_NAME); + } finally { + RestAssured.config = RestAssured.config().sslConfig(originalConfig); + } } /** @@ -227,7 +232,7 @@ public static String getZosmfTokenWebClient(String cookie) { CookieStore cookieStore = new BasicCookieStore(); context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore); - HttpUriRequest request = new HttpPost(String.format("%s://%s:%d%s", zosmfScheme, zosmfHost, zosmfPort, ZOSMF_AUTH_ENDPOINT)); + HttpUriRequest request = new HttpPost(String.format("%s://%s:%d%s", ZOSMF_SCHEME, ZOSMF_HOST, ZOSMF_PORT, ZOSMF_AUTH_ENDPOINT)); request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); request.addHeader(HttpHeaders.AUTHORIZATION, String.format("Basic %s", java.util.Base64.getEncoder().encodeToString(String.format("%s:%s", USERNAME, PASSWORD).getBytes()))); request.addHeader("X-CSRF-ZOSMF-HEADER", "csrf"); @@ -255,27 +260,27 @@ public static String getZosmfTokenWebClient(String cookie) { * @return */ public static String getZosmfToken(String cookie) { - return getZosmfToken(String.format("%s://%s:%d%s", zosmfScheme, zosmfHost, zosmfPort, ZOSMF_AUTH_ENDPOINT), cookie, SC_OK); + return getZosmfToken(String.format("%s://%s:%d%s", ZOSMF_SCHEME, ZOSMF_HOST, ZOSMF_PORT, ZOSMF_AUTH_ENDPOINT), cookie, SC_OK); } private static String getZosmfToken(String url, String cookie, int expectedCode) { SSLConfig originalConfig = RestAssured.config().getSSLConfig(); RestAssured.config = RestAssured.config().sslConfig(getConfiguredSslConfig()); - String zosmfToken = given() - .contentType(JSON) - .auth().preemptive().basic(USERNAME, PASSWORD) - .header("X-CSRF-ZOSMF-HEADER", "") + try { + return given() + .contentType(JSON) + .auth().preemptive().basic(USERNAME, PASSWORD) + .header("X-CSRF-ZOSMF-HEADER", "") .when() - .post(url) + .post(url) .then() - .statusCode(is(expectedCode)) - .cookie(cookie, not(isEmptyString())) - .extract().cookie(cookie); - - RestAssured.config = RestAssured.config().sslConfig(originalConfig); - - return zosmfToken; + .statusCode(is(expectedCode)) + .cookie(cookie, not(isEmptyString())) + .extract().cookie(cookie); + } finally { + RestAssured.config = RestAssured.config().sslConfig(originalConfig); + } } public static String generateZoweJwtWithLtpa(String ltpaToken) throws UnrecoverableKeyException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { @@ -326,9 +331,19 @@ private static Key getKey() throws KeyStoreException, IOException, CertificateEx public static String getClientCertificate() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { KeyStore ks = loadKeystore(SecurityUtils.tlsConfiguration.getClientKeystore()); - Certificate certificate = ks.getCertificate(ks.aliases().nextElement()); + var clientCN = SecurityUtils.tlsConfiguration.getClientCN(); + var aliases = ks.aliases(); - return Base64.encode(certificate.getEncoded()).toString(); + while (aliases.hasMoreElements()) { + var certificate = (X509Certificate) ks.getCertificate(aliases.nextElement()); + if (certificate.getSubjectX500Principal().getName().contains("CN=" + clientCN)) { + return Base64.encode(certificate.getEncoded()).toString(); + } + } + + throw new IllegalArgumentException( + "TlsConfiguration error: provided client CN: %s is not present in client keystore %s" + .formatted(clientCN, tlsConfiguration.getClientKeystore())); } public static String getDummyClientCertificate()throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException, OperatorCreationException, IOException { @@ -372,17 +387,19 @@ public static String personalAccessToken(Set scopes) { SSLConfig originalConfig = RestAssured.config().getSSLConfig(); RestAssured.config = RestAssured.config().sslConfig(getConfiguredSslConfig()); - String token = given() - .contentType(JSON).header("Authorization", "Basic " + Base64.encode(USERNAME + ":" + PASSWORD)) - .body(accessTokenRequest) + try { + return given() + .contentType(JSON).header("Authorization", "Basic " + Base64.encode(USERNAME + ":" + PASSWORD)) + .body(accessTokenRequest) .when() - .post(gatewayGenerateAccessTokenEndpoint) + .post(gatewayGenerateAccessTokenEndpoint) .then() - .statusCode(is(SC_OK)) - .extract().body().asString(); - - RestAssured.config = RestAssured.config().sslConfig(originalConfig); - return token; + .log().ifValidationFails(LogDetail.ALL) + .statusCode(is(SC_OK)) + .extract().body().asString(); + } finally { + RestAssured.config = RestAssured.config().sslConfig(originalConfig); + } } public static String personalAccessTokenWithClientCert(RestAssuredConfig sslConfig) { @@ -393,16 +410,17 @@ public static String personalAccessTokenWithClientCert(RestAssuredConfig sslConf SuccessfulAccessTokenHandler.AccessTokenRequest accessTokenRequest = new SuccessfulAccessTokenHandler.AccessTokenRequest(60, scopes); SSLConfig originalConfig = RestAssured.config().getSSLConfig(); - String token = given().config(sslConfig) - .body(accessTokenRequest) - .when() - .post(gatewayGenerateAccessTokenEndpoint) - .then() - .statusCode(is(SC_OK)) - .extract().body().asString(); - - RestAssured.config = RestAssured.config().sslConfig(originalConfig); - return token; + try { + return given().config(sslConfig) + .body(accessTokenRequest) + .when() + .post(gatewayGenerateAccessTokenEndpoint) + .then() + .statusCode(is(SC_OK)) + .extract().body().asString(); + } finally { + RestAssured.config = RestAssured.config().sslConfig(originalConfig); + } } public static String validOktaAccessToken(boolean userHasMappingDefined) { @@ -418,31 +436,33 @@ public static String validOktaAccessToken(boolean userHasMappingDefined) { assertNotNull(sessionToken, "Failed to get session token from Okta authentication."); // retrieve the access token from Okta using session token - Map queryParams = new HashMap<>(); - queryParams.put("client_id", OKTA_CLIENT_ID); - queryParams.put("redirect_uri", "https://localhost:10010/login/oauth2/code/okta"); - queryParams.put("response_type", "token"); - queryParams.put("response_mode", "form_post"); - queryParams.put("sessionToken", sessionToken); - queryParams.put("scope", "openid"); - queryParams.put("state", "TEST"); - queryParams.put("nonce", "TEST"); - Response authResponse = given() - .config(RestAssured.config().httpClient(HttpClientConfig.httpClientConfig().setParam("http.connection.timeout", 30 * 1000))) - .queryParams(queryParams) - .when() - .get(OKTA_HOSTNAME + "/oauth2/v1/authorize") - .then() - .log().ifValidationFails() - .statusCode(200) - .extract().response(); - // The response is HTML form where access token is hidden input field (this is controlled by response_mode = form_post) + try (CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(getRelaxedSslContext()).build()) { + var uriBuilder = new URIBuilder(OKTA_HOSTNAME + "/oauth2/v1/authorize"); + uriBuilder.setParameter("client_id", OKTA_CLIENT_ID) + .setParameter("redirect_uri", "https://localhost:10010/login/oauth2/code/okta") + .setParameter("response_type", "token") + .setParameter("response_mode", "form_post") + .setParameter("sessionToken", sessionToken) + .setParameter("scope", "openid") + .setParameter("state", "TEST") + .setParameter("nonce", "TEST"); + var request = new HttpGet(uriBuilder.build()); + var response = httpClient.execute(request); - String body = authResponse.getBody().asString(); - String accessToken = StringUtils.substringBetween(body, "name=\"access_token\" value=\"", "\"/>"); - assertNotNull(accessToken, "Failed to locate access token in the Okta /authorize response."); - return accessToken; + if (response.getStatusLine().getStatusCode() == 200) { + // The response is HTML form where access token is hidden input field (this is controlled by response_mode = form_post) + + var body = EntityUtils.toString(response.getEntity()); + var accessToken = StringUtils.substringBetween(body, "name=\"access_token\" value=\"", "\"/>"); + assertNotNull(accessToken, "Failed to locate access token in the Okta /authorize response."); + return accessToken; + } else { + throw new RuntimeException("Failed obtaining OKTA access token: " + response.getStatusLine().getStatusCode() + ": " + EntityUtils.toString(response.getEntity())); + } + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } } private static String getOktaSession(String username, String password) { @@ -456,14 +476,26 @@ private static String getOktaSession(String username, String password) { e.printStackTrace(); } - return given() - .contentType(JSON) - .body(requestBody.toString()) - .when() - .post(OKTA_HOSTNAME + "/api/v1/authn") - .then() - .statusCode(200) - .extract().path("sessionToken"); + try (CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(getRelaxedSslContext()).build()) { + var uriBuilder = new URIBuilder(OKTA_HOSTNAME + "/api/v1/authn"); + + var request = new HttpPost(uriBuilder.build()); + request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + var entity = new StringEntity(requestBody.toString()); + request.setEntity(entity); + var response = httpClient.execute(request); + + if (response.getStatusLine().getStatusCode() == 200) { + // The response is HTML form where access token is hidden input field (this is controlled by response_mode = form_post) + + var responseBody = EntityUtils.toString(response.getEntity()); + return new JSONObject(responseBody).getString("sessionToken"); + } else { + throw new RuntimeException("Failed obtaining OKTA access token: " + response.getStatusLine().getStatusCode() + ": " + EntityUtils.toString(response.getEntity())); + } + } catch (IOException | URISyntaxException | ParseException | JSONException e) { + throw new RuntimeException(e); + } } public static String expiredOktaAccessToken() { diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/categories/BookControllerTest.java b/integration-tests/src/test/java/org/zowe/apiml/util/categories/GraphQLTest.java similarity index 91% rename from integration-tests/src/test/java/org/zowe/apiml/util/categories/BookControllerTest.java rename to integration-tests/src/test/java/org/zowe/apiml/util/categories/GraphQLTest.java index 485cdc2058..19aee3f8d4 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/categories/BookControllerTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/categories/GraphQLTest.java @@ -19,8 +19,8 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; -@Tag("BookControllerTest") +@Tag("GraphQLTest") @Target({ TYPE, METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface BookControllerTest { +public @interface GraphQLTest { } diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/categories/TestsNotMeantForZowe.java b/integration-tests/src/test/java/org/zowe/apiml/util/categories/TestsNotMeantForZowe.java index c9c73b007f..a1cc82cf2c 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/categories/TestsNotMeantForZowe.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/categories/TestsNotMeantForZowe.java @@ -27,4 +27,6 @@ @Target({ TYPE, METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface TestsNotMeantForZowe { + + String value() default ""; } diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReader.java b/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReader.java index 3a5708462e..b002f3a2eb 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReader.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReader.java @@ -17,6 +17,9 @@ import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Objects; import java.util.Optional; @@ -42,7 +45,14 @@ public static EnvironmentConfiguration environmentConfiguration() { if (instance == null) { final String configFileName = configurationFile; ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - File configFile = new File(Objects.requireNonNull(classLoader.getResource(configFileName)).getFile()); + File configFile = null; + try { + Path path = Paths.get(Objects.requireNonNull(classLoader.getResource(configFileName)).toURI()); + configFile = path.toFile(); + } catch (URISyntaxException e) { + log.error("Incorrect environment-configuration.yml location: " + e.getMessage(), e); + configFile = new File(Objects.requireNonNull(classLoader.getResource(configFileName)).getFile()); + } ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); EnvironmentConfiguration configuration; try { @@ -51,7 +61,7 @@ public static EnvironmentConfiguration environmentConfiguration() { log.warn("Can't read service configuration from resource file, using default: http://localhost:10010", e); Credentials credentials = new Credentials("user", "user"); GatewayServiceConfiguration gatewayServiceConfiguration - = new GatewayServiceConfiguration("https", "localhost", 10010, 10017, 1, "10010", ROUTED_SERVICE); + = new GatewayServiceConfiguration("https", "localhost", null, 10010, 10017, 1, "10010", ROUTED_SERVICE); CentralGatewayServiceConfiguration centralGatewayServiceConfiguration = new CentralGatewayServiceConfiguration("https", "localhost", 10010); ZaasConfiguration zaasConfiguration = new ZaasConfiguration("https", "localhost", 10023, 1); DiscoveryServiceConfiguration discoveryServiceConfiguration = new DiscoveryServiceConfiguration("https", "eureka", "password", "localhost","localhost", 10011,10021, 1); @@ -70,7 +80,7 @@ public static EnvironmentConfiguration environmentConfiguration() { AuxiliaryUserList auxiliaryUserList = new AuxiliaryUserList("user,password"); - ZosmfServiceConfiguration zosmfServiceConfiguration = new ZosmfServiceConfiguration("https", "zosmf.acme.com", 1443, "ibmzosmf"); + ZosmfServiceConfiguration zosmfServiceConfiguration = new ZosmfServiceConfiguration("https", "zosmf.acme.com", 1443, "ibmzosmf", ""); IDPConfiguration idpConfiguration = new IDPConfiguration("https://okta-dev.com", "user", "user", "alt_user", "alt_user"); SafIdtConfiguration safIdtConfiguration = new SafIdtConfiguration(true); OidcConfiguration oidcConfiguration = new OidcConfiguration(""); @@ -96,10 +106,11 @@ public static EnvironmentConfiguration environmentConfiguration() { } configuration.getCredentials().setUser(System.getProperty("credentials.user", configuration.getCredentials().getUser())); - configuration.getCredentials().setPassword(System.getProperty("credentials.password", new String(configuration.getCredentials().getPassword()))); + configuration.getCredentials().setPassword(System.getProperty("credentials.password", StringUtils.isEmpty(configuration.getCredentials().getPassword()) ? "" : new String(configuration.getCredentials().getPassword()))); configuration.getGatewayServiceConfiguration().setScheme(System.getProperty("gateway.scheme", configuration.getGatewayServiceConfiguration().getScheme())); configuration.getGatewayServiceConfiguration().setHost(System.getProperty("gateway.host", configuration.getGatewayServiceConfiguration().getHost())); + configuration.getGatewayServiceConfiguration().setDvipaHost(System.getProperty("gateway.dvipaHost", configuration.getGatewayServiceConfiguration().getDvipaHost())); configuration.getGatewayServiceConfiguration().setPort(parseInt(System.getProperty("gateway.port", String.valueOf(configuration.getGatewayServiceConfiguration().getPort())))); configuration.getGatewayServiceConfiguration().setExternalPort(parseInt(System.getProperty("gateway.externalPort", String.valueOf(configuration.getGatewayServiceConfiguration().getExternalPort())))); configuration.getGatewayServiceConfiguration().setInstances(parseInt(System.getProperty("gateway.instances", String.valueOf(configuration.getGatewayServiceConfiguration().getInstances())))); diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReaderZaasClient.java b/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReaderZaasClient.java index 8368aef062..864ac7095a 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReaderZaasClient.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/config/ConfigReaderZaasClient.java @@ -10,6 +10,7 @@ package org.zowe.apiml.util.config; +import org.apache.commons.lang3.StringUtils; import org.zowe.apiml.zaasclient.config.ConfigProperties; import static org.zowe.apiml.util.config.ConfigReader.environmentConfiguration; @@ -17,20 +18,20 @@ public class ConfigReaderZaasClient { - public static ConfigProperties getConfigProperties() { - return ConfigProperties.builder() - .apimlHost(environmentConfiguration().getGatewayServiceConfiguration().getHost()) - .apimlPort(environmentConfiguration().getGatewayServiceConfiguration().getPort() + "") - .apimlBaseUrl(ROUTED_AUTH) - .keyStorePath(environmentConfiguration().getTlsConfiguration().getKeyStore()) - .keyStorePassword(environmentConfiguration().getTlsConfiguration().getKeyStorePassword()) - .keyStoreType(environmentConfiguration().getTlsConfiguration().getKeyStoreType()) - .trustStorePath(environmentConfiguration().getTlsConfiguration().getTrustStore()) - .trustStorePassword(environmentConfiguration().getTlsConfiguration().getTrustStorePassword()) - .trustStoreType(environmentConfiguration().getTlsConfiguration().getTrustStoreType()) - .nonStrictVerifySslCertificatesOfServices(environmentConfiguration().getTlsConfiguration().isNonStrictVerifySslCertificatesOfServices()) - .build(); - } + public static ConfigProperties getConfigProperties() { + var gatewayConfig = environmentConfiguration().getGatewayServiceConfiguration(); + return ConfigProperties.builder() + .apimlHost(StringUtils.isNotBlank(gatewayConfig.getDvipaHost()) ? gatewayConfig.getDvipaHost() : gatewayConfig.getHost()) + .apimlPort(environmentConfiguration().getGatewayServiceConfiguration().getPort() + "") + .apimlBaseUrl(ROUTED_AUTH) + .keyStorePath(environmentConfiguration().getTlsConfiguration().getKeyStore()) + .keyStorePassword(environmentConfiguration().getTlsConfiguration().getKeyStorePassword()) + .keyStoreType(environmentConfiguration().getTlsConfiguration().getKeyStoreType()) + .trustStorePath(environmentConfiguration().getTlsConfiguration().getTrustStore()) + .trustStorePassword(environmentConfiguration().getTlsConfiguration().getTrustStorePassword()) + .trustStoreType(environmentConfiguration().getTlsConfiguration().getTrustStoreType()) + .nonStrictVerifySslCertificatesOfServices(environmentConfiguration().getTlsConfiguration().isNonStrictVerifySslCertificatesOfServices()) + .build(); + } } - diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/config/GatewayServiceConfiguration.java b/integration-tests/src/test/java/org/zowe/apiml/util/config/GatewayServiceConfiguration.java index 6821f96aeb..4cbdf61bea 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/config/GatewayServiceConfiguration.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/config/GatewayServiceConfiguration.java @@ -20,6 +20,7 @@ public class GatewayServiceConfiguration implements ServiceConfiguration { private String scheme; private String host; + private String dvipaHost; private int port; private int externalPort; private int instances; diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/config/ZosmfServiceConfiguration.java b/integration-tests/src/test/java/org/zowe/apiml/util/config/ZosmfServiceConfiguration.java index 0fd7304728..306a268d6a 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/config/ZosmfServiceConfiguration.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/config/ZosmfServiceConfiguration.java @@ -22,4 +22,5 @@ public class ZosmfServiceConfiguration implements ServiceConfiguration { private String host; private int port; private String serviceId; + private String contextRoot; } diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/http/HttpRequestUtils.java b/integration-tests/src/test/java/org/zowe/apiml/util/http/HttpRequestUtils.java index a9b4abd057..3395da6c16 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/http/HttpRequestUtils.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/http/HttpRequestUtils.java @@ -11,6 +11,7 @@ package org.zowe.apiml.util.http; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.methods.HttpGet; @@ -67,7 +68,11 @@ public static HttpGet getRequest(String endpoint) { } public static URI getUri(ServiceConfiguration serviceConfiguration, String endpoint, NameValuePair...arguments) { - return getUri(serviceConfiguration.getScheme(), serviceConfiguration.getHost(), serviceConfiguration.getPort(), endpoint, arguments); + var host = serviceConfiguration.getHost(); + if (serviceConfiguration instanceof GatewayServiceConfiguration s && StringUtils.isNotBlank(s.getDvipaHost())) { + host = s.getDvipaHost(); + } + return getUri(serviceConfiguration.getScheme(), host, serviceConfiguration.getPort(), endpoint, arguments); } public static URI getUri(String scheme, String host, int port, String endpoint, NameValuePair...arguments) { @@ -90,12 +95,13 @@ public static URI getUri(String scheme, String host, int port, String endpoint, public static URI getUriFromService(ServiceConfiguration serviceConfiguration, String endpoint, NameValuePair...arguments) { String scheme = serviceConfiguration.getScheme(); - String host = serviceConfiguration.getHost(); - int port = serviceConfiguration.getPort(); - - StringTokenizer hostnameTokenizer = new StringTokenizer(host, ","); + var host = serviceConfiguration.getHost(); + var hostnameTokenizer = new StringTokenizer(host, ","); host = hostnameTokenizer.nextToken(); - + if (serviceConfiguration instanceof GatewayServiceConfiguration s && StringUtils.isNotBlank(s.getDvipaHost())) { + host = s.getDvipaHost(); + } + int port = serviceConfiguration.getPort(); return getUri(scheme, host, port, endpoint, arguments); } diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/requests/ApiCatalogRequests.java b/integration-tests/src/test/java/org/zowe/apiml/util/requests/ApiCatalogRequests.java index 8d50b34609..3800b3b8a2 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/requests/ApiCatalogRequests.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/requests/ApiCatalogRequests.java @@ -10,9 +10,9 @@ package org.zowe.apiml.util.requests; -import com.jayway.jsonpath.ReadContext; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.utils.URIBuilder; +import org.hamcrest.Matchers; import org.zowe.apiml.util.config.ApiCatalogServiceConfiguration; import org.zowe.apiml.util.config.ConfigReader; import org.zowe.apiml.util.config.Credentials; @@ -27,20 +27,19 @@ @Slf4j public class ApiCatalogRequests { - private static final ApiCatalogServiceConfiguration apiCatalogServiceConfiguration = ConfigReader.environmentConfiguration().getApiCatalogServiceConfiguration(); + private static final ApiCatalogServiceConfiguration apiCatalogServiceConfiguration = ConfigReader.environmentConfiguration().getApiCatalogServiceConfiguration(); private static final Credentials credentials = ConfigReader.environmentConfiguration().getCredentials(); - private final Requests requests; private final String scheme; private final String host; private final int port; private final String instance; public ApiCatalogRequests(String host) { - this(apiCatalogServiceConfiguration.getScheme(), host, apiCatalogServiceConfiguration.getPort(), new Requests()); + this(apiCatalogServiceConfiguration.getScheme(), host, apiCatalogServiceConfiguration.getPort()); } - public ApiCatalogRequests(String scheme, String host, int port, Requests requests) { - this.requests = requests; + + public ApiCatalogRequests(String scheme, String host, int port) { this.scheme = scheme; this.host = host; this.port = port; @@ -54,11 +53,17 @@ public boolean isUp() { try { log.info("ApiCatalogRequests#isUp Instance: {}", instance); - ReadContext healthResponse = requests.getJson(getApiCatalogUriWithPath("/apicatalog" + Endpoints.HEALTH)); - String health = healthResponse.read("$.status"); - - return health.equals("UP"); - } catch (Exception e) { + given() + .contentType(JSON) + .auth() + .basic(credentials.getUser(), new String(credentials.getPassword())) + .when() + .get(getApiCatalogUriWithPath("/apicatalog" + Endpoints.HEALTH)) + .then() + .statusCode(200) + .body("status", Matchers.is("UP")); + return true; + } catch (AssertionError | URISyntaxException e) { log.info("ApiCatalogRequests#isUP", e); return false; diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/requests/GatewayRequests.java b/integration-tests/src/test/java/org/zowe/apiml/util/requests/GatewayRequests.java index 0d800f6058..dd1aa14776 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/requests/GatewayRequests.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/requests/GatewayRequests.java @@ -26,7 +26,7 @@ import static io.restassured.http.ContentType.JSON; import static org.apache.http.HttpStatus.SC_NO_CONTENT; import static org.apache.http.HttpStatus.SC_OK; -import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.zowe.apiml.util.SecurityUtils.*; @@ -105,7 +105,7 @@ public String refresh(String token) { .post(getGatewayUriWithPath(authConfigurationProperties.getGatewayRefreshEndpoint())) .then() .statusCode(is(SC_NO_CONTENT)) - .cookie(GATEWAY_TOKEN_COOKIE_NAME, not(isEmptyString())) + .cookie(GATEWAY_TOKEN_COOKIE_NAME, not(is(emptyString()))) .extract().cookie(GATEWAY_TOKEN_COOKIE_NAME); } catch (URISyntaxException e) { log.info("GatewayRequests#refresh", e); diff --git a/keystore/README.md b/keystore/README.md index a8cb53f62d..3c79bd14e0 100644 --- a/keystore/README.md +++ b/keystore/README.md @@ -12,50 +12,50 @@ You can use the provided key store and trust store, or create your own version o The last section of this README describes how to import and trust the local CA certificate on your system. -## Key stores: - - * `keystore/local/localhost.keystore.cer` - - convenience - - contains the exported server certificate signed by the local CA and private key for the server - - * `keystore/local/localhost.keystore.key` - - convenience - - contains the exported private key - - * `keystore/local/localhost.pem` - - convenience - - contains the exported server certificate in PEM format for use with http clients - - * `keystore/local/localhost.keystore.p12` - - password: `password` - - used for the HTTPS server(s) - - contains the server certificate signed by the local CA and private key for the server - - * `keystore/local/localhost.truststore.p12` - - password: `password` - - used for HTTPS clients (e.g. integration tests, services using the gateway) - - contains the root certificate of the local CA (not the server certificate) - - * `keystore/local/localhost2.keystore.p12` - - password: `password` - - used for tests only, please refer to the particular tests for detils - - * `keystore/local/localhost2.truststore.p12` - - password: `password` - - used for tests only, please refer to the particular tests for detils - -### Local CA: - - * `keystore/local_ca/localca.cer` - - public certificate of local CA +## Key stores + +* `keystore/local/localhost.keystore.cer` + * convenience + * contains the exported server certificate signed by the local CA and private key for the server + +* `keystore/local/localhost.keystore.key` + * convenience + * contains the exported private key + +* `keystore/local/localhost.pem` + * convenience + * contains the exported server certificate in PEM format for use with http clients + +* `keystore/local/localhost.keystore.p12` + * password: `password` + * used for the HTTPS server(s) + * contains the server certificate signed by the local CA and private key for the server + +* `keystore/local/localhost.truststore.p12` + * password: `password` + * used for HTTPS clients (e.g. integration tests, services using the gateway) + * contains the root certificate of the local CA (not the server certificate) + +* `keystore/local/localhost2.keystore.p12` + * password: `password` + * used for tests only, please refer to the particular tests for detils + +* `keystore/local/localhost2.truststore.p12` + * password: `password` + * used for tests only, please refer to the particular tests for detils + +### Local CA + +* `keystore/local_ca/localca.cer` + * public certificate of local CA - * `keystore/local_ca/localca.keystore.p12` - - password: `local_ca_password` - - private key of the local CA +* `keystore/local_ca/localca.keystore.p12` + * password: `local_ca_password` + * private key of the local CA -### Client certificates: +### Client certificates - * `keystore/client_cert/ca/apiml_ca.p12` +* `keystore/client_cert/ca/apiml_ca.p12` API ML External Certificate authority - Certificate and private key of additional certificate authority that is trusted by apiml and can sign certificates that are used for authentication. Convenience export only. * `keystore/client_cert/client-certs.p12` @@ -63,77 +63,92 @@ The last section of this README describes how to import and trust the local CA c Client certificates - used for testing of client certificate authentication functionality. APIMTST, USER and UNKNOWNUSER. Keystore containing all the above including private keys. Used for testing client certificate authentication functionality. - - * `keystore/client_cert/openssl.conf` openssl Configuration for certificate generation -### Certificates for NGINX proxy (for AT-TLS simulation): +### Certificates for NGINX proxy (for AT-TLS simulation) The following files are used by the NGINX proxy to simulate AT_TLS on the CI server: - * `keystore/localhost/Zowe_Service_Zowe_Development_Instances_Certificate_Authority_.cer` - * `keystore/localhost/localca.cer` - * `keystore/localhost/trusted_CAs.cer` +* `keystore/localhost/Zowe_Service_Zowe_Development_Instances_Certificate_Authority_.cer` +* `keystore/localhost/localca.cer` +* `keystore/localhost/trusted_CAs.cer` ## Generate your own certificates for localhost ### (Optional)Generate certificate authority + create private key -``` + +```bash openssl genrsa -out local_ca.key 2048 ``` + create certificate -``` + +```bash openssl req -x509 -new -nodes -key local_ca.key -sha256 -days 1825 -out local_ca.pem ``` + ### Generate certificate #### generate CSR together with private key in PEM format - openssl req -newkey rsa:2048 -nodes -keyout localhost.key -sha256 -out localhost.csr -outform PEM +```bash + openssl req -newkey rsa:2048 -nodes -keyout localhost.key -sha256 -out localhost.csr -outform PEM -subj "/C=CZ/ST=Czechia/L=Prague/O=Broadcom/OU=MSD/CN=*.zowe.svc.cluster.local" -config ../client_cert/openssl.conf -extensions v3_req +``` **Verify CSR** +```bash openssl req -text -noout -verify -in localhost.csr +``` Example of a valid CSR: -``` -Certificate Request: -Data: -Version: 1 (0x0) -Subject: C = CZ, ST = Czechia, L = Prague, O = Broadcom Inc, OU = IT, CN = localhost -Subject Public Key Info: -Public Key Algorithm: rsaEncryption -RSA Public-Key: (2048 bit) -Modulus: -... -Exponent: 65537 (0x10001) -Attributes: -Requested Extensions: -X509v3 Key Usage: -Key Encipherment, Data Encipherment -X509v3 Extended Key Usage: -TLS Web Client Authentication, TLS Web Server Authentication -X509v3 Subject Alternative Name: -DNS:localhost, DNS:127.0.0.1 - Signature Algorithm: sha1WithRSAEncryption -... +```plaintext + Certificate Request: + Data: + Version: 1 (0x0) + Subject: C = CZ, ST = Czechia, L = Prague, O = Broadcom Inc, OU = IT, CN = localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + ... + Exponent: 65537 (0x10001) + Attributes: + Requested Extensions: + X509v3 Key Usage: + Key Encipherment, Data Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, TLS Web Server Authentication + X509v3 Subject Alternative Name: + DNS:localhost, DNS:127.0.0.1 + Signature Algorithm: sha1WithRSAEncryption + ... ``` #### sign the request using CA, this will produce certificate in PEM format -``` + +**Note:** You may need to export the public and private key in PEM format from the local CA keystore + +With PWD as /keystore/local_ca, run the following comnmand: + +```bash openssl x509 -req -in localhost.csr -CA local_ca.pem -CAkey local_ca.key \ --CAcreateserial -out localhost.crt -days 1825 -sha256 -extfile keystore/client_cert/openssl.conf -extensions v3_req +-CAcreateserial -out localhost.crt -days 1825 -sha256 -extfile ../client_cert/openssl.conf -extensions v3_req ``` Use the following script to display the certificate content: - openssl x509 -in localhost.pem -text -noout +```bash +openssl x509 -in localhost.pem -text -noout +``` Example of a valid signed certificate: +```plaintext Certificate: Data: Version: 3 (0x2) @@ -160,24 +175,30 @@ Example of a valid signed certificate: DNS:localhost, DNS:127.0.0.1 Signature Algorithm: sha256WithRSAEncryption ... +``` #### Create PKCS12 truststore and keystore Create truststore -``` + +```bash keytool -import -alias local-ca -file local_ca.pem -keystore localhost.truststore.p12 -storetype pkcs12 ``` Convert certificate to PKCS12 package -``` + +```bash openssl pkcs12 -export -out keystore.p12 -in localhost.crt -inkey localhost.key -name localhost -macalg SHA1 ``` Create keystore +```bash +keytool -J-Dkeystore.pkcs12.legacy -importkeystore -srckeystore keystore.p12 -destkeystore localhost.keystore.p12 -storetype pkcs12 ``` -keytool -J-Dkeystore.pkcs12.legacy -importkeystore -srckeystore mvsde12-keystore.p12 -destkeystore localhost.keystore.p12 -storetype pkcs12 -``` + +You will be prompted to set a password for the keystore. + **(optional) use legacy flag `-J-Dkeystore.pkcs12.legacy` in case you want ZSS to use this keystore** ### Trust certificates of other services @@ -194,30 +215,37 @@ You can add a public certificate to the API ML trust store by calling in the dir Issue the following script: +```bash keytool -import -alias -file -keystore localhost.truststore.p12 -storetype pkcs12 +``` ## Import the root certificate of a local CA to your browser -**Warning!** Be sure to only import the pre-generated certificate to a browser that you use for development and testing. Note that the private key is accessible to anyone. +**Warning!** Be sure to only import the pre-generated certificate to a browser that you use for development and testing. Note that the private key is accessible to anyone. -Import [keystore/local_ca/localca.cer](/keystore/local_ca/localca.cer) to your root certificate store and trust it. +Import [keystore/local_ca/localca.cer](/keystore/local_ca/localca.cer) to your root certificate store and trust it. * For **Windows**, run the following command as an administrator: - ``` + + ```bash certutil -enterprise -f -v -AddStore "Root" keystore/local_ca/localca.cer ``` - You have to open the terminal as administrator. This will install the certificate to the Trusted Root Certification Authorities. + + You have to open the terminal as administrator. This will install the certificate to the Trusted Root Certification Authorities. **Note:** You can use `npm run register-certificates-win` to run the preceding command. This requires `sudo` to be installed. If you don not have `sudo` available, install [chocolatey](https://chocolatey.org/docs/installation#install-downloaded-nuget-package-from-powershell), then run `chocolatey install sudo`. - -* For **MacOS**, run the following command: - ``` - $ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain keystore/local_ca/localca.cer + +* For **MacOS**, run the following command: + + ```bash + sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain keystore/local_ca/localca.cer ``` + Firefox uses its own certificate truststore. You can manually import your root certificate via the Firefox settings, or force Firefox to use the Windows trust store: Create a new Javascript file firefox-windows-truststore.js at C:\Program Files (x86)\Mozilla Firefox\defaults\pref with the following content: - ``` + + ```js /* Enable experimental Windows trust store support */ pref("security.enterprise_roots.enabled", true); ``` @@ -228,10 +256,10 @@ The default configuration of services for local development is to verify certifi Follow these steps to quickly register an existing service without generating a certificate for it -1. Set the `apiml.security.ssl.verifySslCertificatesOfServices` configuration property to `false` from the default `true` for API ML services (Gateway, Discovery service and API Catalog). +1. Set the `apiml.security.ssl.verifySslCertificatesOfServices` configuration property to `false` from the default `true` for API ML services (Gateway, Discovery service and API Catalog). 2. Add the following options to the startup command of each service in `package.json` or in your IDE: - ``` + + ```bash --apiml.security.ssl.verifySslCertificatesOfServices=false ``` - diff --git a/keystore/client_cert/client-certs.p12 b/keystore/client_cert/client-certs.p12 index cc8759a2eb..ed81104bb8 100644 Binary files a/keystore/client_cert/client-certs.p12 and b/keystore/client_cert/client-certs.p12 differ diff --git a/onboarding-enabler-nodejs/.gitignore b/onboarding-enabler-nodejs/.gitignore index f14cc4dd46..24441b6373 100644 --- a/onboarding-enabler-nodejs/.gitignore +++ b/onboarding-enabler-nodejs/.gitignore @@ -1,3 +1,4 @@ node_modules -package-lock.json \ No newline at end of file +package-lock.json +tools/ diff --git a/package.json b/package.json index 3199e69767..32ec6144a9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "api-layer-without-gateway": "concurrently --names \"AD,AC,DC\" -c yellow,white,blue npm:discovery-service npm:api-catalog-service npm:discoverable-client", "api-layer-without-discovery": "concurrently --names \"AZ,AG,GW,AC,DC\" -c red,cyan,yellow,white,blue npm:gateway-service npm:zaas-service npm:gateway-service npm:api-catalog-service npm:discoverable-client", "api-layer-without-catalog": "concurrently --names \"ZO,AZ,AG,DS,DC\" -c white,cyan,yellow,blue,red npm:mock-services npm:zaas-service npm:gateway-service npm:discovery-service npm:discoverable-client", - "caching-service": "java $npm_package_config_jvmArgs -jar caching-service/build/libs/caching-service.jar", + "caching-service": "java $npm_package_config_jvmArgs -jar caching-service/build/libs/caching-service.jar --spring.config.additional-location=file:./config/local/caching-service.yml", "zaas-service": "java $npm_package_config_jvmArgs -javaagent:scripts/jacocoagent.jar=includes=org.zowe.*,output=tcpserver,address=*,port=6301 -jar zaas-service/build/libs/zaas-service.jar --spring.config.additional-location=file:./config/local/zaas-service.yml --apiml.security.ssl.verifySslCertificatesOfServices=true", "zaas-service-ci": "java $npm_package_config_jvmArgs -Dloader.path=build/libs/api-layer-lite-lib-all.jar -jar zaas-service/build/libs/zaas-service-lite.jar --spring.config.additional-location=file:./config/local/zaas-service.yml --apiml.security.ssl.verifySslCertificatesOfServices=true --spring.profiles.include=diag --apiml.security.x509.enabled=true", "zaas-service-thin": "java $npm_package_config_jvmArgs -Dloader.path=build/libs/api-layer-lite-lib-all.jar -jar zaas-service/build/libs/zaas-service-lite.jar --spring.config.additional-location=file:./config/local/zaas-service.yml --apiml.security.ssl.verifySslCertificatesOfServices=true", diff --git a/zaas-service/build.gradle b/zaas-service/build.gradle index 9d8598526c..45782ddf69 100644 --- a/zaas-service/build.gradle +++ b/zaas-service/build.gradle @@ -155,6 +155,8 @@ bootRun { server = true } + workingDir = project.rootDir + systemProperties = System.properties } diff --git a/zaas-service/src/main/resources/application.yml b/zaas-service/src/main/resources/application.yml index 6383df8bf0..fc75ce6905 100644 --- a/zaas-service/src/main/resources/application.yml +++ b/zaas-service/src/main/resources/application.yml @@ -149,7 +149,7 @@ management: web: base-path: /application exposure: - include: health,info,shutdown,hystrixstream + include: health,info,shutdown health: defaults: enabled: false diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/security/service/ticket/SuccessfulTicketHandlerTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/security/service/ticket/SuccessfulTicketHandlerTest.java index b1fdf0aa64..4d773bb734 100644 --- a/zaas-service/src/test/java/org/zowe/apiml/zaas/security/service/ticket/SuccessfulTicketHandlerTest.java +++ b/zaas-service/src/test/java/org/zowe/apiml/zaas/security/service/ticket/SuccessfulTicketHandlerTest.java @@ -89,7 +89,7 @@ void shouldFailWhenGenerationFails() throws JsonProcessingException, Unsupported successfulTicketHandlerHandler.onAuthenticationSuccess(httpServletRequest, httpServletResponse, tokenAuthentication); assertEquals(MediaType.APPLICATION_JSON_VALUE, httpServletResponse.getContentType()); - assertEquals(HttpStatus.BAD_REQUEST.value(), httpServletResponse.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), httpServletResponse.getStatus()); assertTrue(httpServletResponse.getContentAsString().contains("ZWEAG141E")); assertTrue(httpServletResponse.isCommitted()); }