Skip to content

Commit

Permalink
chore: Integration tests runnable with API ML on z/OS (#3967)
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Salac <[email protected]>
Signed-off-by: Andrea Tabone <[email protected]>
Signed-off-by: sj895092 <[email protected]>
Signed-off-by: Pablo Carle <[email protected]>
Signed-off-by: nx673747 <[email protected]>
Co-authored-by: Richard Salac <[email protected]>
Co-authored-by: Andrea Tabone <[email protected]>
Co-authored-by: ShobhaJayanna <[email protected]>
Co-authored-by: Pablo Carle <[email protected]>
Co-authored-by: nx673747 <[email protected]>
Co-authored-by: sj895092 <[email protected]>
  • Loading branch information
7 people authored Jan 31, 2025
1 parent dd7ebd7 commit 6331469
Show file tree
Hide file tree
Showing 46 changed files with 812 additions and 473 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions config/local/gateway-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 61 additions & 6 deletions integration-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -47,27 +52,34 @@
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";
private static final String GET_API_CATALOG_API_DOC_DEFAULT_ENDPOINT = "/apicatalog/api/v1/apidoc/apicatalog";
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<String> 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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -45,8 +46,10 @@ static void setup() throws Exception {

@Nested
class GivenBearerAuthentication {

@Nested
class WhenAccessingProtectedEndpoint {

@Test
void thenAuthenticate() {
String token = SecurityUtils.gatewayToken(USERNAME, PASSWORD);
Expand All @@ -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 + "'";
Expand All @@ -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()
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 6331469

Please sign in to comment.