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

asf-gitbox-commits pushed a commit to branch drop-public-cors-credentials
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit c0497920fe1e38082a993971eaaacedf37ac23c5
Author: Serge Huber <[email protected]>
AuthorDate: Tue Sep 8 20:35:40 2026 +0200

    Drop credentialed CORS on the public tracker endpoints.
    
    Keep any-origin access for browser clients; do not advertise credentials on 
context, eventcollector, or client.
---
 manual/src/main/asciidoc/configuration.adoc        |  2 +-
 .../unomi/rest/endpoints/ClientEndpoint.java       |  2 +-
 .../unomi/rest/endpoints/ContextJsonEndpoint.java  |  2 +-
 .../rest/endpoints/EventsCollectorEndpoint.java    |  2 +-
 .../rest/endpoints/PublicTrackerCorsTest.java      | 52 ++++++++++++++++++++++
 5 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/manual/src/main/asciidoc/configuration.adoc 
b/manual/src/main/asciidoc/configuration.adoc
index c67b8a034..ce7b04dec 100644
--- a/manual/src/main/asciidoc/configuration.adoc
+++ b/manual/src/main/asciidoc/configuration.adoc
@@ -452,7 +452,7 @@ curl -X GET "http://localhost:8181/cxs/tenants"; \
 
 Authentication rules (Unomi 3.1):
 
-1. Public paths (for example `/cxs/context.json`, `/cxs/eventcollector`, 
`/cxs/client/*`) require a valid **public** API key via `X-Unomi-Api-Key` 
(unless <<_v2_compatibility_mode,V2 compatibility mode>> is enabled).
+1. Public paths (for example `/cxs/context.json`, `/cxs/eventcollector`, 
`/cxs/client/*`) require a valid **public** API key via `X-Unomi-Api-Key` 
(unless <<_v2_compatibility_mode,V2 compatibility mode>> is enabled). Browser 
clients may call them from any origin; those responses do not include 
credentialed CORS headers.
 2. Private paths accept `tenantId:privateApiKey` Basic auth, or JAAS admin 
credentials (often with `X-Unomi-Tenant-Id` when a tenant context is required).
 3. Tenant administration (`/cxs/tenants`) requires system administrator JAAS 
credentials.
 4. JAAS admin does **not** replace the public API key on public endpoints.
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java 
b/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java
index 6fae685b0..7a8be14df 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java
@@ -43,7 +43,7 @@ import java.util.Set;
 /**
  * A servlet filter to serve a context-specific Javascript containing the 
current request context object.
  */
-@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true)
+@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = false)
 @Path("/")
 @Component(service = ClientEndpoint.class, property = 
"osgi.jaxrs.resource=true")
 public class ClientEndpoint {
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java 
b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
index 8fa5d56d3..0531750b8 100644
--- 
a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
+++ 
b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
@@ -57,7 +57,7 @@ import java.util.stream.Collectors;
  * events, evaluate personalization filters, and return consented profile data.
  */
 @Consumes(MediaType.APPLICATION_JSON)
-@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true)
+@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = false)
 @Path("/")
 @Component(service = ContextJsonEndpoint.class, property = 
"osgi.jaxrs.resource=true")
 public class ContextJsonEndpoint {
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
 
b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
index 48e7e5c7f..b6f646e24 100644
--- 
a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
+++ 
b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
@@ -49,7 +49,7 @@ import java.util.List;
  */
 @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
 @Consumes(MediaType.APPLICATION_JSON)
-@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true)
+@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = false)
 @Path("/")
 @Component(service = EventsCollectorEndpoint.class, property = 
"osgi.jaxrs.resource=true")
 public class EventsCollectorEndpoint {
diff --git 
a/rest/src/test/java/org/apache/unomi/rest/endpoints/PublicTrackerCorsTest.java 
b/rest/src/test/java/org/apache/unomi/rest/endpoints/PublicTrackerCorsTest.java
new file mode 100644
index 000000000..d9b0bc594
--- /dev/null
+++ 
b/rest/src/test/java/org/apache/unomi/rest/endpoints/PublicTrackerCorsTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.rest.endpoints;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Public tracker endpoints keep a broad origin policy and do not advertise 
credentialed CORS.
+ */
+class PublicTrackerCorsTest {
+
+    @Test
+    void contextJsonAllowsAnyOriginWithoutCredentials() {
+        assertPublicTrackerCors(ContextJsonEndpoint.class);
+    }
+
+    @Test
+    void eventsCollectorAllowsAnyOriginWithoutCredentials() {
+        assertPublicTrackerCors(EventsCollectorEndpoint.class);
+    }
+
+    @Test
+    void clientAllowsAnyOriginWithoutCredentials() {
+        assertPublicTrackerCors(ClientEndpoint.class);
+    }
+
+    private static void assertPublicTrackerCors(Class<?> endpoint) {
+        CrossOriginResourceSharing cors = 
endpoint.getAnnotation(CrossOriginResourceSharing.class);
+        assertNotNull(cors, endpoint.getSimpleName() + " must declare CORS");
+        assertTrue(cors.allowAllOrigins());
+        assertFalse(cors.allowCredentials());
+    }
+}

Reply via email to