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

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

commit d2c07bbb07d4a612854f5ea369ddf09ba147b32f
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Aug 31 10:08:26 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]>
    
    (cherry picked from commit 6e3e4ec510b4966c70ad99b3c032e27d6adbef9d)
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../camel/component/jetty/JettyHttpComponent.java  | 34 ++++++++++++++++++
 .../camel/component/jetty/EnableCORSTest.java      | 42 ++++++++++++++++++++--
 2 files changed, 74 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"));
             }
         };
     }

Reply via email to