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 472159f9c6..b6d70a41f4 100644 --- a/apiml/src/main/java/org/zowe/apiml/filter/BasicLoginFilter.java +++ b/apiml/src/main/java/org/zowe/apiml/filter/BasicLoginFilter.java @@ -79,7 +79,7 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { .switchIfEmpty(Mono.defer(() -> chain.filter(exchange).then(Mono.empty()))) .flatMap(credentials -> { var otelContext = OtelRequestContext.of(exchange); - otelContext.authSourceType(OtelRequestContext.BASIC_AUTH_TYPE); + otelContext.authSourceType(OtelRequestContext.AUTH_TYPE_BASIC); return authenticationManager.authenticate(credentials) .flatMap(authentication -> chain.filter(exchange) .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))); diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryAnonymousBypassTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryAnonymousBypassTest.java new file mode 100644 index 0000000000..979edef984 --- /dev/null +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryAnonymousBypassTest.java @@ -0,0 +1,207 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ + +package org.zowe.apiml.acceptance; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.zowe.apiml.auth.AuthenticationScheme; +import org.zowe.apiml.gateway.MockService; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.awaitility.Awaitility.await; +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies that bypass-authentication routes emit OTel log signals with + * {@code user.id=anonymous} and {@code auth.status=OK} instead of null + * attributes (see #4704). Covers both successful (200) and downstream + * error (500) scenarios. + */ +class OpenTelemetryAnonymousBypassTest { + + @Nested + @AcceptanceTest + @ActiveProfiles({"OpenTelemetryTest"}) + @TestPropertySource( + properties = { + "otel.sdk.disabled=false", + "otel.metrics.exporter=none", + "otel.traces.exporter=none", + "otel.logs.exporter=none" + } + ) + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class WhenAnonymousBypassRequest extends AcceptanceTestWithMockServices { + + @Autowired + private InMemoryLogRecordExporter logExporter; + + @LocalServerPort + private int port; + + private MockService mockServiceBypass; + + @BeforeAll + void init() { + mockServiceBypass = mockService("testservicebp") + .scope(MockService.Scope.CLASS) + .authenticationScheme(AuthenticationScheme.BYPASS) + .addEndpoint("/testservicebp/200") + .responseCode(200) + .and() + .addEndpoint("/testservicebp/500") + .responseCode(500) + .and().start(); + } + + @BeforeEach + void setUp() { + logExporter.reset(); + } + + private List assertLogsExported() { + List logs = new ArrayList<>(); + await("Log export") + .atMost(Duration.ofSeconds(10)) + .until(() -> { + var l = logExporter.getFinishedLogRecordItems(); + if (l.size() > 0) { + logs.addAll(l); + } + logExporter.reset(); + return l.size() > 0; + }); + return logs; + } + + private LogRecordData assertOneLogRecordExported(String expectedUrl) { + var logs = assertLogsExported(); + + var logRecord = logs.stream() + .filter(log -> log.getBodyValue().asString().contains(expectedUrl)) + .findFirst() + .orElseThrow(() -> { + var availableUrls = logs.stream() + .map(log -> log.getBodyValue().asString()) + .collect(Collectors.joining(", ")); + return new AssertionError( + "Expected log record with URL " + expectedUrl + " not found in logs: " + availableUrls); + }); + + assertEquals("INFO", logRecord.getSeverityText(), + "Expected INFO log level, was " + logRecord.getSeverityText()); + + var logBody = logRecord.getBodyValue().asString(); + assertTrue(StringUtils.isNotBlank(logBody)); + + return logRecord; + } + + private Map parseLogBody(String logBody) { + try { + return new ObjectMapper().readValue(logBody, Map.class); + } catch (JsonProcessingException e) { + fail("Invalid JSON: " + logBody, e); + return Map.of(); + } + } + + @Test + void thenLogWithAnonymousUserIdAndAuthOk() { + given() + .get(basePath + "/testservicebp/api/v1/200") + .then() + .statusCode(200); + + var logRecord = assertOneLogRecordExported("/testservicebp/api/v1/200"); + @SuppressWarnings("null") + Map logBody = parseLogBody(logRecord.getBodyValue().asString()); + + // Full attribute set verification + assertEquals("GET", logBody.get("http.request.method"), + "http.request.method should be GET"); + assertEquals("https", logBody.get("url.scheme"), + "url.scheme should be https"); + assertEquals("/testservicebp/api/v1/200", logBody.get("url.path"), + "url.path should match request path"); + assertEquals("testservicebp", logBody.get("service.id"), + "service.id should be testservicebp"); + assertEquals("localhost:testservicebp:" + mockServiceBypass.getPort(), + logBody.get("service.instance.id"), + "service.instance.id should match mock service"); + assertEquals("bypass", logBody.get("auth.service.auth.method"), + "auth.service.auth.method should be bypass"); + assertEquals("200", logBody.get("service.response_code"), + "service.response_code should be 200"); + + // NEW behavior: anonymous user ID and OK auth status + assertEquals("anonymous", logBody.get("user.id"), + "user.id should be 'anonymous' for bypass routes"); + assertEquals("OK", logBody.get("auth.status"), + "auth.status should be 'OK' for bypass routes"); + + // Error attributes should NOT be present for successful bypass + assertNull(logBody.get("auth.error.type"), + "auth.error.type should be null for successful bypass"); + assertNull(logBody.get("auth.error.message"), + "auth.error.message should be null for successful bypass"); + } + + @Test + void thenLogWithErrorAttributesOnRoutingError() { + given() + .get(basePath + "/testservicebp/api/v1/500") + .then() + .statusCode(500); + + var logRecord = assertOneLogRecordExported("/testservicebp/api/v1/500"); + @SuppressWarnings("null") + Map logBody = parseLogBody(logRecord.getBodyValue().asString()); + + // Standard attributes still present + assertEquals("GET", logBody.get("http.request.method")); + assertEquals("https", logBody.get("url.scheme")); + assertEquals("/testservicebp/api/v1/500", logBody.get("url.path")); + assertEquals("testservicebp", logBody.get("service.id")); + assertEquals("localhost:testservicebp:" + mockServiceBypass.getPort(), + logBody.get("service.instance.id")); + assertEquals("bypass", logBody.get("auth.service.auth.method")); + assertEquals("500", logBody.get("service.response_code"), + "service.response_code should be 500 for error endpoint"); + + // Anonymous user ID and OK auth status still apply (bypass auth succeeded) + assertEquals("anonymous", logBody.get("user.id"), + "user.id should be 'anonymous' for bypass routes even on error"); + assertEquals("OK", logBody.get("auth.status"), + "auth.status should be 'OK' for bypass routes even on error"); + + // Error attributes: service error but auth succeeded, so no auth.error attributes + assertNull(logBody.get("auth.error.type"), + "auth.error.type should be null — bypass auth always succeeds"); + assertNull(logBody.get("auth.error.message"), + "auth.error.message should be null — bypass auth always succeeds"); + } + } +} 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 f4ecffb8fd..591dde1343 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryResourceAttributesZosTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryResourceAttributesZosTest.java @@ -538,10 +538,10 @@ void thenLog() { assertAttributesBase(logRecord.getResource().getAttributes(), port); @SuppressWarnings("null") var logBody = logRecord.getBodyValue().asString(); - assertNull(getAttribute(logBody, "user.id")); + assertEquals("anonymous", getAttribute(logBody, "user.id")); assertEquals("testservicebp", getAttribute(logBody, "service.id")); assertEquals("GET", getAttribute(logBody, "http.request.method")); - assertNull(getAttribute(logBody, "auth.status")); + assertEquals("OK", 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")); diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactory.java index ce50a6b97e..a7558621c2 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactory.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactory.java @@ -30,10 +30,15 @@ public OtelServiceFilterFactory() { @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { - OtelRequestContext.of(exchange) + var ctx = OtelRequestContext.of(exchange) .authMethod(AuthenticationScheme.BYPASS) .serviceId(config.serviceId) .instanceId(config.instanceId); + + if (AuthenticationScheme.BYPASS.name().equalsIgnoreCase(config.authenticationScheme)) { + ctx.anonymousUserId() + .authenticationSuccess(); + } return chain.filter(exchange); }; } @@ -44,6 +49,8 @@ public static class Config { private String instanceId; private String serviceId; + /** The {@link AuthenticationScheme#name()} for this route, or null if unavailable. */ + private String authenticationScheme; } diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/service/RouteLocator.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/service/RouteLocator.java index 7093fb5666..d1396217a2 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/service/RouteLocator.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/service/RouteLocator.java @@ -111,7 +111,7 @@ static List join(List a, List b) { return output; } - List getPostRoutingFilters(ServiceInstance serviceInstance, RoutedService routedService) { + List getPostRoutingFilters(ServiceInstance serviceInstance, RoutedService routedService, Authentication auth) { List serviceRelated = new LinkedList<>(); if (forwardingClientCertEnabled && Optional.ofNullable(serviceInstance.getMetadata().get(SERVICE_SUPPORTING_CLIENT_CERT_FORWARDING)) @@ -162,6 +162,9 @@ List getPostRoutingFilters(ServiceInstance serviceInstance, Ro otelRequestBasicFilter.setName("OtelServiceFilterFactory"); otelRequestBasicFilter.addArg("serviceId", serviceInstance.getServiceId()); otelRequestBasicFilter.addArg("instanceId", serviceInstance.getInstanceId()); + if (auth != null && auth.getScheme() != null) { + otelRequestBasicFilter.addArg("authenticationScheme", auth.getScheme().name()); + } serviceRelated.add(otelRequestBasicFilter); } @@ -186,7 +189,7 @@ private List getAuthFilterPerRoute( // generate a new routing rule by a specific produces RouteDefinition routeDefinition = rdp.get(serviceInstance, routedService); routeDefinition.setOrder(orderHolder.getAndIncrement()); - routeDefinition.getFilters().addAll(getPostRoutingFilters(serviceInstance, routedService)); + routeDefinition.getFilters().addAll(getPostRoutingFilters(serviceInstance, routedService, auth)); setAuth(serviceInstance, routeDefinition, auth); return routeDefinition; 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 dc5059b90b..857fa14fcd 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 @@ -31,9 +31,10 @@ public final class OtelRequestContext { public static final String OTEL_CONTEXT = "otel-context"; - private static final String OK = "OK"; - private static final String ERROR = "ERROR"; - public static final String BASIC_AUTH_TYPE = "BASIC"; + public static final String AUTH_STATUS_OK = "OK"; + public static final String AUTH_STATUS_ERROR = "ERROR"; + public static final String AUTH_TYPE_BASIC = "BASIC"; + public static final String ANONYMOUS_USER_ID = "anonymous"; private static final String OTEL_ATTRIBUTE_METHOD = "http.request.method"; private static final String OTEL_ATTRIBUTE_SCHEME = "url.scheme"; @@ -105,7 +106,7 @@ public OtelRequestContext authMethod(String authenticationScheme) { } public OtelRequestContext authenticationFailed() { - return put(OTEL_ATTRIBUTE_AUTH_STATUS, ERROR); + return put(OTEL_ATTRIBUTE_AUTH_STATUS, AUTH_STATUS_ERROR); } public OtelRequestContext authErrorType(String authErrorType) { @@ -117,13 +118,17 @@ public OtelRequestContext authErrorMessage(String authErrorMessage) { } public OtelRequestContext authenticationSuccess() { - return put(OTEL_ATTRIBUTE_AUTH_STATUS, OK); + return put(OTEL_ATTRIBUTE_AUTH_STATUS, AUTH_STATUS_OK); } public OtelRequestContext userId(String userId) { return put(OTEL_ATTRIBUTE_USER_ID, StringUtils.upperCase(userId)); } + public OtelRequestContext anonymousUserId() { + return put(OTEL_ATTRIBUTE_USER_ID, ANONYMOUS_USER_ID); + } + public OtelRequestContext distributedIds(List distributedIds) { attributesBuilder.put(OTEL_ATTRIBUTE_DISTRIBUTED_USER_ID, distributedIds.toArray(new String[0])); return this; diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactoryTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactoryTest.java index afee11ebab..c8a4e00ccc 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactoryTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/filters/OtelServiceFilterFactoryTest.java @@ -19,7 +19,7 @@ import org.zowe.apiml.product.opentelemetry.OtelRequestContext; import reactor.core.publisher.Mono; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; class OtelServiceFilterFactoryTest { @@ -34,6 +34,7 @@ void givenConfiguredFilter_whenApply_thenSetBypassServiceIdAndInstanceId() { var config = new OtelServiceFilterFactory.Config(); config.setServiceId(SERVICE_ID); config.setInstanceId(INSTANCE_ID); + config.setAuthenticationScheme("BYPASS"); new OtelServiceFilterFactory().apply(config).filter(exchange, e -> Mono.empty().then()); @@ -41,6 +42,49 @@ void givenConfiguredFilter_whenApply_thenSetBypassServiceIdAndInstanceId() { assertEquals("bypass", attributes.get(AttributeKey.stringKey("auth.service.auth.method"))); assertEquals(SERVICE_ID.toLowerCase(), attributes.get(AttributeKey.stringKey("service.id"))); assertEquals(INSTANCE_ID.toLowerCase(), attributes.get(AttributeKey.stringKey("service.instance.id"))); + assertEquals("anonymous", attributes.get(AttributeKey.stringKey("user.id"))); + assertEquals(OtelRequestContext.AUTH_STATUS_OK, attributes.get(AttributeKey.stringKey("auth.status"))); + } + + @Test + void givenConfiguredFilterWithoutBypass_whenApply_thenDoNotSetAnonymousUserAndAuthStatus() { + MockServerHttpRequest request = MockServerHttpRequest.get("/aPath").build(); + MockServerWebExchange exchange = MockServerWebExchange.from(request); + + var config = new OtelServiceFilterFactory.Config(); + config.setServiceId(SERVICE_ID); + config.setInstanceId(INSTANCE_ID); + // authenticationScheme left unset (null) — not a BYPASS route + + new OtelServiceFilterFactory().apply(config).filter(exchange, e -> Mono.empty().then()); + + var attributes = ((AttributesBuilder) ReflectionTestUtils.getField(OtelRequestContext.of(exchange), "attributesBuilder")).build(); + assertEquals("bypass", attributes.get(AttributeKey.stringKey("auth.service.auth.method"))); + assertEquals(SERVICE_ID.toLowerCase(), attributes.get(AttributeKey.stringKey("service.id"))); + assertEquals(INSTANCE_ID.toLowerCase(), attributes.get(AttributeKey.stringKey("service.instance.id"))); + // user.id and auth.status should remain unset for non-BYPASS routes + assertNull(attributes.get(AttributeKey.stringKey("user.id"))); + assertNull(attributes.get(AttributeKey.stringKey("auth.status"))); + } + + @Test + void givenConfiguredFilterWithNonBypassAuth_whenApply_thenDoNotSetAnonymousUserAndAuthStatus() { + MockServerHttpRequest request = MockServerHttpRequest.get("/aPath").build(); + MockServerWebExchange exchange = MockServerWebExchange.from(request); + + var config = new OtelServiceFilterFactory.Config(); + config.setServiceId(SERVICE_ID); + config.setInstanceId(INSTANCE_ID); + config.setAuthenticationScheme("ZOWE_JWT"); // explicitly non-BYPASS + + new OtelServiceFilterFactory().apply(config).filter(exchange, e -> Mono.empty().then()); + + var attributes = ((AttributesBuilder) ReflectionTestUtils.getField(OtelRequestContext.of(exchange), "attributesBuilder")).build(); + assertEquals("bypass", attributes.get(AttributeKey.stringKey("auth.service.auth.method"))); + assertEquals(SERVICE_ID.toLowerCase(), attributes.get(AttributeKey.stringKey("service.id"))); + assertEquals(INSTANCE_ID.toLowerCase(), attributes.get(AttributeKey.stringKey("service.instance.id"))); + assertNull(attributes.get(AttributeKey.stringKey("user.id"))); + assertNull(attributes.get(AttributeKey.stringKey("auth.status"))); } } diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/service/RouteLocatorTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/service/RouteLocatorTest.java index ce0cb99403..2e7b94ecff 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/service/RouteLocatorTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/service/RouteLocatorTest.java @@ -269,7 +269,7 @@ void enableForwarding() { void givenServiceAllowingCertForwarding_whenGetPostRoutingFilters_thenAddClientCertFilterFactory() { ServiceInstance serviceInstance = createServiceInstance(Boolean.TRUE, null, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertEquals(3, filterDefinitions.size()); // common filters + PageRedirectionFilterFactory assertEquals("ForwardClientCertFilterFactory", filterDefinitions.get(1).getName()); } @@ -278,7 +278,7 @@ void givenServiceAllowingCertForwarding_whenGetPostRoutingFilters_thenAddClientC void givenServiceNotAllowingCertForwarding_whenGetPostRoutingFilters_thenReturnJustCommon() { ServiceInstance serviceInstance = createServiceInstance(Boolean.FALSE, null, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "ForwardClientCertFilterFactory".equals(filter.getName()))); @@ -289,7 +289,7 @@ void givenServiceNotAllowingCertForwarding_whenGetPostRoutingFilters_thenReturnJ void givenServiceWithoutCertForwardingConfig_whenGetPostRoutingFilters_thenReturnJustCommon() { ServiceInstance serviceInstance = createServiceInstance(null, null, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "ForwardClientCertFilterFactory".equals(filter.getName()))); @@ -309,7 +309,7 @@ void disableForwarding() { void givenAnyService_whenGetPostRoutingFilters_thenReturnJustCommon() { ServiceInstance serviceInstance = createServiceInstance(Boolean.TRUE, null, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "ForwardClientCertFilterFactory".equals(filter.getName()))); @@ -323,7 +323,7 @@ class EncodedCharacters { @Test void givenServiceAllowingEncodedCharacters_whenGetPostRoutingFilters_thenReturnJustCommon() { ServiceInstance serviceInstance = createServiceInstance(null, Boolean.TRUE, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "ForbidEncodedCharactersFilterFactory".equals(filter.getName()))); @@ -332,7 +332,7 @@ void givenServiceAllowingEncodedCharacters_whenGetPostRoutingFilters_thenReturnJ @Test void givenServiceNotAllowingEncodedCharacters_whenGetPostRoutingFilters_thenAddEncodedCharacterFilterFactory() { ServiceInstance serviceInstance = createServiceInstance(null, Boolean.FALSE, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertEquals(3, filterDefinitions.size()); assertEquals("ForbidEncodedCharactersFilterFactory", filterDefinitions.get(1).getName()); } @@ -340,7 +340,7 @@ void givenServiceNotAllowingEncodedCharacters_whenGetPostRoutingFilters_thenAddE @Test void givenServiceWithoutAllowingEncodedCharacters_whenGetPostRoutingFilters_thenAddEncodedCharacterFilterFactory() { ServiceInstance serviceInstance = createServiceInstance(null, null, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "ForbidEncodedCharactersFilterFactory".equals(filter.getName()))); @@ -354,7 +354,7 @@ class RateLimiter { @Test void givenServiceNotAllowingRateLimiter_whenGetPostRoutingFilters_thenReturnJustCommon() { ServiceInstance serviceInstance = createServiceInstance(null, null, Boolean.FALSE); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "InMemoryRateLimiterFilterFactory".equals(filter.getName()))); @@ -363,7 +363,7 @@ void givenServiceNotAllowingRateLimiter_whenGetPostRoutingFilters_thenReturnJust @Test void givenServiceAllowingRateLimiter_whenGetPostRoutingFilters_thenAddInMemoryRateLimiterFilterFactory() { ServiceInstance serviceInstance = createServiceInstance(null, null, Boolean.TRUE); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertEquals(3, filterDefinitions.size()); assertEquals("InMemoryRateLimiterFilterFactory", filterDefinitions.get(1).getName()); } @@ -371,7 +371,7 @@ void givenServiceAllowingRateLimiter_whenGetPostRoutingFilters_thenAddInMemoryRa @Test void givenServiceWithoutAllowingRateLimiter_whenGetPostRoutingFilters_thenDoNotAddInMemoryRateLimiterFilterFactory() { ServiceInstance serviceInstance = createServiceInstance(null, null, null); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertTrue(filterDefinitions.containsAll(COMMON_FILTERS), "Not all common filters are defined"); assertEquals(2, filterDefinitions.size()); assertTrue(filterDefinitions.stream().noneMatch(filter -> "InMemoryRateLimiterFilterFactory".equals(filter.getName()))); @@ -390,7 +390,7 @@ void setUp() { @Test void givenEnabledOtel_whenGetPostRoutingFilters_thenOtelServiceFilterFactoryIsCreated() { ServiceInstance serviceInstance = createServiceInstance(null, null, Boolean.TRUE); - List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, null); assertEquals(4, filterDefinitions.size()); var filter = filterDefinitions.get(3); @@ -399,6 +399,20 @@ void givenEnabledOtel_whenGetPostRoutingFilters_thenOtelServiceFilterFactoryIsCr assertEquals("dummy:instance:80", filter.getArgs().get("instanceId")); } + @Test + void givenEnabledOtelWithBypassAuth_whenGetPostRoutingFilters_thenIncludeAuthenticationSchemeArg() { + ServiceInstance serviceInstance = createServiceInstance(null, null, Boolean.TRUE); + var auth = new Authentication(AuthenticationScheme.BYPASS, null); + List filterDefinitions = routeLocator.getPostRoutingFilters(serviceInstance, routedService, auth); + assertEquals(4, filterDefinitions.size()); + + var filter = filterDefinitions.get(3); + assertEquals("OtelServiceFilterFactory", filter.getName()); + assertEquals("dummy", filter.getArgs().get("serviceId")); + assertEquals("dummy:instance:80", filter.getArgs().get("instanceId")); + assertEquals("BYPASS", filter.getArgs().get("authenticationScheme")); + } + } } 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 e16f595652..f0e2616b44 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,7 @@ void givenOtelContext_whenSetZoweJwtAuthMethod_thenTransformToString() { @Test void givenOtelContext_whenAuthenticationFailed_thenStoreFailedStringAsStatus() { OtelRequestContext.of(exchange).authenticationFailed(); - assertEquals("ERROR", getValue("auth.status")); + assertEquals(OtelRequestContext.AUTH_STATUS_ERROR, getValue("auth.status")); } @Test @@ -131,7 +131,7 @@ void givenOtelContext_whenAuthErrorType_thenStoreErrorTypeAsAuthErrorType() { @Test void givenOtelContext_whenauthenticationSuccess_thenStoreOkStringAsStatus() { OtelRequestContext.of(exchange).authenticationSuccess(); - assertEquals("OK", getValue("auth.status")); + assertEquals(OtelRequestContext.AUTH_STATUS_OK, getValue("auth.status")); } @Test @@ -140,6 +140,17 @@ void givenOtelContext_whenSetUserId_thenStoreUpperCase() { assertEquals("USERID", getValue("user.id")); } + @Test + void givenAnonymousUserIdConstant_whenRead_thenEqualsAnonymous() { + assertEquals("anonymous", OtelRequestContext.ANONYMOUS_USER_ID); + } + + @Test + void givenOtelContext_whenSetAnonymousUserId_thenStoreLowerCaseAnonymous() { + OtelRequestContext.of(exchange).anonymousUserId(); + assertEquals("anonymous", getValue("user.id")); + } + @Test void givenOtelContext_whenSetAuthSourceType_thenStoreIt() { OtelRequestContext.of(exchange).authSourceType("JWT");