diff --git a/core-context-propagation-quarkus/README.md b/core-context-propagation-quarkus/README.md index 6ab77d25c..28a01dcf2 100644 --- a/core-context-propagation-quarkus/README.md +++ b/core-context-propagation-quarkus/README.md @@ -133,27 +133,86 @@ Propagates and allows to get `X-Channel-Request-Id` value. If an incoming reques **Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing requests. -**Enabling propagation:** To allow `X-Channel-Request-Id` to be propagated to outgoing requests, remove it from the -blacklist using one of the following methods: +#### Internal blocklist -1. **Via environment variable:** -```text -HEADERS_BLOCKED= -``` +The framework owns a hard-coded internal blocklist of headers that are not propagated to outgoing +requests. It currently contains: + +- `X-Channel-Request-Id` + +The blocklist itself cannot be changed from configuration. The only externally visible knob is the +`quarkus.context.propagation.allow-blocked-headers` property, which lists header names that should be **exempted** from +this blocklist (i.e. allowed to propagate). + +#### `quarkus.context.propagation.allow-blocked-headers` property + +The property carries a comma-separated list of header names. Every name that matches an entry of +the internal blocklist is removed from the effective blocklist. Names that are not in the +internal blocklist have no effect. + +Examples: + +| Property value | Effect | +|---|---| +| not set / empty | Internal blocklist applies in full. `X-Channel-Request-Id` is not propagated. | +| `X-Channel-Request-Id` | `X-Channel-Request-Id` is propagated to outgoing requests. | +| `Some-Other-Header` | No effect — the header is not in the internal blocklist. | +| `X-Channel-Request-Id, Some-Other-Header` | `X-Channel-Request-Id` is propagated; the second entry is ignored. | + +Comparison is case-insensitive. Whitespace around comma-separated entries is trimmed. + +#### How to set the property + +**Via `application.properties`:** -2. **Via application.properties (Quarkus):** ```properties -quarkus.headers.blocked= +quarkus.context.propagation.allow-blocked-headers=X-Channel-Request-Id +``` + +**Via `application.yaml`:** + +```yaml +quarkus: + context: + propagation: + allow-blocked-headers: X-Channel-Request-Id +``` + +**Via JVM system property:** + +```text +-Dquarkus.context.propagation.allow-blocked-headers=X-Channel-Request-Id ``` -**`quarkus.headers.blocked` rules and limitations** +**Via container ENV (recommended, Qubership pattern)** + +In Qubership deployments configuration values are typically wired from container ENV variables +through `application.yaml` placeholders. The full chain looks like this: + +1. The container declares an ENV variable, for example in the deployment manifest: + + ```yaml + env: + - name: CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS + value: "X-Channel-Request-Id" + ``` + +2. The application's `application.yaml` reads it via a `${VAR}` placeholder: + + ```yaml + quarkus: + context: + propagation: + allow-blocked-headers: ${CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS:} + ``` + + The trailing `:` (empty default) lets the property gracefully fall through to "not set" when + the ENV variable is absent — the internal blocklist then applies in full. -- Source priority: system property `quarkus.headers.blocked` overrides environment variable `HEADERS_BLOCKED`. -- Default when not configured at all: `X-Channel-Request-Id` is blocked. -- Explicit empty value (`quarkus.headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). -- Explicit non-empty value with valid headers (for example `quarkus.headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `quarkus.headers.blocked`/`HEADERS_BLOCKED`, it is ignored. -- If configured value contains only non-blockable entries (for example only `X-Request-Id`), default block is applied and `X-Channel-Request-Id` remains blocked. +3. Quarkus also recognises the ENV variable directly under the canonical name + `CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS` (via MicroProfile Config relaxed env-to-property mapping), so + the same ENV variable works even without the `application.yaml` indirection. The placeholder + form is preferred because it makes the dependency explicit in source control. **MDC Integration:** The channel request ID is automatically stored in MDC under the key `x_channel_request_id` for use in logging. diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java index ff956700c..68e2a77b6 100644 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java @@ -5,20 +5,43 @@ import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithName; +import java.util.List; import java.util.Optional; @ConfigMapping(prefix = "quarkus") @ConfigRoot(phase = ConfigPhase.RUN_TIME) public interface HeadersAllowedConfig { + /** - * Allowed headers to propagate in contexts + * Allowed headers to propagate in contexts. */ @WithName("headers.allowed") Optional allowedHeaders(); /** - * Blocked headers for context propagation. X-Channel-Request-Id is blocked by default. + * Headers that should be allowed to propagate even though they appear in the framework's + * internal blocklist. The blocklist itself is hard-coded and cannot be changed from + * configuration. + * + *

