From 2c5b153d23240f127bf1c6949b05afda35687dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Jare=C5=A1?= <Pavel.Jares@broadcom.com> Date: Fri, 25 Oct 2024 17:08:58 +0200 Subject: [PATCH] draft of fixes (except evaluation of client certificate in the ZAAS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pavel Jareš <Pavel.Jares@broadcom.com> --- .../handlers/NotFoundErrorController.java | 1 - .../compatibility/ApimlErrorController.java | 22 --- config/local/mock-services.yml | 12 ++ config/local/zaas-service.yml | 6 - .../filters/AbstractAuthSchemeFactory.java | 17 ++- .../service/scheme/HttpBasicPassticket.java | 7 + .../gateway/acceptance/PassticketTest.java | 74 ++++++++-- .../scheme/HttpBasicPassticketTest.java | 15 ++ .../zaas/error/check/SafEndpointCheck.java | 62 -------- .../InternalServerErrorController.java | 96 ------------ .../controllers/NotFoundErrorController.java | 55 ------- .../controllers/ZaasErrorController.java | 96 ++++++++++++ .../config/NewSecurityConfiguration.java | 4 +- .../apiml/zaas/zaas/ZaasExceptionHandler.java | 137 +++++++++++++++++- .../InternalServerErrorControllerTest.java | 46 ------ .../zaas/error/ZaasErrorControllerTest.java | 91 ++++++++++++ .../error/check/SafEndpointCheckTest.java | 84 ----------- .../NotFoundErrorControllerTest.java | 63 -------- .../zaas/zaas/ZaasExceptionHandlerTest.java | 44 ++++++ 19 files changed, 467 insertions(+), 465 deletions(-) delete mode 100644 apiml-common/src/main/java/org/zowe/apiml/product/compatibility/ApimlErrorController.java delete mode 100644 zaas-service/src/main/java/org/zowe/apiml/zaas/error/check/SafEndpointCheck.java delete mode 100644 zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/InternalServerErrorController.java delete mode 100644 zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorController.java create mode 100644 zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/ZaasErrorController.java delete mode 100644 zaas-service/src/test/java/org/zowe/apiml/zaas/error/InternalServerErrorControllerTest.java create mode 100644 zaas-service/src/test/java/org/zowe/apiml/zaas/error/ZaasErrorControllerTest.java delete mode 100644 zaas-service/src/test/java/org/zowe/apiml/zaas/error/check/SafEndpointCheckTest.java delete mode 100644 zaas-service/src/test/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorControllerTest.java create mode 100644 zaas-service/src/test/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandlerTest.java diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/controllers/handlers/NotFoundErrorController.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/controllers/handlers/NotFoundErrorController.java index cc2b0e8937..e704b77738 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/controllers/handlers/NotFoundErrorController.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/controllers/handlers/NotFoundErrorController.java @@ -15,7 +15,6 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; -import org.zowe.apiml.product.compatibility.ApimlErrorController; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/compatibility/ApimlErrorController.java b/apiml-common/src/main/java/org/zowe/apiml/product/compatibility/ApimlErrorController.java deleted file mode 100644 index 5b32b5821e..0000000000 --- a/apiml-common/src/main/java/org/zowe/apiml/product/compatibility/ApimlErrorController.java +++ /dev/null @@ -1,22 +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.product.compatibility; - -import org.springframework.boot.web.servlet.error.ErrorController; - -/** - * This class is used to reconcile the breaking change between Spring Boot 2.5 and Zuul. The breaking change - * is due to ErrorController.getErrorPath being removed in Spring Boot 2.5. A BeanPostProcessor is used - * to proxy ZuulHandlerMapping, intercepting the code execution that leads to the NoSuchMethodError. - */ -public interface ApimlErrorController extends ErrorController { - String getErrorPath(); -} diff --git a/config/local/mock-services.yml b/config/local/mock-services.yml index 8a8a8bd073..dac99cdec0 100644 --- a/config/local/mock-services.yml +++ b/config/local/mock-services.yml @@ -1,2 +1,14 @@ zosmf: timeout: 1800 + +server: + ssl: + keyAlias: localhost + keyPassword: password + keyStore: keystore/localhost/localhost.keystore.p12 + keyStorePassword: password + keyStoreType: PKCS12 + trustStore: keystore/localhost/localhost.truststore.p12 + trustStorePassword: password + trustStoreType: PKCS12 + diff --git a/config/local/zaas-service.yml b/config/local/zaas-service.yml index e0742d851a..65fecdb1b2 100644 --- a/config/local/zaas-service.yml +++ b/config/local/zaas-service.yml @@ -47,12 +47,6 @@ spring: enabled: always server: - internal: - enabled: true - port: 10017 - ssl: - keyAlias: localhost-multi - keyStore: keystore/localhost/localhost-multi.keystore.p12 ssl: keyAlias: localhost keyPassword: password diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java index 6032b5334d..0a4f3fa950 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java @@ -37,6 +37,7 @@ import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Stream; import static org.apache.hc.core5.http.HttpStatus.SC_OK; @@ -172,14 +173,14 @@ private Mono<AuthorizationResponse<R>> requestWithHa( Function<ServiceInstance, WebClient.RequestHeadersSpec<?>> requestCreator ) { return requestCreator.apply(serviceInstanceIterator.next()) - .exchangeToMono(clientResp -> switch (clientResp.statusCode().value()) { - case SC_UNAUTHORIZED -> Mono.just(new AuthorizationResponse<R>(clientResp.headers(), null)); - case SC_OK -> clientResp.bodyToMono(getResponseClass()).map(b -> new AuthorizationResponse<R>(clientResp.headers(), b)); - default -> Mono.empty(); - }) - .switchIfEmpty(serviceInstanceIterator.hasNext() ? - requestWithHa(serviceInstanceIterator, requestCreator) : Mono.empty() - ); + .exchangeToMono(clientResp -> { + Supplier<Mono<AuthorizationResponse<R>>> unauthorized = () -> clientResp.bodyToMono(getResponseClass()).map(b -> new AuthorizationResponse<>(clientResp.headers(), b)); + return switch (clientResp.statusCode().value()) { + case SC_UNAUTHORIZED -> Mono.just(new AuthorizationResponse<R>(clientResp.headers(), null)); + case SC_OK -> unauthorized.get(); + default -> serviceInstanceIterator.hasNext() ? requestWithHa(serviceInstanceIterator, requestCreator) : unauthorized.get(); + }; + }); } protected Mono<Void> invoke( diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticket.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticket.java index d4c49aac76..640ef3ffdf 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticket.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticket.java @@ -10,6 +10,7 @@ package org.zowe.apiml.gateway.service.scheme; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.gateway.filter.FilterDefinition; @@ -18,6 +19,7 @@ import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.auth.AuthenticationScheme; +@Slf4j @Component public class HttpBasicPassticket implements SchemeHandler { @@ -28,6 +30,11 @@ public AuthenticationScheme getAuthenticationScheme() { @Override public void apply(ServiceInstance serviceInstance, RouteDefinition routeDefinition, Authentication auth) { + if (StringUtils.isEmpty(auth.getApplid())) { + log.warn("Service {} does not have configured APPLID. The authorization scheme will be ignored", serviceInstance.getServiceId()); + return; + } + FilterDefinition filterDef = new FilterDefinition(); filterDef.setName("PassticketFilterFactory"); filterDef.addArg("applicationName", auth.getApplid()); diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/PassticketTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/PassticketTest.java index 2c769b2ece..f1f898b532 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/PassticketTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/acceptance/PassticketTest.java @@ -10,11 +10,15 @@ package org.zowe.apiml.gateway.acceptance; +import lombok.AllArgsConstructor; +import lombok.Data; import org.apache.http.HttpHeaders; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.zowe.apiml.auth.AuthenticationScheme; import org.zowe.apiml.gateway.acceptance.common.AcceptanceTest; import org.zowe.apiml.gateway.acceptance.common.AcceptanceTestWithMockServices; @@ -27,7 +31,9 @@ import static io.restassured.RestAssured.given; import static org.apache.http.HttpStatus.SC_OK; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; @AcceptanceTest public class PassticketTest extends AcceptanceTestWithMockServices { @@ -38,24 +44,24 @@ public class PassticketTest extends AcceptanceTestWithMockServices { private static final String JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjcxNDYxNjIzLCJleHAiOjE2NzE0OTA0MjMsImlzcyI6IkFQSU1MIiwianRpIjoiYmFlMTkyZTYtYTYxMi00MThhLWI2ZGMtN2I0NWI5NzM4ODI3IiwiZG9tIjoiRHVtbXkgcHJvdmlkZXIifQ.Vt5UjJUlbmuzmmEIodAACtj_AOxlsWqkFrFyWh4_MQRRPCj_zMIwnzpqRN-NJvKtUg1zxOCzXv2ypYNsglrXc7cH9wU3leK1gjYxK7IJjn2SBEb0dUL5m7-h4tFq2zNhcGH2GOmTpE2gTQGSTvDIdja-TIj_lAvUtbkiorm1RqrNu2MGC0WfgOGiak3tj2tNJLv_Y1ZMxNjzyHgXBMuNPozQrd4Vtnew3x4yy85LrTYF7jJM3U-e3AD2yImftxwycQvbkjNb-lWadejTVH0MgHMr04wVdDd8Nq5q7yrZf7YPzhias8ehNbew5CHiKut9SseZ1sO2WwgfhpEfsN4okg"; private static final String PASSTICKET = "ZOWE_DUMMY_PASS_TICKET"; - @BeforeEach - void setup() throws IOException { - TicketResponse response = new TicketResponse(); - response.setToken(JWT); - response.setUserId(USER_ID); - response.setApplicationName("IZUDFLT"); - response.setTicket(PASSTICKET); + @Nested + class GivenValidAuthentication { - mockService("zaas").scope(MockService.Scope.CLASS) - .addEndpoint("/zaas/scheme/ticket") + @BeforeEach + void setup() throws IOException { + TicketResponse response = new TicketResponse(); + response.setToken(JWT); + response.setUserId(USER_ID); + response.setApplicationName("IZUDFLT"); + response.setTicket(PASSTICKET); + + mockService("zaas").scope(MockService.Scope.CLASS) + .addEndpoint("/zaas/scheme/ticket") .assertion(he -> assertEquals(SERVICE_ID, he.getRequestHeaders().getFirst("X-Service-Id"))) .assertion(he -> assertEquals(COOKIE_NAME + "=" + JWT, he.getRequestHeaders().getFirst("Cookie"))) .bodyJson(response) - .and().start(); - } - - @Nested - class GivenValidAuthentication { + .and().start(); + } @Test void whenRequestingPassticketForAllowedAPPLID_thenTranslate() throws IOException { @@ -78,4 +84,44 @@ void whenRequestingPassticketForAllowedAPPLID_thenTranslate() throws IOException } + @Nested + class GivenInvalidAuthentication { + + @ParameterizedTest + @ValueSource(ints = { 400, 401, 403, 404, 405, 500 }) + void whenCannotGeneratePassticket_thenIgnoreTransformation(int responseCode) throws IOException { + mockService("zaas").scope(MockService.Scope.TEST) + .addEndpoint("/zaas/scheme/ticket") + .responseCode(responseCode) + .and().start(); + + var mockService = mockService(SERVICE_ID).scope(MockService.Scope.TEST) + .authenticationScheme(AuthenticationScheme.HTTP_BASIC_PASSTICKET).applid("IZUDFLT") + .addEndpoint("/" + SERVICE_ID + "/test") + .responseCode(401) + .bodyJson(new ResponseDto("ok")) + .assertion(he -> assertFalse(he.getRequestHeaders().containsKey(HttpHeaders.AUTHORIZATION))) + .and().start(); + + given() + .cookie(COOKIE_NAME, JWT) + .when() + .get(basePath + "/" + SERVICE_ID + "/api/v1/test") + .then() + .statusCode(Matchers.is(SC_UNAUTHORIZED)) + .body("status", Matchers.is("ok")); + + assertEquals(1, mockService.getEndpoint().getCounter()); + } + + } + + @Data + @AllArgsConstructor + static class ResponseDto { + + private String status; + + } + } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticketTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticketTest.java index 4c711a6883..e85e53c6b7 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticketTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/service/scheme/HttpBasicPassticketTest.java @@ -18,6 +18,7 @@ import org.zowe.apiml.auth.AuthenticationScheme; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -44,4 +45,18 @@ void givenRouteDefinition_whenApply_thenFulfillFilterFactorArgs() { assertEquals("PassticketFilterFactory", filterDefinition.getName()); } + @Test + void givenNoApplid_whenApply_thenSkipConfiguration() { + RouteDefinition routeDefinition = new RouteDefinition(); + new HttpBasicPassticket().apply(mock(ServiceInstance.class), routeDefinition, new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, null)); + assertTrue(routeDefinition.getFilters().isEmpty()); + } + + @Test + void givenEmptyApplid_whenApply_thenSkipConfiguration() { + RouteDefinition routeDefinition = new RouteDefinition(); + new HttpBasicPassticket().apply(mock(ServiceInstance.class), routeDefinition, new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, "")); + assertTrue(routeDefinition.getFilters().isEmpty()); + } + } diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/check/SafEndpointCheck.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/error/check/SafEndpointCheck.java deleted file mode 100644 index 94e1daae57..0000000000 --- a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/check/SafEndpointCheck.java +++ /dev/null @@ -1,62 +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.error.check; - -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.zowe.apiml.message.api.ApiMessageView; -import org.zowe.apiml.message.core.MessageService; -import org.zowe.apiml.security.common.auth.saf.EndpointImproprietyConfigureException; -import org.zowe.apiml.security.common.auth.saf.UnsupportedResourceClassException; - -import jakarta.servlet.http.HttpServletRequest; - -@RequiredArgsConstructor -public class SafEndpointCheck implements ErrorCheck { - - private final MessageService messageService; - - private ResponseEntity<ApiMessageView> createResponse(EndpointImproprietyConfigureException eice) { - ApiMessageView apiMessage = messageService.createMessage( - "org.zowe.apiml.security.common.auth.saf.endpoint.endpointImproprietyConfigure", - eice.getEndpoint() - ).mapToView(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(apiMessage); - } - - private ResponseEntity<ApiMessageView> createResponse(UnsupportedResourceClassException urce) { - ApiMessageView apiMessage = messageService.createMessage( - "org.zowe.apiml.security.common.auth.saf.endpoint.nonZoweClass", - urce.getResourceClass() - ).mapToView(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(apiMessage); - } - - @Override - public ResponseEntity<ApiMessageView> checkError(HttpServletRequest request, Throwable exc) { - int exceptionIndex; - - exceptionIndex = ExceptionUtils.indexOfType(exc, EndpointImproprietyConfigureException.class); - if (exceptionIndex != -1) { - return createResponse((EndpointImproprietyConfigureException) ExceptionUtils.getThrowables(exc)[exceptionIndex]); - } - - exceptionIndex = ExceptionUtils.indexOfType(exc, UnsupportedResourceClassException.class); - if (exceptionIndex != -1) { - return createResponse((UnsupportedResourceClassException) ExceptionUtils.getThrowables(exc)[exceptionIndex]); - } - - return null; - } - -} diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/InternalServerErrorController.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/InternalServerErrorController.java deleted file mode 100644 index 0568bfd3e9..0000000000 --- a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/InternalServerErrorController.java +++ /dev/null @@ -1,96 +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.error.controllers; - -import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Primary; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.zowe.apiml.message.api.ApiMessageView; -import org.zowe.apiml.message.core.Message; -import org.zowe.apiml.message.core.MessageService; -import org.zowe.apiml.product.compatibility.ApimlErrorController; -import org.zowe.apiml.zaas.error.ErrorUtils; -import org.zowe.apiml.zaas.error.check.ErrorCheck; -import org.zowe.apiml.zaas.error.check.SafEndpointCheck; - -import java.util.ArrayList; -import java.util.List; - -/** - * Handles errors in REST API processing. - */ -@RestController -@Order(Ordered.HIGHEST_PRECEDENCE) -@Primary -public class InternalServerErrorController implements ApimlErrorController { - public static final String ERROR_ENDPOINT = "/internal_error"; - - private final MessageService messageService; - private final List<ErrorCheck> errorChecks = new ArrayList<>(); - - @Autowired - public InternalServerErrorController(MessageService messageService) { - this.messageService = messageService; - - errorChecks.add(new SafEndpointCheck(messageService)); - } - - @Override - public String getErrorPath() { - return ERROR_ENDPOINT; - } - - /** - * Error endpoint controller - * Creates response and logs the error - * - * @param request Http request - * @return Http response entity - */ - @SuppressWarnings("squid:S3752") - @RequestMapping(value = ERROR_ENDPOINT, produces = "application/json") - public ResponseEntity<ApiMessageView> error(HttpServletRequest request) { - final Throwable exc = (Throwable) request.getAttribute(ErrorUtils.ATTR_ERROR_EXCEPTION); - - ResponseEntity<ApiMessageView> entity = checkForSpecificErrors(request, exc); - if (entity != null) { - return entity; - } - - return createResponseForInternalError(request, exc); - } - - private ResponseEntity<ApiMessageView> createResponseForInternalError(HttpServletRequest request, Throwable exc) { - final int status = ErrorUtils.getErrorStatus(request); - Message message = messageService.createMessage("org.zowe.apiml.common.internalRequestError", - ErrorUtils.getForwardUri(request), - ExceptionUtils.getMessage(exc), - ExceptionUtils.getRootCauseMessage(exc)); - - return ResponseEntity.status(status).body(message.mapToView()); - } - - private ResponseEntity<ApiMessageView> checkForSpecificErrors(HttpServletRequest request, Throwable exc) { - for (ErrorCheck check : errorChecks) { - ResponseEntity<ApiMessageView> entity = check.checkError(request, exc); - if (entity != null) { - return entity; - } - } - return null; - } -} diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorController.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorController.java deleted file mode 100644 index 63919e86d4..0000000000 --- a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorController.java +++ /dev/null @@ -1,55 +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.error.controllers; - - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.zowe.apiml.message.api.ApiMessageView; -import org.zowe.apiml.message.core.Message; -import org.zowe.apiml.message.core.MessageService; -import org.zowe.apiml.product.compatibility.ApimlErrorController; -import org.zowe.apiml.zaas.error.ErrorUtils; - -/** - * Not found endpoint controller - */ -@RestController -@RequiredArgsConstructor -@Order(Ordered.HIGHEST_PRECEDENCE) -public class NotFoundErrorController implements ApimlErrorController { - private static final String NOT_FOUND_ENDPOINT = "/not_found"; - private final MessageService messageService; - - @Override - public String getErrorPath() { - return NOT_FOUND_ENDPOINT; - } - - /** - * Not found endpoint controller - * Creates response and logs the error - * - * @param request Http request - * @return Http response entity - */ - @GetMapping(value = NOT_FOUND_ENDPOINT, produces = "application/json") - public ResponseEntity<ApiMessageView> notFound400HttpResponse(HttpServletRequest request) { - Message message = messageService.createMessage("org.zowe.apiml.common.endPointNotFound", - ErrorUtils.getForwardUri(request)); - return ResponseEntity.status(ErrorUtils.getErrorStatus(request)).body(message.mapToView()); - } -} diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/ZaasErrorController.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/ZaasErrorController.java new file mode 100644 index 0000000000..cbf689b303 --- /dev/null +++ b/zaas-service/src/main/java/org/zowe/apiml/zaas/error/controllers/ZaasErrorController.java @@ -0,0 +1,96 @@ +/* + * 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.error.controllers; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.context.annotation.Primary; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.ResponseEntity; +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.message.api.ApiMessageView; +import org.zowe.apiml.message.core.Message; +import org.zowe.apiml.message.core.MessageService; +import org.zowe.apiml.zaas.error.ErrorUtils; + +import static org.apache.hc.core5.http.HttpStatus.*; + +/** + * Handles errors in REST API processing. + */ +@RestController +@Order(Ordered.HIGHEST_PRECEDENCE) +@Primary +@RequiredArgsConstructor +public class ZaasErrorController implements ErrorController { + + private static final String NOT_FOUND_ENDPOINT = "/not_found"; + public static final String ERROR_ENDPOINT = "/internal_error"; + + private final MessageService messageService; + + @RequestMapping(value = "/error/400", produces = "application/json") + public ResponseEntity<ApiMessageView> errorBadRequest(HttpServletRequest request) { + Message message = messageService.createMessage("org.zowe.apiml.common.badRequest"); + return ResponseEntity.status(SC_BAD_REQUEST).body(message.mapToView()); + } + + /** + * Not found endpoint controller + * Creates response and logs the error + * + * @param request Http request + * @return Http response entity + */ + @GetMapping(value = NOT_FOUND_ENDPOINT, produces = "application/json") + public ResponseEntity<ApiMessageView> notFound404HttpResponse(HttpServletRequest request) { + Message message = messageService.createMessage("org.zowe.apiml.common.endPointNotFound", + ErrorUtils.getForwardUri(request)); + return ResponseEntity.status(SC_NOT_FOUND).body(message.mapToView()); + } + + @RequestMapping(value = "/error/405", produces = "application/json") + public ResponseEntity<ApiMessageView> errorMethodNotAllowed(HttpServletRequest request) { + Message message = messageService.createMessage("org.zowe.apiml.common.methodNotAllowed"); + return ResponseEntity.status(SC_METHOD_NOT_ALLOWED).body(message.mapToView()); + } + + @RequestMapping(value = "/error/415", produces = "application/json") + public ResponseEntity<ApiMessageView> errorUnsupportedMediaType(HttpServletRequest request) { + Message message = messageService.createMessage("org.zowe.apiml.common.unsupportedMediaType"); + return ResponseEntity.status(SC_METHOD_NOT_ALLOWED).body(message.mapToView()); + } + + /** + * Error endpoint controller + * Creates response and logs the error + * + * @param request Http request + * @return Http response entity + */ + @SuppressWarnings("squid:S3752") + @RequestMapping(value = ERROR_ENDPOINT, produces = "application/json") + public ResponseEntity<ApiMessageView> error(HttpServletRequest request) { + final Throwable exc = (Throwable) request.getAttribute(ErrorUtils.ATTR_ERROR_EXCEPTION); + Message message = messageService.createMessage("org.zowe.apiml.common.internalRequestError", + ErrorUtils.getForwardUri(request), + ExceptionUtils.getMessage(exc), + ExceptionUtils.getRootCauseMessage(exc)); + + return ResponseEntity.status(SC_INTERNAL_SERVER_ERROR).body(message.mapToView()); + } + +} diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/security/config/NewSecurityConfiguration.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/security/config/NewSecurityConfiguration.java index c3bce5def3..a742efc278 100644 --- a/zaas-service/src/main/java/org/zowe/apiml/zaas/security/config/NewSecurityConfiguration.java +++ b/zaas-service/src/main/java/org/zowe/apiml/zaas/security/config/NewSecurityConfiguration.java @@ -57,7 +57,7 @@ import org.zowe.apiml.security.common.verify.CertificateValidator; import org.zowe.apiml.zaas.controllers.AuthController; import org.zowe.apiml.zaas.controllers.SafResourceAccessController; -import org.zowe.apiml.zaas.error.controllers.InternalServerErrorController; +import org.zowe.apiml.zaas.error.controllers.ZaasErrorController; import org.zowe.apiml.zaas.security.login.x509.X509AuthenticationProvider; import org.zowe.apiml.zaas.security.query.QueryFilter; import org.zowe.apiml.zaas.security.query.SuccessfulQueryHandler; @@ -602,7 +602,7 @@ public WebSecurityCustomizer webSecurityCustomizer() { // Endpoints that skip Spring Security completely // There is no CORS filter on these endpoints. If you require CORS processing, use a defined filter chain web.ignoring() - .requestMatchers(InternalServerErrorController.ERROR_ENDPOINT, "/error", + .requestMatchers(ZaasErrorController.ERROR_ENDPOINT, "/error", "/application/info", "/application/version", AuthController.CONTROLLER_PATH + AuthController.ALL_PUBLIC_KEYS_PATH, AuthController.CONTROLLER_PATH + AuthController.CURRENT_PUBLIC_KEYS_PATH); diff --git a/zaas-service/src/main/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandler.java b/zaas-service/src/main/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandler.java index a457beba4b..c2ac354106 100644 --- a/zaas-service/src/main/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandler.java +++ b/zaas-service/src/main/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandler.java @@ -10,35 +10,63 @@ package org.zowe.apiml.zaas.zaas; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.util.Assert; +import org.springframework.web.HttpMediaTypeException; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.zowe.apiml.zaas.security.service.saf.SafIdtAuthException; -import org.zowe.apiml.zaas.security.service.saf.SafIdtException; -import org.zowe.apiml.zaas.security.service.schema.source.AuthSchemeException; -import org.zowe.apiml.zaas.security.ticket.ApplicationNameNotFoundException; +import org.springframework.web.reactive.resource.NoResourceFoundException; +import org.springframework.web.server.MethodNotAllowedException; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.NoHandlerFoundException; import org.zowe.apiml.message.api.ApiMessageView; import org.zowe.apiml.message.core.MessageService; import org.zowe.apiml.passticket.IRRPassTicketGenerationException; +import org.zowe.apiml.security.common.auth.saf.EndpointImproprietyConfigureException; +import org.zowe.apiml.security.common.auth.saf.UnsupportedResourceClassException; import org.zowe.apiml.security.common.token.TokenExpireException; import org.zowe.apiml.security.common.token.TokenNotValidException; +import org.zowe.apiml.zaas.error.ErrorUtils; +import org.zowe.apiml.zaas.security.service.saf.SafIdtAuthException; +import org.zowe.apiml.zaas.security.service.saf.SafIdtException; +import org.zowe.apiml.zaas.security.service.schema.source.AuthSchemeException; +import org.zowe.apiml.zaas.security.ticket.ApplicationNameNotFoundException; import javax.management.ServiceNotFoundException; +import javax.net.ssl.SSLException; +@Slf4j @ControllerAdvice +@Order(Ordered.HIGHEST_PRECEDENCE) @RequiredArgsConstructor -public class ZaasExceptionHandler { +public class ZaasExceptionHandler implements HandlerExceptionResolver { + + private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + private static String WWW_AUTHENTICATE_FORMAT = "Basic realm=\"%s\""; + private static final String DEFAULT_REALM = "Realm"; + private final MessageService messageService; @ExceptionHandler(value = {IRRPassTicketGenerationException.class}) public ResponseEntity<ApiMessageView> handlePassTicketException(IRRPassTicketGenerationException ex) { + log.error(ex.getMessage()); ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", ex.getErrorCode().getMessage()).mapToView(); return ResponseEntity - .status(ex.getHttpStatus()) + .status(HttpStatus.INTERNAL_SERVER_ERROR) .contentType(MediaType.APPLICATION_JSON) .body(messageView); } @@ -96,4 +124,101 @@ public ResponseEntity<ApiMessageView> handleTokenExpiredException(TokenExpireExc .contentType(MediaType.APPLICATION_JSON) .body(messageView); } + + private static String createHeaderValue(String realm) { + Assert.notNull(realm, "realm cannot be null"); + return String.format(WWW_AUTHENTICATE_FORMAT, realm); + } + + public void setWwwAuthenticateResponse(HttpServletResponse response) { + response.addHeader(WWW_AUTHENTICATE, createHeaderValue(DEFAULT_REALM)); + } + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity<ApiMessageView> handleAuthenticationException(HttpServletResponse response, AuthenticationException authenticationException) { + log.debug("Unauthorized access", authenticationException); + setWwwAuthenticateResponse(response); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.common.unauthorized").mapToView(); + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity<ApiMessageView> handleAccessDeniedException(HttpServletRequest request, AccessDeniedException accessDeniedException) { + log.debug("Unauthenticated access", accessDeniedException); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.forbidden", request.getRequestURI()).mapToView(); + return ResponseEntity + .status(HttpStatus.FORBIDDEN) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler({NoResourceFoundException.class, NoHandlerFoundException.class}) + public ResponseEntity<ApiMessageView> handleNoResourceFoundException(NoResourceFoundException noResourceFoundException) { + log.debug("Resource not found", noResourceFoundException); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.common.notFound").mapToView(); + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(SSLException.class) + public ResponseEntity<ApiMessageView> handleSslException(HttpServletRequest request, SSLException sslException) { + log.debug("SSL exception", sslException); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.common.tlsError", request.getRequestURI(), sslException.getMessage()).mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + + @ExceptionHandler(UnsupportedResourceClassException.class) + public ResponseEntity<ApiMessageView> handleUnsupportedResourceClassException(UnsupportedResourceClassException unsupportedResourceClassException) { + log.debug("Unsupported resource class", unsupportedResourceClassException); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.common.auth.saf.endpoint.nonZoweClass", unsupportedResourceClassException.getResourceClass()).mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(EndpointImproprietyConfigureException.class) + public ResponseEntity<ApiMessageView> handleEndpointImproprietyConfigureException(EndpointImproprietyConfigureException improprietyConfigureException) { + log.debug("Endpoint is impropriety configured", improprietyConfigureException); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.common.auth.saf.endpoint.endpointImproprietyConfigure", improprietyConfigureException.getEndpoint()).mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity<ApiMessageView> handleInternalException(Exception exception) { + log.debug("Unexpected internal error", exception); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.common.internalServerError").mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + request.setAttribute(ErrorUtils.ATTR_ERROR_EXCEPTION, ex); + + if (ex instanceof MethodNotAllowedException || ex instanceof HttpRequestMethodNotSupportedException) { + return new ModelAndView("/error/405"); + } else if (ex instanceof HttpMediaTypeException) { + return new ModelAndView("/error/415"); + } else if (ex instanceof HttpMessageNotReadableException) { + return new ModelAndView("/error/400"); + } + + return null; + } + } diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/InternalServerErrorControllerTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/error/InternalServerErrorControllerTest.java deleted file mode 100644 index 36c5712820..0000000000 --- a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/InternalServerErrorControllerTest.java +++ /dev/null @@ -1,46 +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.error; - - -import org.junit.jupiter.api.Test; -import org.zowe.apiml.zaas.error.controllers.InternalServerErrorController; -import org.zowe.apiml.message.api.ApiMessageView; -import org.zowe.apiml.message.core.MessageService; -import org.zowe.apiml.message.yaml.YamlMessageService; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockHttpServletRequest; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import jakarta.servlet.RequestDispatcher; - -class InternalServerErrorControllerTest { - @Test - void testGenericError() { - MessageService messageService = new YamlMessageService(); - InternalServerErrorController errorController = new InternalServerErrorController(messageService); - - MockHttpServletRequest request = new MockHttpServletRequest(); - - request.setAttribute(ErrorUtils.ATTR_ERROR_EXCEPTION, new Exception("Hello")); - request.setAttribute(ErrorUtils.ATTR_ERROR_STATUS_CODE, 523); - request.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI, "/uri"); - - ResponseEntity<ApiMessageView> response = errorController.error(request); - - assertEquals(523, response.getStatusCodeValue()); - assertEquals("org.zowe.apiml.common.internalRequestError", response.getBody().getMessages().get(0).getMessageKey()); - assertTrue(response.getBody().getMessages().get(0).getMessageContent().contains("Hello")); - assertTrue(response.getBody().getMessages().get(0).getMessageContent().contains("/uri")); - } -} diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/ZaasErrorControllerTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/error/ZaasErrorControllerTest.java new file mode 100644 index 0000000000..a84e09302e --- /dev/null +++ b/zaas-service/src/test/java/org/zowe/apiml/zaas/error/ZaasErrorControllerTest.java @@ -0,0 +1,91 @@ +/* + * 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.error; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.zowe.apiml.message.core.Message; +import org.zowe.apiml.message.core.MessageType; +import org.zowe.apiml.message.template.MessageTemplate; +import org.zowe.apiml.zaas.error.controllers.ZaasErrorController; +import org.zowe.apiml.message.api.ApiMessageView; +import org.zowe.apiml.message.core.MessageService; +import org.zowe.apiml.message.yaml.YamlMessageService; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import jakarta.servlet.RequestDispatcher; + +class ZaasErrorControllerTest { + + private MockMvc mockMvc; + + @Nested + class GivenNotFoundErrorRequest { + + @BeforeEach + void init() { + MessageTemplate messageTemplate = new MessageTemplate("org.zowe.apiml.common.endPointNotFound", "number", MessageType.ERROR, "text"); + Message message = Message.of("org.zowe.apiml.common.endPointNotFound", messageTemplate, new Object[0]); + MessageService messageService = mock(MessageService.class); + + when(messageService.createMessage(anyString(), (Object[]) any())).thenReturn(message); + + NotFoundErrorController notFoundErrorController = new NotFoundErrorController(messageService); + MockitoAnnotations.openMocks(this); + + mockMvc = MockMvcBuilders + .standaloneSetup(notFoundErrorController) + .build(); + } + + @Test + void whenCallingWithRequestAttribute_thenReturnProperErrorStatus() throws Exception { + mockMvc.perform(get("/not_found").requestAttr("javax.servlet.error.status_code", 404)) + .andExpect(status().isNotFound()); + } + + } + + @Test + void testGenericError() { + MessageService messageService = new YamlMessageService(); + ZaasErrorController errorController = new ZaasErrorController(messageService); + + MockHttpServletRequest request = new MockHttpServletRequest(); + + request.setAttribute(ErrorUtils.ATTR_ERROR_EXCEPTION, new Exception("Hello")); + request.setAttribute(ErrorUtils.ATTR_ERROR_STATUS_CODE, 523); + request.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI, "/uri"); + + ResponseEntity<ApiMessageView> response = errorController.error(request); + + assertEquals(523, response.getStatusCodeValue()); + assertEquals("org.zowe.apiml.common.internalRequestError", response.getBody().getMessages().get(0).getMessageKey()); + assertTrue(response.getBody().getMessages().get(0).getMessageContent().contains("Hello")); + assertTrue(response.getBody().getMessages().get(0).getMessageContent().contains("/uri")); + } + +} diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/check/SafEndpointCheckTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/error/check/SafEndpointCheckTest.java deleted file mode 100644 index f9e6b91ef9..0000000000 --- a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/check/SafEndpointCheckTest.java +++ /dev/null @@ -1,84 +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.error.check; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.util.NestedServletException; -import org.zowe.apiml.message.api.ApiMessage; -import org.zowe.apiml.message.api.ApiMessageView; -import org.zowe.apiml.message.core.MessageService; -import org.zowe.apiml.message.yaml.YamlMessageService; -import org.zowe.apiml.security.common.auth.saf.EndpointImproprietyConfigureException; -import org.zowe.apiml.security.common.auth.saf.UnsupportedResourceClassException; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class SafEndpointCheckTest { - - private static final String ENDPOINT_URL = "http://endpoint/url"; - private static final String UNEXPECTED_RESOURCE_CLASS = "UnexpectedResourceClass"; - - private SafEndpointCheck safEndpointCheck; - - @BeforeAll - void setUp() { - MessageService messageService = new YamlMessageService("/security-common-log-messages.yml"); - safEndpointCheck = new SafEndpointCheck(messageService); - } - - private ApiMessage getApiMessage(ResponseEntity<ApiMessageView> response) { - assertNotNull(response); - assertNotNull(response.getBody()); - List<ApiMessage> messages = response.getBody().getMessages(); - assertEquals(1, messages.size()); - return messages.get(0); - } - - @Test - void givenCoveredEndpointImproprietyConfigureException_whenCheck_thenReturnMessage() { - EndpointImproprietyConfigureException eice = new EndpointImproprietyConfigureException("An error", ENDPOINT_URL); - NestedServletException coveredException = new NestedServletException("msg", eice); - ResponseEntity<ApiMessageView> response = safEndpointCheck.checkError(new MockHttpServletRequest(), coveredException); - - ApiMessage message = getApiMessage(response); - assertEquals("org.zowe.apiml.security.common.auth.saf.endpoint.endpointImproprietyConfigure", message.getMessageKey()); - assertEquals("ZWEAT603E", message.getMessageNumber()); - assertTrue(message.getMessageContent().contains(ENDPOINT_URL)); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - } - - @Test - void givenCoveredUnsupportedResourceClassException_whenCheck_thenReturnMessage() { - UnsupportedResourceClassException urce = new UnsupportedResourceClassException(UNEXPECTED_RESOURCE_CLASS, "message"); - NestedServletException coveredException = new NestedServletException("msg", urce); - ResponseEntity<ApiMessageView> response = safEndpointCheck.checkError(new MockHttpServletRequest(), coveredException); - - ApiMessage message = getApiMessage(response); - assertEquals("org.zowe.apiml.security.common.auth.saf.endpoint.nonZoweClass", message.getMessageKey()); - assertEquals("ZWEAT602E", message.getMessageNumber()); - assertTrue(message.getMessageContent().contains(UNEXPECTED_RESOURCE_CLASS)); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - } - - @Test - void givenNonEndpointException_whenCheck_thenReturnNull() { - assertNull(safEndpointCheck.checkError(new MockHttpServletRequest(), new RuntimeException())); - } - -} \ No newline at end of file diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorControllerTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorControllerTest.java deleted file mode 100644 index f5cd0d6700..0000000000 --- a/zaas-service/src/test/java/org/zowe/apiml/zaas/error/controllers/NotFoundErrorControllerTest.java +++ /dev/null @@ -1,63 +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.error.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.MockitoAnnotations; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.zowe.apiml.message.core.Message; -import org.zowe.apiml.message.core.MessageService; -import org.zowe.apiml.message.core.MessageType; -import org.zowe.apiml.message.template.MessageTemplate; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@ExtendWith(SpringExtension.class) -class NotFoundErrorControllerTest { - - private MockMvc mockMvc; - - @BeforeEach - void init() { - MessageTemplate messageTemplate = new MessageTemplate("org.zowe.apiml.common.endPointNotFound", "number", MessageType.ERROR, "text"); - Message message = Message.of("org.zowe.apiml.common.endPointNotFound", messageTemplate, new Object[0]); - MessageService messageService = mock(MessageService.class); - - when(messageService.createMessage(anyString(), (Object[]) any())).thenReturn(message); - - NotFoundErrorController notFoundErrorController = new NotFoundErrorController(messageService); - MockitoAnnotations.openMocks(this); - - mockMvc = MockMvcBuilders - .standaloneSetup(notFoundErrorController) - .build(); - } - - @Nested - class GivenNotFoundErrorRequest { - @Test - void whenCallingWithRequestAttribute_thenReturnProperErrorStatus() throws Exception { - mockMvc.perform(get("/not_found").requestAttr("javax.servlet.error.status_code", 404)) - .andExpect(status().isNotFound()); - } - } - -} diff --git a/zaas-service/src/test/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandlerTest.java b/zaas-service/src/test/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandlerTest.java new file mode 100644 index 0000000000..9f74ca44de --- /dev/null +++ b/zaas-service/src/test/java/org/zowe/apiml/zaas/zaas/ZaasExceptionHandlerTest.java @@ -0,0 +1,44 @@ +/* + * 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.zaas; + +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ZaasExceptionHandlerTest { + + @LocalServerPort + private int port; + + @BeforeEach + void init() { + RestAssured.baseURI = "https://localhost"; + RestAssured.port = port; + RestAssured.useRelaxedHTTPSValidation(); + } + + @Test + void givenUnknownEndpoint_whenCallZaas_thenReturns404WithMessage() { + given().when() + .get("/unknown/endpoint") + .then() + .statusCode(404) + .body("messages[0].messageKey", is("org.zowe.apiml.apicatalog.serviceNotFound")); + } + +}