Skip to content

Commit

Permalink
fix: Fix error message in case of TLS error (#3864)
Browse files Browse the repository at this point in the history
* fix

Signed-off-by: Pavel Jareš <[email protected]>

* show stacktrace on SSL error if debug is enabled

Signed-off-by: Pavel Jareš <[email protected]>

* code review - test updates

Signed-off-by: Pavel Jareš <[email protected]>

---------

Signed-off-by: Pavel Jareš <[email protected]>
Co-authored-by: Pablo Carle <[email protected]>
  • Loading branch information
pj892031 and pablocarle authored Oct 25, 2024
1 parent 386109e commit 945fc9c
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -136,6 +138,12 @@ public Mono<Void> handleHttpMediaTypeException(ServerWebExchange exchange, Excep
return setBodyResponse(exchange, SC_UNSUPPORTED_MEDIA_TYPE, "org.zowe.apiml.common.unsupportedMediaType");
}

@ExceptionHandler(SSLException.class)
public Mono<Void> 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<Void> handleInternalError(ServerWebExchange exchange, Exception ex) {
log.debug("Unhandled internal error on {}: {}", exchange.getRequest().getURI(), ex.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<Exception> 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<Exception> 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<Arguments> 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<Arguments> 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<Void> 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"
);
}

}
Expand Down

0 comments on commit 945fc9c

Please sign in to comment.