This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 6e3e4ec510b4 CAMEL-24450: camel-jetty - do not grant CORS credentials 
to an origin the operator did not name (#25829)
6e3e4ec510b4 is described below

commit 6e3e4ec510b4966c70ad99b3c032e27d6adbef9d
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 28 11:35:09 2026 +0200

    CAMEL-24450: camel-jetty - do not grant CORS credentials to an origin the 
operator did not name (#25829)
    
    enableCORS=true added new CrossOriginFilter() with no init parameters, so 
Jetty's own
    defaults applied. Confirmed against jetty-ee10-servlets 12.1.12: 
DEFAULT_ALLOWED_ORIGINS
    is "*" and credentials default to true. The filter reflects the request's 
origin rather
    than sending "*", so that pairing is the credentialed any-origin 
configuration the fetch
    specification refuses to express - reflecting the origin being the usual 
way around that
    rule. An option named "enable CORS" should not mean "every origin, with 
credentials".
    
    Default allowCredentials to false when CORS is enabled. The origin is still 
reflected, so
    enabling CORS keeps working for requests that carry no credentials; an 
operator who needs
    credentialed cross-origin requests sets filterInit.allowCredentials=true 
and names the
    origins in filterInit.allowedOrigins. Asking for credentials while leaving 
the origins at
    "*" is logged as a warning, since that combination reproduces the original 
behaviour.
    
    The defaults are applied where the init parameter map is built, not where 
the filter is
    added: the map is handed to the endpoint earlier and only when it is 
non-empty, so
    applying them later would drop them in exactly the case that matters - 
enableCORS on its
    own, with no filterInit parameters at all.
    
    EnableCORSTest.testCORSenabled asserted that credentials are granted, so it 
encoded the
    previous behaviour; it now asserts the opposite, and a second test covers 
the opt-in.
    
    Matches the change made to camel-platform-http-vertx under CAMEL-24436.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
    Co-authored-by: Federico Mariani <[email protected]>
---
 .../camel/component/jetty/JettyHttpComponent.java  | 34 ++++++++++++++++++
 .../camel/component/jetty/EnableCORSTest.java      | 42 ++++++++++++++++++++--
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    | 25 +++++++++++++
 3 files changed, 99 insertions(+), 2 deletions(-)

diff --git 
a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
 
b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
index a82c5217795e..d99f7883407d 100644
--- 
a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
+++ 
b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
@@ -201,6 +201,10 @@ public abstract class JettyHttpComponent extends 
HttpCommonComponent
 
         // extract filterInit. parameters
         Map filterInitParameters = 
PropertiesHelper.extractProperties(parameters, "filterInit.");
+        if (Boolean.TRUE.equals(enableCors)) {
+            // has to happen before the map is handed to the endpoint below, 
which is skipped when it is empty
+            applyCorsDefaults(filterInitParameters);
+        }
 
         URI addressUri = new 
URI(UnsafeUriCharactersEncoder.encodeHttpURI(remaining));
         URI endpointUri = URISupport.createRemainingURI(addressUri, 
parameters);
@@ -427,6 +431,36 @@ public abstract class JettyHttpComponent extends 
HttpCommonComponent
         }
     }
 