Semantics: + *

+ * + *

Example {@code application.yaml} with a container ENV indirection: + *

+     * quarkus:
+     *   context:
+     *     propagation:
+     *       allow-blocked-headers: ${CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS:}
+     * 
+ * The container then sets {@code CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS=X-Channel-Request-Id} when it needs + * that header to be propagated. */ - @WithName("headers.blocked") - Optional blockedHeaders(); + @WithName("context.propagation.allow-blocked-headers") + Optional> allowedHeadersFromBlocklist(); } diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java index e795f9919..c0dabdb9e 100644 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java @@ -8,7 +8,12 @@ public class HeadersAllowedRecorder { public void setAllowedHeadersToSystemProperty() { HeadersAllowedConfig allowedConfig = Arc.container().instance(HeadersAllowedConfig.class).get(); - allowedConfig.allowedHeaders().ifPresent(allowedHeaders -> System.setProperty("headers.allowed", allowedHeaders)); - allowedConfig.blockedHeaders().ifPresent(blockedHeaders -> System.setProperty("headers.blocked", blockedHeaders)); + + allowedConfig.allowedHeaders() + .ifPresent(allowedHeaders -> System.setProperty("headers.allowed", allowedHeaders)); + + allowedConfig.allowedHeadersFromBlocklist() + .filter(list -> !list.isEmpty()) + .ifPresent(list -> System.setProperty("context.propagation.allow-blocked-headers", String.join(",", list))); } } diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java new file mode 100644 index 000000000..0d6f17fb1 --- /dev/null +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java @@ -0,0 +1,33 @@ +package com.netcracker.cloud.framework.quarkus.contexts.allowedheaders; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@QuarkusTest +class HeadersAllowBlockedRecorderEffectTest { + + @Test + void shouldExposeAllowBlockedValueAsSystemProperty() { + assertEquals("X-Channel-Request-Id", + System.getProperty("context.propagation.allow-blocked-headers"), + "Recorder must propagate the quarkus.context.propagation.allow-blocked-headers value " + + "to the context.propagation.allow-blocked-headers system property."); + } + + @Test + void shouldRemoveExemptedHeaderFromInternalBlocklist() { + // The blocked list is cached on first access; ensure we read the post-recorder state. + HeaderPropagationConfiguration.resetCache(); + + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "The only entry of the internal blocklist (X-Channel-Request-Id) must be removed " + + "by the exemption configured in application.properties."); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "X-Channel-Request-Id must not be blocked when explicitly exempted."); + } +} diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java new file mode 100644 index 000000000..61f016c4d --- /dev/null +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java @@ -0,0 +1,34 @@ +package com.netcracker.cloud.framework.quarkus.contexts.allowedheaders; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +@QuarkusTest +@TestProfile(HeadersAllowedConfigNotSetTest.NotSetProfile.class) +class HeadersAllowedConfigNotSetTest { + + @Inject + HeadersAllowedConfig headersAllowedConfig; + + @Test + void shouldReportAllowedFromBlocklistAsEmptyWhenNotConfigured() { + assertFalse(headersAllowedConfig.allowedHeadersFromBlocklist().isPresent(), + "quarkus.context.propagation.allow-blocked-headers must resolve to Optional.empty() " + + "when no exemption value is configured"); + } + + public static class NotSetProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.context.propagation.allow-blocked-headers", ""); + } + } +} diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java index 0d5618251..cf03260d5 100644 --- a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java @@ -4,12 +4,15 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @QuarkusTest class HeadersAllowedConfigTest { + @Inject HeadersAllowedConfig headersAllowedConfig; @@ -21,5 +24,13 @@ void shouldReadHeadersAllowedFromProperty() { assertEquals("test-quarkus.headers.allowed", value.get()); } -} + @Test + void shouldReadAllowedHeadersFromBlocklist() { + Optional> value = headersAllowedConfig.allowedHeadersFromBlocklist(); + assertTrue(value.isPresent(), + "quarkus.context.propagation.allow-blocked-headers must be present when configured"); + assertEquals(List.of("X-Channel-Request-Id"), value.get(), + "SmallRye must parse the comma-separated value into a list"); + } +} diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties index 6fecc5866..544d085e0 100644 --- a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties @@ -4,4 +4,6 @@ quarkus.devservices.enabled=false quarkus.rest-client.my-client.url=http://localhost:${quarkus.http.test-port} quarkus.headers.allowed=test-quarkus.headers.allowed + +quarkus.context.propagation.allow-blocked-headers=X-Channel-Request-Id headers.allowed=test-headers.allowed diff --git a/core-context-propagation/README.md b/core-context-propagation/README.md index 40f550846..ff5d3e453 100644 --- a/core-context-propagation/README.md +++ b/core-context-propagation/README.md @@ -147,32 +147,82 @@ Propagates and allows to get `X-Channel-Request-Id` value. If an incoming reques **Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing requests. -**Enabling propagation:** To allow `X-Channel-Request-Id` to be propagated to outgoing requests, remove it from the -blacklist using one of the following methods: +#### Internal blocklist -1. **Via environment variable:** -```text -HEADERS_BLOCKED= +The framework owns a hard-coded internal blocklist of headers that are not propagated to outgoing +requests. It currently contains: + +- `X-Channel-Request-Id` + +The blocklist itself cannot be changed from configuration. The only externally visible knob is the +`context.propagation.allow-blocked-headers` property, which lists header names that should be **exempted** from this +blocklist (i.e. allowed to propagate). + +#### `context.propagation.allow-blocked-headers` property + +The property carries a comma-separated list of header names. Every name that matches an entry of +the internal blocklist is removed from the effective blocklist. Names that are not in the +internal blocklist have no effect. + +Examples: + +| Property value | Effect | +|---|---| +| not set / empty | Internal blocklist applies in full. `X-Channel-Request-Id` is not propagated. | +| `X-Channel-Request-Id` | `X-Channel-Request-Id` is propagated to outgoing requests. | +| `Some-Other-Header` | No effect — the header is not in the internal blocklist. | +| `X-Channel-Request-Id, Some-Other-Header` | `X-Channel-Request-Id` is propagated; the second entry is ignored. | + +Comparison is case-insensitive. Whitespace around comma-separated entries is trimmed. + +#### How to set the property + +**Spring (`application.properties` / `application.yml`):** + +```properties +context.propagation.allow-blocked-headers=X-Channel-Request-Id ``` -2. **Via system property:** -```text --Dheaders.blocked= +```yaml +context: + propagation: + allow-blocked-headers: X-Channel-Request-Id ``` -3. **Via application.properties (Spring):** +**Via JVM system property:** + ```text -headers.blocked= +-Dcontext.propagation.allow-blocked-headers=X-Channel-Request-Id ``` -**`headers.blocked` rules and limitations** +**Via container ENV (recommended, Qubership pattern)** + +In Qubership deployments configuration values are typically wired from container ENV variables +through `application.yaml` placeholders. The full chain looks like this: + +1. The container declares an ENV variable, for example in the deployment manifest: + + ```yaml + env: + - name: CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS + value: "X-Channel-Request-Id" + ``` + +2. The application's `application.yaml` reads it via a `${VAR}` placeholder: + + ```yaml + context: + propagation: + allow-blocked-headers: ${CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS:} + ``` + + The trailing `:` (empty default) lets the property gracefully fall through to "not set" when + the ENV variable is absent — the internal blocklist then applies in full. -- Source priority: system property `headers.blocked` overrides environment variable `HEADERS_BLOCKED`. -- Default when not configured at all: `X-Channel-Request-Id` is blocked. -- Explicit empty value (`headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). -- Explicit non-empty value with valid headers (for example `headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `headers.blocked`/`HEADERS_BLOCKED`, it is ignored. -- If configured value contains only non-blockable entries (for example only `X-Request-Id`), default block is applied and `X-Channel-Request-Id` remains blocked. +3. Spring's relaxed binding also recognises the ENV variable directly under the property name + `context.propagation.allow-blocked-headers` (i.e. without an explicit placeholder), so the same ENV variable works + even without the `application.yaml` indirection. The placeholder form is preferred because it + makes the dependency explicit in source control. **MDC Integration:** The `X-Channel-Request-Id` is automatically integrated with SLF4J's Mapped Diagnostic Context (MDC) for seamless logging. diff --git a/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java b/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java index aa9de6028..6e2291cb5 100644 --- a/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java +++ b/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java @@ -1,21 +1,43 @@ package com.netcracker.cloud.framework.contexts.allowedheaders; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +/** + * Computes the effective set of headers that must not be propagated to outgoing requests. + * + *

Model

+ *
    + *
  • The framework owns an internal blocklist ({@link #INTERNAL_BLOCKED_HEADERS}). It is + * hard-coded and cannot be changed from configuration. By default it blocks + * {@code X-Channel-Request-Id}.
  • + *
  • A user-facing system property {@value #ALLOW_BLOCKED_PROPERTY} carries a + * comma-separated list of header names that should be exempted from the internal + * blocklist (i.e. allowed to propagate). Names that are not in the internal + * blocklist have no effect.
  • + *
+ * + *

How the property is supplied

+ * The Quarkus extension copies {@code quarkus.context.propagation.allow-blocked-headers} into this system property + * at {@code RUNTIME_INIT}; the Spring configuration does the same for the Spring-style + * {@code context.propagation.allow-blocked-headers} property. In both cases standard env-to-property mapping + * applies, so a container ENV variable like {@code CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS=X-Channel-Request-Id} + * propagates all the way through to this class. + */ public final class HeaderPropagationConfiguration { - public static final String HEADERS_BLOCKED_PROPERTY = "headers.blocked"; - public static final String HEADERS_BLOCKED_ENV = "HEADERS_BLOCKED"; - public static final List DEFAULT_BLOCKED_HEADERS = - List.of("X-Channel-Request-Id"); - public static final List NON_BLOCKABLE_HEADERS = - List.of("X-Request-Id"); + /** System property carrying exempted-from-blocklist header names, comma-separated. */ + public static final String ALLOW_BLOCKED_PROPERTY = "context.propagation.allow-blocked-headers"; + + /** + * Hard-coded internal blocklist of headers that the framework refuses to propagate + * unless explicitly exempted via {@link #ALLOW_BLOCKED_PROPERTY}. + */ + public static final List INTERNAL_BLOCKED_HEADERS = List.of("X-Channel-Request-Id"); private static final AtomicReference cachedHeaders = new AtomicReference<>(null); @@ -41,7 +63,7 @@ private static CachedHeaders getOrInit() { synchronized (HeaderPropagationConfiguration.class) { local = cachedHeaders.get(); if (local == null) { - local = new CachedHeaders(readBlockedHeaders()); + local = new CachedHeaders(computeEffectiveBlocklist()); cachedHeaders.set(local); } } @@ -64,28 +86,25 @@ public static boolean isBlacklisted(String headerName) { return getOrInit().lowerSet.contains(headerName.toLowerCase(Locale.ROOT)); } - private static List readBlockedHeaders() { - boolean propertySpecified = System.getProperties().containsKey(HEADERS_BLOCKED_PROPERTY); - String envValue = System.getenv(HEADERS_BLOCKED_ENV); - boolean envSpecified = envValue != null; - - String blockedHeaders = propertySpecified - ? System.getProperty(HEADERS_BLOCKED_PROPERTY) - : envValue; - - boolean anySourceSpecified = propertySpecified || envSpecified; + private static List computeEffectiveBlocklist() { + Set exemptions = readExemptions(); + if (exemptions.isEmpty()) { + return INTERNAL_BLOCKED_HEADERS; + } + return INTERNAL_BLOCKED_HEADERS.stream() + .filter(h -> !exemptions.contains(h.toLowerCase(Locale.ROOT))) + .toList(); + } - if (blockedHeaders == null || blockedHeaders.isBlank()) { - return anySourceSpecified ? Collections.emptyList() : DEFAULT_BLOCKED_HEADERS; + private static Set readExemptions() { + String raw = System.getProperty(ALLOW_BLOCKED_PROPERTY); + if (raw == null || raw.isBlank()) { + return Set.of(); } - - List configured = Arrays.stream(blockedHeaders.split(",")) + return Arrays.stream(raw.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) - .filter(s -> NON_BLOCKABLE_HEADERS.stream() - .noneMatch(s::equalsIgnoreCase)) - .toList(); - - return configured.isEmpty() ? DEFAULT_BLOCKED_HEADERS : configured; + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); } } diff --git a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java index cf5944adf..de57edc09 100644 --- a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java +++ b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java @@ -1,137 +1,84 @@ package com.netcracker.cloud.framework.contexts.allowedheaders; -import java.util.List; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; -import uk.org.webcompere.systemstubs.jupiter.SystemStub; -import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -@ExtendWith(SystemStubsExtension.class) class HeaderPropagationConfigurationTest { - @SystemStub - private EnvironmentVariables environmentVariables = new EnvironmentVariables("TEST_PROP_FOR_ENV_SETUP", "1"); @BeforeEach void setup() { - System.clearProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY); + System.clearProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY); HeaderPropagationConfiguration.resetCache(); } @AfterEach void cleanup() { - System.clearProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY); + System.clearProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY); HeaderPropagationConfiguration.resetCache(); } @Test - void shouldBlacklistHeaderByPropertyName() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Channel-Request-Id"); - HeaderPropagationConfiguration.resetCache(); - + void shouldBlockXChannelRequestIdByDefault() { Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); + Assertions.assertEquals(HeaderPropagationConfiguration.INTERNAL_BLOCKED_HEADERS, + HeaderPropagationConfiguration.blockedHeaders()); } @Test - void shouldParseCommaSeparatedBlacklistedHeaders() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Channel-Request-Id, X-Request-Id"); + void shouldNotBlockXChannelRequestIdWhenExempted() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, "X-Channel-Request-Id"); HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("Custom-Header")); - } - - @Test - void shouldBlacklistXChannelRequestIdByDefault() { - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); + Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); } @Test - void shouldNotBlacklistXChannelRequestIdWhenBlockedHeadersExplicitlyEmpty() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, ""); + void shouldApplyExemptionsCaseInsensitively() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, "x-channel-request-id"); HeaderPropagationConfiguration.resetCache(); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); } @Test - void shouldNotBlacklistXChannelRequestIdWhenOtherHeadersExplicitlyBlocked() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Request-Id"); + void shouldIgnoreUnknownExemptionEntries() { + // Names that are not in the internal blocklist must not change anything. + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, "Custom-Header, X-Request-Id"); HeaderPropagationConfiguration.resetCache(); + Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must remain unchanged when no exemption matches it"); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("Custom-Header")); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); } @Test - void shouldApplyDefaultBlacklistWhenOnlyNonBlockableHeadersConfigured() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Request-Id, x-request-id"); + void shouldTreatEmptyExemptionPropertyAsNoExemption() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, ""); HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertEquals(HeaderPropagationConfiguration.DEFAULT_BLOCKED_HEADERS, + Assertions.assertEquals(HeaderPropagationConfiguration.INTERNAL_BLOCKED_HEADERS, HeaderPropagationConfiguration.blockedHeaders()); } @Test - void shouldBlacklistHeaderByEnvWhenPropertyNotSet() throws Exception { - environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, "X-Request-Id"); - try { - HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - } finally { - environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); - HeaderPropagationConfiguration.resetCache(); - } - } - - @Test - void shouldNotBlockXRequestIdEvenWhenExplicitlyListed() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Channel-Request-Id, X-Request-Id"); + void shouldTreatBlankAndCommaOnlyExemptionPropertyAsNoExemption() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, " , ,, "); HeaderPropagationConfiguration.resetCache(); - - // X-Request-Id is never blocked - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - // X-Channel-Request-Id blocked + Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - // there is no X-Request-Id in a list - Assertions.assertFalse(HeaderPropagationConfiguration.blockedHeaders() - .stream().anyMatch(h -> h.equalsIgnoreCase("X-Request-Id"))); } - - @Test - void shouldReturnEmptyListWhenEnvExplicitlyEmpty() throws Exception { - environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, ""); - try { - HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); - } finally { - environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); - HeaderPropagationConfiguration.resetCache(); - } - } - + @Test - void shouldBlockedHeadersReturnCorrectList() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "Custom-Header, X-Request-Id"); - HeaderPropagationConfiguration.resetCache(); - - List blocked = HeaderPropagationConfiguration.blockedHeaders(); - Assertions.assertTrue(blocked.contains("Custom-Header")); - Assertions.assertFalse(blocked.stream().anyMatch(h -> h.equalsIgnoreCase("X-Request-Id"))); + void isBlacklistedShouldReturnFalseForNullAndBlank() { + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted(null)); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted(" ")); } } diff --git a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java index e806c6573..c26e5c2a3 100644 --- a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java +++ b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java @@ -69,20 +69,17 @@ void testXChannelRequestIdPropagationWithResponsePropagatableData() { } @Test - void testXChannelRequestIdPropagationIsBlockedWhenConfigured() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, X_CHANNEL_REQUEST_ID); + void testXChannelRequestIdPropagationIsBlockedByInternalBlocklist() { + System.clearProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY); HeaderPropagationConfiguration.resetCache(); - try { - RequestContextPropagation.initRequestContext(new ContextDataRequest()); // filter - ContextDataResponse responseContextData = new ContextDataResponse(); - RequestContextPropagation.setResponsePropagatableData(responseContextData); - Assertions.assertEquals("-", responseContextData.getResponseHeaders().get(X_CHANNEL_REQUEST_ID)); - - Map> serializableContextData = ContextManager.getSerializableContextData(); - Assertions.assertTrue(serializableContextData.getOrDefault(X_CHANNEL_REQUEST_ID_CONTEXT_NAME, Collections.emptyMap()).isEmpty()); - } finally { - System.clearProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY); - } + + RequestContextPropagation.initRequestContext(new ContextDataRequest()); // filter + ContextDataResponse responseContextData = new ContextDataResponse(); + RequestContextPropagation.setResponsePropagatableData(responseContextData); + Assertions.assertEquals("-", responseContextData.getResponseHeaders().get(X_CHANNEL_REQUEST_ID)); + + Map> serializableContextData = ContextManager.getSerializableContextData(); + Assertions.assertTrue(serializableContextData.getOrDefault(X_CHANNEL_REQUEST_ID_CONTEXT_NAME, Collections.emptyMap()).isEmpty()); } @Test diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java index 50cfdc264..01bbb10e9 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java @@ -30,7 +30,7 @@ class RequestPropagationSpringCommonTest { @BeforeEach void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilter(filter).build(); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); } @Test diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java index a9f9fb421..e62da4eba 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java @@ -34,7 +34,8 @@ TestController.class, RequestPropagationTestConfig.class}) @TestPropertySource(properties = { "headers.allowed=custom-header", - "headers.blocked=", + // context.propagation.allow-blocked-headers deliberately not set — the internal blocklist applies and + // X-Channel-Request-Id should not propagate to outgoing requests. "cloud-core.context-propagation.url=/test_url/v111/test" }) class RequestPropagationTest { @@ -64,7 +65,7 @@ class RequestPropagationTest { @BeforeEach void setUp() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(preAuthnFilter, postAuthnFilter).build(); @@ -107,15 +108,14 @@ void testRequestPropagation() throws Exception { } @Test - void testXRequestIdStillPropagatesWhenAddedToBlockedList() throws Exception { - System.setProperty("headers.blocked", X_REQUEST_ID_NAME); + void testXRequestIdNotAffectedByExemptionConfig() throws Exception { + System.setProperty("context.propagation.allow-blocked-headers", X_REQUEST_ID_NAME); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); mockServer.expect(requestTo("/chain_request")) .andExpect(header(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE_VALUE)) - // X-Request-Id is non-blockable and must still be propagated. .andExpect(header(X_REQUEST_ID_NAME, X_REQUEST_ID_VALUE)) .andExpect(header(CUSTOM_NAME, CUSTOM_VALUE)) .andRespond(withSuccess()); diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java index fa47551a4..3c567f1fb 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java @@ -33,7 +33,7 @@ TestController.class, RequestPropagationTestConfig.class}) @TestPropertySource(properties = { "headers.allowed=custom-header", - "headers.blocked=", + "context.propagation.allow-blocked-headers=X-Channel-Request-Id", "cloud-core.context-propagation.url=/test_url/v111/test" }) class RequestPropagationXChannelRequestIdAllowedTest { @@ -64,7 +64,7 @@ class RequestPropagationXChannelRequestIdAllowedTest { @BeforeAll static void beforeAll() { System.setProperty("headers.allowed", "custom-header"); - System.setProperty("headers.blocked", ""); + System.setProperty("context.propagation.allow-blocked-headers", "X-Channel-Request-Id"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } @@ -72,7 +72,7 @@ static void beforeAll() { @AfterAll static void afterAll() { System.clearProperty("headers.allowed"); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java index d60295141..19d649a2c 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java @@ -31,7 +31,7 @@ @TestPropertySource(properties = { "headers.allowed=custom-header", "cloud-core.context-propagation.url=/test_url/v111/test" - // headers.blocked is not set, X-Channel-Request-Id blocked for outgoing requests + // context.propagation.allow-blocked-headers is not set, internal blocklist applies → X-Channel-Request-Id blocked for outgoing requests }) class RequestPropagationXChannelRequestIdResponseTest { @@ -55,14 +55,14 @@ class RequestPropagationXChannelRequestIdResponseTest { @BeforeAll static void beforeAll() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } @AfterAll static void afterAll() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml index 3694ec174..c208c3358 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml @@ -56,6 +56,12 @@ spring-test test + + uk.org.webcompere + system-stubs-jupiter + 2.1.8 + test + diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java index 3166e2397..b06fc1b31 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java @@ -13,12 +13,14 @@ @Configuration public class SpringContextProviderConfiguration { + @Bean - public SpringPostAuthnContextProviderFilter springPostAuthnContextProviderFilter(){ + public SpringPostAuthnContextProviderFilter springPostAuthnContextProviderFilter() { return new SpringPostAuthnContextProviderFilter(); } + @Bean - public SpringPreAuthnContextProviderFilter springPreAuthnContextProviderFilter(){ + public SpringPreAuthnContextProviderFilter springPreAuthnContextProviderFilter() { return new SpringPreAuthnContextProviderFilter(); } @@ -29,14 +31,14 @@ public SpringPreAuthnContextProviderFilter springPreAuthnContextProviderFilter() @Value("${headers.allowed:}") private String allowedHeaders; - @Value("${headers.blocked:}") - private String blockedHeaders; + @Value("${context.propagation.allow-blocked-headers:}") + private String allowedFromBlocklist; @PostConstruct public void init() { System.setProperty("headers.allowed", allowedHeaders); - if (environment.containsProperty("headers.blocked")) { - System.setProperty("headers.blocked", blockedHeaders); + if (environment.containsProperty("context.propagation.allow-blocked-headers")) { + System.setProperty("context.propagation.allow-blocked-headers", allowedFromBlocklist); } } } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java new file mode 100644 index 000000000..3f92991b4 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java @@ -0,0 +1,26 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; + +public class HeaderPropagationStateReset implements BeforeAllCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext ctx) { + reset(); + } + + @Override + public void afterAll(ExtensionContext ctx) { + reset(); + } + + private static void reset() { + System.clearProperty("context.propagation.allow-blocked-headers"); + System.clearProperty("headers.allowed"); + HeaderPropagationConfiguration.resetCache(); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java new file mode 100644 index 000000000..b5fe96476 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java @@ -0,0 +1,33 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "context.propagation.allow-blocked-headers=" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationEmptyTest { + + @Test + void shouldSetEmptySystemPropertyAndStillApplyInternalBlocklist() { + assertEquals("", System.getProperty("context.propagation.allow-blocked-headers"), + "Spring init() must propagate explicit empty value to the system property"); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must still apply when the exemption property is blank"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java new file mode 100644 index 000000000..121acc73b --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java @@ -0,0 +1,41 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith({HeaderPropagationStateReset.class, SystemStubsExtension.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + // context.propagation.allow-blocked-headers deliberately NOT declared here — it must come from the env var + "headers.allowed=custom-header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationFromEnvVarTest { + + @SystemStub + static EnvironmentVariables envVars = new EnvironmentVariables("CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS", "X-Channel-Request-Id"); + + @Test + void shouldReadHeadersAllowBlockedFromEnvVar() { + assertEquals("X-Channel-Request-Id", System.getProperty("context.propagation.allow-blocked-headers"), + "Spring init() must propagate env-sourced value to the system property"); + + HeaderPropagationConfiguration.resetCache(); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Env-sourced exemption must take effect"); + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "The only entry of the internal blocklist must be removed by the exemption"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java new file mode 100644 index 000000000..a9da90895 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java @@ -0,0 +1,32 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + // context.propagation.allow-blocked-headers intentionally absent + "headers.allowed=custom-header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationNotConfiguredTest { + + @Test + void shouldNotTouchSystemPropertyAndKeepInternalBlocklist() { + assertNull(System.getProperty("context.propagation.allow-blocked-headers"), + "context.propagation.allow-blocked-headers must remain unset when no source configures it"); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must apply when no exemption is configured"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java new file mode 100644 index 000000000..1a8a766c1 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java @@ -0,0 +1,34 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "context.propagation.allow-blocked-headers=Custom-Header, X-Some-Other-Header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationUnknownExemptionTest { + + @Test + void shouldLeaveInternalBlocklistIntactWhenExemptionsDontMatch() { + assertEquals("Custom-Header, X-Some-Other-Header", + System.getProperty("context.propagation.allow-blocked-headers")); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must remain intact when no exemption matches it"); + assertEquals(HeaderPropagationConfiguration.INTERNAL_BLOCKED_HEADERS, + HeaderPropagationConfiguration.blockedHeaders()); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java new file mode 100644 index 000000000..b496265a1 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java @@ -0,0 +1,34 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "context.propagation.allow-blocked-headers=X-Channel-Request-Id" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationWithValueTest { + + @Test + void shouldExemptListedHeaderFromInternalBlocklist() { + assertEquals("X-Channel-Request-Id", System.getProperty("context.propagation.allow-blocked-headers")); + + HeaderPropagationConfiguration.resetCache(); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Exempted header must not be blocked"); + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "The only entry of the internal blocklist (X-Channel-Request-Id) must be removed"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java index afe9ff138..62a134218 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java @@ -61,7 +61,7 @@ public class ContextPropagationTest { @BeforeAll static void setup() { System.setProperty("headers.allowed", CUSTOM_HEADER.toLowerCase()); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); } @@ -73,7 +73,7 @@ void beforeEach() { @AfterEach void afterEach() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); } @@ -100,8 +100,10 @@ void testContextPropagationBlocksXChannelRequestIdByDefault() throws Exception { @Test @Timeout(30) - public void testContextPropagationAllowsXChannelRequestIdWhenHeadersBlockedEmpty() throws Exception { - System.setProperty("headers.blocked", ""); + public void testContextPropagationAllowsXChannelRequestIdWhenExempted() throws Exception { + System.setProperty("context.propagation.allow-blocked-headers", X_CHANNEL_REQUEST_ID_NAME); + HeaderPropagationConfiguration.resetCache(); + ChannelRequestIdContext.set(X_CHANNEL_REQUEST_ID_VALUE); AcceptLanguageContext.set(TEST_LANG); AllowedHeadersContext.set(Map.of(CUSTOM_HEADER, CUSTOM_HEADER_VALUE)); diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java index bc539c6e5..581b06fd8 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java @@ -87,7 +87,7 @@ public static void setup() throws Exception { channel.queueBind("orders", "orders", "invoice"); } System.setProperty("headers.allowed", CUSTOM_HEADER.toLowerCase()); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); } @AfterAll @@ -103,7 +103,7 @@ void beforeEach() { @AfterEach void afterEach() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); } @@ -125,8 +125,11 @@ public void testXChannelRequestIdBlockedByDefault() throws InterruptedException @Test @Timeout(value = 20, unit = TimeUnit.SECONDS) - public void testXChannelRequestIdAllowedWhenHeadersBlockedEmpty() throws InterruptedException { - System.setProperty("headers.blocked", ""); + public void testXChannelRequestIdAllowedWhenExempted() throws InterruptedException { + // Exempt X-Channel-Request-Id from the internal blocklist — it must propagate. + System.setProperty("context.propagation.allow-blocked-headers", X_CHANNEL_REQUEST_ID_NAME); + HeaderPropagationConfiguration.resetCache(); + AcceptLanguageContext.set("ZULU"); AllowedHeadersContext.set(Map.of(CUSTOM_HEADER, CUSTOM_HEADER_VALUE)); ChannelRequestIdContext.set(X_CHANNEL_REQUEST_ID_VALUE); @@ -142,12 +145,17 @@ public void testXChannelRequestIdAllowedWhenHeadersBlockedEmpty() throws Interru @Test @Timeout(value = 20, unit = TimeUnit.SECONDS) - public void testCustomHeaderBlockedWhenConfiguredByProperty() throws InterruptedException { - System.setProperty("headers.blocked", ANOTHER_HEADER); + public void testUnknownExemptionDoesNotAffectInternalBlocklist() throws InterruptedException { + // Listing a header that is NOT in the internal blocklist has no effect — the + // internal blocklist (containing X-Channel-Request-Id) still applies. + System.setProperty("context.propagation.allow-blocked-headers", ANOTHER_HEADER); + HeaderPropagationConfiguration.resetCache(); + AcceptLanguageContext.set("ZULU"); AllowedHeadersContext.set(Map.of( CUSTOM_HEADER, CUSTOM_HEADER_VALUE, ANOTHER_HEADER, ANOTHER_HEADER_VALUE)); + ChannelRequestIdContext.set(X_CHANNEL_REQUEST_ID_VALUE); template.convertAndSend("orders", "invoice", "rye wheat"); ContextManager.clearAll(); @@ -155,8 +163,10 @@ public void testCustomHeaderBlockedWhenConfiguredByProperty() throws Interrupted fail("Message listener failed or message doesn't even arrived in 10 seconds"); } - assertNull(getHeaderIgnoreCase(receivedHeaders.get(), ANOTHER_HEADER)); + assertNull(getHeaderIgnoreCase(receivedHeaders.get(), X_CHANNEL_REQUEST_ID_NAME), + "Internal blocklist must remain intact when no exemption matches it"); assertEquals(CUSTOM_HEADER_VALUE, getHeaderIgnoreCase(receivedHeaders.get(), CUSTOM_HEADER)); + assertEquals(ANOTHER_HEADER_VALUE, getHeaderIgnoreCase(receivedHeaders.get(), ANOTHER_HEADER)); } diff --git a/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java b/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java index addb62f3b..a5ee8744c 100644 --- a/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java +++ b/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java @@ -103,8 +103,8 @@ void testDumpDoesNotContainXChannelRequestIdByDefault() { } @Test - void testDumpContainsXChannelRequestIdWhenNotBlocked() { - System.setProperty("headers.blocked", ""); + void testDumpContainsXChannelRequestIdWhenExempted() { + System.setProperty("context.propagation.allow-blocked-headers", X_CHANNEL_REQUEST_ID); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); @@ -117,7 +117,7 @@ void testDumpContainsXChannelRequestIdWhenNotBlocked() { assertEquals("ch-456", dumped.get(X_CHANNEL_REQUEST_ID)); } finally { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); }