diff --git a/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/SafMethodSecurityExpressionControllerTest.java b/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/SafMethodSecurityExpressionControllerTest.java index 91bb7955e3..3a2f4f0f85 100644 --- a/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/SafMethodSecurityExpressionControllerTest.java +++ b/apiml-security-common/src/test/java/org/zowe/apiml/security/common/auth/SafMethodSecurityExpressionControllerTest.java @@ -12,8 +12,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; +import org.apache.hc.client5.http.utils.Base64; import org.apache.hc.core5.http.HttpHeaders; -import org.apache.tomcat.util.codec.binary.Base64; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -29,6 +29,7 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -146,9 +147,9 @@ public FailedAuthenticationHandler failedAuthenticationHandler() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http - .authorizeRequests(requests -> requests.anyRequest().authenticated()) - .apply(new CustomSecurityFilters()) - .and().build(); + .authorizeHttpRequests(requests -> requests.anyRequest().authenticated()) + .with(new CustomSecurityFilters(), Customizer.withDefaults()) + .build(); } @Bean @@ -161,7 +162,7 @@ public SafMethodSecurityExpressionRoot safMethodSecurityExpressionRoot( private class CustomSecurityFilters extends AbstractHttpConfigurer { @Override - public void configure(HttpSecurity http) throws Exception { + public void configure(HttpSecurity http) { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); http.addFilterBefore(new BasicContentFilter( diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/ValidateAPIController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/ValidateAPIController.java index dff6936497..b346697d5a 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/ValidateAPIController.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/ValidateAPIController.java @@ -27,11 +27,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import org.zowe.apiml.constants.EurekaMetadataDefinition; import org.zowe.apiml.message.core.Message; import org.zowe.apiml.message.core.MessageService; import org.zowe.apiml.product.gateway.GatewayClient; +import org.zowe.apiml.security.common.token.TokenAuthentication; import java.util.*; import java.util.regex.Matcher; @@ -103,7 +105,7 @@ public class ValidateAPIController { )) }) public ResponseEntity checkConformance(@Parameter(in = ParameterIn.PATH, required = true, description = "Service ID of the service to check") @PathVariable String serviceId, - @Parameter(hidden = true) @CookieValue(value = "apimlAuthenticationToken", defaultValue = "dummy") String authenticationToken) { + Authentication authentication) { ConformanceProblemsContainer foundNonConformanceIssues = new ConformanceProblemsContainer(serviceId); foundNonConformanceIssues.add(CONFORMANCE_PROBLEMS, validateServiceIdFormat(serviceId)); if (!foundNonConformanceIssues.isEmpty()) @@ -121,7 +123,7 @@ public ResponseEntity checkConformance(@Parameter(in = ParameterIn.PATH, checkMetadataCanBeRetrieved(metadata); Optional swaggerUrl = verificationOnboardService.findSwaggerUrl(metadata); - validateSwaggerDocument(serviceId, foundNonConformanceIssues, metadata, swaggerUrl, authenticationToken); + validateSwaggerDocument(serviceId, foundNonConformanceIssues, metadata, swaggerUrl, getToken(authentication)); } catch (ValidationException e) { switch (e.getKey()) { case WRONG_SERVICE_ID_KEY: @@ -142,6 +144,13 @@ public ResponseEntity checkConformance(@Parameter(in = ParameterIn.PATH, return new ResponseEntity<>("{\"message\":\"Service " + serviceId + " fulfills all checked conformance criteria\"}", HttpStatus.OK); } + private String getToken(Authentication authentication) { + if (authentication instanceof TokenAuthentication tokenAuthentication) { + return tokenAuthentication.getCredentials(); + } + return null; + } + private void validateSwaggerDocument(String serviceId, ConformanceProblemsContainer foundNonConformanceIssues, Map metadata, Optional swaggerUrl, String token) throws ValidationException { if (swaggerUrl.isEmpty()) { throw new ValidationException("Could not find Swagger Url", NON_CONFORMANT_KEY); @@ -206,11 +215,11 @@ private void validateSwaggerDocument(String serviceId, ConformanceProblemsContai } )) }) - public ResponseEntity checkValidateLegacy(@RequestBody String serviceId, @Parameter(hidden = true) @CookieValue(value = "apimlAuthenticationToken", defaultValue = "dummy") String authenticationToken) { + public ResponseEntity checkValidateLegacy(@RequestBody String serviceId, Authentication authentication) { if (serviceId.startsWith("serviceID")) { serviceId = serviceId.replace("serviceID=", ""); } - return checkConformance(serviceId, authenticationToken); + return checkConformance(serviceId, authentication); } /** diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/VerificationOnboardService.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/VerificationOnboardService.java index 0c886406f0..54d4849933 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/VerificationOnboardService.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/conformance/VerificationOnboardService.java @@ -13,28 +13,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; import org.zowe.apiml.constants.EurekaMetadataDefinition; -import org.zowe.apiml.product.constants.CoreService; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; /** * Service class that offers methods for checking onboarding information and also checks availability metadata from @@ -107,11 +94,7 @@ public String getSwagger(String swaggerUrl) { */ public List testEndpointsByCalling(Set endpoints, String passedAuthenticationToken) { ArrayList result = new ArrayList<>(checkEndpointsNoSSO(endpoints)); - try { - result.addAll(checkEndpointsWithSSO(endpoints, passedAuthenticationToken)); - } catch (ValidationException e) { - result.add(e.getMessage()); - } + result.addAll(checkEndpointsWithSSO(endpoints, passedAuthenticationToken)); return result; } @@ -119,11 +102,15 @@ public List testEndpointsByCalling(Set endpoints, String passe private List checkEndpointsWithSSO(Set endpoints, String passedAuthenticationToken) { ArrayList result = new ArrayList<>(); - String ssoCookie = getAuthenticationCookie(passedAuthenticationToken); + if (passedAuthenticationToken == null) { + result.add("Authentication token is not available, serviceId '%s' endpoints SSO check skipped.".formatted( + endpoints.stream().findAny().map(Endpoint::getServiceId).orElse("unknown"))); + return result; + } HttpHeaders headersSSO = new HttpHeaders(); headersSSO.setContentType(MediaType.APPLICATION_JSON); - headersSSO.add("Cookie", "apimlAuthenticationToken=" + ssoCookie); + headersSSO.add("Cookie", "apimlAuthenticationToken=" + passedAuthenticationToken); HttpEntity requestSSO = new HttpEntity<>(headersSSO); for (Endpoint endpoint : endpoints) { @@ -201,33 +188,6 @@ public static List getProblemsWithEndpointUrls(AbstractSwaggerValidator return swaggerParser.getProblemsWithEndpointUrls(); } - private String getAuthenticationCookie(String passedAuthenticationToken) { - String errorMsg = "Error retrieving ZAAS connection details"; - // FIXME This keeps the current behaviour - if (passedAuthenticationToken.equals("dummy")) { - URI uri = discoveryClient.getServices().stream() - .filter(service -> CoreService.ZAAS.getServiceId().equalsIgnoreCase(service)) - .flatMap(service -> discoveryClient.getInstances(service).stream()) - .findFirst() - .map(ServiceInstance::getUri) - .orElseThrow(() -> new ValidationException(errorMsg, ValidateAPIController.NO_METADATA_KEY)); - - String zaasAuthValidateUri = String.format("%s://%s:%d%s", uri.getScheme() == null ? "https" : uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath() + "/zaas/validate/auth"); - try { - restTemplate.exchange(zaasAuthValidateUri, HttpMethod.GET, null, String.class); - } catch (HttpClientErrorException.Conflict e) { - throw new ValidationException(e.getResponseBodyAsString(), ValidateAPIController.NON_CONFORMANT_KEY); - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Error getting authentication support", e); - } - throw new ValidationException("Error validating the authentication support", ValidateAPIController.NO_METADATA_KEY); - } - - } - return passedAuthenticationToken; - } - public static boolean supportsSSO(Map metadata) { if (metadata.containsKey(EurekaMetadataDefinition.AUTHENTICATION_SSO)) { return metadata.get(EurekaMetadataDefinition.AUTHENTICATION_SSO).equals("true"); diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/ValidateAPIControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/ValidateAPIControllerTest.java index 57abba5703..2c338f4d09 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/ValidateAPIControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/ValidateAPIControllerTest.java @@ -41,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; @@ -106,7 +106,7 @@ void checkValidJson() { void whenServiceIdTooLong_thenNonconformant() { when(messageService.createMessage(NON_CONFORMANT_KEY, "ThisWillBeRemoved")).thenReturn(NON_CONFORMANT_MESSAGE); String testString = "qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop"; - result = validateAPIController.checkConformance(testString, "dummy"); + result = validateAPIController.checkConformance(testString, null); assertNotNull(result.getBody()); assertTrue(result.getBody().contains("The serviceId is longer than 64 characters")); } @@ -115,7 +115,7 @@ void whenServiceIdTooLong_thenNonconformant() { void whenServiceIdTooLongAndSymbols_thenNonconformant() { when(messageService.createMessage(NON_CONFORMANT_KEY, "ThisWillBeRemoved")).thenReturn(NON_CONFORMANT_MESSAGE); String testString = "qwertyuiopqwertyuiop--qwertyuiopqwertyuio-pqwertyuio-pqwertyuiopqwertyuiop"; - result = validateAPIController.checkConformance(testString, "dummy"); + result = validateAPIController.checkConformance(testString, null); assertNotNull(result.getBody()); assertTrue(result.getBody().contains("The serviceId is longer than 64 characters")); assertTrue(result.getBody().contains("The serviceId contains symbols or upper case letters")); @@ -126,7 +126,7 @@ void whenServiceIdTooLongAndSymbols_thenNonconformant() { @ValueSource(strings = {"test-test", "TEST", "Test"}) void whenServiceIdNonAlphaNumeric_thenNonconformant(String testString) { when(messageService.createMessage(NON_CONFORMANT_KEY, "ThisWillBeRemoved")).thenReturn(NON_CONFORMANT_MESSAGE); - result = validateAPIController.checkConformance(testString, "dummy"); + result = validateAPIController.checkConformance(testString, null); assertNotNull(result.getBody()); assertTrue(result.getBody().contains("The serviceId contains symbols or upper case letters")); } @@ -135,7 +135,7 @@ void whenServiceIdNonAlphaNumeric_thenNonconformant(String testString) { void notInvalidTextFormat() { when(messageService.createMessage(WRONG_SERVICE_ID_KEY, "ThisWillBeRemoved")).thenReturn(WRONG_SERVICE_ID_MESSAGE); String testString = "test"; - result = validateAPIController.checkConformance(testString, "dummy"); + result = validateAPIController.checkConformance(testString, null); assertNotNull(result.getBody()); assertFalse(result.getBody().contains("Message service is requested to create a message with an invalid text format")); } @@ -162,7 +162,7 @@ void checkValidJson() { void whenServiceNotOboarded_thenError() { when(messageService.createMessage(WRONG_SERVICE_ID_KEY, "ThisWillBeRemoved")).thenReturn(WRONG_SERVICE_ID_MESSAGE); String testString = "notonboarded"; - result = validateAPIController.checkConformance(testString, "dummy"); + result = validateAPIController.checkConformance(testString, null); assertNotNull(result.getBody()); assertTrue(result.getBody().contains("The service is not registered")); } @@ -171,7 +171,7 @@ void whenServiceNotOboarded_thenError() { void legacyWhenServiceNotOboarded_thenError() { when(messageService.createMessage(WRONG_SERVICE_ID_KEY, "ThisWillBeRemoved")).thenReturn(WRONG_SERVICE_ID_MESSAGE); String testString = "notonboarded"; - result = validateAPIController.checkValidateLegacy(testString, "dummy"); + result = validateAPIController.checkValidateLegacy(testString, null); assertNotNull(result.getBody()); assertTrue(result.getBody().contains("The service is not registered")); @@ -203,7 +203,7 @@ void whenEmpty_thenCorrectConformanceResponse() { when(discoveryClient.getInstances(serviceId)).thenReturn(new ArrayList<>(Collections.singleton(serviceInstance))); when(serviceInstance.getMetadata()).thenReturn(mockMetadata); when(messageService.createMessage(NO_METADATA_KEY, "ThisWillBeRemoved")).thenReturn(NO_METADATA_MESSAGE); - result = validateAPIController.checkConformance(serviceId, "dummy"); + result = validateAPIController.checkConformance(serviceId, null); assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); } @@ -261,11 +261,11 @@ void whenEverythingOk_thenOkResponse(String mockSwaggerFileLocation) throws IOEx when(verificationOnboardService.getSwagger("a")).thenReturn(new String(Files.readAllBytes(mockSwaggerFile.getAbsoluteFile().toPath()))); - when(verificationOnboardService.testEndpointsByCalling(any(), eq("dummy"))).thenReturn(new ArrayList<>()); + when(verificationOnboardService.testEndpointsByCalling(any(), isNull())).thenReturn(new ArrayList<>()); try (MockedStatic validatorFactoryMockedStatic = mockStatic(ValidatorFactory.class)) { validatorFactoryMockedStatic.when(() -> ValidatorFactory.parseSwagger(any(), any(), any(), any())).thenReturn(swaggerValidator); - result = validateAPIController.checkConformance(serviceId, "dummy"); + result = validateAPIController.checkConformance(serviceId, null); assertEquals(HttpStatus.OK, result.getStatusCode()); } } @@ -281,7 +281,7 @@ void whenBadMetadata_thenBadMetadataResponse() { when(serviceInstance.getMetadata()).thenReturn(mockMetadata); when(messageService.createMessage(NO_METADATA_KEY, "ThisWillBeRemoved")).thenReturn(NO_METADATA_MESSAGE); - result = validateAPIController.checkConformance(serviceId, "dummy"); + result = validateAPIController.checkConformance(serviceId, null); assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); assertNotNull(result.getBody()); assertTrue(result.getBody().contains("Cannot Retrieve MetaData")); diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/VerificationOnboardServiceTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/VerificationOnboardServiceTest.java index 4eeaa1f661..8181673354 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/VerificationOnboardServiceTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/conformance/VerificationOnboardServiceTest.java @@ -10,7 +10,6 @@ package org.zowe.apiml.gateway.conformance; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,33 +17,17 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -127,18 +110,6 @@ void whenDoesntSupportSSO_thenFalse() { @Nested class GivenEndpoint { - @BeforeEach - void setup() throws URISyntaxException { - setUpZaasService(); - } - - private void setUpZaasService() throws URISyntaxException { - ServiceInstance serviceInstance = mock(ServiceInstance.class); - when(serviceInstance.getUri()).thenReturn(new URI("https://localhost:1000")); - when(discoveryClient.getServices()).thenReturn(asList("zaas")); - when(discoveryClient.getInstances("zaas")).thenReturn(asList(serviceInstance)); - } - @Test void whenEndpointNotFound_thenReturnCorrectError() { String url = "https://localhost:8000/test"; @@ -149,12 +120,11 @@ void whenEndpointNotFound_thenReturnCorrectError() { HashMap> responses = new HashMap<>(); responses.put("GET", new HashSet<>(Collections.singleton("404"))); - doReturn(response).when(restTemplate).exchange(eq("https://localhost:1000/zaas/validate/auth"), any(), any(), eq(String.class)); doReturn(response).when(restTemplate).exchange(eq(url), any(), any(), eq(String.class)); Endpoint endpoint = new Endpoint(url, "testservice", methods, responses); HashSet endpoints = new HashSet<>(); endpoints.add(endpoint); - List result = verificationOnboardService.testEndpointsByCalling(endpoints, "dummy"); + List result = verificationOnboardService.testEndpointsByCalling(endpoints, null); assertTrue(result.get(0).contains("could not be located, attempting to call it through gateway gives the ZWEAM104E")); } @@ -168,14 +138,14 @@ void whenEndpointReturnsDocumented400_thenReturnEmptyList() { HashMap> responses = new HashMap<>(); responses.put("GET", new HashSet<>(Collections.singleton("400"))); - doReturn(response).when(restTemplate).exchange(eq("https://localhost:1000/zaas/validate/auth"), any(), any(), eq(String.class)); doReturn(response).when(restTemplate).exchange(eq(url), any(), any(), eq(String.class)); Endpoint endpoint = new Endpoint(url, "testservice", methods, responses); HashSet endpoints = new HashSet<>(); endpoints.add(endpoint); - List result = verificationOnboardService.testEndpointsByCalling(endpoints, "dummy"); + List result = verificationOnboardService.testEndpointsByCalling(endpoints, null); - assertTrue(result.isEmpty()); + assertEquals(1, result.size()); + assertTrue(result.get(0).contains("token is not available")); } @Test @@ -188,14 +158,14 @@ void whenEndpointReturnsDocumented200Response_thenReturnEmptyList() { HashMap> responses = new HashMap<>(); responses.put("GET", new HashSet<>(Collections.singleton("200"))); - doReturn(response).when(restTemplate).exchange(eq("https://localhost:1000/zaas/validate/auth"), any(), any(), eq(String.class)); doReturn(response).when(restTemplate).exchange(eq(url), any(), any(), eq(String.class)); Endpoint endpoint = new Endpoint(url, "testservice", methods, responses); HashSet endpoints = new HashSet<>(); endpoints.add(endpoint); - List result = verificationOnboardService.testEndpointsByCalling(endpoints, "dummy"); + List result = verificationOnboardService.testEndpointsByCalling(endpoints, null); - assertTrue(result.isEmpty()); + assertEquals(1, result.size()); + assertTrue(result.get(0).contains("token is not available")); } @Test @@ -208,13 +178,12 @@ void whenEndpointReturnsUndocumented500_thenReturnCorrectError() { HashMap> responses = new HashMap<>(); responses.put("GET", new HashSet<>(Collections.singleton("0"))); - doReturn(response).when(restTemplate).exchange(eq("https://localhost:1000/zaas/validate/auth"), any(), any(), eq(String.class)); doReturn(response).when(restTemplate).exchange(eq(url), any(), any(), eq(String.class)); Endpoint endpoint = new Endpoint(url, "testservice", methods, responses); HashSet endpoints = new HashSet<>(); endpoints.add(endpoint); - List result = verificationOnboardService.testEndpointsByCalling(endpoints, "dummy"); + List result = verificationOnboardService.testEndpointsByCalling(endpoints, null); assertTrue(result.get(0).contains("returns undocumented")); } diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/AuthenticationOnDeploymentTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/AuthenticationOnDeploymentTest.java index 7ff11912f5..37aacbc729 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/AuthenticationOnDeploymentTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/AuthenticationOnDeploymentTest.java @@ -75,13 +75,13 @@ void testMultipleAuthenticationSchemes() throws Exception { try ( final VirtualService service1 = new VirtualService("testService", ports.get(0)); - final VirtualService service2 = new VirtualService("testService", ports.get(1)); + final VirtualService service2 = new VirtualService("testService", ports.get(1)) ) { // start first instance - without passTickets service1 .addVerifyServlet() .start() - .waitForGatewayRegistration(1, TIMEOUT); + .waitForGatewayRegistration(TIMEOUT); // on each gateway make a call to service @@ -102,19 +102,19 @@ void testMultipleAuthenticationSchemes() throws Exception { .addVerifyServlet() .setAuthentication(new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, "ZOWEAPPL")) .start() - .waitForGatewayRegistration(2, TIMEOUT); + .waitForGatewayRegistration(TIMEOUT); // on each gateway make calls (count same as instances) to service service1.getGatewayVerifyUrls().forEach(x -> given() .cookie(GATEWAY_TOKEN_COOKIE_NAME, jwt) .when().get(x + "/test") .then().statusCode(is(SC_OK))); - service2.getGatewayVerifyUrls().forEach(x -> { + service2.getGatewayVerifyUrls().forEach(x -> given() .cookie(GATEWAY_TOKEN_COOKIE_NAME, jwt) .when().get(x + "/test") - .then().statusCode(is(SC_OK)); - }); + .then().statusCode(is(SC_OK)) + ); // verify if each gateway sent request to service (one with and one without passTicket) service1.getGatewayVerifyUrls().forEach(gw -> { @@ -134,19 +134,19 @@ void testMultipleAuthenticationSchemes() throws Exception { .stop(); // check second service, all called second one with passTicket, same url like service1 (removed) - service1.getGatewayVerifyUrls().forEach(x -> { + service1.getGatewayVerifyUrls().forEach(x -> given() .cookie(GATEWAY_TOKEN_COOKIE_NAME, jwt) .when().get(x + "/test") - .then().statusCode(is(SC_OK)); - }); - service1.getGatewayVerifyUrls().forEach(gw -> { + .then().statusCode(is(SC_OK)) + ); + service1.getGatewayVerifyUrls().forEach(gw -> verifier.existAndClean(service2, x -> { assertNotNull(x.getHeader(HttpHeaders.AUTHORIZATION)); assertEquals("/verify/test", x.getRequestURI()); return true; - }); - }); + }) + ); } } @@ -167,7 +167,7 @@ void testReregistration() throws Exception { serviceList.forEach(s -> { try { - s.addVerifyServlet().start().waitForGatewayRegistration(1, TIMEOUT); + s.addVerifyServlet().start().waitForGatewayRegistration(TIMEOUT); } catch (IOException | LifecycleException | JSONException e) { e.printStackTrace(); } @@ -181,7 +181,7 @@ void testReregistration() throws Exception { } }); // register service with the same name - service4.addVerifyServlet().start().waitForGatewayRegistration(1, TIMEOUT); + service4.addVerifyServlet().start().waitForGatewayRegistration(TIMEOUT); // on each gateway make a call to service service4.getGatewayVerifyUrls().forEach(x -> given() diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/ServiceHaModeTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/ServiceHaModeTest.java index 1ec66637ff..3bb1bc0076 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/ServiceHaModeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/ServiceHaModeTest.java @@ -96,7 +96,7 @@ void setUp() throws LifecycleException, IOException, JSONException { service2 = new VirtualService("testHaModeService1", ports.get(1)); service1.start(); - service2.start().waitForGatewayRegistration(2, TIMEOUT); + service2.start().waitForGatewayRegistration(TIMEOUT); service2.zombie(); } @@ -134,7 +134,7 @@ private void routeAndVerifyRetry(List gatewayUrls, Method method, int ti assertEquals(HttpStatus.SC_OK, response.getStatusCode()); break; } catch (RuntimeException | AssertionError e) { - if (System.currentTimeMillis() - time0 > timeoutSec * 1000) throw e; + if (System.currentTimeMillis() - time0 > timeoutSec * 1000L) throw e; await().timeout(1, TimeUnit.SECONDS); } } @@ -158,7 +158,7 @@ void setUp() throws LifecycleException, IOException, JSONException { service2.addHttpStatusCodeServlet(HttpStatus.SC_SERVICE_UNAVAILABLE); service1.start(); - service2.start().waitForGatewayRegistration(2, TIMEOUT); + service2.start().waitForGatewayRegistration(TIMEOUT); } @AfterAll @@ -178,17 +178,17 @@ void cleanUp() { @ParameterizedTest @MethodSource("org.zowe.apiml.functional.gateway.ServiceHaModeTest#retryableHttpMethods") void verifyThatGatewayRetriesGet(Method method) { - routeAndVerifyRetries(service1.getGatewayUrls(), method, 2); + routeAndVerifyRetries(service1.getGatewayUrls(), method); } @ParameterizedTest @MethodSource("org.zowe.apiml.functional.gateway.ServiceHaModeTest#nonRetryableHttpMethods") void verifyThatGatewayNotRetriesPost(Method method) { - routeAndVerifyRetries(service1.getGatewayUrls(), method, 1); + routeAndVerifyRetries(service1.getGatewayUrls(), method); } // TODO This method used to verify how many retries happened based on an optional response header with debug information - private void routeAndVerifyRetries(List gatewayUrls, Method method, int maximumRetries) { + private void routeAndVerifyRetries(List gatewayUrls, Method method) { for (String gatewayUrl : gatewayUrls) { IntStream.rangeClosed(0, 1).forEach(x -> { diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/service/VirtualService.java b/integration-tests/src/test/java/org/zowe/apiml/util/service/VirtualService.java index 6c863bb472..30eedc6859 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/service/VirtualService.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/service/VirtualService.java @@ -10,6 +10,7 @@ package org.zowe.apiml.util.service; +import io.restassured.http.Header; import io.restassured.response.Response; import io.restassured.response.ResponseBody; import jakarta.servlet.Servlet; @@ -271,36 +272,26 @@ public VirtualService addVerifyServlet() { * The check means make serviceCount calls. There is a preposition that load balancer is based on cyclic queue and * if it will call multiple times (same to count of instances of same service), it should call all instances. * - * @param instanceCount Assumed count of instances of the same service at the moment * @param timeoutSec Timeout in secs to break waiting */ - public VirtualService waitForGatewayRegistration(int instanceCount, int timeoutSec) { + public VirtualService waitForGatewayRegistration(int timeoutSec) { final long time0 = System.currentTimeMillis(); boolean slept = false; + for (String gatewayUrl : getGatewayUrls()) { String url = gatewayUrl + "/application/instance"; - // count of calls (to make instanceCount times until sleep) - int testCounter = 0; while (true) { try { final ResponseBody responseBody = given().when() .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-InstanceId", instanceId)) .get(url) .body(); - // FIXME it seems the routing is not deterministic, the first time it's only one instance, the second time it's not guaranteeing that it's the same one replying. - // A possible fix would be to use deterministic routing once implemented. assertEquals(HttpMethod.GET + " " + instanceId, responseBody.print()); break; } catch (RuntimeException | AssertionError e) { - testCounter++; - // less calls than instance counts, continue without waiting - if (testCounter < instanceCount) continue; - - // instance should be called, but didn't, wait for a while and then try call again (instanceCount times) - testCounter = 0; - - if (System.currentTimeMillis() - time0 > timeoutSec * 1000) throw e; + if (System.currentTimeMillis() - time0 > timeoutSec * 1000L) throw e; await().timeout(1, TimeUnit.SECONDS); slept = true; @@ -339,7 +330,7 @@ public VirtualService waitForGatewayUnregistering(int instanceCountBefore, int t } break; } catch (RuntimeException | AssertionError e) { - if (System.currentTimeMillis() - time0 > timeoutSec * 1000) throw e; + if (System.currentTimeMillis() - time0 > timeoutSec * 1000L) throw e; await().timeout(1, TimeUnit.SECONDS); slept = true; @@ -644,7 +635,7 @@ public void run() { await().timeout(100, TimeUnit.MILLISECONDS); if (instanceId == null) continue; - if (lastCall + 1000 * heartbeatInterval < System.currentTimeMillis()) { + if (lastCall + 1000L * heartbeatInterval < System.currentTimeMillis()) { lastCall = System.currentTimeMillis(); sendHeartBeat(); } @@ -687,7 +678,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) { /** * Servlet answer on /application/instance Http method and instanceId. This is base part of method to verify registration on - * gateways, see {@link #waitForGatewayRegistration(int, int)} and {@link #waitForGatewayUnregistering(int, int)} + * gateways, see {@link #waitForGatewayRegistration(int)} and {@link #waitForGatewayUnregistering(int, int)} */ @NoArgsConstructor public class InstanceServlet extends HttpServlet { diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/controllers/AuthConfigValidationController.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/controllers/AuthConfigValidationController.java deleted file mode 100644 index 4983581ee0..0000000000 --- a/zaas-service/src/main/java/org/zowe/apiml/zaas/controllers/AuthConfigValidationController.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This program and the accompanying materials are made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v20.html - * - * SPDX-License-Identifier: EPL-2.0 - * - * Copyright Contributors to the Zowe Project. - */ - -package org.zowe.apiml.zaas.controllers; - -import io.swagger.v3.oas.annotations.Operation; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.zowe.apiml.zaas.security.login.Providers; -import org.zowe.apiml.zaas.security.service.TokenCreationService; - -@RequiredArgsConstructor -@RestController -@RequestMapping(AuthConfigValidationController.CONTROLLER_PATH) -public class AuthConfigValidationController { - - public static final String CONTROLLER_PATH = "zaas/validate"; - - private final Providers providers; - private final TokenCreationService tokenCreationService; - - @GetMapping(path = "auth") - @Operation(summary = "Provides information for service conformance validation") - public ResponseEntity validateAuth(Authentication authentication) { - if (authentication != null && authentication.getPrincipal() != null) { - return ResponseEntity.ok().build(); - } - if (providers.isZosfmUsed()) { - return ResponseEntity - .status(HttpStatus.CONFLICT) - .body("apimlAuthenticationToken cookie was not provided and a PassTicket cannot be generated with the z/OSMF provider"); - } - try { - tokenCreationService.createJwtTokenWithoutCredentials("validate"); - return ResponseEntity.ok().build(); - } catch (Exception e) { - return ResponseEntity - .status(HttpStatus.CONFLICT) - .body("Failed creating a PassTicket " + e.getMessage()); - } - } -} diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/controllers/AuthConfigValidationControllerTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/controllers/AuthConfigValidationControllerTest.java deleted file mode 100644 index 70e13dacf9..0000000000 --- a/zaas-service/src/test/java/org/zowe/apiml/zaas/controllers/AuthConfigValidationControllerTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This program and the accompanying materials are made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v20.html - * - * SPDX-License-Identifier: EPL-2.0 - * - * Copyright Contributors to the Zowe Project. - */ - -package org.zowe.apiml.zaas.controllers; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.zowe.apiml.zaas.security.login.Providers; -import org.zowe.apiml.zaas.security.service.TokenCreationService; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class AuthConfigValidationControllerTest { - - @InjectMocks - private AuthConfigValidationController authConfigValidationController; - - @Mock - private TokenCreationService tokenCreationService; - - @Mock - private Providers providers; - - @Mock - private Authentication authentication; - - @Nested - class GivenRequestToValidateAuthConfig { - - @Nested - class GivenUnauthenticated { - - @BeforeEach - void setUp() { - when(authentication.getPrincipal()).thenReturn(null); - } - - @Test - void whenZosmf_thenConflict() { - when(providers.isZosfmUsed()).thenReturn(Boolean.TRUE); - ResponseEntity entity = authConfigValidationController.validateAuth(authentication); - assertEquals(HttpStatusCode.valueOf(409), entity.getStatusCode()); - assertEquals("apimlAuthenticationToken cookie was not provided and a PassTicket cannot be generated with the z/OSMF provider", entity.getBody()); - } - - @Test - void whenNotZosmf_thenTokenOk() { - when(providers.isZosfmUsed()).thenReturn(Boolean.FALSE); - when(tokenCreationService.createJwtTokenWithoutCredentials("validate")).thenReturn("token"); - ResponseEntity entity = authConfigValidationController.validateAuth(authentication); - assertEquals(HttpStatusCode.valueOf(200), entity.getStatusCode()); - } - - @Test - void whenNotZosmf_thenTokenFail() { - when(providers.isZosfmUsed()).thenReturn(Boolean.FALSE); - when(tokenCreationService.createJwtTokenWithoutCredentials("validate")).thenThrow(new RuntimeException()); - ResponseEntity entity = authConfigValidationController.validateAuth(authentication); - assertEquals(HttpStatusCode.valueOf(409), entity.getStatusCode()); - } - } - - @Nested - class GivenAuthenticated { - - @BeforeEach - void setUp() { - when(authentication.getPrincipal()).thenReturn(new Object()); - } - - @Test - void whenAuthConfigValidate_thenOk() { - ResponseEntity entity = authConfigValidationController.validateAuth(authentication); - assertEquals(HttpStatusCode.valueOf(200), entity.getStatusCode()); - } - - } - - } - -}