+    /**
+     * Supplies the CORS defaults Camel wants, for the init parameters the 
operator did not set.
+     * <p>
+     * {@code new CrossOriginFilter()} with no init parameters takes Jetty's 
own defaults, which are
+     * {@code allowedOrigins=*} together with {@code allowCredentials=true}. 
Since the filter reflects the request's
+     * origin rather than sending {@code *}, that is the credentialed 
any-origin configuration the fetch specification
+     * refuses to express - reflection being the usual way around that rule. 
An option named "enable CORS" should not
+     * mean "every origin, with credentials".
+     * <p>
+     * Credentials therefore default to off. Reflection of the origin is left 
as it was, so enabling CORS keeps working
+     * for requests that carry no credentials; an operator who needs 
credentialed cross-origin requests sets
+     * {@code filterInit.allowCredentials=true} and is expected to name the 
origins in {@code filterInit.allowedOrigins}
+     * at the same time, which is warned about here if they do not.
+     */
+    @SuppressWarnings("unchecked")
+    private void applyCorsDefaults(Map<String, Object> filterInitParameters) {
+        Object configuredCredentials = 
filterInitParameters.get(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM);
+        if (configuredCredentials == null) {
+            
filterInitParameters.put(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "false");
+            return;
+        }
+        Object configuredOrigins = 
filterInitParameters.get(CrossOriginFilter.ALLOWED_ORIGINS_PARAM);
+        if (Boolean.parseBoolean(configuredCredentials.toString())
+                && (configuredOrigins == null || 
"*".equals(configuredOrigins.toString().trim()))) {
+            LOG.warn("enableCORS is configured with {}=true and no specific 
{}."
+                     + " Every origin will be able to make credentialed 
cross-origin requests to this endpoint.",
+                    CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, 
CrossOriginFilter.ALLOWED_ORIGINS_PARAM);
+        }
+    }
+
     private void setFilters(JettyHttpEndpoint endpoint, Server server) {
         ServletContextHandler context = 
server.getDescendant(ServletContextHandler.class);
         List<Filter> filters = endpoint.getFilters();
diff --git 
a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
 
b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
index 5d067835054d..43786d86cad7 100644
--- 
a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
+++ 
b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
@@ -17,11 +17,13 @@
 package org.apache.camel.component.jetty;
 
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.AvailablePortFinder;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
 import org.apache.hc.client5.http.impl.classic.HttpClients;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -29,6 +31,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class EnableCORSTest extends BaseJettyTest {
 
+    @RegisterExtension
+    static AvailablePortFinder.Port port3 = AvailablePortFinder.find();
+
+    private static int getPort3() {
+        return port3.getPort();
+    }
+
     @Test
     public void testCORSdisabled() throws Exception {
         HttpGet httpMethod = new HttpGet("http://localhost:"; + getPort() + 
"/test1");
@@ -44,19 +53,45 @@ public class EnableCORSTest extends BaseJettyTest {
         }
     }
 
+    /**
+     * enableCORS on its own reflects the request origin, which is what makes 
CORS work at all, but must not also grant
+     * credentials: reflecting the origin is the usual way around the fetch 
specification's refusal to pair "*" with
+     * credentials, so the two together are the credentialed any-origin 
configuration.
+     */
     @Test
-    public void testCORSenabled() throws Exception {
+    public void testCORSenabledDoesNotGrantCredentials() throws Exception {
         HttpGet httpMethod = new HttpGet("http://localhost:"; + getPort2() + 
"/test2");
         httpMethod.addHeader("Origin", "http://localhost:9000";);
         httpMethod.addHeader("Referer", "http://localhost:9000";);
 
+        try (CloseableHttpClient client = HttpClients.createDefault();
+             CloseableHttpResponse response = client.execute(httpMethod)) {
+
+            assertEquals(200, response.getCode(), "Get a wrong response 
status");
+
+            // the origin is still reflected, so CORS itself keeps working
+            assertEquals("http://localhost:9000";, 
response.getFirstHeader("Access-Control-Allow-Origin").getValue());
+
+            Object credentials = 
response.getFirstHeader("Access-Control-Allow-Credentials");
+            assertTrue(credentials == null
+                    || 
!Boolean.parseBoolean(response.getFirstHeader("Access-Control-Allow-Credentials").getValue()),
+                    "credentials must not be granted to an origin the operator 
did not name");
+        }
+    }
+
+    @Test
+    public void testCORSCredentialsCanBeAskedFor() throws Exception {
+        HttpGet httpMethod = new HttpGet("http://localhost:"; + getPort3() + 
"/test3");
+        httpMethod.addHeader("Origin", "http://localhost:9000";);
+        httpMethod.addHeader("Referer", "http://localhost:9000";);
+
         try (CloseableHttpClient client = HttpClients.createDefault();
              CloseableHttpResponse response = client.execute(httpMethod)) {
 
             assertEquals(200, response.getCode(), "Get a wrong response 
status");
 
             String responseHeader = 
response.getFirstHeader("Access-Control-Allow-Credentials").getValue();
-            assertTrue(Boolean.parseBoolean(responseHeader), "CORS not 
enabled");
+            assertTrue(Boolean.parseBoolean(responseHeader), "credentials 
should be granted when configured");
         }
     }
 
@@ -66,6 +101,9 @@ public class EnableCORSTest extends BaseJettyTest {
             public void configure() {
                 
from("jetty://http://localhost:{{port}}/test1?enableCORS=false";).transform(simple("OK"));
                 
from("jetty://http://localhost:{{port2}}/test2?enableCORS=true";).transform(simple("OK"));
+                from("jetty://http://localhost:"; + getPort3() + 
"/test3?enableCORS=true"
+                     + "&filterInit.allowedOrigins=http://localhost:9000";
+                     + 
"&filterInit.allowCredentials=true").transform(simple("OK"));
             }
         };
     }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 4efe1e0bc545..a83d5cfaef0a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -548,6 +548,31 @@ is truncated rather than recreated and would otherwise 
keep its original permiss
 Deployments where another account legitimately reads these files — a sidecar 
or a backup agent running as
 a different user — need to run as the owner, or use a group-aware key store 
instead.
 
+=== camel-jetty
+
+`enableCORS=true` added `new CrossOriginFilter()` with no init parameters, so 
Jetty's own defaults
+applied: `allowedOrigins=*` together with `allowCredentials=true`. Since the 
filter reflects the request's
+origin rather than sending `*`, that is the credentialed any-origin 
configuration the fetch specification
+refuses to express — reflecting the origin being the usual way around that 
rule. An option named "enable
+CORS" should not mean "every origin, with credentials".
+
+`allowCredentials` now defaults to `false` when CORS is enabled. The origin is 
still reflected, so
+enabling CORS keeps working for requests that carry no credentials.
+
+Deployments that need credentialed cross-origin requests must ask for them 
explicitly:
+
+[source,text]
+----
+jetty://http://0.0.0.0:8080/api?enableCORS=true
+    &filterInit.allowedOrigins=https://app.example
+    &filterInit.allowCredentials=true
+----
+
+Setting `filterInit.allowCredentials=true` while leaving 
`filterInit.allowedOrigins` unset or `*` is
+logged as a warning at startup, because that combination lets any origin make 
credentialed requests.
+
+The same change was made to camel-platform-http-vertx.
+
 === camel-mllp
 
 `logPhi` now defaults to `false`. It previously defaulted to `true`, so 
message content — which for MLLP

Reply via email to