diff --git a/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java b/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java
index 035338719c..fb3942831d 100644
--- a/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java
+++ b/apiml-common/src/testFixtures/java/org/zowe/apiml/util/config/SslContext.java
@@ -56,6 +56,10 @@ public synchronized static void reset() {
isInitialized.set(false);
}
+ public static boolean isInitialized() {
+ return isInitialized.get();
+ }
+
public synchronized static void prepareSslAuthentication(SslContextConfigurer providedConfigurer) throws Exception {
if (configurer.get() != null && !configurer.get().equals(providedConfigurer)) {
@@ -174,7 +178,7 @@ public synchronized static void prepareSslAuthentication(SslContextConfigurer pr
isInitialized.set(true);
}
- }
-}
+ }
+}
diff --git a/apiml-security-common/src/testFixtures/java/org/zowe/apiml/security/common/util/JWTTestUtils.java b/apiml-security-common/src/testFixtures/java/org/zowe/apiml/security/common/util/JWTTestUtils.java
index 5eb5aeff35..1d85d8269c 100644
--- a/apiml-security-common/src/testFixtures/java/org/zowe/apiml/security/common/util/JWTTestUtils.java
+++ b/apiml-security-common/src/testFixtures/java/org/zowe/apiml/security/common/util/JWTTestUtils.java
@@ -12,6 +12,7 @@
import io.jsonwebtoken.Jwts;
import lombok.SneakyThrows;
+import org.apache.commons.lang3.StringUtils;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.zowe.apiml.security.HttpsConfig;
@@ -31,28 +32,73 @@
public class JWTTestUtils {
public static String createZoweJwtToken(String username, String domain, String ltpaToken, HttpsConfig config) {
- return createToken(username, domain, ltpaToken, config, "APIML");
+ return createToken(username, domain, ltpaToken, null, config, "APIML");
+ }
+
+ public static String createExpiredZoweJwtToken(String username, String domain, String ltpaToken, HttpsConfig config) {
+ return createExpiredToken(username, domain, ltpaToken, null, config, "APIML");
}
public static String createZosmfJwtToken(String username, String domain, String ltpaToken, HttpsConfig config) {
- return createToken(username, domain, ltpaToken, config, "zOSMF");
+ return createToken(username, domain, ltpaToken, null, config, "zOSMF");
+ }
+
+ public static String createZowePatJwtToken(String username, String domain, List scopes, HttpsConfig config) {
+ return createToken(username, domain, null, scopes, config, "APIML_PAT");
}
- public static String createToken(String username, String domain, String ltpaToken, HttpsConfig config, String issuer) {
+ public static String createToken(String username, String domain, String ltpaToken, List scopes, HttpsConfig config, String issuer) {
long now = System.currentTimeMillis();
long expiration = now + 100_000L;
Key jwtSecret = SecurityUtils.loadKey(config);
- return Jwts.builder()
+ var builder = Jwts.builder();
+
+ builder
.subject(username)
.claim("dom", domain)
- .claim("ltpa", ltpaToken)
.issuedAt(new Date(now))
.expiration(new Date(expiration))
.issuer(issuer)
.id(UUID.randomUUID().toString())
- .signWith(jwtSecret)
- .compact();
+ .signWith(jwtSecret);
+
+ if (!StringUtils.isEmpty(ltpaToken)) {
+ builder.claim("ltpa", ltpaToken);
+ }
+
+ if (scopes != null && scopes.size() > 0) {
+ builder.claim("scopes", scopes);
+ }
+
+ return builder.compact();
+ }
+
+ public static String createExpiredToken(String username, String domain, String ltpaToken, List scopes, HttpsConfig config, String issuer) {
+ long now = System.currentTimeMillis();
+ long expiration = now - 200_000L;
+ Key jwtSecret = SecurityUtils.loadKey(config);
+
+ var builder = Jwts.builder();
+
+ builder
+ .subject(username)
+ .claim("dom", domain)
+ .issuedAt(new Date(now))
+ .expiration(new Date(expiration))
+ .issuer(issuer)
+ .id(UUID.randomUUID().toString())
+ .signWith(jwtSecret);
+
+ if (!StringUtils.isEmpty(ltpaToken)) {
+ builder.claim("ltpa", ltpaToken);
+ }
+
+ if (scopes != null && scopes.size() > 0) {
+ builder.claim("scopes", scopes);
+ }
+
+ return builder.compact();
}
public static String createDummyJwtToken(String username, String issuer, long expiration) {
diff --git a/apiml/build.gradle b/apiml/build.gradle
index df18bbf8d7..fa0b16f2ae 100644
--- a/apiml/build.gradle
+++ b/apiml/build.gradle
@@ -77,6 +77,7 @@ dependencies {
implementation libs.opentelemetry.spring.boot.autoconfigure
testImplementation(testFixtures(project(":apiml-common")))
+ testImplementation(project(":zaas-service"))
testImplementation(testFixtures(project(":apiml-security-common")))
testImplementation(testFixtures(project(":gateway-service")))
testImplementation libs.spring.boot.starter.test
diff --git a/apiml/src/main/java/org/zowe/apiml/ZaasSchemeTransformApi.java b/apiml/src/main/java/org/zowe/apiml/ZaasSchemeTransformApi.java
index 0824974131..4ab3066ab6 100644
--- a/apiml/src/main/java/org/zowe/apiml/ZaasSchemeTransformApi.java
+++ b/apiml/src/main/java/org/zowe/apiml/ZaasSchemeTransformApi.java
@@ -10,6 +10,8 @@
package org.zowe.apiml;
+import com.nimbusds.jwt.proc.BadJWTException;
+import com.nimbusds.jwt.proc.ExpiredJWTException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpUpgradeHandler;
@@ -19,13 +21,21 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.server.ServerWebExchange;
import org.zowe.apiml.constants.ApimlConstants;
-import org.zowe.apiml.gateway.filters.*;
+import org.zowe.apiml.gateway.filters.AbstractAuthSchemeFactory.AuthorizationResponse;
+import org.zowe.apiml.gateway.filters.ErrorHeaders;
+import org.zowe.apiml.gateway.filters.RequestCredentials;
+import org.zowe.apiml.gateway.filters.ZaasInternalErrorException;
+import org.zowe.apiml.gateway.filters.ZaasSchemeTransform;
import org.zowe.apiml.message.core.MessageService;
+import org.zowe.apiml.passticket.ApplicationNameNotProvidedException;
import org.zowe.apiml.passticket.IRRPassTicketGenerationException;
import org.zowe.apiml.passticket.PassTicketService;
+import org.zowe.apiml.product.opentelemetry.OtelRequestContext;
import org.zowe.apiml.ticket.TicketResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;
import org.zowe.apiml.zaas.security.service.TokenCreationService;
@@ -39,7 +49,12 @@
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.util.*;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Optional;
import static org.zowe.apiml.security.SecurityUtils.COOKIE_AUTH_NAME;
import static org.zowe.apiml.security.common.filter.CategorizeCertsFilter.ATTR_NAME_CLIENT_AUTH_X509_CERTIFICATE;
@@ -59,7 +74,6 @@
* z/OSMF token exchange
* Zowe JWT generation
*
- *
*
*
* This bean is only active when {@code modulithConfig} is present in the Spring context.
@@ -86,41 +100,50 @@ public class ZaasSchemeTransformApi implements ZaasSchemeTransform {
@Value("${apiml.service.apimlId:apiml}")
private String currentApimlId;
- private Mono> createErrorMessage(String errorMessage) {
- var headers = new ErrorHeaders(errorMessage);
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(headers, null));
+ private ErrorHeaders createErrorMessage(String errorMessage) {
+ return new ErrorHeaders(errorMessage);
}
- private Mono> createInvalidAuthenticationErrorMessage() {
- String messageKey = "org.zowe.apiml.common.unauthorized";
- String logMessage = messageService.createMessage(messageKey).mapToLogMessage();
+ private Mono> createInvalidAuthenticationErrorMessage() {
+ var messageKey = "org.zowe.apiml.common.unauthorized";
+ var logMessage = messageService.createMessage(messageKey).mapToLogMessage();
var headers = new ErrorHeaders(logMessage);
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(headers, null));
+ return Mono.just(new AuthorizationResponse<>(headers, null));
}
- private Mono> createMissingAuthenticationErrorMessage() {
- String messageKey = "org.zowe.apiml.zaas.security.schema.missingAuthentication";
- String logMessage = messageService.createMessage(messageKey).mapToLogMessage();
- var headers = new ErrorHeaders(logMessage);
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(headers, null));
+ private AuthorizationResponse createMissingAuthenticationErrorMessage() {
+ var messageKey = "org.zowe.apiml.zaas.security.schema.missingAuthentication";
+ var logMessage = messageService.createMessage(messageKey).mapToLogMessage();
+ return new AuthorizationResponse<>(createErrorMessage(logMessage), InsufficientAuthenticationException.class.getName());
+ }
+
+ private Mono> createAuthorizationResponse(ErrorHeaders headers, R response) {
+ return Mono.just(new AuthorizationResponse<>(headers, response));
}
@Override
- public Mono> passticket(RequestCredentials requestCredentials) {
+ public Mono> passticket(RequestCredentials requestCredentials, ServerWebExchange exchange) {
var applicationName = requestCredentials.getApplId();
+ var otelRequestContext = OtelRequestContext.of(exchange);
if (StringUtils.isBlank(applicationName)) {
- return createErrorMessage("ApplicationName not provided.");
+ otelRequestContext.authErrorType(ApplicationNameNotProvidedException.class.getName());
+ return createAuthorizationResponse(createErrorMessage("ApplicationName not provided."),null);
}
try {
var request = new RequestCredentialsHttpServletRequestAdapter(requestCredentials);
Optional authSource = authSourceService.getAuthSourceFromRequest(request);
+ AuthorizationResponse missingAuthenticationErrorResponse;
if (authSource.isEmpty()) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
updateServiceId(authSource, request);
if (!authSourceService.isValid(authSource.get())) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
var authSourceParsed = authSourceService.parse(authSource.get());
@@ -137,12 +160,13 @@ public Mono> pas
.authSourceType(authSource.map(AuthSource::getType).map(Enum::name).orElse(null))
.build();
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(EMPTY_HEADERS, response));
+ return Mono.just(new AuthorizationResponse<>(EMPTY_HEADERS, response));
} catch (IRRPassTicketGenerationException e) {
log.debug("Cannot generate ticket", e);
return Mono.error(new ZaasInternalErrorException(currentApimlId, e.getMessage()));
} catch (Exception e) {
log.debug("Token has expired", e);
+ otelRequestContext.authErrorType(e.getClass().getName());
return createInvalidAuthenticationErrorMessage();
}
}
@@ -156,21 +180,28 @@ private void updateServiceId(Optional authSource, RequestCredentials
}
@Override
- public Mono> safIdt(RequestCredentials requestCredentials) {
+ public Mono> safIdt(RequestCredentials requestCredentials, ServerWebExchange exchange) {
var applicationName = requestCredentials.getApplId();
+ var otelRequestContext = OtelRequestContext.of(exchange);
if (StringUtils.isBlank(applicationName)) {
- return createErrorMessage("ApplicationName not provided.");
+ otelRequestContext.authErrorType(ApplicationNameNotProvidedException.class.getName());
+ return createAuthorizationResponse(createErrorMessage("ApplicationName not provided."), null);
}
try {
var request = new RequestCredentialsHttpServletRequestAdapter(requestCredentials);
Optional authSource = authSourceService.getAuthSourceFromRequest(request);
+ AuthorizationResponse missingAuthenticationErrorResponse;
if (authSource.isEmpty()) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
updateServiceId(authSource, request);
if (!authSourceService.isValid(authSource.get())) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
var authSourceParsed = authSourceService.parse(authSource.get());
@@ -186,24 +217,31 @@ public Mono>
)
.authSourceType(authSource.map(AuthSource::getType).map(Enum::name).orElse(null))
.build();
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(EMPTY_HEADERS, response));
+ return Mono.just(new AuthorizationResponse<>(EMPTY_HEADERS, response));
} catch (Exception e) {
log.debug("Cannot generate SAF IDT", e);
- return createErrorMessage(e.getMessage());
+ otelRequestContext.authErrorType(e.getClass().getName());
+ return createAuthorizationResponse(createErrorMessage(e.getMessage()), null);
}
}
@Override
- public Mono> zosmf(RequestCredentials requestCredentials) {
+ public Mono> zosmf(RequestCredentials requestCredentials, ServerWebExchange exchange) {
+ var otelRequestContext = OtelRequestContext.of(exchange);
try {
var request = new RequestCredentialsHttpServletRequestAdapter(requestCredentials);
Optional authSource = authSourceService.getAuthSourceFromRequest(request);
+ AuthorizationResponse missingAuthenticationErrorResponse;
if (authSource.isEmpty()) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
updateServiceId(authSource, request);
if (!authSourceService.isValid(authSource.get())) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
var authSourceParsed = authSourceService.parse(authSource.get());
@@ -215,29 +253,35 @@ public Mono>
.ifPresent(response::setDistributedIds);
authSource.map(AuthSource::getType).map(Enum::name).ifPresent(response::setAuthSourceType);
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(EMPTY_HEADERS, response));
+ return Mono.just(new AuthorizationResponse<>(EMPTY_HEADERS, response));
} catch (Exception e) {
log.debug("Cannot obtain z/OSMF token", e);
- return createErrorMessage(e.getMessage());
+ otelRequestContext.authErrorType(e.getClass().getName());
+ return createAuthorizationResponse(createErrorMessage(e.getMessage()), null);
}
}
@Override
- public Mono> zoweJwt(RequestCredentials requestCredentials) {
+ public Mono> zoweJwt(RequestCredentials requestCredentials, ServerWebExchange exchange) {
+ var otelRequestContext = OtelRequestContext.of(exchange);
try {
var request = new RequestCredentialsHttpServletRequestAdapter(requestCredentials);
Optional authSource = authSourceService.getAuthSourceFromRequest(request);
+ AuthorizationResponse missingAuthenticationErrorResponse;
if (authSource.isEmpty()) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
updateServiceId(authSource, request);
if (!authSourceService.isValid(authSource.get())) {
- return createMissingAuthenticationErrorMessage();
+ missingAuthenticationErrorResponse = createMissingAuthenticationErrorMessage();
+ otelRequestContext.authErrorType(missingAuthenticationErrorResponse.getBody());
+ return createAuthorizationResponse((ErrorHeaders) missingAuthenticationErrorResponse.getHeaders(), null);
}
var authSourceParsed = authSourceService.parse(authSource.get());
var token = authSourceService.getJWT(authSource.get());
- var response = ZaasTokenResponse.builder()
- .cookieName(COOKIE_AUTH_NAME)
+ var response = ZaasTokenResponse.builder().cookieName(COOKIE_AUTH_NAME)
.token(token)
.userId(authSourceParsed.getUserId())
.distributedIds(authSource.filter(OIDCAuthSource.class::isInstance)
@@ -247,9 +291,13 @@ public Mono>
)
.authSourceType(authSource.map(AuthSource::getType).map(Enum::name).orElse(null))
.build();
- return Mono.just(new AbstractAuthSchemeFactory.AuthorizationResponse<>(EMPTY_HEADERS, response));
+ return Mono.just(new AuthorizationResponse<>(EMPTY_HEADERS, response));
} catch (Exception e) {
log.debug("Cannot obtain Zowe JWT token", e);
+ if (e.getCause() instanceof BadJWTException || e.getCause() instanceof ParseException || e.getCause() instanceof ExpiredJWTException) {
+ otelRequestContext.authSourceType(AuthSource.AuthSourceType.JWT.name());
+ }
+ otelRequestContext.authErrorType(e.getClass().getName());
return createInvalidAuthenticationErrorMessage();
}
}
diff --git a/apiml/src/main/java/org/zowe/apiml/filter/BasicLoginFilter.java b/apiml/src/main/java/org/zowe/apiml/filter/BasicLoginFilter.java
index 82ba534c12..ad9470a1fc 100644
--- a/apiml/src/main/java/org/zowe/apiml/filter/BasicLoginFilter.java
+++ b/apiml/src/main/java/org/zowe/apiml/filter/BasicLoginFilter.java
@@ -25,6 +25,7 @@
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.zowe.apiml.handler.FailedAuthenticationWebHandler;
+import org.zowe.apiml.product.opentelemetry.OtelRequestContext;
import org.zowe.apiml.security.common.login.LoginFilter;
import org.zowe.apiml.security.common.login.LoginRequest;
import org.zowe.apiml.zaas.security.config.CompoundAuthProvider;
@@ -50,7 +51,7 @@
*
*
* This filter is intended to be used on /login endpoints.
- *
+ *
* Caution: Filter will read the body and make it available as a request attribute
*
* @see LoginRequest
@@ -72,6 +73,8 @@ public BasicLoginFilter(CompoundAuthProvider compoundAuthProvider, FailedAuthent
@Override
public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
var hasBody = Optional.ofNullable(exchange.getAttribute(CachedBodyFilter.CACHED_BODY_ATTR)).isPresent();
+ var otelContext = OtelRequestContext.of(exchange);
+ otelContext.authMethod(OtelRequestContext.BASIC_AUTH_TYPE);
exchange.getAttributes().put(X509AuthFilter.SKIP_X509_AUTH_ATTR, hasBody);
return extractBasicAuth(exchange)
.map(this::useCredentials)
diff --git a/apiml/src/main/java/org/zowe/apiml/handler/FailedAuthenticationWebHandler.java b/apiml/src/main/java/org/zowe/apiml/handler/FailedAuthenticationWebHandler.java
index 2e20cd68b3..cf36697ffd 100644
--- a/apiml/src/main/java/org/zowe/apiml/handler/FailedAuthenticationWebHandler.java
+++ b/apiml/src/main/java/org/zowe/apiml/handler/FailedAuthenticationWebHandler.java
@@ -26,6 +26,7 @@
import org.zowe.apiml.message.api.ApiMessageView;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
+import org.zowe.apiml.product.opentelemetry.OtelRequestContext;
import org.zowe.apiml.security.common.error.AuthExceptionHandler;
import reactor.core.publisher.Mono;
@@ -48,7 +49,11 @@ public class FailedAuthenticationWebHandler implements ServerAuthenticationFailu
public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
var exchange = webFilterExchange.getExchange();
var requestUri = exchange.getRequest().getURI().getPath();
+ var otelContext = OtelRequestContext.of(exchange);
log.debug("Unauthorized access to '{}' endpoint", requestUri);
+ otelContext.authenticationFailed();
+ otelContext.authErrorMessage(exception.getMessage());
+ otelContext.authErrorType(exception.getClass().getName());
var bufferFactory = new DefaultDataBufferFactory();
AtomicReference buffer = new AtomicReference<>();
BiConsumer consumer = (message, status) -> {
@@ -64,7 +69,7 @@ public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, A
buffer.set(bufferFactory.wrap(new byte[0]));
}
};
- var addHeader = (BiConsumer)(name, value) -> exchange.getResponse().getHeaders().add(name, value);
+ var addHeader = (BiConsumer) (name, value) -> exchange.getResponse().getHeaders().add(name, value);
try {
handler.handleException(requestUri, consumer, addHeader, exception);
} catch (ServletException e) {
diff --git a/apiml/src/test/java/org/zowe/apiml/ZaasSchemeTransformApiTest.java b/apiml/src/test/java/org/zowe/apiml/ZaasSchemeTransformApiTest.java
index 73345b32ae..609a612383 100644
--- a/apiml/src/test/java/org/zowe/apiml/ZaasSchemeTransformApiTest.java
+++ b/apiml/src/test/java/org/zowe/apiml/ZaasSchemeTransformApiTest.java
@@ -14,6 +14,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.springframework.web.server.ServerWebExchange;
import org.zowe.apiml.gateway.filters.RequestCredentials;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
@@ -97,7 +98,7 @@ void thenReturnsExpectedTicket() throws PassTicketException {
when(passTicketService.generate("USER1", "app1")).thenReturn("ticket123");
- StepVerifier.create(transformApi.passticket(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.passticket(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertNotNull(result);
TicketResponse response = result.getBody();
assertNotNull(response);
@@ -112,7 +113,7 @@ void whenTicketGenerationFails_writeErrorHeader() throws PassTicketException {
when(passTicketService.generate("USER1", "app1")).thenThrow(new RuntimeException("boom"));
- StepVerifier.create(transformApi.passticket(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.passticket(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals(INVALID_AUTH_MSG, result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
}).verifyComplete();
}
@@ -126,7 +127,7 @@ void whenAuthSourceMissing_returnsMissingAuthError() {
RequestCredentials credentials = mockCredentials();
when(authSourceService.getAuthSourceFromRequest(any())).thenReturn(Optional.empty());
- StepVerifier.create(transformApi.passticket(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.passticket(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertNotNull(result);
assertNull(result.getBody());
assertTrue(result.getHeaders().header("x-zowe-error").isEmpty());
@@ -141,7 +142,7 @@ void whenAuthSourceInvalid_writeErrorHeader() throws PassTicketException {
when(authSourceService.getAuthSourceFromRequest(any())).thenReturn(Optional.of(authSource));
when(authSourceService.isValid(authSource)).thenReturn(false);
- StepVerifier.create(transformApi.passticket(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.passticket(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals(MISSING_AUTH_MSG, result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -150,7 +151,7 @@ void whenAuthSourceInvalid_writeErrorHeader() throws PassTicketException {
@Test
void whenApplicationNameIsMissing_inPassticket_thenReturnsError() {
when(credentials.getApplId()).thenReturn(null);
- StepVerifier.create(transformApi.passticket(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.passticket(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals("ApplicationName not provided.", result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -168,7 +169,7 @@ class GivenSafIdtScheme {
void whenMissingAppId_returnsError() {
when(credentials.getApplId()).thenReturn(null);
- StepVerifier.create(transformApi.safIdt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.safIdt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals("ApplicationName not provided.", result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -191,7 +192,7 @@ void whenValidUser_returnsToken() throws PassTicketException {
when(tokenCreationService.createSafIdTokenWithoutCredentials("USER1", "app1"))
.thenReturn("saf-idt");
- StepVerifier.create(transformApi.safIdt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.safIdt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertNotNull(result);
assertEquals("saf-idt", result.getBody().getToken());
}).verifyComplete();
@@ -209,7 +210,7 @@ void whenOidc_returnsDistributedId() throws PassTicketException {
when(tokenCreationService.createSafIdTokenWithoutCredentials("USER1", "app1"))
.thenReturn("saf-idt");
- StepVerifier.create(transformApi.safIdt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.safIdt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertNotNull(result);
assertEquals("saf-idt", result.getBody().getToken());
assertNotNull(result.getBody().getDistributedIds());
@@ -224,7 +225,7 @@ void whenSafIdTokenCreationFails_returnsError() {
when(tokenCreationService.createSafIdTokenWithoutCredentials("USER1", "app1"))
.thenThrow(new RuntimeException("Simulated SAF IDT failure"));
- StepVerifier.create(transformApi.safIdt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.safIdt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals("Simulated SAF IDT failure", result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -241,7 +242,7 @@ void whenAuthSourceInvalid_returnsError() {
when(authSourceService.getAuthSourceFromRequest(any())).thenReturn(Optional.of(authSource));
when(authSourceService.isValid(authSource)).thenReturn(false);
- StepVerifier.create(transformApi.safIdt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.safIdt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals(MISSING_AUTH_MSG, result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -251,7 +252,7 @@ void whenAuthSourceInvalid_returnsError() {
void whenApplicationNameIsMissing_inSafIdt_thenReturnsError() {
RequestCredentials credentials = mockCredentialsWithAppId(" "); // blank
- StepVerifier.create(transformApi.safIdt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.safIdt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals("ApplicationName not provided.", result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -283,7 +284,7 @@ void thenReturnsJwt() {
when(authSourceService.getJWT(authSource)).thenReturn("jwt-token");
- StepVerifier.create(transformApi.zoweJwt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.zoweJwt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertNotNull(result);
ZaasTokenResponse response = result.getBody();
assertNotNull(response);
@@ -295,7 +296,7 @@ void thenReturnsJwt() {
void whenJwtRetrievalFails_returnsErrorResponse() {
when(authSourceService.getJWT(authSource)).thenThrow(new RuntimeException("boom"));
- StepVerifier.create(transformApi.zoweJwt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.zoweJwt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals(INVALID_AUTH_MSG, result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -307,7 +308,7 @@ void whenMissingAuthSource_returnsError() {
when(authSourceService.getAuthSourceFromRequest(any())).thenReturn(Optional.empty());
- StepVerifier.create(transformApi.zoweJwt(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.zoweJwt(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals(MISSING_AUTH_MSG, result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -353,7 +354,7 @@ void whenValidAuthSource_returnsTokenResponse() throws ServiceNotFoundException
when(zosmfService.exchangeAuthenticationForZosmfToken(anyString(), eq(parsed)))
.thenReturn(mockResponse);
- StepVerifier.create(transformApi.zosmf(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.zosmf(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertNotNull(result);
assertEquals("zosmf-token", result.getBody().getToken());
@@ -365,7 +366,7 @@ void testZosmf_serviceThrowsException_returnsError() throws ServiceNotFoundExcep
when(zosmfService.exchangeAuthenticationForZosmfToken(any(), any()))
.thenThrow(new RuntimeException("Error returned from zosmf"));
- StepVerifier.create(transformApi.zosmf(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.zosmf(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals("Error returned from zosmf", result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -376,7 +377,7 @@ void testZosmf_serviceThrowsException_returnsError() throws ServiceNotFoundExcep
void whenAuthSourceMissing_returnsMissingAuthError() {
when(authSourceService.getAuthSourceFromRequest(any())).thenReturn(Optional.empty());
- StepVerifier.create(transformApi.zosmf(credentials)).assertNext(result -> {
+ StepVerifier.create(transformApi.zosmf(credentials, mock(ServerWebExchange.class))).assertNext(result -> {
assertEquals(MISSING_AUTH_MSG, result.getHeaders().header(AUTH_FAIL_HEADER).get(0));
assertNull(result.getBody());
}).verifyComplete();
@@ -415,7 +416,7 @@ void setup() {
void giveOidcToken_whenPassticket_thenReturnUserIds() {
var requestCredentials = RequestCredentials.builder().applId("APPLID").build();
- StepVerifier.create(transformApi.passticket(requestCredentials))
+ StepVerifier.create(transformApi.passticket(requestCredentials, mock(ServerWebExchange.class)))
.assertNext(response -> {
assertSame(DISTRIBUTED_IDS, response.getBody().getDistributedIds());
assertSame(USER_ID, response.getBody().getUserId());
@@ -426,7 +427,7 @@ void giveOidcToken_whenPassticket_thenReturnUserIds() {
void giveOidcToken_whenSafIdt_thenReturnUserIds() {
var requestCredentials = RequestCredentials.builder().applId("APPLID").build();
- StepVerifier.create(transformApi.safIdt(requestCredentials))
+ StepVerifier.create(transformApi.safIdt(requestCredentials, mock(ServerWebExchange.class)))
.assertNext(response -> {
assertSame(DISTRIBUTED_IDS, response.getBody().getDistributedIds());
assertSame(USER_ID, response.getBody().getUserId());
@@ -438,7 +439,7 @@ void giveOidcToken_whenZosmf_thenReturnUserIds() throws ServiceNotFoundException
var requestCredentials = RequestCredentials.builder().build();
doReturn(ZaasTokenResponse.builder().distributedIds(DISTRIBUTED_IDS).userId(USER_ID).build()).when(zosmfService).exchangeAuthenticationForZosmfToken(any(), any());
- StepVerifier.create(transformApi.zosmf(requestCredentials))
+ StepVerifier.create(transformApi.zosmf(requestCredentials, mock(ServerWebExchange.class)))
.assertNext(response -> {
assertSame(DISTRIBUTED_IDS, response.getBody().getDistributedIds());
assertSame(USER_ID, response.getBody().getUserId());
@@ -449,7 +450,7 @@ void giveOidcToken_whenZosmf_thenReturnUserIds() throws ServiceNotFoundException
void giveOidcToken_whenZoweJwt_thenReturnUserIds() {
var requestCredentials = RequestCredentials.builder().build();
- StepVerifier.create(transformApi.zoweJwt(requestCredentials))
+ StepVerifier.create(transformApi.zoweJwt(requestCredentials, mock(ServerWebExchange.class)))
.assertNext(response -> {
assertSame(DISTRIBUTED_IDS, response.getBody().getDistributedIds());
assertSame(USER_ID, response.getBody().getUserId());
diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/AcceptanceTestWithMockServices.java b/apiml/src/test/java/org/zowe/apiml/acceptance/AcceptanceTestWithMockServices.java
index ae04bcd43b..99de7c47b2 100644
--- a/apiml/src/test/java/org/zowe/apiml/acceptance/AcceptanceTestWithMockServices.java
+++ b/apiml/src/test/java/org/zowe/apiml/acceptance/AcceptanceTestWithMockServices.java
@@ -22,6 +22,7 @@
import org.springframework.context.ApplicationEventPublisher;
import org.zowe.apiml.gateway.ApplicationRegistry;
import org.zowe.apiml.gateway.MockService;
+import org.zowe.apiml.gateway.MockWebSocketService;
@AcceptanceTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -78,6 +79,15 @@ protected MockService.MockServiceBuilder mockService(String serviceId) {
.serviceId(serviceId);
}
+ protected MockWebSocketService.MockWsServiceBuilder mockServiceWs(String serviceId) {
+ return MockWebSocketService.wsBuilder()
+ .statusChangedListener(mockService -> {
+ applicationRegistry.update(mockService);
+ updateRoutingRules();
+ })
+ .serviceId(serviceId);
+ }
+
@AfterEach
void stopMocksWithTestScope() {
applicationRegistry.afterTest();
diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryResourceAttributesZosTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryResourceAttributesZosTest.java
index 8ffa801eed..d99bad2b5e 100644
--- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryResourceAttributesZosTest.java
+++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryResourceAttributesZosTest.java
@@ -19,20 +19,33 @@
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import io.restassured.http.ContentType;
import org.apache.commons.lang3.StringUtils;
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.NestedTestConfiguration;
+import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.constants.ApimlConstants;
import org.zowe.apiml.gateway.MockService;
import org.zowe.apiml.gateway.MockService.Scope;
+import org.zowe.apiml.passticket.PassTicketException;
+import org.zowe.apiml.product.web.HttpConfig;
import org.zowe.apiml.util.config.SslContext;
import org.zowe.apiml.util.config.SslContextConfigurer;
import org.zowe.apiml.zaas.security.mapping.OIDCExternalMapper;
+import org.zowe.apiml.zaas.security.mapping.X509NativeMapper;
+import org.zowe.apiml.zaas.security.service.TokenCreationService;
+import org.zowe.apiml.zaas.security.service.token.ApimlAccessTokenProvider;
import org.zowe.apiml.zaas.security.service.token.OIDCTokenProvider;
import java.net.URI;
@@ -45,9 +58,17 @@
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.restassured.RestAssured.given;
import static org.awaitility.Awaitility.await;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
+import static org.zowe.apiml.constants.ApimlConstants.PAT_HEADER_NAME;
+import static org.zowe.apiml.security.common.util.JWTTestUtils.createExpiredZoweJwtToken;
+import static org.zowe.apiml.security.common.util.JWTTestUtils.createZowePatJwtToken;
class OpenTelemetryResourceAttributesZosTest {
@@ -69,7 +90,7 @@ private boolean assertAttributesBase(Attributes attributes, int port) {
@Nested
@AcceptanceTest
- @ActiveProfiles({ "OpenTelemetryTest", "zos" })
+ @ActiveProfiles({"OpenTelemetryTest", "zos"})
@TestPropertySource(
properties = {
"otel.sdk.disabled=false",
@@ -114,10 +135,13 @@ void thenLogCustomAttributes() {
"apiml.security.oidc.validationType=endpoint",
"apiml.security.oidc.enabled=true",
"apiml.security.oidc.userInfo.uri=https://oidc.provider.com/user/info",
- "apiml.security.filterChainConfiguration=new"
+ "apiml.security.filterChainConfiguration=new",
+ "apiml.security.personalAccessToken.enabled=true"
}
)
- @ActiveProfiles({ "OpenTelemetryTest", "zos" })
+ @ActiveProfiles({"OpenTelemetryTest", "zos"})
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
class WhenOnboardedService extends AcceptanceTestWithMockServices {
private static final String VALID_OIDC_TOKEN = "ewogICJ0eXAiOiAiSldUIiwKICAibm9uY2UiOiAiYVZhbHVlVG9CZVZlcmlmaWVkIiwKICAiYWxnIjogIlJTMjU2IiwKICAia2lkIjogIlNlQ1JldEtleSIKfQ.ewogICJhdWQiOiAiMDAwMDAwMDMtMDAwMC0wMDAwLWMwMDAtMDAwMDAwMDAwMDAwIiwKICAiaXNzIjogImh0dHBzOi8vb2lkYy5wcm92aWRlci5vcmcvYXBwIiwKICAiaWF0IjogMTcyMjUxNDEyOSwKICAibmJmIjogMTcyMjUxNDEyOSwKICAiZXhwIjogODcyMjUxODEyNSwKICAic3ViIjogIm9pZGMudXNlcm5hbWUiCn0.c29tZVNpZ25lZEhhc2hDb2Rl";
@@ -125,50 +149,30 @@ class WhenOnboardedService extends AcceptanceTestWithMockServices {
@Autowired
private LogRecordExporter logExporter;
+ @Autowired
+ private HttpConfig httpConfig;
+
@MockitoBean
private OIDCExternalMapper oidcExternalMapper;
@MockitoBean
private OIDCTokenProvider oidcTokenProvider;
- private MockService mockServiceZoweJwt;
- private MockService mockServicePassTicket;
- private MockService mockServicePassTicketMisconfigured;
- private MockService mockServiceBypass;
+ @MockitoBean
+ private X509NativeMapper x509TokenProvider;
+
+ @MockitoBean
+ private TokenCreationService tokenCreationService;
+
+ @MockitoBean
+ private ApimlAccessTokenProvider apimlAccessTokenProvider;
@BeforeAll
void startMockServices() throws Exception {
- SslContextConfigurer configurer = new SslContextConfigurer("password".toCharArray(), "../keystore/client_cert/client-certs.p12", "../keystore/localhost/localhost.keystore.p12");
- SslContext.prepareSslAuthentication(configurer);
-
- mockServiceZoweJwt = mockService("testservice")
- .scope(Scope.CLASS)
- .authenticationScheme(AuthenticationScheme.ZOWE_JWT)
- .addEndpoint("/testservice/200")
- .responseCode(200)
- .and().start();
-
- mockServicePassTicket = mockService("testservicept")
- .scope(Scope.CLASS)
- .authenticationScheme(AuthenticationScheme.HTTP_BASIC_PASSTICKET)
- .applid("TSTSVRPT")
- .addEndpoint("/testservicept/200")
- .responseCode(200)
- .and().start();
-
- mockServicePassTicketMisconfigured = mockService("testservicepterror")
- .scope(Scope.CLASS)
- .authenticationScheme(AuthenticationScheme.HTTP_BASIC_PASSTICKET)
- .addEndpoint("/testservicepterror/200")
- .responseCode(200)
- .and().start();
-
- mockServiceBypass = mockService("testservicebp")
- .scope(Scope.CLASS)
- .authenticationScheme(AuthenticationScheme.BYPASS)
- .addEndpoint("/testservicebp/200")
- .responseCode(200)
- .and().start();
+ if (!SslContext.isInitialized()) {
+ SslContextConfigurer configurer = new SslContextConfigurer("password".toCharArray(), "../keystore/client_cert/client-certs.p12", "../keystore/localhost/localhost.keystore.p12");
+ SslContext.prepareSslAuthentication(configurer);
+ }
}
@AfterAll
@@ -214,264 +218,639 @@ private LogRecordData assertOneLogRecordExported(String expectedUrl) {
return logRecord;
}
- @Test
- void givenRouted_whenAuthFail_thenLog() {
- given()
- .get(basePath + "/testservice/api/v1/200")
- .then()
- .statusCode(200);
+ // Requests that target API ML (/login, /query, /logout, /services, etc.)
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class WhenRequestToAPIML {
+
+ @Test
+ void givenLoginEndpoint_failure_thenLog() {
+ given()
+ .auth().preemptive()
+ .basic("wronguser", "wrongpass")
+ .post(basePath + "/gateway/api/v1/auth/login")
+ .then()
+ .statusCode(401);
+
+ var logRecord = assertOneLogRecordExported("/gateway/api/v1/auth/login");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("gateway", getAttribute(logBody, "service.id"));
+ assertEquals("POST", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("EACCES: Permission is denied; the specified password is incorrect", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.zowe.apiml.security.common.error.ZosAuthenticationException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:gateway:" + port, getAttribute(logBody, "service.instance.id"));
+ assertEquals("401", getAttribute(logBody, "service.response_code"));
+ assertEquals("/gateway/api/v1/auth/login", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("basicAuth", getAttribute(logBody, "auth.service.auth.method"));
+ }
- var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+ @Test
+ void givenCatalogEndpoint_thenLog() {
+ given()
+ .get(basePath + "/apicatalog/ui/v1/index.html")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/apicatalog/ui/v1/index.html");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertNull(getAttribute(logBody, "user.id"));
+ assertEquals("apicatalog", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertNull(getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:apicatalog:" + port, getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/apicatalog/ui/v1/index.html", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ }
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertEquals("testservice", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("FAILED", getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertNull(getAttribute(logBody, "auth.method"));
- assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
}
- private Object getAttribute(String logBody, String attributeName) {
- var objectMapper = new ObjectMapper();
- try {
- return objectMapper.readValue(logBody, Map.class).get(attributeName);
- } catch (JsonProcessingException e) {
- fail("Invalid JSON", e);
- return null;
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class WhenServiceDoesNotExist {
+
+ @Test
+ void givenNoRoute_thenLog() {
+ given()
+ .get(basePath + "/nonexistant/api/v1/200")
+ .then()
+ .statusCode(404);
+
+ var logRecord = assertOneLogRecordExported("/nonexistant/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertNull(getAttribute(logBody, "user.id"));
+ assertEquals("nonexistant", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertNull(getAttribute(logBody, "auth.status"));
+ assertNull(getAttribute(logBody, "service.instance.id"));
+ assertEquals("404", getAttribute(logBody, "service.response_code"));
+ assertEquals("/nonexistant/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertNull(getAttribute(logBody, "auth.method"));
}
+
}
- @Test
- @Disabled("This test is for invalid authentication (server error). To be reviewed in follow up story")
- void givenLoginEndpoint_thenLog() {
- given()
- .auth().preemptive()
- .basic("wronguser", "wrongpass")
- .post(basePath + "/gateway/api/v1/auth/login")
- .then()
- .statusCode(500);
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class WhenServiceBypass {
- var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertEquals("apicatalog", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("FAILED", getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertEquals("zoweJwt", getAttribute(logBody, "auth.method"));
- }
+ private MockService mockServiceBypass;
- @Test
- void givenCatalogEndpoint_thenLog() {
- given()
- .get(basePath + "/apicatalog/ui/v1/index.html")
- .then()
- .statusCode(200);
+ @BeforeAll
+ void init() {
+ mockServiceBypass = mockService("testservicebp")
+ .scope(Scope.CLASS)
+ .authenticationScheme(AuthenticationScheme.BYPASS)
+ .addEndpoint("/testservicebp/200")
+ .responseCode(200)
+ .and().start();
+ }
+
+ @Test
+ void thenLog() {
+ given()
+ .cookie(AUTH_COOKIE, login())
+ .get(basePath + "/testservicebp/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicebp/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertNull(getAttribute(logBody, "user.id"));
+ assertEquals("testservicebp", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertNull(getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservicebp:" + mockServiceBypass.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicebp/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ assertEquals("bypass", getAttribute(logBody, "auth.service.auth.method"));
+ }
- var logRecord = assertOneLogRecordExported("/apicatalog/ui/v1/index.html");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertNull(getAttribute(logBody, "user.id"));
- assertEquals("apicatalog", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertNull(getAttribute(logBody, "auth.status"));
- assertEquals("localhost:apicatalog:" + port, getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/apicatalog/ui/v1/index.html", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertNull(getAttribute(logBody, "auth.method"));
}
- @Test
- void givenRouted_withAuthSuccess_thenLog() {
- given()
- .cookie("apimlAuthenticationToken", login())
- .get(basePath + "/testservice/api/v1/200")
- .then()
- .statusCode(200);
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class WhenServiceRequiresJwt {
+
+ private MockService mockServiceZoweJwt;
+
+ @BeforeAll
+ void init() {
+ mockServiceZoweJwt = mockService("testservice")
+ .scope(Scope.CLASS)
+ .authenticationScheme(AuthenticationScheme.ZOWE_JWT)
+ .addEndpoint("/testservice/200")
+ .responseCode(200)
+ .and()
+ .addEndpoint("/testservice/401")
+ .responseCode(401)
+ .and().start();
+ }
- var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertEquals("USER", getAttribute(logBody, "user.id"));
- assertEquals("testservice", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("OK", getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
- assertEquals("JWT", getAttribute(logBody, "auth.method"));
- }
+ @Nested
+ class WhenAuthPresent {
+
+ @Nested
+ class WhenAuthSuccess {
+
+ @Test
+ void givenRouted_withAuthJwt_success_thenLog() {
+ given()
+ .cookie("apimlAuthenticationToken", login())
+ .get(basePath + "/testservice/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("USER", getAttribute(logBody, "user.id"));
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("OK", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("JWT", getAttribute(logBody, "auth.method"));
+ }
- @Test
- void givenNoRoute_thenLog() {
- given()
- .get(basePath + "/nonexistant/api/v1/200")
- .then()
- .statusCode(404);
+ @Test
+ void givenRouted_withX509_success_thenLog() {
+ given()
+ .config(SslContext.clientCertUser)
+ .get(basePath + "/testservice/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("USER", getAttribute(logBody, "user.id"));
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("OK", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("CLIENT_CERT", getAttribute(logBody, "auth.method"));
+ }
- var logRecord = assertOneLogRecordExported("/nonexistant/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertNull(getAttribute(logBody, "user.id"));
- assertEquals("nonexistant", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertNull(getAttribute(logBody, "auth.status"));
- assertNull(getAttribute(logBody, "service.instance.id"));
- assertEquals("404", getAttribute(logBody, "service.response_code"));
- assertEquals("/nonexistant/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertNull(getAttribute(logBody, "auth.method"));
- }
+ @Test
+ void givenRouted_withOidc_success_thenLog() {
+ when(oidcTokenProvider.isValid(VALID_OIDC_TOKEN)).thenReturn(true);
+ when(oidcExternalMapper.mapToMainframeUserId(any())).thenReturn("USER");
+
+ given()
+ .header(HttpHeaders.AUTHORIZATION, ApimlConstants.BEARER_AUTHENTICATION_PREFIX + " " + VALID_OIDC_TOKEN)
+ .get(basePath + "/testservice/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("USER", getAttribute(logBody, "user.id"));
+ assertEquals(List.of("oidc.username"), getAttribute(logBody, "user.distributed.id"));
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("OK", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("OIDC", getAttribute(logBody, "auth.method"));
+ }
- @Test
- void givenRouted_withAuthPassTicketSucess_thenLog() {
- given()
- .cookie(AUTH_COOKIE, login())
- .get(basePath + "/testservicept/api/v1/200")
- .then()
- .statusCode(200);
+ @Test
+ void givenRouted_withPAT_success_thenLog() {
+ var pat = createZowePatJwtToken("USER", "z/OS", List.of("testservice"), httpConfig.getHttpsConfig());
+ when(apimlAccessTokenProvider.isValidForScopes(pat, "testservice")).thenReturn(true);
+ when(apimlAccessTokenProvider.isInvalidated(pat)).thenReturn(false);
+ given()
+ .header(PAT_HEADER_NAME, pat)
+ .get(basePath + "/testservice/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("USER", getAttribute(logBody, "user.id"));
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("OK", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("PAT", getAttribute(logBody, "auth.method"));
+ }
- var logRecord = assertOneLogRecordExported("/testservicept/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertEquals("USER", getAttribute(logBody, "user.id"));
- assertEquals("testservicept", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("OK", getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservicept:" + mockServicePassTicket.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservicept/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertEquals("httpBasicPassTicket", getAttribute(logBody, "auth.service.auth.method"));
- assertEquals("JWT", getAttribute(logBody, "auth.method"));
- }
+ }
- @Test
- void givenRouted_withBypass_thenLog() {
- given()
- .cookie(AUTH_COOKIE, login())
- .get(basePath + "/testservicebp/api/v1/200")
- .then()
- .statusCode(200);
+ @Nested
+ class WhenAuthFailure {
+
+ @Test
+ void givenRouted_whenAuthFail_thenLog() {
+ given()
+ .get(basePath + "/testservice/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ }
- var logRecord = assertOneLogRecordExported("/testservicebp/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertNull(getAttribute(logBody, "user.id"));
- assertEquals("testservicebp", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertNull(getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservicebp:" + mockServiceBypass.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservicebp/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertNull(getAttribute(logBody, "auth.method"));
- assertEquals("bypass", getAttribute(logBody, "auth.service.auth.method"));
- }
+ @Test
+ void whenOidcTokenInvalid_thenLog() {
+ when(oidcTokenProvider.isValid(VALID_OIDC_TOKEN)).thenReturn(false);
+
+ given()
+ .header(HttpHeaders.AUTHORIZATION, ApimlConstants.BEARER_AUTHENTICATION_PREFIX + " " + VALID_OIDC_TOKEN)
+ .get(basePath + "/testservice/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
+
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("ZWEAG160E No authentication provided in the request", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.springframework.security.authentication.InsufficientAuthenticationException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ }
- @Test
- void givenRouted_withMisconfiguredAuthPassTicket_thenLog() {
- given()
- .cookie(AUTH_COOKIE, login())
- .get(basePath + "/testservicepterror/api/v1/200")
- .then()
- .statusCode(200);
+ @Test
+ void whenInvalidToken_thenLog() {
+ given()
+ .cookie("apimlAuthenticationToken", "invalid.jwt.token")
+ .get(basePath + "/testservice/api/v1/401")
+ .then()
+ .statusCode(401);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/401");
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("ZWEAO402E The request has not been applied because it lacks valid authentication credentials.", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.zowe.apiml.security.common.token.TokenNotValidException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("401", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/401", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("JWT", getAttribute(logBody, "auth.method"));
+ }
+
+ @Test
+ void whenExpiredToken_thenLog() {
+ given()
+ .cookie("apimlAuthenticationToken", createExpiredZoweJwtToken("USER", "z/OS", "Ltpa", httpConfig.getHttpsConfig()))
+ .get(basePath + "/testservice/api/v1/401")
+ .then()
+ .statusCode(401);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/401");
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("ZWEAO402E The request has not been applied because it lacks valid authentication credentials.", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.zowe.apiml.security.common.token.TokenExpireException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("401", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/401", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("JWT", getAttribute(logBody, "auth.method"));
+ }
+
+ }
+
+ }
+
+ @Nested
+ class WhenAuthAbsent {
+
+ @Test
+ void whenNoJwtProvided_thenLog() {
+ given()
+ .get(basePath + "/testservice/api/v1/401")
+ .then()
+ .statusCode(401);
+
+ var logRecord = assertOneLogRecordExported("/testservice/api/v1/401");
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservice", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("ZWEAG160E No authentication provided in the request", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.springframework.security.authentication.InsufficientAuthenticationException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("401", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservice/api/v1/401", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ }
+
+ }
- var logRecord = assertOneLogRecordExported("/testservicepterror/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertNull(getAttribute(logBody, "user.id"));
- assertEquals("testservicepterror", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("FAILED", getAttribute(logBody, "auth.status"));
- assertEquals(mockServicePassTicketMisconfigured.getInstanceId(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservicepterror/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertEquals("httpBasicPassTicket", getAttribute(logBody, "auth.service.auth.method"));
- assertNull(getAttribute(logBody, "auth.method"));
}
- @Test
- void givenRouted_withOidc_thenLog() {
- when(oidcTokenProvider.isValid(VALID_OIDC_TOKEN)).thenReturn(true);
- when(oidcExternalMapper.mapToMainframeUserId(any())).thenReturn("USER");
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class WhenServiceRequiresPassTicket {
+
+ private MockService mockServicePassTicket;
+ private MockService mockServicePassTicketMisconfigured;
+
+ @BeforeAll
+ void init() {
+ mockServicePassTicket = mockService("testservicept")
+ .scope(Scope.CLASS)
+ .authenticationScheme(AuthenticationScheme.HTTP_BASIC_PASSTICKET)
+ .applid("TSTSVRPT")
+ .addEndpoint("/testservicept/200")
+ .responseCode(200)
+ .and().start();
+
+ mockServicePassTicketMisconfigured = mockService("testservicepterror")
+ .scope(Scope.CLASS)
+ .authenticationScheme(AuthenticationScheme.HTTP_BASIC_PASSTICKET)
+ .addEndpoint("/testservicepterror/200")
+ .responseCode(200)
+ .and().start();
+ }
- given()
- .header(HttpHeaders.AUTHORIZATION, ApimlConstants.BEARER_AUTHENTICATION_PREFIX + " " + VALID_OIDC_TOKEN)
- .get(basePath + "/testservice/api/v1/200")
- .then()
- .statusCode(200);
+ @Nested
+ class WhenMisconfigured {
+
+ @Test
+ void thenLog() {
+ given()
+ .cookie(AUTH_COOKIE, login())
+ .get(basePath + "/testservicepterror/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicepterror/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertNull(getAttribute(logBody, "user.id"));
+ assertEquals("testservicepterror", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals(mockServicePassTicketMisconfigured.getInstanceId(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicepterror/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("httpBasicPassTicket", getAttribute(logBody, "auth.service.auth.method"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ }
+
+ }
+
+ @Nested
+ class WhenAuthPresent {
+
+ @Test
+ void whenSucess_thenLog() {
+ given()
+ .cookie(AUTH_COOKIE, login())
+ .get(basePath + "/testservicept/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicept/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("USER", getAttribute(logBody, "user.id"));
+ assertEquals("testservicept", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("OK", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservicept:" + mockServicePassTicket.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicept/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("httpBasicPassTicket", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("JWT", getAttribute(logBody, "auth.method"));
+ }
+
+ }
+
+ @Nested
+ class WhenAuthAbsent {
+
+ @Test
+ void whenNoTokenProvided_thenLog() {
+ given()
+ .get(basePath + "/testservicept/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicept/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertNull(getAttribute(logBody, "user.id"));
+ assertEquals("testservicept", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservicept:" + mockServicePassTicket.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicept/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("httpBasicPassTicket", getAttribute(logBody, "auth.service.auth.method"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ assertEquals("ZWEAG160E No authentication provided in the request", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.springframework.security.authentication.InsufficientAuthenticationException", getAttribute(logBody, "auth.error.type"));
+ }
+
+ }
- var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertEquals("USER", getAttribute(logBody, "user.id"));
- assertEquals(List.of("oidc.username"), getAttribute(logBody, "user.distributed.id"));
- assertEquals("testservice", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("OK", getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
- assertEquals("OIDC", getAttribute(logBody, "auth.method"));
}
- @Test
- void givenRouted_withX509_thenLog() {
- given()
- .config(SslContext.clientCertUser)
- .get(basePath + "/testservice/api/v1/200")
- .then()
- .statusCode(200);
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class WhenServiceRequiresSafIdt {
+
+ private MockService mockServiceSafIdt;
+
+ @BeforeAll
+ void init() {
+ mockServiceSafIdt = mockService("testservicesafidt")
+ .scope(Scope.CLASS)
+ .authenticationScheme(AuthenticationScheme.SAF_IDT)
+ .applid("TSTSVRID")
+ .addEndpoint("/testservicesafidt/200")
+ .responseCode(200)
+ .and().start();
+ }
+
+ @BeforeEach
+ void setUp() {
+ Mockito.reset(tokenCreationService);
+ }
+
+ @Nested
+ class WhenAuthPresent {
+
+ @Test
+ void whenSuccess_thenLog() {
+ when(tokenCreationService.createSafIdTokenWithoutCredentials("USER", "TSTSVRID")).thenReturn("validsafidt");
+ given()
+ .cookie(AUTH_COOKIE, login())
+ .get(basePath + "/testservicesafidt/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicesafidt/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("USER", getAttribute(logBody, "user.id"));
+ assertEquals("testservicesafidt", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("OK", getAttribute(logBody, "auth.status"));
+ assertEquals("localhost:testservicesafidt:" + mockServiceSafIdt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicesafidt/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("safIdt", getAttribute(logBody, "auth.service.auth.method"));
+ assertEquals("JWT", getAttribute(logBody, "auth.method"));
+ assertNull(getAttribute(logBody, "auth.error.type"));
+ assertNull(getAttribute(logBody, "auth.error.message"));
+ }
+
+ @Test
+ void whenFailure_thenLog() {
+ when(tokenCreationService.createSafIdTokenWithoutCredentials("USER", "TSTSVRID")).thenThrow(new PassTicketException("Test exception"));
+ given()
+ .cookie(AUTH_COOKIE, login())
+ .get(basePath + "/testservicesafidt/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicesafidt/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservicesafidt", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("Test exception", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.zowe.apiml.passticket.PassTicketException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:testservicesafidt:" + mockServiceSafIdt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicesafidt/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("safIdt", getAttribute(logBody, "auth.service.auth.method"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ }
+
+ }
+
+ @Nested
+ class WhenAuthAbsent {
+
+ @Test
+ void thenLog() {
+ given()
+ .get(basePath + "/testservicesafidt/api/v1/200")
+ .then()
+ .statusCode(200);
+
+ var logRecord = assertOneLogRecordExported("/testservicesafidt/api/v1/200");
+ assertAttributesBase(logRecord.getResource().getAttributes(), port);
+ @SuppressWarnings("null")
+ var logBody = logRecord.getBodyValue().asString();
+ assertEquals("testservicesafidt", getAttribute(logBody, "service.id"));
+ assertEquals("GET", getAttribute(logBody, "http.request.method"));
+ assertEquals("ERROR", getAttribute(logBody, "auth.status"));
+ assertEquals("ZWEAG160E No authentication provided in the request", getAttribute(logBody, "auth.error.message"));
+ assertEquals("org.springframework.security.authentication.InsufficientAuthenticationException", getAttribute(logBody, "auth.error.type"));
+ assertEquals("localhost:testservicesafidt:" + mockServiceSafIdt.getPort(), getAttribute(logBody, "service.instance.id"));
+ assertEquals("200", getAttribute(logBody, "service.response_code"));
+ assertEquals("/testservicesafidt/api/v1/200", getAttribute(logBody, "url.path"));
+ assertEquals("https", getAttribute(logBody, "url.scheme"));
+ assertEquals("safIdt", getAttribute(logBody, "auth.service.auth.method"));
+ // assertEquals("JWT", getAttribute(logBody, "auth.method"));
+ assertNull(getAttribute(logBody, "auth.method"));
+ }
+
+ }
- var logRecord = assertOneLogRecordExported("/testservice/api/v1/200");
- assertAttributesBase(logRecord.getResource().getAttributes(), port);
- @SuppressWarnings("null")
- var logBody = logRecord.getBodyValue().asString();
- assertEquals("USER", getAttribute(logBody, "user.id"));
- assertEquals("testservice", getAttribute(logBody, "service.id"));
- assertEquals("GET", getAttribute(logBody, "http.request.method"));
- assertEquals("OK", getAttribute(logBody, "auth.status"));
- assertEquals("localhost:testservice:" + mockServiceZoweJwt.getPort(), getAttribute(logBody, "service.instance.id"));
- assertEquals("200", getAttribute(logBody, "service.response_code"));
- assertEquals("/testservice/api/v1/200", getAttribute(logBody, "url.path"));
- assertEquals("https", getAttribute(logBody, "url.scheme"));
- assertEquals("zoweJwt", getAttribute(logBody, "auth.service.auth.method"));
- assertEquals("CLIENT_CERT", getAttribute(logBody, "auth.method"));
+ }
+
+ private Object getAttribute(String logBody, String attributeName) {
+ var objectMapper = new ObjectMapper();
+ try {
+ return objectMapper.readValue(logBody, Map.class).get(attributeName);
+ } catch (JsonProcessingException e) {
+ fail("Invalid JSON", e);
+ return null;
+ }
}
private String login() {
var token = given()
.contentType(ContentType.JSON)
.body("""
- {
- "username": "USER",
- "password": "validPassword"
- }
- """)
+ {
+ "username": "USER",
+ "password": "validPassword"
+ }
+ """)
.log().all()
.when()
.post(URI.create(basePath + LOGIN_ENDPOINT))
diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java
index fcfaa4d73e..d835a53ccc 100644
--- a/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java
+++ b/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java
@@ -25,6 +25,8 @@
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.NestedTestConfiguration;
+import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.util.ReflectionTestUtils;
import org.zowe.apiml.discovery.ApimlInstanceRegistry;
@@ -47,6 +49,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
class StartupMessageAcceptanceTest {
abstract static class BaseStartupTest extends AcceptanceTestWithMockServices {
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java
index 7cc474a8b8..75957e09ad 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/AbstractAuthSchemeFactory.java
@@ -16,6 +16,7 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
@@ -34,7 +35,11 @@
import java.net.HttpCookie;
import java.security.cert.CertificateEncodingException;
-import java.util.*;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -96,7 +101,6 @@
* public static class Config extends AbstractAuthSchemeFactory.AbstractConfig {
* }
* }
- *
* @Data class MyResponse {
* private String token;
* }
@@ -111,28 +115,30 @@ public abstract class AbstractAuthSchemeFactory CERTIFICATE_HEADERS_TEST = headerName ->
- StringUtils.equalsIgnoreCase(headerName, CERTIFICATE_HEADERS[0]) ||
- StringUtils.equalsIgnoreCase(headerName, CERTIFICATE_HEADERS[1]) ||
- StringUtils.equalsIgnoreCase(headerName, CERTIFICATE_HEADERS[2]);
+ Strings.CI.equals(headerName, CERTIFICATE_HEADERS[0]) ||
+ Strings.CI.equals(headerName, CERTIFICATE_HEADERS[1]) ||
+ Strings.CI.equals(headerName, CERTIFICATE_HEADERS[2]);
private static final Predicate CREDENTIALS_COOKIE_INPUT = cookie ->
- StringUtils.equalsIgnoreCase(cookie.getName(), PAT_COOKIE_AUTH_NAME) ||
- StringUtils.equalsIgnoreCase(cookie.getName(), COOKIE_AUTH_NAME) ||
- StringUtils.startsWithIgnoreCase(cookie.getName(), COOKIE_AUTH_NAME + ".");
+ Strings.CI.equals(cookie.getName(), PAT_COOKIE_AUTH_NAME) ||
+ Strings.CI.equals(cookie.getName(), COOKIE_AUTH_NAME) ||
+ StringUtils.startsWithIgnoreCase(cookie.getName(), COOKIE_AUTH_NAME + ".");
+
private static final Predicate CREDENTIALS_COOKIE = cookie ->
CREDENTIALS_COOKIE_INPUT.test(cookie) ||
- StringUtils.equalsIgnoreCase(cookie.getName(), "jwtToken") ||
- StringUtils.equalsIgnoreCase(cookie.getName(), "LtpaToken2");
+ Strings.CI.equals(cookie.getName(), "jwtToken") ||
+ Strings.CI.equals(cookie.getName(), "LtpaToken2");
private static final Predicate CREDENTIALS_HEADER_INPUT = headerName ->
- StringUtils.equalsIgnoreCase(headerName, HttpHeaders.AUTHORIZATION) ||
- StringUtils.equalsIgnoreCase(headerName, PAT_HEADER_NAME);
+ Strings.CI.equals(headerName, HttpHeaders.AUTHORIZATION) ||
+ Strings.CI.equals(headerName, PAT_HEADER_NAME);
+
private static final Predicate CREDENTIALS_HEADER = headerName ->
CREDENTIALS_HEADER_INPUT.test(headerName) ||
- CERTIFICATE_HEADERS_TEST.test(headerName) ||
- StringUtils.equalsIgnoreCase(headerName, "X-SAF-Token") ||
- StringUtils.equalsIgnoreCase(headerName, CLIENT_CERT_HEADER) ||
- StringUtils.equalsIgnoreCase(headerName, HttpHeaders.COOKIE);
+ CERTIFICATE_HEADERS_TEST.test(headerName) ||
+ Strings.CI.equals(headerName, "X-SAF-Token") ||
+ Strings.CI.equals(headerName, CLIENT_CERT_HEADER) ||
+ Strings.CI.equals(headerName, HttpHeaders.COOKIE);
protected final InstanceInfoService instanceInfoService;
protected final MessageService messageService;
@@ -150,7 +156,7 @@ protected AbstractAuthSchemeFactory(Class configClazz, InstanceInfoService in
protected abstract AuthenticationScheme getAuthenticationScheme();
- protected abstract Function>> getAuthorizationResponseTransformer();
+ protected abstract Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange);
/**
* The method responsible for reading a response from a ZAAS component and decorating of user request (i.e. set
@@ -196,7 +202,7 @@ protected RequestCredentials.RequestCredentialsBuilder createRequestCredentials(
}
/**
- * This method remove a necessary subset of credentials in case of authentication fail. If ZAAS cannot generate a
+ * This method removes a necessary subset of credentials in case of authentication fail. If ZAAS cannot generate a
* new credentials (i.e. because of basic authentication, expired token, etc.) the Gateway should provide the original
* credentials passed by a user. But there are headers that could be removed to avoid misusing (see forwarding
* certificate - user cannot provide a public certificate to take foreign privileges).
@@ -209,6 +215,7 @@ protected RequestCredentials.RequestCredentialsBuilder createRequestCredentials(
protected ServerHttpRequest cleanHeadersOnAuthFail(ServerWebExchange exchange, String errorMessage) {
var otelContext = OtelRequestContext.of(exchange);
otelContext.authenticationFailed();
+ otelContext.authErrorMessage(errorMessage);
Optional.ofNullable(getAuthenticationScheme()).ifPresent(otelContext::authMethod);
return exchange.getRequest().mutate().headers(headers -> {
@@ -253,7 +260,7 @@ protected ServerHttpRequest cleanHeadersOnAuthSuccess(ServerWebExchange exchange
}
protected GatewayFilter createGatewayFilter(T config) {
- return (exchange, chain) -> getAuthorizationResponseTransformer()
+ return (exchange, chain) -> getAuthorizationResponseTransformer(exchange)
.apply(createRequestCredentials(exchange, config).build())
.flatMap(response -> processResponse(exchange, chain, response));
}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/PassticketFilterFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/PassticketFilterFactory.java
index 43ef3400c5..e3513cc6a1 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/PassticketFilterFactory.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/PassticketFilterFactory.java
@@ -56,8 +56,8 @@ protected AuthenticationScheme getAuthenticationScheme() {
}
@Override
- protected Function>> getAuthorizationResponseTransformer() {
- return zaasSchemeTransform::passticket;
+ protected Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange) {
+ return requestCredentials -> zaasSchemeTransform.passticket(requestCredentials, exchange);
}
@Override
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactory.java
index 906f1bb56d..6a2fad028f 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactory.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactory.java
@@ -37,7 +37,7 @@ protected AuthenticationScheme getAuthenticationScheme() {
}
@Override
- protected Function>> getAuthorizationResponseTransformer() {
+ protected Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange) {
throw new IllegalStateException("not implemented");
}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/SafIdtFilterFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/SafIdtFilterFactory.java
index ec3c145aa9..9c2b380dc5 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/SafIdtFilterFactory.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/SafIdtFilterFactory.java
@@ -39,8 +39,8 @@ protected AuthenticationScheme getAuthenticationScheme() {
}
@Override
- protected Function>> getAuthorizationResponseTransformer() {
- return zaasSchemeTransform::safIdt;
+ protected Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange) {
+ return requestCredentials -> zaasSchemeTransform.safIdt(requestCredentials, exchange);
}
@Override
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransform.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransform.java
index ff104bd3ba..2263e04784 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransform.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransform.java
@@ -10,18 +10,19 @@
package org.zowe.apiml.gateway.filters;
+import org.springframework.web.server.ServerWebExchange;
import org.zowe.apiml.ticket.TicketResponse;
import org.zowe.apiml.zaas.ZaasTokenResponse;
import reactor.core.publisher.Mono;
public interface ZaasSchemeTransform {
- Mono> passticket(RequestCredentials requestCredentials);
+ Mono> passticket(RequestCredentials requestCredentials, ServerWebExchange exchange);
- Mono> safIdt(RequestCredentials requestCredentials);
+ Mono> safIdt(RequestCredentials requestCredentials, ServerWebExchange exchange);
- Mono> zosmf(RequestCredentials requestCredentials);
+ Mono> zosmf(RequestCredentials requestCredentials, ServerWebExchange exchange);
- Mono> zoweJwt(RequestCredentials requestCredentials);
+ Mono> zoweJwt(RequestCredentials requestCredentials, ServerWebExchange exchange);
}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransformRest.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransformRest.java
index c75e4256d7..263bf2d6f3 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransformRest.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransformRest.java
@@ -24,6 +24,7 @@
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.server.ServerWebExchange;
import org.zowe.apiml.product.constants.CoreService;
import org.zowe.apiml.security.common.error.ServiceNotAccessibleException;
import org.zowe.apiml.ticket.TicketRequest;
@@ -133,7 +134,7 @@ private String getUrl(String pattern, ServiceInstance instance) {
}
@Override
- public Mono> passticket(RequestCredentials requestCredentials) {
+ public Mono> passticket(RequestCredentials requestCredentials, ServerWebExchange exchange) {
try {
var jsonBody = WRITER.writeValueAsString(new TicketRequest(requestCredentials.getApplId()));
return call(
@@ -152,7 +153,7 @@ public Mono> pas
}
@Override
- public Mono> safIdt(RequestCredentials requestCredentials) {
+ public Mono> safIdt(RequestCredentials requestCredentials, ServerWebExchange exchange) {
try {
String jsonBody = WRITER.writeValueAsString(new TicketRequest(requestCredentials.getApplId()));
return call(
@@ -172,7 +173,7 @@ public Mono>
}
@Override
- public Mono> zosmf(RequestCredentials requestCredentials) {
+ public Mono> zosmf(RequestCredentials requestCredentials, ServerWebExchange exchange) {
return call(
ZaasTokenResponse.class,
instance -> createRequest(
@@ -184,7 +185,7 @@ public Mono>
}
@Override
- public Mono> zoweJwt(RequestCredentials requestCredentials) {
+ public Mono> zoweJwt(RequestCredentials requestCredentials, ServerWebExchange exchange) {
return call(
ZaasTokenResponse.class,
instance -> createRequest(
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZosmfFilterFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZosmfFilterFactory.java
index 95ade7a225..d93034ef57 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZosmfFilterFactory.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZosmfFilterFactory.java
@@ -11,6 +11,7 @@
package org.zowe.apiml.gateway.filters;
import org.springframework.stereotype.Service;
+import org.springframework.web.server.ServerWebExchange;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.gateway.service.InstanceInfoService;
import org.zowe.apiml.message.core.MessageService;
@@ -36,8 +37,8 @@ protected AuthenticationScheme getAuthenticationScheme() {
}
@Override
- protected Function>> getAuthorizationResponseTransformer() {
- return zaasSchemeTransform::zosmf;
+ protected Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange) {
+ return requestCredentials -> zaasSchemeTransform.zosmf(requestCredentials, exchange);
}
}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZoweFilterFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZoweFilterFactory.java
index 10e406a8dc..767a443c7f 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZoweFilterFactory.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZoweFilterFactory.java
@@ -43,8 +43,8 @@ protected AuthenticationScheme getAuthenticationScheme() {
}
@Override
- protected Function>> getAuthorizationResponseTransformer() {
- return zaasSchemeTransform::zoweJwt;
+ protected Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange) {
+ return requestCredentials -> zaasSchemeTransform.zoweJwt(requestCredentials, exchange);
}
@Override
diff --git a/gateway-service/src/main/java/org/zowe/apiml/product/opentelemetry/OtelRequestContext.java b/gateway-service/src/main/java/org/zowe/apiml/product/opentelemetry/OtelRequestContext.java
index dc4e5e433b..1d70c42771 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/product/opentelemetry/OtelRequestContext.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/product/opentelemetry/OtelRequestContext.java
@@ -32,7 +32,8 @@ public final class OtelRequestContext {
public static final String OTEL_CONTEXT = "otel-context";
private static final String OK = "OK";
- private static final String FAILED = "FAILED";
+ private static final String ERROR = "ERROR";
+ public static final String BASIC_AUTH_TYPE = "basicAuth";
private static final String OTEL_ATTRIBUTE_METHOD = "http.request.method";
private static final String OTEL_ATTRIBUTE_SCHEME = "url.scheme";
@@ -43,6 +44,8 @@ public final class OtelRequestContext {
private static final String OTEL_ATTRIBUTE_AUTH_METHOD = "auth.service.auth.method";
private static final String OTEL_ATTRIBUTE_AUTH_SOURCE_TYPE = "auth.method";
private static final String OTEL_ATTRIBUTE_AUTH_STATUS = "auth.status";
+ private static final String OTEL_ATTRIBUTE_AUTH_ERROR_TYPE = "auth.error.type";
+ private static final String OTEL_ATTRIBUTE_AUTH_ERROR_MESSAGE = "auth.error.message";
private static final String OTEL_ATTRIBUTE_USER_ID = "user.id";
private static final String OTEL_ATTRIBUTE_DISTRIBUTED_USER_ID = "user.distributed.id";
@@ -97,8 +100,20 @@ public OtelRequestContext authMethod(AuthenticationScheme authenticationScheme)
return put(OTEL_ATTRIBUTE_AUTH_METHOD, String.valueOf(authenticationScheme));
}
+ public OtelRequestContext authMethod(String authenticationScheme) {
+ return put(OTEL_ATTRIBUTE_AUTH_METHOD, authenticationScheme);
+ }
+
public OtelRequestContext authenticationFailed() {
- return put(OTEL_ATTRIBUTE_AUTH_STATUS, FAILED);
+ return put(OTEL_ATTRIBUTE_AUTH_STATUS, ERROR);
+ }
+
+ public OtelRequestContext authErrorType(String authErrorType) {
+ return put(OTEL_ATTRIBUTE_AUTH_ERROR_TYPE, authErrorType);
+ }
+
+ public OtelRequestContext authErrorMessage(String authErrorMessage) {
+ return put(OTEL_ATTRIBUTE_AUTH_ERROR_MESSAGE, authErrorMessage);
}
public OtelRequestContext authenticationSuccess() {
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/AbstractTokenFilterFactoryTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/AbstractTokenFilterFactoryTest.java
index 0631263bd9..20218fc0cc 100644
--- a/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/AbstractTokenFilterFactoryTest.java
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/AbstractTokenFilterFactoryTest.java
@@ -18,6 +18,7 @@
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
@@ -52,7 +53,7 @@ private ServerHttpRequest testRequestMutation(AbstractAuthSchemeFactory.Authoriz
new AbstractTokenFilterFactory<>(AbstractTokenFilterFactory.Config.class, null, null) {
@Override
- protected Function>> getAuthorizationResponseTransformer() {
+ protected Function>> getAuthorizationResponseTransformer(ServerWebExchange exchange) {
return null;
}
@@ -73,7 +74,7 @@ class ValidResponse {
@Test
void givenHeaderResponse_whenHandling_thenUpdateTheRequest() {
- var request = testRequestMutation(new AbstractAuthSchemeFactory.AuthorizationResponse<>(null,ZaasTokenResponse.builder()
+ var request = testRequestMutation(new AbstractAuthSchemeFactory.AuthorizationResponse<>(null, ZaasTokenResponse.builder()
.headerName("headerName")
.token("headerValue")
.build()
@@ -83,13 +84,13 @@ void givenHeaderResponse_whenHandling_thenUpdateTheRequest() {
@Test
void givenCookieResponse_whenHandling_thenUpdateTheRequest() {
- var request = testRequestMutation(new AbstractAuthSchemeFactory.AuthorizationResponse<>(null,ZaasTokenResponse.builder()
+ var request = testRequestMutation(new AbstractAuthSchemeFactory.AuthorizationResponse<>(null, ZaasTokenResponse.builder()
.cookieName("cookieName")
.token("cookieValue")
.build()
));
assertEquals("cookieName=cookieValue", request.getHeaders().getFirst("cookie"));
- assertEquals("Bearer cookieValue" , request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
+ assertEquals("Bearer cookieValue", request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
}
}
@@ -126,7 +127,7 @@ void givenEmptyResponseWithError_whenHandling_thenProvideErrorHeader() {
@Test
void givenCookieAndHeaderInResponse_whenHandling_thenSetBoth() {
- var request = testRequestMutation(new AbstractAuthSchemeFactory.AuthorizationResponse<>(null,ZaasTokenResponse.builder()
+ var request = testRequestMutation(new AbstractAuthSchemeFactory.AuthorizationResponse<>(null, ZaasTokenResponse.builder()
.cookieName("cookie")
.headerName("header")
.token("jwt")
@@ -144,20 +145,23 @@ void givenCookieAndHeaderInResponse_whenHandling_thenSetBoth() {
class Otel {
MockServerHttpRequest request = MockServerHttpRequest.get("/aPath").build();
- MockServerWebExchange exchange = MockServerWebExchange.from(request);
+ MockServerWebExchange exchange;
OtelRequestContext otelRequestContext;
@BeforeEach
void mockOtelContext() {
+ exchange = MockServerWebExchange.from(request);
otelRequestContext = spy(OtelRequestContext.of(exchange));
exchange.getAttributes().put("otel-context", otelRequestContext);
}
@Test
void givenOtelRequestContext_whenFail_thenCallAuthenticationFailed() {
+ exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
spy(AbstractAuthSchemeFactory.class).cleanHeadersOnAuthFail(exchange, "test");
verify(otelRequestContext, times(1)).authenticationFailed();
+ verify(otelRequestContext, times(1)).authErrorMessage("test");
}
@Test
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactoryTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactoryTest.java
index db29456a3a..71da0bca8e 100644
--- a/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactoryTest.java
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/RoutingConfigurationErrorFilterFactoryTest.java
@@ -10,9 +10,12 @@
package org.zowe.apiml.gateway.filters;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
@@ -21,39 +24,57 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@ExtendWith(MockitoExtension.class)
class RoutingConfigurationErrorFilterFactoryTest {
private static final String MESSAGE = "test message";
- private RoutingConfigurationErrorFilterFactory underTest;
private GatewayFilter filter;
private MockServerHttpRequest request = MockServerHttpRequest.get("https://localhost/some/url").build();
private MockServerWebExchange exchange = MockServerWebExchange.from(request);
- @BeforeAll
+ @Spy
+ private RoutingConfigurationErrorFilterFactory underTest = new RoutingConfigurationErrorFilterFactory(null, null);
+ @Spy
+ private OtelRequestContext otelContext = OtelRequestContext.of(exchange);
+
+ @BeforeEach
void init() {
+ exchange = MockServerWebExchange.from(request);
var config = new RoutingConfigurationErrorFilterFactory.Config();
config.setMessage(MESSAGE);
config.setAuthenticationScheme("safIdt");
config.setServiceId("serviceId");
- underTest = spy(new RoutingConfigurationErrorFilterFactory(null, null));
filter = underTest.apply(config);
}
@Test
- void givenConfig_whenApply_thenSetAuthInformation() {
- var otelContext = spy(OtelRequestContext.of(exchange));
+ void givenConfig_whenApply_thenSetAuthInformationWithoutErrorType() {
exchange.getAttributes().put(OtelRequestContext.OTEL_CONTEXT, otelContext);
StepVerifier.create(filter.filter(exchange, e -> Mono.empty())).verifyComplete();
verify(otelContext).authenticationFailed();
+ verify(otelContext).authErrorMessage(MESSAGE);
+
+ verify(otelContext).authMethod(AuthenticationScheme.SAF_IDT);
+ verify(underTest).cleanHeadersOnAuthFail(exchange, MESSAGE);
+ }
+
+ @Test
+ void givenConfig_whenApply_thenSetFailedAuthInformationWithErrorType() {
+ exchange.getAttributes().put(OtelRequestContext.OTEL_CONTEXT, otelContext);
+
+ StepVerifier.create(filter.filter(exchange, e -> Mono.empty())).verifyComplete();
+
+ verify(otelContext).authenticationFailed();
+ verify(otelContext).authErrorMessage(MESSAGE);
+
verify(otelContext).authMethod(AuthenticationScheme.SAF_IDT);
verify(underTest).cleanHeadersOnAuthFail(exchange, MESSAGE);
}
diff --git a/gateway-service/src/test/java/org/zowe/apiml/product/opentelemetry/OtelRequestContextTest.java b/gateway-service/src/test/java/org/zowe/apiml/product/opentelemetry/OtelRequestContextTest.java
index 922b156005..e16f595652 100644
--- a/gateway-service/src/test/java/org/zowe/apiml/product/opentelemetry/OtelRequestContextTest.java
+++ b/gateway-service/src/test/java/org/zowe/apiml/product/opentelemetry/OtelRequestContextTest.java
@@ -113,7 +113,19 @@ void givenOtelContext_whenSetZoweJwtAuthMethod_thenTransformToString() {
@Test
void givenOtelContext_whenAuthenticationFailed_thenStoreFailedStringAsStatus() {
OtelRequestContext.of(exchange).authenticationFailed();
- assertEquals("FAILED", getValue("auth.status"));
+ assertEquals("ERROR", getValue("auth.status"));
+ }
+
+ @Test
+ void givenOtelContext_whenAuthErrorMessage_thenStoreMessageAsAuthErrorMessage() {
+ OtelRequestContext.of(exchange).authErrorMessage("Invalid credentials");
+ assertEquals("Invalid credentials", getValue("auth.error.message"));
+ }
+
+ @Test
+ void givenOtelContext_whenAuthErrorType_thenStoreErrorTypeAsAuthErrorType() {
+ OtelRequestContext.of(exchange).authErrorType("Forbidden");
+ assertEquals("Forbidden", getValue("auth.error.type"));
}
@Test
@@ -154,7 +166,8 @@ void givenInvalidData_whenObjectMapperFails_thenThrowIllegalStateException() thr
exchange = MockServerWebExchange.from(request);
var objectMapper = mock(ObjectMapper.class);
var otelRequestContext = spy(OtelRequestContext.of(exchange));
- var jsonProcessingException = new JsonProcessingException("test") {};
+ var jsonProcessingException = new JsonProcessingException("test") {
+ };
doReturn(objectMapper).when(otelRequestContext).getObjectMapper();
doThrow(jsonProcessingException).when(objectMapper).writeValueAsString(any());
diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogAuthenticationTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogAuthenticationTest.java
index a64d30af38..a5eae63a9a 100644
--- a/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogAuthenticationTest.java
+++ b/integration-tests/src/test/java/org/zowe/apiml/functional/apicatalog/ApiCatalogAuthenticationTest.java
@@ -16,7 +16,6 @@
import io.restassured.response.Validatable;
import io.restassured.specification.RequestSpecification;
import org.apache.commons.lang3.StringUtils;
-
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -35,7 +34,6 @@
import org.zowe.apiml.util.config.SslContext;
import org.zowe.apiml.util.service.DiscoveryUtils;
-
import java.util.LinkedList;
import java.util.stream.Stream;
@@ -145,6 +143,7 @@ void givenValidBasicAuthentication(String endpoint, Request request) {
)
.then()
.log().all()
+ .onFailMessage("On Gateway URL: " + endpoint)
.statusCode(is(SC_OK));
}
@@ -159,7 +158,8 @@ void givenValidBearerAuthentication(String endpoint, Request request) {
endpoint
)
.then()
- .statusCode(is(SC_OK));
+ .statusCode(is(SC_OK))
+ .onFailMessage("On Gateway URL: " + endpoint);
}
@ParameterizedTest(name = "givenValidBasicAuthenticationAndCertificate {index} {0} ")
@@ -174,7 +174,8 @@ void givenValidBasicAuthenticationAndCertificate(String endpoint, Request reques
)
.then()
.log().all()
- .statusCode(is(SC_OK));
+ .statusCode(is(SC_OK))
+ .onFailMessage("On Gateway URL: " + endpoint);
}
}
@@ -197,7 +198,8 @@ void givenNoAuthentication(String endpoint, Request request) {
.header(HttpHeaders.WWW_AUTHENTICATE, BASIC_AUTHENTICATION_PREFIX)
.body(
"messages.find { it.messageNumber == '" + UNAUTHENTICATED_ERROR_NUMBER + "' }.messageContent", equalTo(expectedMessage)
- );
+ )
+ .onFailMessage("On Gateway URL: " + endpoint);
}
@ParameterizedTest(name = "givenInvalidBasicAuthentication {index} {0}")
@@ -214,6 +216,7 @@ void givenInvalidBasicAuthentication(String endpoint, Request request) {
)
.then()
.statusCode(is(SC_UNAUTHORIZED))
+ .onFailMessage("On Gateway URL: " + endpoint)
.body(
"messages.find { it.messageNumber == '" + UNAUTHENTICATED_ERROR_NUMBER + "' }.messageContent", equalTo(expectedMessage)
);
@@ -232,6 +235,7 @@ void givenInvalidBearerAuthentication(String endpoint, Request request) {
)
.then()
.log().ifValidationFails()
+ .onFailMessage("On Gateway URL: " + endpoint)
.body(
"messages.find { it.messageNumber == 'ZWEAO402E' }.messageContent", equalTo(expectedMessage)
).statusCode(is(SC_UNAUTHORIZED));
@@ -253,7 +257,8 @@ void givenInvalidTokenInCookie(String endpoint, Request request) {
.statusCode(is(SC_UNAUTHORIZED))
.body(
"messages.find { it.messageNumber == 'ZWEAO402E' }.messageContent", equalTo(expectedMessage)
- );
+ )
+ .onFailMessage("On Gateway URL: " + endpoint);
}
}
}
@@ -277,7 +282,8 @@ void givenValidCertificate(String endpoint, Request request) {
)
.then()
.log().all()
- .statusCode(HttpStatus.OK.value());
+ .statusCode(HttpStatus.OK.value())
+ .onFailMessage("On Gateway URL: " + endpoint);
}
@ParameterizedTest(name = "givenValidCertificateAndBasicAuth {index} {0} ")
@@ -291,7 +297,8 @@ void givenValidCertificateAndBasicAuth(String endpoint, Request request) {
endpoint
)
.then()
- .statusCode(is(SC_OK));
+ .statusCode(is(SC_OK))
+ .onFailMessage("On Gateway URL: " + endpoint);
}
}
@@ -307,7 +314,8 @@ void givenUnTrustedCertificateAndNoBasicAuth_thenReturnUnauthorized(String endpo
endpoint
)
.then()
- .statusCode(HttpStatus.UNAUTHORIZED.value());
+ .statusCode(HttpStatus.UNAUTHORIZED.value())
+ .onFailMessage("On Gateway URL: " + endpoint);
}
@ParameterizedTest(name = "givenNoCertificateAndNoBasicAuth_thenReturnUnauthorized {index} {0} ")
@@ -319,7 +327,8 @@ void givenNoCertificateAndNoBasicAuth_thenReturnUnauthorized(String endpoint, Re
endpoint
)
.then()
- .statusCode(HttpStatus.UNAUTHORIZED.value());
+ .statusCode(HttpStatus.UNAUTHORIZED.value())
+ .onFailMessage("On Gateway URL: " + endpoint);
}
}
}