From ee4c94a4bb8d588576b73f4dfcb5390c0c2f5155 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Tue, 28 Nov 2023 10:01:41 +0100 Subject: [PATCH 01/19] New ZAAS safIdt endpoint to generate SAF ID tokens for authenticated user. Signed-off-by: Petr Weinfurt --- .../zowe/apiml/zaas/ZaasSafIdtResponse.java | 23 ++++++ .../apiml/gateway/zaas/ZaasController.java | 51 +++++++++++++ .../gateway/zaas/ZaasControllerTest.java | 74 +++++++++++++++++-- 3 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java diff --git a/common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java b/common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java new file mode 100644 index 0000000000..66a5183a39 --- /dev/null +++ b/common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java @@ -0,0 +1,23 @@ +/* + * 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; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ZaasSafIdtResponse { + + private String safIdToken; +} diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java index b3994f8491..a4993796af 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java @@ -18,6 +18,9 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.zowe.apiml.gateway.security.service.saf.SafIdtAuthException; +import org.zowe.apiml.gateway.security.service.saf.SafIdtException; +import org.zowe.apiml.gateway.security.service.saf.SafIdtProvider; import org.zowe.apiml.gateway.security.service.schema.source.AuthSource; import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; @@ -27,8 +30,11 @@ import org.zowe.apiml.passticket.PassTicketService; import org.zowe.apiml.ticket.TicketRequest; import org.zowe.apiml.ticket.TicketResponse; +import org.zowe.apiml.zaas.ZaasSafIdtResponse; import org.zowe.apiml.zaas.ZaasTokenResponse; +import java.util.Arrays; + import static org.zowe.apiml.gateway.filters.pre.ExtractAuthSourceFilter.AUTH_SOURCE_ATTR; import static org.zowe.apiml.gateway.filters.pre.ExtractAuthSourceFilter.AUTH_SOURCE_PARSED_ATTR; import static org.zowe.apiml.security.SecurityUtils.COOKIE_AUTH_NAME; @@ -44,6 +50,7 @@ public class ZaasController { private final MessageService messageService; private final PassTicketService passTicketService; private final ZosmfService zosmfService; + private final SafIdtProvider safIdtProvider; @PostMapping(path = "ticket", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides PassTicket for authenticated user.") @@ -116,4 +123,48 @@ public ResponseEntity getZoweJwt(@RequestAttribute(AUTH_SOURCE_ATTR) Aut } } + @PostMapping(path = "safIdt", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Provides SAF Identity Token for authenticated user.") + public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) { + + final String userId = authSourceParsed.getUserId(); + if (StringUtils.isEmpty(userId)) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .build(); + } + + final String applicationName = ticketRequest.getApplicationName(); + if (StringUtils.isBlank(applicationName)) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.invalidApplicationName").mapToView(); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(messageView); + } + + char[] passTicket = "".toCharArray(); + try { + passTicket = passTicketService.generate(userId, applicationName).toCharArray(); + String safIdToken = safIdtProvider.generate(userId, passTicket, applicationName); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new ZaasSafIdtResponse(safIdToken)); + + } catch (IRRPassTicketGenerationException e) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", + e.getErrorCode().getMessage()).mapToView(); + return ResponseEntity + .status(e.getHttpStatus()) + .body(messageView); + } catch (SafIdtException | SafIdtAuthException e) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.idt.failed", e.getMessage()).mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(messageView); + } finally { + Arrays.fill(passTicket, (char) 0); + } + } + } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java index ac91ab5942..862bb508d2 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java @@ -23,6 +23,8 @@ 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.gateway.security.service.saf.SafIdtException; +import org.zowe.apiml.gateway.security.service.saf.SafIdtProvider; import org.zowe.apiml.gateway.security.service.schema.source.AuthSource; import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; import org.zowe.apiml.gateway.security.service.schema.source.JwtAuthSource; @@ -62,6 +64,9 @@ class ZaasControllerTest { @Mock private ZosmfService zosmfService; + @Mock + private SafIdtProvider safIdtProvider; + private MockMvc mockMvc; private JSONObject ticketBody; private AuthSource authSource; @@ -70,18 +75,20 @@ class ZaasControllerTest { private static final String PASSTICKET_URL = "/gateway/zaas/ticket"; private static final String ZOSMF_TOKEN_URL = "/gateway/zaas/zosmf"; private static final String ZOWE_TOKEN_URL = "/gateway/zaas/zoweJwt"; + private static final String SAFIDT_URL = "/gateway/zaas/safIdt"; private static final String USER = "test_user"; private static final String PASSTICKET = "test_passticket"; private static final String APPLID = "test_applid"; private static final String JWT_TOKEN = "jwt_test_token"; + private static final String SAFIDT = "saf_id_token"; @BeforeEach void setUp() throws IRRPassTicketGenerationException, JSONException { MessageService messageService = new YamlMessageService("/gateway-messages.yml"); when(passTicketService.generate(anyString(), anyString())).thenReturn(PASSTICKET); - ZaasController zaasController = new ZaasController(authSourceService, messageService, passTicketService, zosmfService); + ZaasController zaasController = new ZaasController(authSourceService, messageService, passTicketService, zosmfService, safIdtProvider); mockMvc = MockMvcBuilders.standaloneSetup(zaasController).build(); ticketBody = new JSONObject() .put("applicationName", APPLID); @@ -146,16 +153,40 @@ void whenRequestPassticketAndNoApplNameProvided_thenBadRequest() throws Exceptio .andExpect(jsonPath("$.messages[0].messageContent", is("The 'applicationName' parameter name is missing."))); } + @Test + void whenRequestSafIdtAndApplNameProvided_thenResponseOk() throws Exception { + when(safIdtProvider.generate(USER, PASSTICKET.toCharArray(), APPLID)).thenReturn(SAFIDT); + mockMvc.perform(post(SAFIDT_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(ticketBody.toString()) + .requestAttr(AUTH_SOURCE_PARSED_ATTR, authParsedSource)) + .andExpect(status().is(SC_OK)) + .andExpect(jsonPath("$.safIdToken", is(SAFIDT))); + } + + @Test + void whenRequestSafIdtAndNoApplNameProvided_thenBadRequest() throws Exception { + when(safIdtProvider.generate(USER, PASSTICKET.toCharArray(), APPLID)).thenReturn(SAFIDT); + ticketBody.put("applicationName", ""); + + mockMvc.perform(post(SAFIDT_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(ticketBody.toString()) + .requestAttr(AUTH_SOURCE_PARSED_ATTR, authParsedSource)) + .andExpect(status().is(SC_BAD_REQUEST)) + .andExpect(jsonPath("$.messages", hasSize(1))) + .andExpect(jsonPath("$.messages[0].messageType").value("ERROR")) + .andExpect(jsonPath("$.messages[0].messageNumber").value("ZWEAG140E")) + .andExpect(jsonPath("$.messages[0].messageContent", is("The 'applicationName' parameter name is missing."))); + } + @Nested class WhenExceptionOccurs { - @BeforeEach - void setUp() throws IRRPassTicketGenerationException { - when(passTicketService.generate(anyString(), anyString())).thenThrow(new IRRPassTicketGenerationException(8, 8, 8)); - } - @Test void whenRequestingPassticket_thenInternalServerError() throws Exception { + when(passTicketService.generate(USER, APPLID)).thenThrow(new IRRPassTicketGenerationException(8, 8, 8)); + mockMvc.perform(post(PASSTICKET_URL) .contentType(MediaType.APPLICATION_JSON) .content(ticketBody.toString()) @@ -196,6 +227,35 @@ void whenRequestingZoweTokens_thenInternalServerError() throws Exception { .andExpect(jsonPath("$.messages[0].messageContent", containsString(expectedMessage))); } + @Test + void whenRequestingSafIdtAndPassticketException_thenInternalServerError() throws Exception { + when(passTicketService.generate(USER, APPLID)).thenThrow(new IRRPassTicketGenerationException(8, 8, 8)); + + mockMvc.perform(post(SAFIDT_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(ticketBody.toString()) + .requestAttr(AUTH_SOURCE_PARSED_ATTR, authParsedSource)) + .andExpect(status().is(SC_INTERNAL_SERVER_ERROR)) + .andExpect(jsonPath("$.messages", hasSize(1))) + .andExpect(jsonPath("$.messages[0].messageType").value("ERROR")) + .andExpect(jsonPath("$.messages[0].messageNumber").value("ZWEAG141E")) + .andExpect(jsonPath("$.messages[0].messageContent", is("The generation of the PassTicket failed. Reason: An internal error was encountered."))); + } + + @Test + void whenRequestingSafIdtAndSafIdtException_thenInternalServerError() throws Exception { + when(safIdtProvider.generate(USER, PASSTICKET.toCharArray(), APPLID)).thenThrow(new SafIdtException("Test exception message.")); + + mockMvc.perform(post(SAFIDT_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(ticketBody.toString()) + .requestAttr(AUTH_SOURCE_PARSED_ATTR, authParsedSource)) + .andExpect(status().is(SC_INTERNAL_SERVER_ERROR)) + .andExpect(jsonPath("$.messages", hasSize(1))) + .andExpect(jsonPath("$.messages[0].messageType").value("ERROR")) + .andExpect(jsonPath("$.messages[0].messageNumber").value("ZWEAG150E")) + .andExpect(jsonPath("$.messages[0].messageContent", is("SAF IDT generation failed. Reason: Test exception message."))); + } } } @@ -208,7 +268,7 @@ void setUp() { } @ParameterizedTest - @ValueSource(strings = {PASSTICKET_URL}) + @ValueSource(strings = {PASSTICKET_URL, SAFIDT_URL}) void thenRespondUnauthorized(String url) throws Exception { mockMvc.perform(post(url) .contentType(MediaType.APPLICATION_JSON) From 31e9b615c6a588819434eaacc39e3ad403ec0fbd Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Tue, 28 Nov 2023 10:03:14 +0100 Subject: [PATCH 02/19] IT tests for safIdt endpoint. Signed-off-by: Petr Weinfurt --- .../integration/zaas/SafIdTokensTest.java | 187 ++++++++++++++++++ .../integration/zaas/ZaasNegativeTest.java | 1 + .../apiml/integration/zaas/ZaasTestUtil.java | 2 + .../zowe/apiml/util/requests/Endpoints.java | 1 + 4 files changed, 191 insertions(+) create mode 100644 integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java new file mode 100644 index 0000000000..754b2e854e --- /dev/null +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java @@ -0,0 +1,187 @@ +/* + * 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.integration.zaas; + +import io.restassured.RestAssured; +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.MethodSource; +import org.zowe.apiml.passticket.PassTicketService; +import org.zowe.apiml.ticket.TicketRequest; +import org.zowe.apiml.util.TestWithStartedInstances; +import org.zowe.apiml.util.categories.ZaasTest; +import org.zowe.apiml.util.config.ConfigReader; + +import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Collections; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.*; +import static org.zowe.apiml.integration.zaas.ZaasTestUtil.*; +import static org.zowe.apiml.util.SecurityUtils.*; + +@ZaasTest +public class SafIdTokensTest implements TestWithStartedInstances { + + private final static String APPLICATION_NAME = ConfigReader.environmentConfiguration().getDiscoverableClientConfiguration().getApplId(); + + @BeforeEach + void setUpCertificate() { + RestAssured.config = RestAssured.config().sslConfig(getConfiguredSslConfig()); + } + + @Nested + class WhenGeneratingSafIdToken_thenReturnValidToken { + + private final TicketRequest ticketRequest = new TicketRequest(APPLICATION_NAME); + + @Test + void givenValidZosmfToken() { + String zosmfToken = getZosmfJwtToken(); + + //@formatter:off + given() + .cookie(COOKIE, zosmfToken) + .body(ticketRequest) + .contentType(JSON) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(SC_OK) + .body("safIdToken", not("")); + //@formatter:on + } + + @Test + void givenValidZoweTokenWithLtpa() throws UnrecoverableKeyException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { + String ltpaToken = getZosmfToken(LTPA_COOKIE); + String zoweToken = generateZoweJwtWithLtpa(ltpaToken); + + //@formatter:off + given() + .header("Authorization", "Bearer " + zoweToken) + .body(ticketRequest) + .contentType(JSON) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(SC_OK) + .body("safIdToken", not("")); + //@formatter:on + } + + @Test + void givenValidAccessToken() { + String serviceId = "gateway"; + String pat = personalAccessToken(Collections.singleton(serviceId)); + + //@formatter:off + given() + .header("Authorization", "Bearer " + pat) + .header("X-Service-Id", serviceId) + .body(ticketRequest) + .contentType(JSON) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(SC_OK) + .body("safIdToken", not("")); + //@formatter:on + } + + @ParameterizedTest + @MethodSource("org.zowe.apiml.integration.zaas.ZaasTestUtil#provideClientCertificates") + void givenX509Certificate(String certificate) { + //@formatter:off + given() + .header("Client-Cert", certificate) + .body(ticketRequest) + .contentType(JSON) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(SC_OK) + .body("safIdToken", not("")); + //@formatter:on + } + + @Test + void givenValidOAuthToken() { + String oAuthToken = validOktaAccessToken(true); + + //@formatter:off + given() + .cookie(COOKIE, oAuthToken) + .body(ticketRequest) + .contentType(JSON) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(SC_OK) + .body("ticket", not("")); + //@formatter:on + } + } + + @Nested + class WhenGeneratingSafIdToken_returnBadRequest { + + private final String jwt = getZosmfJwtToken(); + + @Test + void givenNoApplicationName() { + String expectedMessage = "The 'applicationName' parameter name is missing."; + + //@formatter:off + given() + .contentType(JSON) + .body(new TicketRequest()) + .cookie(COOKIE, jwt) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(is(SC_BAD_REQUEST)) + .body("messages.find { it.messageNumber == 'ZWEAG140E' }.messageContent", equalTo(expectedMessage)); + //@formatter:on + } + + @Test + void givenInvalidApplicationName() { + String expectedMessage = "The generation of the PassTicket failed. Reason:"; + TicketRequest ticketRequest = new TicketRequest(PassTicketService.DefaultPassTicketImpl.UNKNOWN_APPLID); + + //@formatter:off + given() + .contentType(JSON) + .body(ticketRequest) + .cookie(COOKIE, jwt) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(is(SC_BAD_REQUEST)) + .body("messages.find { it.messageNumber == 'ZWEAG141E' }.messageContent", containsString(expectedMessage)); + //@formatter:on + } + } + //@formatter:on + + // Additional negative tests are in ZaasNegativeTest since they are common for the whole service +} diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index df79db03e0..fb026fc45a 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -41,6 +41,7 @@ public class ZaasNegativeTest { add(ZAAS_TICKET_URI); add(ZAAS_ZOWE_URI); add(ZAAS_ZOSMF_URI); + add(ZAAS_SAFIDT_URI); }}; private static final Set tokens = new HashSet() {{ diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasTestUtil.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasTestUtil.java index 2eba9ab061..07cc30ad88 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasTestUtil.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasTestUtil.java @@ -34,6 +34,8 @@ public class ZaasTestUtil { static final URI ZAAS_ZOSMF_URI = HttpRequestUtils.getUriFromGateway(ZAAS_ZOSMF_ENDPOINT); static final URI ZAAS_ZOWE_URI = HttpRequestUtils.getUriFromGateway(ZAAS_ZOWE_ENDPOINT); + static final URI ZAAS_SAFIDT_URI = HttpRequestUtils.getUriFromGateway(ZAAS_SAFIDT_ENDPOINT); + static final String COOKIE = "apimlAuthenticationToken"; static final String LTPA_COOKIE = "LtpaToken2"; diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/requests/Endpoints.java b/integration-tests/src/test/java/org/zowe/apiml/util/requests/Endpoints.java index 53ef23a0f7..eb78d40c5c 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/requests/Endpoints.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/requests/Endpoints.java @@ -27,6 +27,7 @@ public class Endpoints { public final static String ZAAS_TICKET_ENDPOINT = "/gateway/zaas/ticket"; public final static String ZAAS_ZOSMF_ENDPOINT = "/gateway/zaas/zosmf"; public final static String ZAAS_ZOWE_ENDPOINT = "/gateway/zaas/zoweJwt"; + public final static String ZAAS_SAFIDT_ENDPOINT = "/gateway/zaas/safIdt"; public final static String ROUTED_LOGIN_OLD_FORMAT = "/gateway/api/v1/auth/login"; public final static String ROUTED_LOGOUT_OLD_FORMAT = "/gateway/api/v1/auth/logout"; From e21c6de0862f71c280b9bfd3b8386f24206de0bf Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Thu, 30 Nov 2023 14:44:26 +0100 Subject: [PATCH 03/19] Address code review comments. Signed-off-by: Petr Weinfurt --- .../service/TokenCreationService.java | 14 +++ .../apiml/gateway/zaas/ZaasController.java | 20 +--- .../service/TokenCreationServiceTest.java | 40 ++++++- .../gateway/zaas/ZaasControllerTest.java | 12 +- .../integration/zaas/SafIdTokensTest.java | 109 ++++++++++++++---- 5 files changed, 151 insertions(+), 44 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java index 3bad98eae0..7a90213acb 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java @@ -18,12 +18,14 @@ import org.springframework.stereotype.Service; import org.zowe.apiml.gateway.security.login.Providers; import org.zowe.apiml.gateway.security.login.zosmf.ZosmfAuthenticationProvider; +import org.zowe.apiml.gateway.security.service.saf.SafIdtProvider; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; import org.zowe.apiml.passticket.IRRPassTicketGenerationException; import org.zowe.apiml.passticket.PassTicketService; import org.zowe.apiml.security.common.error.AuthenticationTokenException; import org.zowe.apiml.security.common.token.TokenAuthentication; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -37,6 +39,7 @@ public class TokenCreationService { private final ZosmfService zosmfService; private final PassTicketService passTicketService; private final AuthenticationService authenticationService; + private final SafIdtProvider safIdtProvider; @Value("${apiml.security.zosmf.applid:IZUDFLT}") protected String zosmfApplId; @@ -74,6 +77,17 @@ public Map createZosmfTokensWithoutCredentials(S return zosmfService.authenticate(new UsernamePasswordAuthenticationToken(user, passTicket)).getTokens(); } + public String createSafIdTokenWithoutCredentials(String user, String applId) throws IRRPassTicketGenerationException { + + char[] passTicket = "".toCharArray(); + try { + passTicket = passTicketService.generate(user, applId).toCharArray(); + return safIdtProvider.generate(user, passTicket, applId); + } finally { + Arrays.fill(passTicket, (char) 0); + } + } + private boolean isZosmfAvailable() { try { return providers.isZosfmUsed() && providers.isZosmfAvailable(); diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java index a4993796af..5ba9a67558 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java @@ -18,9 +18,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.zowe.apiml.gateway.security.service.saf.SafIdtAuthException; -import org.zowe.apiml.gateway.security.service.saf.SafIdtException; -import org.zowe.apiml.gateway.security.service.saf.SafIdtProvider; +import org.zowe.apiml.gateway.security.service.TokenCreationService; import org.zowe.apiml.gateway.security.service.schema.source.AuthSource; import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; @@ -30,11 +28,8 @@ import org.zowe.apiml.passticket.PassTicketService; import org.zowe.apiml.ticket.TicketRequest; import org.zowe.apiml.ticket.TicketResponse; -import org.zowe.apiml.zaas.ZaasSafIdtResponse; import org.zowe.apiml.zaas.ZaasTokenResponse; -import java.util.Arrays; - import static org.zowe.apiml.gateway.filters.pre.ExtractAuthSourceFilter.AUTH_SOURCE_ATTR; import static org.zowe.apiml.gateway.filters.pre.ExtractAuthSourceFilter.AUTH_SOURCE_PARSED_ATTR; import static org.zowe.apiml.security.SecurityUtils.COOKIE_AUTH_NAME; @@ -50,7 +45,7 @@ public class ZaasController { private final MessageService messageService; private final PassTicketService passTicketService; private final ZosmfService zosmfService; - private final SafIdtProvider safIdtProvider; + private final TokenCreationService tokenCreationService; @PostMapping(path = "ticket", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides PassTicket for authenticated user.") @@ -142,14 +137,11 @@ public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketReq .body(messageView); } - char[] passTicket = "".toCharArray(); try { - passTicket = passTicketService.generate(userId, applicationName).toCharArray(); - String safIdToken = safIdtProvider.generate(userId, passTicket, applicationName); - + String safIdToken = tokenCreationService.createSafIdTokenWithoutCredentials(userId, applicationName); return ResponseEntity .status(HttpStatus.OK) - .body(new ZaasSafIdtResponse(safIdToken)); + .body(new ZaasTokenResponse(null, safIdToken)); } catch (IRRPassTicketGenerationException e) { ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", @@ -157,13 +149,11 @@ public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketReq return ResponseEntity .status(e.getHttpStatus()) .body(messageView); - } catch (SafIdtException | SafIdtAuthException e) { + } catch (Exception e) { ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.idt.failed", e.getMessage()).mapToView(); return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(messageView); - } finally { - Arrays.fill(passTicket, (char) 0); } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java index 9638aabe32..176d964063 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java @@ -19,6 +19,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.zowe.apiml.gateway.security.login.Providers; import org.zowe.apiml.gateway.security.login.zosmf.ZosmfAuthenticationProvider; +import org.zowe.apiml.gateway.security.service.saf.SafIdtException; +import org.zowe.apiml.gateway.security.service.saf.SafIdtProvider; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; import org.zowe.apiml.passticket.IRRPassTicketGenerationException; import org.zowe.apiml.passticket.PassTicketService; @@ -58,15 +60,19 @@ class TokenCreationServiceTest { @Mock private ZosmfService zosmfService; + @Mock + private SafIdtProvider safIdtProvider; + private final String VALID_USER_ID = "validUserId"; private final String VALID_ZOSMF_TOKEN = "validZosmfToken"; private final String VALID_APIML_TOKEN = "validApimlToken"; private final String PASSTICKET = "passTicket"; private final String VALID_ZOSMF_APPLID = "IZUDFLT"; + private final String VALID_SAFIDT = "validSAFIdentityToken"; @BeforeEach void setUp() { - underTest = new TokenCreationService(providers, Optional.of(zosmfAuthenticationProvider), zosmfService, passTicketService, authenticationService); + underTest = new TokenCreationService(providers, Optional.of(zosmfAuthenticationProvider), zosmfService, passTicketService, authenticationService, safIdtProvider); underTest.zosmfApplId = "IZUDFLT"; } @@ -140,4 +146,36 @@ void givenZosmfAvailable_whenCreatingZosmfToken_thenReturnEmptyResult() throws I assertEquals(expectedTokens, tokens); } + @Test + void givenPassTicketGenerated_whenCreatingSafIdToken_thenTokenReturned() throws IRRPassTicketGenerationException { + when(passTicketService.generate(VALID_USER_ID, VALID_ZOSMF_APPLID)).thenReturn(PASSTICKET); + when(safIdtProvider.generate(VALID_USER_ID, PASSTICKET.toCharArray(), VALID_ZOSMF_APPLID)).thenReturn(VALID_SAFIDT); + + String safIdt = underTest.createSafIdTokenWithoutCredentials(VALID_USER_ID, VALID_ZOSMF_APPLID); + + assertEquals(VALID_SAFIDT, safIdt); + } + + @Test + void givenPassTicketException_whenCreatingSafIdToken_thenExceptionThrown() throws IRRPassTicketGenerationException { + when(passTicketService.generate(VALID_USER_ID, VALID_ZOSMF_APPLID)).thenThrow(new IRRPassTicketGenerationException(8, 8, 8)); + + Exception e = assertThrows(IRRPassTicketGenerationException.class, () -> { + underTest.createSafIdTokenWithoutCredentials(VALID_USER_ID, VALID_ZOSMF_APPLID); + }); + + assertEquals(e.getMessage(), "Error on generation of PassTicket: An internal error was encountered."); + } + + @Test + void givenSafIdtException_whenCreatingSafIdToken_thenExceptionThrown() throws IRRPassTicketGenerationException { + when(passTicketService.generate(VALID_USER_ID, VALID_ZOSMF_APPLID)).thenReturn(PASSTICKET); + when(safIdtProvider.generate(VALID_USER_ID, PASSTICKET.toCharArray(), VALID_ZOSMF_APPLID)).thenThrow(new SafIdtException("Test exception")); + + Exception e = assertThrows(SafIdtException.class, () -> { + underTest.createSafIdTokenWithoutCredentials(VALID_USER_ID, VALID_ZOSMF_APPLID); + }); + + assertEquals(e.getMessage(), "Test exception"); + } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java index 862bb508d2..336f5e4769 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java @@ -23,8 +23,8 @@ 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.gateway.security.service.TokenCreationService; import org.zowe.apiml.gateway.security.service.saf.SafIdtException; -import org.zowe.apiml.gateway.security.service.saf.SafIdtProvider; import org.zowe.apiml.gateway.security.service.schema.source.AuthSource; import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; import org.zowe.apiml.gateway.security.service.schema.source.JwtAuthSource; @@ -65,7 +65,7 @@ class ZaasControllerTest { private ZosmfService zosmfService; @Mock - private SafIdtProvider safIdtProvider; + private TokenCreationService tokenCreationService; private MockMvc mockMvc; private JSONObject ticketBody; @@ -88,7 +88,7 @@ void setUp() throws IRRPassTicketGenerationException, JSONException { MessageService messageService = new YamlMessageService("/gateway-messages.yml"); when(passTicketService.generate(anyString(), anyString())).thenReturn(PASSTICKET); - ZaasController zaasController = new ZaasController(authSourceService, messageService, passTicketService, zosmfService, safIdtProvider); + ZaasController zaasController = new ZaasController(authSourceService, messageService, passTicketService, zosmfService, tokenCreationService); mockMvc = MockMvcBuilders.standaloneSetup(zaasController).build(); ticketBody = new JSONObject() .put("applicationName", APPLID); @@ -155,7 +155,7 @@ void whenRequestPassticketAndNoApplNameProvided_thenBadRequest() throws Exceptio @Test void whenRequestSafIdtAndApplNameProvided_thenResponseOk() throws Exception { - when(safIdtProvider.generate(USER, PASSTICKET.toCharArray(), APPLID)).thenReturn(SAFIDT); + when(tokenCreationService.createSafIdTokenWithoutCredentials(USER, APPLID)).thenReturn(SAFIDT); mockMvc.perform(post(SAFIDT_URL) .contentType(MediaType.APPLICATION_JSON) .content(ticketBody.toString()) @@ -166,7 +166,7 @@ void whenRequestSafIdtAndApplNameProvided_thenResponseOk() throws Exception { @Test void whenRequestSafIdtAndNoApplNameProvided_thenBadRequest() throws Exception { - when(safIdtProvider.generate(USER, PASSTICKET.toCharArray(), APPLID)).thenReturn(SAFIDT); + when(tokenCreationService.createSafIdTokenWithoutCredentials(USER, APPLID)).thenReturn(SAFIDT); ticketBody.put("applicationName", ""); mockMvc.perform(post(SAFIDT_URL) @@ -244,7 +244,7 @@ void whenRequestingSafIdtAndPassticketException_thenInternalServerError() throws @Test void whenRequestingSafIdtAndSafIdtException_thenInternalServerError() throws Exception { - when(safIdtProvider.generate(USER, PASSTICKET.toCharArray(), APPLID)).thenThrow(new SafIdtException("Test exception message.")); + when(tokenCreationService.createSafIdTokenWithoutCredentials(USER, APPLID)).thenThrow(new SafIdtException("Test exception message.")); mockMvc.perform(post(SAFIDT_URL) .contentType(MediaType.APPLICATION_JSON) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java index 754b2e854e..77bb651e50 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java @@ -12,6 +12,7 @@ import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -31,8 +32,8 @@ import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_OK; +import static io.restassured.http.ContentType.XML; +import static javax.servlet.http.HttpServletResponse.*; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.*; import static org.zowe.apiml.integration.zaas.ZaasTestUtil.*; @@ -62,11 +63,12 @@ void givenValidZosmfToken() { .cookie(COOKIE, zosmfToken) .body(ticketRequest) .contentType(JSON) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(SC_OK) - .body("safIdToken", not("")); + .body("safIdToken", not(isEmptyOrNullString())) + .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -80,11 +82,12 @@ void givenValidZoweTokenWithLtpa() throws UnrecoverableKeyException, Certificate .header("Authorization", "Bearer " + zoweToken) .body(ticketRequest) .contentType(JSON) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(SC_OK) - .body("safIdToken", not("")); + .body("safIdToken", not(isEmptyOrNullString())) + .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -99,11 +102,12 @@ void givenValidAccessToken() { .header("X-Service-Id", serviceId) .body(ticketRequest) .contentType(JSON) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(SC_OK) - .body("safIdToken", not("")); + .body("safIdToken", not(isEmptyOrNullString())) + .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -115,11 +119,12 @@ void givenX509Certificate(String certificate) { .header("Client-Cert", certificate) .body(ticketRequest) .contentType(JSON) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(SC_OK) - .body("safIdToken", not("")); + .body("safIdToken", not(isEmptyOrNullString())) + .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -132,11 +137,12 @@ void givenValidOAuthToken() { .cookie(COOKIE, oAuthToken) .body(ticketRequest) .contentType(JSON) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(SC_OK) - .body("ticket", not("")); + .body("ticket", not(isEmptyOrNullString())) + .body("cookieName", isEmptyOrNullString()); //@formatter:on } } @@ -155,9 +161,9 @@ void givenNoApplicationName() { .contentType(JSON) .body(new TicketRequest()) .cookie(COOKIE, jwt) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(is(SC_BAD_REQUEST)) .body("messages.find { it.messageNumber == 'ZWEAG140E' }.messageContent", equalTo(expectedMessage)); //@formatter:on @@ -173,15 +179,74 @@ void givenInvalidApplicationName() { .contentType(JSON) .body(ticketRequest) .cookie(COOKIE, jwt) - .when() + .when() .post(ZAAS_SAFIDT_URI) - .then() + .then() .statusCode(is(SC_BAD_REQUEST)) .body("messages.find { it.messageNumber == 'ZWEAG141E' }.messageContent", containsString(expectedMessage)); //@formatter:on } + + @Test + @Disabled("Enable once it runs on z/OS. Mimic the behaviour in Mock service.") + void givenLongApplicationName() { + //@formatter:off + given() + .contentType(JSON) + .body(new TicketRequest("TooLongAppName")) + .cookie(COOKIE, jwt) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(is(SC_BAD_REQUEST)); + //@formatter:on + } + } - //@formatter:on + @Nested + class WhenGeneratingSafIdToken_returnNotFound { + + private final String jwt = getZosmfJwtToken(); + + @Test + void givenNoContentType() { + //@formatter:off + given() + .body(new TicketRequest(APPLICATION_NAME)) + .cookie(COOKIE, jwt) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(is(SC_NOT_FOUND)); + //@formatter:on + } + + @Test + void givenInvalidContentType() { + //@formatter:off + given() + .contentType(XML) + .body(new TicketRequest(APPLICATION_NAME)) + .cookie(COOKIE, jwt) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(is(SC_NOT_FOUND)); + //@formatter:on + } + + @Test + void givenNoBody() { + //@formatter:off + given() + .cookie(COOKIE, jwt) + .when() + .post(ZAAS_SAFIDT_URI) + .then() + .statusCode(is(SC_NOT_FOUND)); + //@formatter:on + } + } // Additional negative tests are in ZaasNegativeTest since they are common for the whole service } From 84e1a547e2af12f8f6d1804fe7b6eb05cc3df041 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Thu, 30 Nov 2023 14:47:52 +0100 Subject: [PATCH 04/19] Remove unused class Signed-off-by: Petr Weinfurt --- .../zowe/apiml/zaas/ZaasSafIdtResponse.java | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java diff --git a/common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java b/common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java deleted file mode 100644 index 66a5183a39..0000000000 --- a/common-service-core/src/main/java/org/zowe/apiml/zaas/ZaasSafIdtResponse.java +++ /dev/null @@ -1,23 +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; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class ZaasSafIdtResponse { - - private String safIdToken; -} From 61d2316f0d72075c6e655d76fb8ead47bff1b3ac Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Thu, 30 Nov 2023 15:36:44 +0100 Subject: [PATCH 05/19] Fix tests Signed-off-by: Petr Weinfurt --- .../org/zowe/apiml/gateway/zaas/ZaasController.java | 2 +- .../zowe/apiml/integration/zaas/SafIdTokensTest.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java index 5ba9a67558..cccf2a87c9 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java @@ -141,7 +141,7 @@ public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketReq String safIdToken = tokenCreationService.createSafIdTokenWithoutCredentials(userId, applicationName); return ResponseEntity .status(HttpStatus.OK) - .body(new ZaasTokenResponse(null, safIdToken)); + .body(new ZaasTokenResponse("", safIdToken)); } catch (IRRPassTicketGenerationException e) { ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java index 77bb651e50..004cc6139f 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java @@ -67,7 +67,7 @@ void givenValidZosmfToken() { .post(ZAAS_SAFIDT_URI) .then() .statusCode(SC_OK) - .body("safIdToken", not(isEmptyOrNullString())) + .body("token", not(isEmptyOrNullString())) .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -86,7 +86,7 @@ void givenValidZoweTokenWithLtpa() throws UnrecoverableKeyException, Certificate .post(ZAAS_SAFIDT_URI) .then() .statusCode(SC_OK) - .body("safIdToken", not(isEmptyOrNullString())) + .body("token", not(isEmptyOrNullString())) .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -106,7 +106,7 @@ void givenValidAccessToken() { .post(ZAAS_SAFIDT_URI) .then() .statusCode(SC_OK) - .body("safIdToken", not(isEmptyOrNullString())) + .body("token", not(isEmptyOrNullString())) .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -123,7 +123,7 @@ void givenX509Certificate(String certificate) { .post(ZAAS_SAFIDT_URI) .then() .statusCode(SC_OK) - .body("safIdToken", not(isEmptyOrNullString())) + .body("token", not(isEmptyOrNullString())) .body("cookieName", isEmptyOrNullString()); //@formatter:on } @@ -141,7 +141,7 @@ void givenValidOAuthToken() { .post(ZAAS_SAFIDT_URI) .then() .statusCode(SC_OK) - .body("ticket", not(isEmptyOrNullString())) + .body("token", not(isEmptyOrNullString())) .body("cookieName", isEmptyOrNullString()); //@formatter:on } From 51ff2c49c5f6459e9e35aaef5fa813106ca0d6bb Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Thu, 30 Nov 2023 21:31:48 +0100 Subject: [PATCH 06/19] Fix IT tests Signed-off-by: Petr Weinfurt --- .../java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java | 2 +- .../java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java index 004cc6139f..9512f0dee8 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/SafIdTokensTest.java @@ -226,9 +226,9 @@ void givenNoContentType() { void givenInvalidContentType() { //@formatter:off given() - .contentType(XML) .body(new TicketRequest(APPLICATION_NAME)) .cookie(COOKIE, jwt) + .contentType(XML) .when() .post(ZAAS_SAFIDT_URI) .then() diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index 0a72fc7219..2b1a16ad5a 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -47,11 +47,11 @@ public class ZaasNegativeTest { private static final Set tokenEndpoints = new HashSet() {{ add(ZAAS_ZOWE_URI); add(ZAAS_ZOSMF_URI); - add(ZAAS_SAFIDT_URI); }}; private static final Set endpoints = new HashSet() {{ add(ZAAS_TICKET_URI); + add(ZAAS_SAFIDT_URI); addAll(tokenEndpoints); }}; From 1b548338189f101d9182c7072dfc4ff0e93472aa Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Thu, 30 Nov 2023 22:54:24 +0100 Subject: [PATCH 07/19] Fix unit tests Signed-off-by: Petr Weinfurt --- .../java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java index 336f5e4769..38320d1a72 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java @@ -161,7 +161,7 @@ void whenRequestSafIdtAndApplNameProvided_thenResponseOk() throws Exception { .content(ticketBody.toString()) .requestAttr(AUTH_SOURCE_PARSED_ATTR, authParsedSource)) .andExpect(status().is(SC_OK)) - .andExpect(jsonPath("$.safIdToken", is(SAFIDT))); + .andExpect(jsonPath("$.token", is(SAFIDT))); } @Test @@ -229,7 +229,7 @@ void whenRequestingZoweTokens_thenInternalServerError() throws Exception { @Test void whenRequestingSafIdtAndPassticketException_thenInternalServerError() throws Exception { - when(passTicketService.generate(USER, APPLID)).thenThrow(new IRRPassTicketGenerationException(8, 8, 8)); + when(tokenCreationService.createSafIdTokenWithoutCredentials(USER, APPLID)).thenThrow(new IRRPassTicketGenerationException(8, 8, 8)); mockMvc.perform(post(SAFIDT_URL) .contentType(MediaType.APPLICATION_JSON) From b5697593d55cedc65a8c7777df822b7b8b0327ad Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Fri, 1 Dec 2023 00:07:26 +0100 Subject: [PATCH 08/19] Add Controller Advice to handle exceptions. Signed-off-by: Petr Weinfurt --- .../apiml/gateway/zaas/ZaasController.java | 51 +++++----------- .../gateway/zaas/ZaasExceptionHandler.java | 58 +++++++++++++++++++ .../service/TokenCreationServiceTest.java | 4 +- .../gateway/zaas/ZaasControllerTest.java | 2 +- 4 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java index cccf2a87c9..c35f174a5d 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java @@ -22,6 +22,7 @@ import org.zowe.apiml.gateway.security.service.schema.source.AuthSource; import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; +import org.zowe.apiml.gateway.security.ticket.ApplicationNameNotFoundException; import org.zowe.apiml.message.api.ApiMessageView; import org.zowe.apiml.message.core.MessageService; import org.zowe.apiml.passticket.IRRPassTicketGenerationException; @@ -49,7 +50,8 @@ public class ZaasController { @PostMapping(path = "ticket", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides PassTicket for authenticated user.") - public ResponseEntity getPassTicket(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) { + public ResponseEntity getPassTicket(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) + throws IRRPassTicketGenerationException, ApplicationNameNotFoundException { if (StringUtils.isEmpty(authSourceParsed.getUserId())) { return ResponseEntity @@ -59,22 +61,11 @@ public ResponseEntity getPassTicket(@RequestBody TicketRequest ticketReq final String applicationName = ticketRequest.getApplicationName(); if (StringUtils.isBlank(applicationName)) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.invalidApplicationName").mapToView(); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(messageView); + throw new ApplicationNameNotFoundException("ApplicationName not provided."); } - String ticket = null; - try { - ticket = passTicketService.generate(authSourceParsed.getUserId(), applicationName); - } catch (IRRPassTicketGenerationException e) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", - e.getErrorCode().getMessage()).mapToView(); - return ResponseEntity - .status(e.getHttpStatus()) - .body(messageView); - } + String ticket = passTicketService.generate(authSourceParsed.getUserId(), applicationName); + return ResponseEntity .status(HttpStatus.OK) .body(new TicketResponse(null, authSourceParsed.getUserId(), applicationName, ticket)); @@ -120,7 +111,8 @@ public ResponseEntity getZoweJwt(@RequestAttribute(AUTH_SOURCE_ATTR) Aut @PostMapping(path = "safIdt", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides SAF Identity Token for authenticated user.") - public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) { + public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) + throws IRRPassTicketGenerationException, ApplicationNameNotFoundException { final String userId = authSourceParsed.getUserId(); if (StringUtils.isEmpty(userId)) { @@ -131,30 +123,13 @@ public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketReq final String applicationName = ticketRequest.getApplicationName(); if (StringUtils.isBlank(applicationName)) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.invalidApplicationName").mapToView(); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(messageView); + throw new ApplicationNameNotFoundException("ApplicationName not provided."); } - try { - String safIdToken = tokenCreationService.createSafIdTokenWithoutCredentials(userId, applicationName); - return ResponseEntity - .status(HttpStatus.OK) - .body(new ZaasTokenResponse("", safIdToken)); - - } catch (IRRPassTicketGenerationException e) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", - e.getErrorCode().getMessage()).mapToView(); - return ResponseEntity - .status(e.getHttpStatus()) - .body(messageView); - } catch (Exception e) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.idt.failed", e.getMessage()).mapToView(); - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(messageView); - } + String safIdToken = tokenCreationService.createSafIdTokenWithoutCredentials(userId, applicationName); + return ResponseEntity + .status(HttpStatus.OK) + .body(new ZaasTokenResponse("", safIdToken)); } } diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java new file mode 100644 index 0000000000..9d5ab1ccba --- /dev/null +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java @@ -0,0 +1,58 @@ +/* + * 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.gateway.zaas; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.zowe.apiml.gateway.security.service.saf.SafIdtAuthException; +import org.zowe.apiml.gateway.security.service.saf.SafIdtException; +import org.zowe.apiml.gateway.security.ticket.ApplicationNameNotFoundException; +import org.zowe.apiml.message.api.ApiMessageView; +import org.zowe.apiml.message.core.MessageService; +import org.zowe.apiml.passticket.IRRPassTicketGenerationException; + +@ControllerAdvice +@RequiredArgsConstructor +public class ZaasExceptionHandler { + private final MessageService messageService; + + @ExceptionHandler(value = {IRRPassTicketGenerationException.class}) + public ResponseEntity handlePassTicketException(IRRPassTicketGenerationException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.generateFailed", + ex.getErrorCode().getMessage()).mapToView(); + return ResponseEntity + .status(ex.getHttpStatus()) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(value = {SafIdtException.class, SafIdtAuthException.class}) + public ResponseEntity handleSafIdtExceptions(RuntimeException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.idt.failed", ex.getMessage()).mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(value = {ApplicationNameNotFoundException.class}) + public ResponseEntity handleApplIdNotFoundException(ApplicationNameNotFoundException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.invalidApplicationName").mapToView(); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } +} diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java index 176d964063..e5e6e27dd6 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java @@ -164,7 +164,7 @@ void givenPassTicketException_whenCreatingSafIdToken_thenExceptionThrown() throw underTest.createSafIdTokenWithoutCredentials(VALID_USER_ID, VALID_ZOSMF_APPLID); }); - assertEquals(e.getMessage(), "Error on generation of PassTicket: An internal error was encountered."); + assertEquals("Error on generation of PassTicket: An internal error was encountered.", e.getMessage()); } @Test @@ -176,6 +176,6 @@ void givenSafIdtException_whenCreatingSafIdToken_thenExceptionThrown() throws IR underTest.createSafIdTokenWithoutCredentials(VALID_USER_ID, VALID_ZOSMF_APPLID); }); - assertEquals(e.getMessage(), "Test exception"); + assertEquals("Test exception", e.getMessage()); } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java index 38320d1a72..6689dfaa3d 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java @@ -89,7 +89,7 @@ void setUp() throws IRRPassTicketGenerationException, JSONException { when(passTicketService.generate(anyString(), anyString())).thenReturn(PASSTICKET); ZaasController zaasController = new ZaasController(authSourceService, messageService, passTicketService, zosmfService, tokenCreationService); - mockMvc = MockMvcBuilders.standaloneSetup(zaasController).build(); + mockMvc = MockMvcBuilders.standaloneSetup(zaasController).setControllerAdvice(new ZaasExceptionHandler(messageService)).build(); ticketBody = new JSONObject() .put("applicationName", APPLID); } From 1897532ceebe0253635404c2a52ab95aa6247771 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Fri, 1 Dec 2023 10:14:08 +0100 Subject: [PATCH 09/19] Handle more exceptions. Signed-off-by: Petr Weinfurt --- .../apiml/gateway/zaas/ZaasController.java | 72 ++++++------------- .../gateway/zaas/ZaasExceptionHandler.java | 32 +++++++-- .../gateway/zaas/ZaasControllerTest.java | 28 +------- 3 files changed, 50 insertions(+), 82 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java index c35f174a5d..15eae10852 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasController.java @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -23,14 +22,14 @@ import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; import org.zowe.apiml.gateway.security.ticket.ApplicationNameNotFoundException; -import org.zowe.apiml.message.api.ApiMessageView; -import org.zowe.apiml.message.core.MessageService; import org.zowe.apiml.passticket.IRRPassTicketGenerationException; import org.zowe.apiml.passticket.PassTicketService; import org.zowe.apiml.ticket.TicketRequest; import org.zowe.apiml.ticket.TicketResponse; import org.zowe.apiml.zaas.ZaasTokenResponse; +import javax.management.ServiceNotFoundException; + import static org.zowe.apiml.gateway.filters.pre.ExtractAuthSourceFilter.AUTH_SOURCE_ATTR; import static org.zowe.apiml.gateway.filters.pre.ExtractAuthSourceFilter.AUTH_SOURCE_PARSED_ATTR; import static org.zowe.apiml.security.SecurityUtils.COOKIE_AUTH_NAME; @@ -38,27 +37,19 @@ @RequiredArgsConstructor @RestController @RequestMapping(ZaasController.CONTROLLER_PATH) -@Slf4j public class ZaasController { public static final String CONTROLLER_PATH = "gateway/zaas"; private final AuthSourceService authSourceService; - private final MessageService messageService; private final PassTicketService passTicketService; private final ZosmfService zosmfService; private final TokenCreationService tokenCreationService; @PostMapping(path = "ticket", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides PassTicket for authenticated user.") - public ResponseEntity getPassTicket(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) + public ResponseEntity getPassTicket(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) throws IRRPassTicketGenerationException, ApplicationNameNotFoundException { - if (StringUtils.isEmpty(authSourceParsed.getUserId())) { - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED) - .build(); - } - final String applicationName = ticketRequest.getApplicationName(); if (StringUtils.isBlank(applicationName)) { throw new ApplicationNameNotFoundException("ApplicationName not provided."); @@ -68,65 +59,44 @@ public ResponseEntity getPassTicket(@RequestBody TicketRequest ticketReq return ResponseEntity .status(HttpStatus.OK) - .body(new TicketResponse(null, authSourceParsed.getUserId(), applicationName, ticket)); + .body(new TicketResponse("", authSourceParsed.getUserId(), applicationName, ticket)); } @PostMapping(path = "zosmf", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides z/OSMF JWT or LTPA token for authenticated user.") - public ResponseEntity getZosmfToken(@RequestAttribute(AUTH_SOURCE_ATTR) AuthSource authSource, - @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) { - try { - ZaasTokenResponse zaasTokenResponse = zosmfService.exchangeAuthenticationForZosmfToken(authSource.getRawSource().toString(), authSourceParsed); - - return ResponseEntity - .status(HttpStatus.OK) - .body(zaasTokenResponse); - - } catch (Exception e) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.zaas.zosmf.noZosmfTokenReceived", e.getMessage()).mapToView(); - return ResponseEntity - .status(HttpStatus.SERVICE_UNAVAILABLE) - .body(messageView); - } + public ResponseEntity getZosmfToken(@RequestAttribute(AUTH_SOURCE_ATTR) AuthSource authSource, + @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) throws ServiceNotFoundException { + + ZaasTokenResponse zaasTokenResponse = zosmfService.exchangeAuthenticationForZosmfToken(authSource.getRawSource().toString(), authSourceParsed); + + return ResponseEntity + .status(HttpStatus.OK) + .body(zaasTokenResponse); } @PostMapping(path = "zoweJwt", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides zoweJwt for authenticated user.") - public ResponseEntity getZoweJwt(@RequestAttribute(AUTH_SOURCE_ATTR) AuthSource authSource) { - try { - String token = authSourceService.getJWT(authSource); - - return ResponseEntity - .status(HttpStatus.OK) - .body(new ZaasTokenResponse(COOKIE_AUTH_NAME, token)); - - } catch (Exception e) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.zaas.zoweJwt.noToken", e.getMessage()).mapToView(); - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(messageView); - } + public ResponseEntity getZoweJwt(@RequestAttribute(AUTH_SOURCE_ATTR) AuthSource authSource) { + + String token = authSourceService.getJWT(authSource); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new ZaasTokenResponse(COOKIE_AUTH_NAME, token)); } @PostMapping(path = "safIdt", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Provides SAF Identity Token for authenticated user.") - public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) + public ResponseEntity getSafIdToken(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) throws IRRPassTicketGenerationException, ApplicationNameNotFoundException { - final String userId = authSourceParsed.getUserId(); - if (StringUtils.isEmpty(userId)) { - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED) - .build(); - } - final String applicationName = ticketRequest.getApplicationName(); if (StringUtils.isBlank(applicationName)) { throw new ApplicationNameNotFoundException("ApplicationName not provided."); } - String safIdToken = tokenCreationService.createSafIdTokenWithoutCredentials(userId, applicationName); + String safIdToken = tokenCreationService.createSafIdTokenWithoutCredentials(authSourceParsed.getUserId(), applicationName); return ResponseEntity .status(HttpStatus.OK) .body(new ZaasTokenResponse("", safIdToken)); diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java index 9d5ab1ccba..29d71313d1 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java @@ -18,10 +18,14 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.zowe.apiml.gateway.security.service.saf.SafIdtAuthException; import org.zowe.apiml.gateway.security.service.saf.SafIdtException; +import org.zowe.apiml.gateway.security.service.schema.source.AuthSchemeException; import org.zowe.apiml.gateway.security.ticket.ApplicationNameNotFoundException; 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.token.TokenNotValidException; + +import javax.management.ServiceNotFoundException; @ControllerAdvice @RequiredArgsConstructor @@ -49,10 +53,28 @@ public ResponseEntity handleSafIdtExceptions(RuntimeException ex @ExceptionHandler(value = {ApplicationNameNotFoundException.class}) public ResponseEntity handleApplIdNotFoundException(ApplicationNameNotFoundException ex) { - ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.invalidApplicationName").mapToView(); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(messageView); + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.security.ticket.invalidApplicationName").mapToView(); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(value = {ServiceNotFoundException.class}) + public ResponseEntity handleServiceNotFoundException(ServiceNotFoundException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.zaas.zosmf.noZosmfTokenReceived", ex.getMessage()).mapToView(); + return ResponseEntity + .status(HttpStatus.SERVICE_UNAVAILABLE) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(value = {TokenNotValidException.class, IllegalStateException.class, AuthSchemeException.class}) + public ResponseEntity handleZoweJwtCreationErrors(RuntimeException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.zaas.zoweJwt.noToken", ex.getMessage()).mapToView(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java index 6689dfaa3d..2fb87d100a 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java @@ -16,8 +16,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -85,10 +83,9 @@ class ZaasControllerTest { @BeforeEach void setUp() throws IRRPassTicketGenerationException, JSONException { - MessageService messageService = new YamlMessageService("/gateway-messages.yml"); - when(passTicketService.generate(anyString(), anyString())).thenReturn(PASSTICKET); - ZaasController zaasController = new ZaasController(authSourceService, messageService, passTicketService, zosmfService, tokenCreationService); + ZaasController zaasController = new ZaasController(authSourceService, passTicketService, zosmfService, tokenCreationService); + MessageService messageService = new YamlMessageService("/gateway-messages.yml"); mockMvc = MockMvcBuilders.standaloneSetup(zaasController).setControllerAdvice(new ZaasExceptionHandler(messageService)).build(); ticketBody = new JSONObject() .put("applicationName", APPLID); @@ -258,25 +255,4 @@ void whenRequestingSafIdtAndSafIdtException_thenInternalServerError() throws Exc } } } - - @Nested - class GivenNotAuthenticated { - - @BeforeEach - void setUp() { - authParsedSource = new ParsedTokenAuthSource(null, null, null, null); - } - - @ParameterizedTest - @ValueSource(strings = {PASSTICKET_URL, SAFIDT_URL}) - void thenRespondUnauthorized(String url) throws Exception { - mockMvc.perform(post(url) - .contentType(MediaType.APPLICATION_JSON) - .content(ticketBody.toString()) - .requestAttr(AUTH_SOURCE_ATTR, authParsedSource) - .requestAttr(AUTH_SOURCE_PARSED_ATTR, authParsedSource)) - .andExpect(status().is(SC_UNAUTHORIZED)); - } - - } } From e89e6dd9a24417bd02c1cb0ce0d150100bd205fb Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Mon, 4 Dec 2023 09:10:47 +0100 Subject: [PATCH 10/19] Add content type and body to negative test. Signed-off-by: Petr Weinfurt --- .../zowe/apiml/integration/zaas/ZaasNegativeTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index 2b1a16ad5a..8edd086c4b 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -12,6 +12,7 @@ import io.jsonwebtoken.Jwts; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; @@ -19,6 +20,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Value; import org.zowe.apiml.security.common.token.QueryResponse; +import org.zowe.apiml.ticket.TicketRequest; import org.zowe.apiml.util.SecurityUtils; import org.zowe.apiml.util.categories.ZaasTest; import org.zowe.apiml.util.config.ConfigReader; @@ -44,14 +46,16 @@ @ZaasTest public class ZaasNegativeTest { + private final static String APPLICATION_NAME = ConfigReader.environmentConfiguration().getDiscoverableClientConfiguration().getApplId(); + private static final Set tokenEndpoints = new HashSet() {{ add(ZAAS_ZOWE_URI); add(ZAAS_ZOSMF_URI); + add(ZAAS_SAFIDT_URI); }}; private static final Set endpoints = new HashSet() {{ add(ZAAS_TICKET_URI); - add(ZAAS_SAFIDT_URI); addAll(tokenEndpoints); }}; @@ -146,10 +150,14 @@ void givenClientAndHeaderCertificates_thenReturnTokenFromClientCert(URI uri) thr SslContextConfigurer sslContextConfigurer = new SslContextConfigurer(tlsCfg.getKeyStorePassword(), tlsCfg.getClientKeystore(), tlsCfg.getKeyStore()); SslContext.prepareSslAuthentication(sslContextConfigurer); + TicketRequest request = new TicketRequest(APPLICATION_NAME); + //@formatter:off String token = given() .config(SslContext.clientCertValid) .header("Client-Cert", getDummyClientCertificate()) + .contentType(ContentType.JSON) + .body(request) .when() .post(uri) .then() From 7e413c89314500a125ff5b6521655528bde6430e Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Mon, 4 Dec 2023 11:32:02 +0100 Subject: [PATCH 11/19] Add content type and body to negative test. Signed-off-by: Petr Weinfurt --- .../integration/zaas/ZaasNegativeTest.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index 8edd086c4b..260d4c68c7 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -13,6 +13,7 @@ import io.jsonwebtoken.Jwts; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; @@ -82,7 +83,16 @@ private static Stream provideZaasEndpoints() { } private static Stream provideZaasTokenEndpoints() { - return tokenEndpoints.stream().map(Arguments::of); + List argumentsList = new ArrayList<>(); + for (URI uri : tokenEndpoints) { + if (uri.equals(ZAAS_SAFIDT_URI)) { + TicketRequest body = new TicketRequest(APPLICATION_NAME); + argumentsList.add(Arguments.of(uri, body)); + } else { + argumentsList.add(Arguments.of(uri, null)); + } + } + return argumentsList.stream(); } @Nested @@ -145,19 +155,21 @@ void givenNoCertificate_thenReturnUnauthorized(URI uri) { @ParameterizedTest @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasTokenEndpoints") - void givenClientAndHeaderCertificates_thenReturnTokenFromClientCert(URI uri) throws Exception { + void givenClientAndHeaderCertificates_thenReturnTokenFromClientCert(URI uri, Object requestBody) throws Exception { TlsConfiguration tlsCfg = ConfigReader.environmentConfiguration().getTlsConfiguration(); SslContextConfigurer sslContextConfigurer = new SslContextConfigurer(tlsCfg.getKeyStorePassword(), tlsCfg.getClientKeystore(), tlsCfg.getKeyStore()); SslContext.prepareSslAuthentication(sslContextConfigurer); - TicketRequest request = new TicketRequest(APPLICATION_NAME); + RequestSpecification requestSpecification = given() + .config(SslContext.clientCertValid) + .header("Client-Cert", getDummyClientCertificate()); + + if (requestBody != null) { + requestSpecification.contentType(ContentType.JSON).body(requestBody); + } //@formatter:off - String token = given() - .config(SslContext.clientCertValid) - .header("Client-Cert", getDummyClientCertificate()) - .contentType(ContentType.JSON) - .body(request) + String token = requestSpecification .when() .post(uri) .then() From d6393ff114030a8be91f9ada93a6cc4657051154 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Mon, 4 Dec 2023 18:33:51 +0100 Subject: [PATCH 12/19] Add content type and body to negative test. Signed-off-by: Petr Weinfurt --- .../integration/zaas/ZaasNegativeTest.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index 260d4c68c7..4b1b75fd0c 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -84,13 +84,12 @@ private static Stream provideZaasEndpoints() { private static Stream provideZaasTokenEndpoints() { List argumentsList = new ArrayList<>(); + RequestSpecification requestSpec = given(); for (URI uri : tokenEndpoints) { if (uri.equals(ZAAS_SAFIDT_URI)) { - TicketRequest body = new TicketRequest(APPLICATION_NAME); - argumentsList.add(Arguments.of(uri, body)); - } else { - argumentsList.add(Arguments.of(uri, null)); + requestSpec.contentType(ContentType.JSON).body(new TicketRequest(APPLICATION_NAME)); } + argumentsList.add(Arguments.of(uri, requestSpec)); } return argumentsList.stream(); } @@ -155,21 +154,15 @@ void givenNoCertificate_thenReturnUnauthorized(URI uri) { @ParameterizedTest @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasTokenEndpoints") - void givenClientAndHeaderCertificates_thenReturnTokenFromClientCert(URI uri, Object requestBody) throws Exception { + void givenClientAndHeaderCertificates_thenReturnTokenFromClientCert(URI uri, RequestSpecification requestSpecification) throws Exception { TlsConfiguration tlsCfg = ConfigReader.environmentConfiguration().getTlsConfiguration(); SslContextConfigurer sslContextConfigurer = new SslContextConfigurer(tlsCfg.getKeyStorePassword(), tlsCfg.getClientKeystore(), tlsCfg.getKeyStore()); SslContext.prepareSslAuthentication(sslContextConfigurer); - RequestSpecification requestSpecification = given() - .config(SslContext.clientCertValid) - .header("Client-Cert", getDummyClientCertificate()); - - if (requestBody != null) { - requestSpecification.contentType(ContentType.JSON).body(requestBody); - } - //@formatter:off String token = requestSpecification + .config(SslContext.clientCertValid) + .header("Client-Cert", getDummyClientCertificate()) .when() .post(uri) .then() From 4d20affb765d5deb0b13b336fd884c1b90dacdcb Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Mon, 4 Dec 2023 19:00:11 +0100 Subject: [PATCH 13/19] Add negative tests with valid Okta token and no mapping. Signed-off-by: Petr Weinfurt --- .github/workflows/integration-tests.yml | 3 ++- .../apiml/integration/zaas/ZaasNegativeTest.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index a68468bbfb..4a73938dcc 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -413,7 +413,8 @@ jobs: ./gradlew :integration-tests:runZaasTest --info -Denvironment.config=-docker -Denvironment.offPlatform=true -Partifactory_user=${{ secrets.ARTIFACTORY_USERNAME }} -Partifactory_password=${{ secrets.ARTIFACTORY_PASSWORD }} -Dokta.client.id=${{ secrets.OKTA_CLIENT_ID }} -Doidc.test.user=${{ secrets.OIDC_TEST_USER }} - -Doidc.test.pass=${{ secrets.OIDC_TEST_PASS }} + -Doidc.test.pass=${{ secrets.OIDC_TEST_PASS }} -Doidc.test.alt_user=${{ secrets.OKTA_WINNIE_USER }} + -Doidc.test.alt_pass=${{ secrets.OKTA_WINNIE_PASS }} - name: Dump DC jacoco data run: > diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index 4b1b75fd0c..36e37ff524 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -49,6 +49,8 @@ public class ZaasNegativeTest { private final static String APPLICATION_NAME = ConfigReader.environmentConfiguration().getDiscoverableClientConfiguration().getApplId(); + private static final String OKTA_TOKEN_NO_MAPPING = SecurityUtils.validOktaAccessToken(false); + private static final Set tokenEndpoints = new HashSet() {{ add(ZAAS_ZOWE_URI); add(ZAAS_ZOSMF_URI); @@ -126,6 +128,18 @@ void givenInvalidToken(URI uri, String token) { //@formatter:on } + @ParameterizedTest + @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasTokenEndpoints") + void givenOKTATokenWithNoMapping(URI uri, RequestSpecification requestSpecification) { + //@formatter:off + requestSpecification + .header("Authorization", "Bearer " + OKTA_TOKEN_NO_MAPPING) + .when() + .post(uri) + .then() + .statusCode(SC_UNAUTHORIZED); + //@formatter:on + } } @Nested From aad69b8e61204c579e4318d593d326aa844e5e6d Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Mon, 4 Dec 2023 19:35:59 +0100 Subject: [PATCH 14/19] Fix Rest assured RequestSpec preparation. Signed-off-by: Petr Weinfurt --- .../integration/zaas/ZaasNegativeTest.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java index 36e37ff524..ecef0c3c80 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/zaas/ZaasNegativeTest.java @@ -37,7 +37,6 @@ import java.util.stream.Stream; import static io.restassured.RestAssured.given; -import static io.restassured.RestAssured.when; import static org.apache.http.HttpStatus.SC_OK; import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -72,8 +71,12 @@ public class ZaasNegativeTest { private static Stream provideZaasEndpointsWithAllTokens() { List argumentsList = new ArrayList<>(); for (URI uri : endpoints) { + RequestSpecification requestSpec = given(); + if (ZAAS_SAFIDT_URI.equals(uri) || ZAAS_TICKET_URI.equals(uri)) { + requestSpec.contentType(ContentType.JSON).body(new TicketRequest(APPLICATION_NAME)); + } for (String token : tokens) { - argumentsList.add(Arguments.of(uri, token)); + argumentsList.add(Arguments.of(uri, requestSpec, token)); } } @@ -81,14 +84,22 @@ private static Stream provideZaasEndpointsWithAllTokens() { } private static Stream provideZaasEndpoints() { - return endpoints.stream().map(Arguments::of); + List argumentsList = new ArrayList<>(); + for (URI uri : endpoints) { + RequestSpecification requestSpec = given(); + if (ZAAS_SAFIDT_URI.equals(uri) || ZAAS_TICKET_URI.equals(uri)) { + requestSpec.contentType(ContentType.JSON).body(new TicketRequest(APPLICATION_NAME)); + } + argumentsList.add(Arguments.of(uri, requestSpec)); + } + return argumentsList.stream(); } private static Stream provideZaasTokenEndpoints() { List argumentsList = new ArrayList<>(); - RequestSpecification requestSpec = given(); for (URI uri : tokenEndpoints) { - if (uri.equals(ZAAS_SAFIDT_URI)) { + RequestSpecification requestSpec = given(); + if (ZAAS_SAFIDT_URI.equals(uri)) { requestSpec.contentType(ContentType.JSON).body(new TicketRequest(APPLICATION_NAME)); } argumentsList.add(Arguments.of(uri, requestSpec)); @@ -106,9 +117,10 @@ void setUpCertificateAndToken() { @ParameterizedTest @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasEndpoints") - void givenNoToken(URI uri) { + void givenNoToken(URI uri, RequestSpecification requestSpecification) { //@formatter:off - when() + requestSpecification + .when() .post(uri) .then() .statusCode(SC_UNAUTHORIZED); @@ -117,9 +129,9 @@ void givenNoToken(URI uri) { @ParameterizedTest @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasEndpointsWithAllTokens") - void givenInvalidToken(URI uri, String token) { + void givenInvalidToken(URI uri, RequestSpecification requestSpecification, String token) { //@formatter:off - given() + requestSpecification .header("Authorization", "Bearer " + token) .when() .post(uri) @@ -129,7 +141,7 @@ void givenInvalidToken(URI uri, String token) { } @ParameterizedTest - @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasTokenEndpoints") + @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasEndpoints") void givenOKTATokenWithNoMapping(URI uri, RequestSpecification requestSpecification) { //@formatter:off requestSpecification @@ -154,9 +166,9 @@ class GivenBadCertificate { @ParameterizedTest @MethodSource("org.zowe.apiml.integration.zaas.ZaasNegativeTest#provideZaasEndpoints") - void givenNoCertificate_thenReturnUnauthorized(URI uri) { + void givenNoCertificate_thenReturnUnauthorized(URI uri, RequestSpecification requestSpecification) { //@formatter:off - given() + requestSpecification .relaxedHTTPSValidation() .cookie(COOKIE, SecurityUtils.gatewayToken()) .when() From e280d5e8f189e4eb0491decd3151081632c493e9 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Tue, 5 Dec 2023 11:33:28 +0100 Subject: [PATCH 15/19] Checkout the main branch before Sonar scan to resolve issue 'Could not find ref 'v2.x.x' in refs/heads, refs/remotes/upstream or refs/remotes/origin.' Signed-off-by: Petr Weinfurt --- .github/workflows/integration-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 4a73938dcc..db455953b7 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1494,6 +1494,9 @@ jobs: timeout-minutes: 15 steps: + - uses: actions/checkout@v3 + with: + ref: v2.x.x - uses: actions/checkout@v3 with: ref: ${{ github.head_ref }} From 67b217de840aa47bdf333d0cce93f628ff4bc154 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Wed, 6 Dec 2023 09:29:43 +0100 Subject: [PATCH 16/19] Fetch the main branch before Sonar scan to resolve issue 'Could not find ref 'v2.x.x' in refs/heads, refs/remotes/upstream or refs/remotes/origin.' Signed-off-by: Petr Weinfurt --- .github/workflows/integration-tests.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index db455953b7..8939cc5f8c 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1494,9 +1494,6 @@ jobs: timeout-minutes: 15 steps: - - uses: actions/checkout@v3 - with: - ref: v2.x.x - uses: actions/checkout@v3 with: ref: ${{ github.head_ref }} @@ -1543,10 +1540,9 @@ jobs: path: cloudgatewayservicerouting - name: Code coverage and publish results - run: > - ./gradlew --info coverage sonar -Dresults="containercitests/results,citestswithinfinispan/results,containercitestszosmfrsu2012/results,ContainerCITestsWithRedisReplica/results,ContainerCITestsWithRedisSentinel/results,containercitestsinternalport/results,cloudgatewayproxy/results,citestswebsocketchaoticha/results,cloudgatewayservicerouting/results,containercitestszaas/results" - -Psonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN - -Partifactory_user=$ARTIFACTORY_USERNAME -Partifactory_password=$ARTIFACTORY_PASSWORD + run: | + git fetch origin v2.x.x + ./gradlew --info coverage sonar -Dresults="containercitests/results,citestswithinfinispan/results,containercitestszosmfrsu2012/results,ContainerCITestsWithRedisReplica/results,ContainerCITestsWithRedisSentinel/results,containercitestsinternalport/results,cloudgatewayproxy/results,citestswebsocketchaoticha/results,cloudgatewayservicerouting/results,containercitestszaas/results" -Psonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN -Partifactory_user=$ARTIFACTORY_USERNAME -Partifactory_password=$ARTIFACTORY_PASSWORD env: ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} From e3ba8bad8ad0f18882b41382bd2f00b12e6ad62c Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Wed, 6 Dec 2023 10:07:23 +0100 Subject: [PATCH 17/19] Replace deprecated sonar.login property Signed-off-by: Petr Weinfurt --- .github/workflows/integration-tests.yml | 2 +- gradle/sonar.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 8939cc5f8c..f42d4bfc2c 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1542,7 +1542,7 @@ jobs: - name: Code coverage and publish results run: | git fetch origin v2.x.x - ./gradlew --info coverage sonar -Dresults="containercitests/results,citestswithinfinispan/results,containercitestszosmfrsu2012/results,ContainerCITestsWithRedisReplica/results,ContainerCITestsWithRedisSentinel/results,containercitestsinternalport/results,cloudgatewayproxy/results,citestswebsocketchaoticha/results,cloudgatewayservicerouting/results,containercitestszaas/results" -Psonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN -Partifactory_user=$ARTIFACTORY_USERNAME -Partifactory_password=$ARTIFACTORY_PASSWORD + ./gradlew --info coverage sonar -Dresults="containercitests/results,citestswithinfinispan/results,containercitestszosmfrsu2012/results,ContainerCITestsWithRedisReplica/results,ContainerCITestsWithRedisSentinel/results,containercitestsinternalport/results,cloudgatewayproxy/results,citestswebsocketchaoticha/results,cloudgatewayservicerouting/results,containercitestszaas/results" -Psonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Partifactory_user=$ARTIFACTORY_USERNAME -Partifactory_password=$ARTIFACTORY_PASSWORD env: ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/gradle/sonar.gradle b/gradle/sonar.gradle index 3bf7dad7e8..5cac66eaaa 100644 --- a/gradle/sonar.gradle +++ b/gradle/sonar.gradle @@ -5,7 +5,7 @@ ext.pullRequest = System.getenv()['CHANGE_ID'] ?: null sonar { properties { property "sonar.host.url", project.getProperty('sonar.host.url') - property "sonar.login", project.hasProperty('sonar.login') ? project.getProperty('sonar.login') : null + property "sonar.token", project.hasProperty('sonar.token') ? project.getProperty('sonar.token') : null property "sonar.sourceEncoding", "UTF-8" property "sonar.organization", "zowe" property "sonar.projectKey", "zowe_api-layer" From c5ae34f5848ea28dccdd83ca68b3665d05648890 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Wed, 6 Dec 2023 11:41:11 +0100 Subject: [PATCH 18/19] Fetch depth 0 Signed-off-by: Petr Weinfurt --- .github/workflows/integration-tests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f42d4bfc2c..664242616a 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1494,8 +1494,9 @@ jobs: timeout-minutes: 15 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: + fetch-depth: 0 ref: ${{ github.head_ref }} - uses: ./.github/actions/setup @@ -1540,9 +1541,9 @@ jobs: path: cloudgatewayservicerouting - name: Code coverage and publish results - run: | - git fetch origin v2.x.x - ./gradlew --info coverage sonar -Dresults="containercitests/results,citestswithinfinispan/results,containercitestszosmfrsu2012/results,ContainerCITestsWithRedisReplica/results,ContainerCITestsWithRedisSentinel/results,containercitestsinternalport/results,cloudgatewayproxy/results,citestswebsocketchaoticha/results,cloudgatewayservicerouting/results,containercitestszaas/results" -Psonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Partifactory_user=$ARTIFACTORY_USERNAME -Partifactory_password=$ARTIFACTORY_PASSWORD + run: > + ./gradlew --info coverage sonar -Dresults="containercitests/results,citestswithinfinispan/results,containercitestszosmfrsu2012/results,ContainerCITestsWithRedisReplica/results,ContainerCITestsWithRedisSentinel/results,containercitestsinternalport/results,cloudgatewayproxy/results,citestswebsocketchaoticha/results,cloudgatewayservicerouting/results,containercitestszaas/results" + -Psonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Partifactory_user=$ARTIFACTORY_USERNAME -Partifactory_password=$ARTIFACTORY_PASSWORD env: ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} From 0f71a153e6ffd8dcb5d231c72b516cffa18ac046 Mon Sep 17 00:00:00 2001 From: Petr Weinfurt Date: Fri, 8 Dec 2023 09:55:15 +0100 Subject: [PATCH 19/19] Handle TokenNotValid and TokenExpired exception with 401 response. Signed-off-by: Petr Weinfurt --- .../gateway/zaas/ZaasExceptionHandler.java | 23 ++++++++- .../gateway/zaas/ZaasControllerTest.java | 51 ++++++++++++++++--- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java index 29d71313d1..ed27f66e85 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/zaas/ZaasExceptionHandler.java @@ -23,6 +23,7 @@ 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.token.TokenExpireException; import org.zowe.apiml.security.common.token.TokenNotValidException; import javax.management.ServiceNotFoundException; @@ -69,12 +70,30 @@ public ResponseEntity handleServiceNotFoundException(ServiceNotF .body(messageView); } - @ExceptionHandler(value = {TokenNotValidException.class, IllegalStateException.class, AuthSchemeException.class}) - public ResponseEntity handleZoweJwtCreationErrors(RuntimeException ex) { + @ExceptionHandler(value = {IllegalStateException.class}) + public ResponseEntity handleZoweJwtCreationErrors(IllegalStateException ex) { ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.zaas.zoweJwt.noToken", ex.getMessage()).mapToView(); return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .contentType(MediaType.APPLICATION_JSON) .body(messageView); } + + @ExceptionHandler(value = {TokenNotValidException.class, AuthSchemeException.class}) + public ResponseEntity handleTokenNotValidException(RuntimeException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.gateway.security.invalidToken").mapToView(); + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } + + @ExceptionHandler(value = {TokenExpireException.class}) + public ResponseEntity handleTokenExpiredException(TokenExpireException ex) { + ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.gateway.security.expiredToken").mapToView(); + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .contentType(MediaType.APPLICATION_JSON) + .body(messageView); + } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java index 2fb87d100a..882df9f76a 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/zaas/ZaasControllerTest.java @@ -23,15 +23,13 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.zowe.apiml.gateway.security.service.TokenCreationService; import org.zowe.apiml.gateway.security.service.saf.SafIdtException; -import org.zowe.apiml.gateway.security.service.schema.source.AuthSource; -import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService; -import org.zowe.apiml.gateway.security.service.schema.source.JwtAuthSource; -import org.zowe.apiml.gateway.security.service.schema.source.ParsedTokenAuthSource; +import org.zowe.apiml.gateway.security.service.schema.source.*; import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService; import org.zowe.apiml.message.core.MessageService; import org.zowe.apiml.message.yaml.YamlMessageService; import org.zowe.apiml.passticket.IRRPassTicketGenerationException; import org.zowe.apiml.passticket.PassTicketService; +import org.zowe.apiml.security.common.token.TokenExpireException; import org.zowe.apiml.security.common.token.TokenNotValidException; import org.zowe.apiml.zaas.ZaasTokenResponse; @@ -211,10 +209,49 @@ void whenRequestingZosmfTokens_thenServiceUnavailable() throws Exception { } @Test - void whenRequestingZoweTokens_thenInternalServerError() throws Exception { - String expectedMessage = "No mainframe identity found."; + void whenRequestingZoweTokensAndTokenNotValidException_thenUnauthorized() throws Exception { when(authSourceService.getJWT(authSource)) - .thenThrow(new TokenNotValidException(expectedMessage)); + .thenThrow(new TokenNotValidException("token_not_valid")); + + mockMvc.perform(post(ZOWE_TOKEN_URL) + .requestAttr(AUTH_SOURCE_ATTR, authSource)) + .andExpect(status().is(SC_UNAUTHORIZED)) + .andExpect(jsonPath("$.messages", hasSize(1))) + .andExpect(jsonPath("$.messages[0].messageNumber").value("ZWEAG102E")) + .andExpect(jsonPath("$.messages[0].messageContent", is("Token is not valid"))); + } + + @Test + void whenRequestingZoweTokensAndTokenExpireException_thenUnauthorized() throws Exception { + when(authSourceService.getJWT(authSource)) + .thenThrow(new TokenExpireException("token_expired")); + + mockMvc.perform(post(ZOWE_TOKEN_URL) + .requestAttr(AUTH_SOURCE_ATTR, authSource)) + .andExpect(status().is(SC_UNAUTHORIZED)) + .andExpect(jsonPath("$.messages", hasSize(1))) + .andExpect(jsonPath("$.messages[0].messageNumber").value("ZWEAG103E")) + .andExpect(jsonPath("$.messages[0].messageContent", is("The token has expired"))); + } + + @Test + void whenRequestingZoweTokensAndAuthSchemeException_thenUnauthorized() throws Exception { + when(authSourceService.getJWT(authSource)) + .thenThrow(new AuthSchemeException("No mainframe identity found.")); + + mockMvc.perform(post(ZOWE_TOKEN_URL) + .requestAttr(AUTH_SOURCE_ATTR, authSource)) + .andExpect(status().is(SC_UNAUTHORIZED)) + .andExpect(jsonPath("$.messages", hasSize(1))) + .andExpect(jsonPath("$.messages[0].messageNumber").value("ZWEAG102E")) + .andExpect(jsonPath("$.messages[0].messageContent", is("Token is not valid"))); + } + + @Test + void whenRequestingZoweTokensAndIllegalStateException_thenInternalServerError() throws Exception { + String expectedMessage = "The z/OSMF is not configured."; + when(authSourceService.getJWT(authSource)) + .thenThrow(new IllegalStateException(expectedMessage)); mockMvc.perform(post(ZOWE_TOKEN_URL) .requestAttr(AUTH_SOURCE_ATTR, authSource))