From 945fc9cd8e2c85fb5fc4a6ea7d30c1cb0e1f744d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Jare=C5=A1?= <58428711+pj892031@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:35:38 +0200 Subject: [PATCH] fix: Fix error message in case of TLS error (#3864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix Signed-off-by: Pavel Jareš * show stacktrace on SSL error if debug is enabled Signed-off-by: Pavel Jareš * code review - test updates Signed-off-by: Pavel Jareš --------- Signed-off-by: Pavel Jareš Co-authored-by: Pablo Carle --- .../controllers/GatewayExceptionHandler.java | 8 + .../GatewayExceptionHandlerTest.java | 144 +++++++++++------- 2 files changed, 100 insertions(+), 52 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandler.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandler.java index 993a9cd8cc..6eac086a0a 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandler.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandler.java @@ -41,6 +41,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import javax.net.ssl.SSLException; + import static org.apache.http.HttpStatus.*; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -136,6 +138,12 @@ public Mono handleHttpMediaTypeException(ServerWebExchange exchange, Excep return setBodyResponse(exchange, SC_UNSUPPORTED_MEDIA_TYPE, "org.zowe.apiml.common.unsupportedMediaType"); } + @ExceptionHandler(SSLException.class) + public Mono handleSslException(ServerWebExchange exchange, SSLException ex) { + log.debug("SSL exception on " + exchange.getRequest().getURI(), ex); + return setBodyResponse(exchange, SC_INTERNAL_SERVER_ERROR, "org.zowe.apiml.common.tlsError", exchange.getRequest().getURI(), ex.getMessage()); + } + @ExceptionHandler({Exception.class}) public Mono handleInternalError(ServerWebExchange exchange, Exception ex) { log.debug("Unhandled internal error on {}: {}", exchange.getRequest().getURI(), ex.getMessage()); diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandlerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandlerTest.java index e9f182ccb4..0150537506 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandlerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/controllers/GatewayExceptionHandlerTest.java @@ -11,6 +11,8 @@ package org.zowe.apiml.gateway.controllers; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -22,9 +24,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatusCode; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.test.context.ActiveProfiles; import org.springframework.web.reactive.function.client.WebClientResponseException; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; import org.zowe.apiml.gateway.GatewayServiceApplication; import org.zowe.apiml.gateway.acceptance.common.AcceptanceTest; import org.zowe.apiml.gateway.acceptance.common.AcceptanceTestWithMockServices; @@ -33,75 +38,110 @@ import org.zowe.apiml.gateway.filters.ForbidSlashException; import reactor.core.publisher.Mono; +import javax.net.ssl.SSLException; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; -@AcceptanceTest -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@ActiveProfiles("gatewayExceptionHandlerTest") -@SpringBootTest(classes = GatewayServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class GatewayExceptionHandlerTest extends AcceptanceTestWithMockServices { +class GatewayExceptionHandlerTest { - private static final AtomicReference mockException = new AtomicReference<>(); + @Nested + @AcceptanceTest + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @ActiveProfiles("gatewayExceptionHandlerTest") + @SpringBootTest(classes = GatewayServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) + class BasicResponseCodes extends AcceptanceTestWithMockServices { - @BeforeAll - void createAllZaasServices() { - mockService("serv1ce").scope(MockService.Scope.CLASS).start(); - } + private static final AtomicReference mockException = new AtomicReference<>(); - @ParameterizedTest - @CsvSource({ - "400,org.zowe.apiml.common.badRequest", - "401,org.zowe.apiml.common.unauthorized", - "403,org.zowe.apiml.security.forbidden", - "404,org.zowe.apiml.common.notFound", - "405,org.zowe.apiml.common.methodNotAllowed", - "415,org.zowe.apiml.common.unsupportedMediaType", - "500,org.zowe.apiml.common.internalServerError", - "503,org.zowe.apiml.common.serviceUnavailable", - }) - void givenErrorResponse_whenCallGateway_thenDecorateIt(int code, String messageKey) throws MalformedURLException { - mockException.set(WebClientResponseException.create(code, "msg", null, null, null)); - - given().when() - .get(new URL(basePath + "/serv1ce/api/v1/test")) - .then() - .statusCode(code) - .body("messages[0].messageKey", containsString(messageKey)); - } + @BeforeAll + void createAllZaasServices() { + mockService("serv1ce").scope(MockService.Scope.CLASS).start(); + } - Stream getExceptions() { - return Stream.of( - Arguments.of(new ForbidSlashException(""), 400, "org.zowe.apiml.gateway.requestContainEncodedSlash"), - Arguments.of(new ForbidCharacterException(""), 400, "org.zowe.apiml.gateway.requestContainEncodedCharacter"), - Arguments.of(new ResponseStatusException(HttpStatusCode.valueOf(504)), 504, "org.zowe.apiml.gateway.responseStatusError") - ); - } + @ParameterizedTest + @CsvSource({ + "400,org.zowe.apiml.common.badRequest", + "401,org.zowe.apiml.common.unauthorized", + "403,org.zowe.apiml.security.forbidden", + "404,org.zowe.apiml.common.notFound", + "405,org.zowe.apiml.common.methodNotAllowed", + "415,org.zowe.apiml.common.unsupportedMediaType", + "500,org.zowe.apiml.common.internalServerError", + "503,org.zowe.apiml.common.serviceUnavailable", + }) + void givenErrorResponse_whenCallGateway_thenDecorateIt(int code, String messageKey) throws MalformedURLException { + mockException.set(WebClientResponseException.create(code, "msg", null, null, null)); + + given().when() + .get(new URL(basePath + "/serv1ce/api/v1/test")) + .then() + .statusCode(code) + .body("messages[0].messageKey", containsString(messageKey)); + } + + Stream getExceptions() { + return Stream.of( + Arguments.of(new ForbidSlashException(""), 400, "org.zowe.apiml.gateway.requestContainEncodedSlash"), + Arguments.of(new ForbidCharacterException(""), 400, "org.zowe.apiml.gateway.requestContainEncodedCharacter"), + Arguments.of(new ResponseStatusException(HttpStatusCode.valueOf(504)), 504, "org.zowe.apiml.gateway.responseStatusError") + ); + } + @ParameterizedTest + @MethodSource("getExceptions") + void whenExceptionIsThrown_thenTranslateToCorrectMessage(Exception ex, int status, String message) throws MalformedURLException { + mockException.set(ex); + given().when() + .get(new URL(basePath + "/serv1ce/api/v1/test")) + .then() + .statusCode(status) + .body("messages[0].messageKey", containsString(message)); + } + + @Configuration + @Profile("gatewayExceptionHandlerTest") + static class Config { + + @Bean + GlobalFilter exceptionFilter() { + return (exchange, chain) -> Mono.error(mockException.get()); + } + + } - @ParameterizedTest - @MethodSource("getExceptions") - void whenExceptionIsThrown_thenTranslateToCorrectMessage(Exception ex, int status, String message) throws MalformedURLException { - mockException.set(ex); - given().when() - .get(new URL(basePath + "/serv1ce/api/v1/test")) - .then() - .statusCode(status) - .body("messages[0].messageKey", containsString(message)); } - @Configuration - @Profile("gatewayExceptionHandlerTest") - static class Config { + @Nested + class Tls { + + private GatewayExceptionHandler gatewayExceptionHandler = spy(new GatewayExceptionHandler(null, null, null) { + @Override + public Mono setBodyResponse(ServerWebExchange exchange, int responseCode, String messageCode, Object... args) { + return Mono.empty(); + } + }); + + @Test + void givenTlsError_whenHandleException_thenShowTheDetailMessage() throws URISyntaxException { + SSLException sslException = new SSLException("Test TLS exception"); + MockServerHttpRequest request = MockServerHttpRequest.get("https://localhost/some/url").build(); + MockServerWebExchange exchange = MockServerWebExchange.from(request); + + gatewayExceptionHandler.handleSslException(exchange, sslException); - @Bean - GlobalFilter exceptionFilter() { - return (exchange, chain) -> Mono.error(mockException.get()); + verify(gatewayExceptionHandler).setBodyResponse( + exchange, 500, "org.zowe.apiml.common.tlsError", + new URI("https://localhost/some/url"), "Test TLS exception" + ); } }