Skip to content

Commit

Permalink
Adds zoweJwt endpoint to ZAAS (#3199)
Browse files Browse the repository at this point in the history
* Add /zaas/zoweJwt endpoint
  • Loading branch information
JirkaAichler authored Nov 20, 2023
1 parent 6cc68a6 commit 7d42791
Show file tree
Hide file tree
Showing 16 changed files with 458 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
import org.springframework.web.server.ServerWebExchange;
import org.zowe.apiml.cloudgatewayservice.service.InstanceInfoService;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.zaas.zosmf.ZosmfResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;
import reactor.core.publisher.Mono;

import java.net.HttpCookie;


@Service
public class ZosmfFilterFactory extends AbstractAuthSchemeFactory<ZosmfFilterFactory.Config, ZosmfResponse, Object> {
public class ZosmfFilterFactory extends AbstractAuthSchemeFactory<ZosmfFilterFactory.Config, ZaasTokenResponse, Object> {

private static final String ZOSMF_URL = "%s://%s:%d/%s/zaas/zosmf";

Expand All @@ -49,13 +49,13 @@ public GatewayFilter apply(Config config) {
}

@Override
protected Class<ZosmfResponse> getResponseClass() {
return ZosmfResponse.class;
protected Class<ZaasTokenResponse> getResponseClass() {
return ZaasTokenResponse.class;
}

@Override
protected ZosmfResponse getResponseFor401() {
return new ZosmfResponse();
protected ZaasTokenResponse getResponseFor401() {
return new ZaasTokenResponse();
}

@Override
Expand All @@ -67,7 +67,7 @@ protected WebClient.RequestHeadersSpec<?> createRequest(ServiceInstance instance

@Override
@SuppressWarnings("squid:S2092") // the internal API cannot define generic more specifically
protected Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterChain chain, ZosmfResponse response) {
protected Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterChain chain, ZaasTokenResponse response) {
ServerHttpRequest request;
if (response.getToken() != null) {
request = exchange.getRequest().mutate().headers(headers ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.zowe.apiml.cloudgatewayservice.acceptance.common.AcceptanceTest;
import org.zowe.apiml.cloudgatewayservice.acceptance.common.AcceptanceTestWithMockServices;
import org.zowe.apiml.cloudgatewayservice.acceptance.common.MockService;
import org.zowe.apiml.zaas.zosmf.ZosmfResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;

import java.io.IOException;
import java.net.HttpCookie;
Expand All @@ -38,7 +38,7 @@ public class ZosmfSchemeTest {

private static final String COOKIE_NAME = "zosmf_cookie";
private static final String JWT = "jwt";
private static final ZosmfResponse OK_RESPONSE = new ZosmfResponse(COOKIE_NAME, JWT);
private static final ZaasTokenResponse OK_RESPONSE = new ZaasTokenResponse(COOKIE_NAME, JWT);

private String getCookie(HttpExchange httpExchange, String cookieName) {
List<HttpCookie> cookies = Optional.ofNullable(httpExchange.getRequestHeaders().get("Cookie"))
Expand Down Expand Up @@ -76,7 +76,7 @@ void createAllZaasServices() throws IOException {
.and().build();
zaasZombie = mockService("gateway").scope(MockService.Scope.CLASS)
.addEndpoint("/gateway/zaas/zosmf")
.bodyJson(new ZosmfResponse())
.bodyJson(new ZaasTokenResponse())
.and().build();
zaasOk = mockService("gateway").scope(MockService.Scope.CLASS)
.addEndpoint("/gateway/zaas/zosmf")
Expand Down Expand Up @@ -213,7 +213,7 @@ void createZaas() throws IOException {
zaas = mockService("gateway").scope(MockService.Scope.CLASS)
.addEndpoint("/gateway/zaas/zosmf")
.responseCode(200)
.bodyJson(new ZosmfResponse())
.bodyJson(new ZaasTokenResponse())
.and().start();


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.zaas.zosmf;
package org.zowe.apiml.zaas;

import lombok.AllArgsConstructor;
import lombok.Data;
Expand All @@ -17,8 +17,9 @@
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ZosmfResponse {
public class ZaasTokenResponse {

private String cookieName;
private String token;

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import org.zowe.apiml.security.common.login.ChangePasswordRequest;
import org.zowe.apiml.security.common.login.LoginRequest;
import org.zowe.apiml.security.common.token.TokenNotValidException;
import org.zowe.apiml.zaas.zosmf.ZosmfResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;

import javax.annotation.PostConstruct;
import javax.management.ServiceNotFoundException;
Expand Down Expand Up @@ -226,22 +226,22 @@ public String getZosmfRealm(String infoURIEndpoint) {
}

@SuppressWarnings("java:S128") // Break in ZOWE case is left intentionally
public ZosmfResponse exchangeAuthenticationForZosmfToken(String token, AuthSource.Parsed authSource) throws ServiceNotFoundException {
public ZaasTokenResponse exchangeAuthenticationForZosmfToken(String token, AuthSource.Parsed authSource) throws ServiceNotFoundException {
switch (authSource.getOrigin()) {
case ZOSMF:
return new ZosmfResponse(ZosmfService.TokenType.JWT.getCookieName(), token);
return new ZaasTokenResponse(ZosmfService.TokenType.JWT.getCookieName(), token);
case ZOWE:
String ltpaToken = authenticationService.getLtpaToken(token);
if (ltpaToken != null) {
return new ZosmfResponse(ZosmfService.TokenType.LTPA.getCookieName(), ltpaToken);
return new ZaasTokenResponse(ZosmfService.TokenType.LTPA.getCookieName(), ltpaToken);
}
default:
Map<ZosmfService.TokenType, String> zosmfTokens = tokenCreationService.createZosmfTokensWithoutCredentials(authSource.getUserId());

if (zosmfTokens.containsKey(JWT)) {
return new ZosmfResponse(ZosmfService.TokenType.JWT.getCookieName(), zosmfTokens.get(JWT));
return new ZaasTokenResponse(ZosmfService.TokenType.JWT.getCookieName(), zosmfTokens.get(JWT));
} else if (zosmfTokens.containsKey(LTPA)) {
return new ZosmfResponse(ZosmfService.TokenType.LTPA.getCookieName(), zosmfTokens.get(LTPA));
return new ZaasTokenResponse(ZosmfService.TokenType.LTPA.getCookieName(), zosmfTokens.get(LTPA));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
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.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.zosmf.ZosmfResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;

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;

@RequiredArgsConstructor
@RestController
Expand All @@ -38,13 +40,13 @@
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;

@PostMapping(path = "ticket", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Provides PassTicket for authenticated user.")
@ResponseBody
public ResponseEntity<Object> getPassTicket(@RequestBody TicketRequest ticketRequest, @RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) {

if (StringUtils.isEmpty(authSourceParsed.getUserId())) {
Expand Down Expand Up @@ -78,15 +80,14 @@ public ResponseEntity<Object> getPassTicket(@RequestBody TicketRequest ticketReq

@PostMapping(path = "zosmf", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Provides z/OSMF JWT or LTPA token for authenticated user.")
@ResponseBody
public ResponseEntity<Object> getZosmfToken(@RequestAttribute(AUTH_SOURCE_ATTR) AuthSource authSource,
@RequestAttribute(AUTH_SOURCE_PARSED_ATTR) AuthSource.Parsed authSourceParsed) {
try {
ZosmfResponse zosmfResponse = zosmfService.exchangeAuthenticationForZosmfToken(authSource.getRawSource().toString(), authSourceParsed);
ZaasTokenResponse zaasTokenResponse = zosmfService.exchangeAuthenticationForZosmfToken(authSource.getRawSource().toString(), authSourceParsed);

return ResponseEntity
.status(HttpStatus.OK)
.body(zosmfResponse);
.body(zaasTokenResponse);

} catch (Exception e) {
ApiMessageView messageView = messageService.createMessage("org.zowe.apiml.zaas.zosmf.noZosmfTokenReceived", e.getMessage()).mapToView();
Expand All @@ -96,4 +97,23 @@ public ResponseEntity<Object> getZosmfToken(@RequestAttribute(AUTH_SOURCE_ATTR)
}
}


@PostMapping(path = "zoweJwt", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Provides zoweJwt for authenticated user.")
public ResponseEntity<Object> 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);
}
}

}
9 changes: 8 additions & 1 deletion gateway-service/src/main/resources/gateway-log-messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -452,9 +452,16 @@ messages:

# ZAAS error messages (#600) TODO: Messaging requires clean up

- key: org.zowe.apiml.zaas.zosmf.noZosmfTokenReceived
- key: org.zowe.apiml.zaas.zoweJwt.noToken
number: ZWEAZ600
type: WARNING
text: "ZAAS cannot generate or obtain Zowe token. Reason: %s"
reason: Review the reason section of the message.
action: Make sure z/OSMF is available when using the z/OSMF authentication provider or whether Zowe can generate tokens for other authentication providers. Make also sure that the identity mapping is correctly configured and set for the requested authentication.

- key: org.zowe.apiml.zaas.zosmf.noZosmfTokenReceived
number: ZWEAZ601
type: WARNING
text: "z/OSMF is not available or z/OSMF response does not contain any token. Reason: %s"
reason: z/OSMF does not return JWT or LTPA tokens.
action: Make sure z/OSMF is available to API ML or review your z/OSMF configuration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.acceptance;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource;
import org.zowe.apiml.acceptance.common.AcceptanceTest;
import org.zowe.apiml.acceptance.common.AcceptanceTestWithBasePath;
import org.zowe.apiml.gateway.utils.JWTUtils;
import org.zowe.apiml.security.HttpsConfig;

import static io.restassured.RestAssured.given;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;

@AcceptanceTest
@TestPropertySource(properties = {
"apiml.security.auth.provider=dummy" // To simulate SAF auth provider that does not run outside of mainframe
})
class ZaasTest extends AcceptanceTestWithBasePath {

private static final String COOKIE = "apimlAuthenticationToken";

@Value("${server.ssl.keyStore}")
private String keystore;

@Value("${server.ssl.keyStorePassword:password}")
private char[] keystorePassword;

@Value("${server.ssl.keyAlias:#{null}}")
private String keyAlias;

@Test
void givenZosmfCookieAndDummyAuthProvider_whenZoweJwtRequest_thenUnauthorized() {
HttpsConfig config = HttpsConfig.builder().keyAlias(keyAlias).keyPassword(keystorePassword).keyStore(keystore).build();
String zosmfJwt = JWTUtils.createZosmfJwtToken("user", "z/OS", "Ltpa", config);

//@formatter:off
given()
.cookie(COOKIE, zosmfJwt)
.when()
.post(basePath + "/gateway/zaas/zoweJwt")
.then()
.statusCode(SC_UNAUTHORIZED);
//@formatter:on
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import org.zowe.apiml.security.common.login.ChangePasswordRequest;
import org.zowe.apiml.security.common.login.LoginRequest;
import org.zowe.apiml.security.common.token.TokenNotValidException;
import org.zowe.apiml.zaas.zosmf.ZosmfResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;

import javax.management.ServiceNotFoundException;
import javax.net.ssl.SSLHandshakeException;
Expand Down Expand Up @@ -943,10 +943,10 @@ void setup() {
void givenZosmfAuthSource_thenSameTokenIsReturned() throws ServiceNotFoundException {
AuthSource.Parsed authParsedSource = new ParsedTokenAuthSource(USER, new Date(), new Date(), AuthSource.Origin.ZOSMF);

ZosmfResponse zosmfResponse = underTest.exchangeAuthenticationForZosmfToken(ZOSMF_JWT_TOKEN, authParsedSource);
ZaasTokenResponse zaasTokenResponse = underTest.exchangeAuthenticationForZosmfToken(ZOSMF_JWT_TOKEN, authParsedSource);

assertEquals(ZOSMF_JWT_TOKEN, zosmfResponse.getToken());
assertEquals(JWT.getCookieName(), zosmfResponse.getCookieName());
assertEquals(ZOSMF_JWT_TOKEN, zaasTokenResponse.getToken());
assertEquals(JWT.getCookieName(), zaasTokenResponse.getCookieName());
}

@Test
Expand All @@ -955,10 +955,10 @@ void givenZoweAuthSourceWithLtpa_thenSameLtpaTokenIsReturned() throws ServiceNot

when(authenticationService.getLtpaToken(ZOWE_JWT_TOKEN)).thenReturn(LTPA_TOKEN);

ZosmfResponse zosmfResponse = underTest.exchangeAuthenticationForZosmfToken(ZOWE_JWT_TOKEN, authParsedSource);
ZaasTokenResponse zaasTokenResponse = underTest.exchangeAuthenticationForZosmfToken(ZOWE_JWT_TOKEN, authParsedSource);

assertEquals(LTPA_TOKEN, zosmfResponse.getToken());
assertEquals(LTPA.getCookieName(), zosmfResponse.getCookieName());
assertEquals(LTPA_TOKEN, zaasTokenResponse.getToken());
assertEquals(LTPA.getCookieName(), zaasTokenResponse.getCookieName());
}

@Test
Expand All @@ -971,10 +971,10 @@ void givenZoweAuthSourceWithoutLtpa_thenNewJwtTokenIsReturned() throws ServiceNo
when(authenticationService.getLtpaToken(ZOWE_JWT_TOKEN)).thenReturn(null);
when(tokenCreationService.createZosmfTokensWithoutCredentials(USER)).thenReturn(tokens);

ZosmfResponse zosmfResponse = underTest.exchangeAuthenticationForZosmfToken(ZOWE_JWT_TOKEN, authParsedSource);
ZaasTokenResponse zaasTokenResponse = underTest.exchangeAuthenticationForZosmfToken(ZOWE_JWT_TOKEN, authParsedSource);

assertEquals(ZOSMF_JWT_TOKEN, zosmfResponse.getToken());
assertEquals(JWT.getCookieName(), zosmfResponse.getCookieName());
assertEquals(ZOSMF_JWT_TOKEN, zaasTokenResponse.getToken());
assertEquals(JWT.getCookieName(), zaasTokenResponse.getCookieName());
}

@Test
Expand All @@ -987,10 +987,10 @@ void givenOtherAuthSourceAndZosmfProducesJwt_thenNewJwtTokenIsReturned() throws
}};
when(tokenCreationService.createZosmfTokensWithoutCredentials(USER)).thenReturn(tokens);

ZosmfResponse zosmfResponse = underTest.exchangeAuthenticationForZosmfToken("OidcToken", authParsedSource);
ZaasTokenResponse zaasTokenResponse = underTest.exchangeAuthenticationForZosmfToken("OidcToken", authParsedSource);

assertEquals(ZOSMF_JWT_TOKEN, zosmfResponse.getToken());
assertEquals(JWT.getCookieName(), zosmfResponse.getCookieName());
assertEquals(ZOSMF_JWT_TOKEN, zaasTokenResponse.getToken());
assertEquals(JWT.getCookieName(), zaasTokenResponse.getCookieName());
}

@Test
Expand All @@ -1002,10 +1002,10 @@ void givenOtherAuthSourceAndZosmfProducesOnlyLtpa_thenNewLtpaTokenIsReturned() t
}};
when(tokenCreationService.createZosmfTokensWithoutCredentials(USER)).thenReturn(tokens);

ZosmfResponse zosmfResponse = underTest.exchangeAuthenticationForZosmfToken("OidcToken", authParsedSource);
ZaasTokenResponse zaasTokenResponse = underTest.exchangeAuthenticationForZosmfToken("OidcToken", authParsedSource);

assertEquals(LTPA_TOKEN, zosmfResponse.getToken());
assertEquals(LTPA.getCookieName(), zosmfResponse.getCookieName());
assertEquals(LTPA_TOKEN, zaasTokenResponse.getToken());
assertEquals(LTPA.getCookieName(), zaasTokenResponse.getCookieName());
}

@Test
Expand Down
Loading

0 comments on commit 7d42791

Please sign in to comment.