This is an automated email from the ASF dual-hosted git repository.
sergehuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/master by this push:
new b6e454521 Apply the restricted-event-type gate to events submitted
over GraphQL (#870)
b6e454521 is described below
commit b6e4545219bcc35f7e00c1f8cb01b5b1d77b0178
Author: Serge Huber <[email protected]>
AuthorDate: Thu Sep 24 14:22:12 2026 +0200
Apply the restricted-event-type gate to events submitted over GraphQL (#870)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../graphql/commands/ProcessEventsCommand.java | 28 +++++++-
.../graphql/commands/ProcessEventsCommandTest.java | 82 ++++++++++++++++++++++
2 files changed, 109 insertions(+), 1 deletion(-)
diff --git
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/ProcessEventsCommand.java
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/ProcessEventsCommand.java
index e1a72ebb5..5d4147f58 100644
---
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/ProcessEventsCommand.java
+++
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/ProcessEventsCommand.java
@@ -20,7 +20,9 @@ import graphql.language.InputObjectTypeDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import org.apache.unomi.api.Event;
+import org.apache.unomi.api.ExecutionContext;
import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ExecutionContextManager;
import org.apache.unomi.api.services.ProfileService;
import org.apache.unomi.graphql.CDPGraphQLConstants;
import org.apache.unomi.graphql.types.input.CDPConsentUpdateEventInput;
@@ -161,7 +163,15 @@ public class ProcessEventsCommand extends
BaseCommand<Integer> {
}
private void processEvent(final Event event) {
- int eventCode =
serviceManager.getService(EventService.class).send(event);
+ final EventService eventService =
serviceManager.getService(EventService.class);
+
+ if (!isEventAllowedForCurrentTenant(event, eventService,
+ serviceManager.getService(ExecutionContextManager.class))) {
+ LOGGER.debug("Event type {} is not authorized for this tenant,
skipping it", event.getEventType());
+ return;
+ }
+
+ int eventCode = eventService.send(event);
if (eventCode == EventService.PROFILE_UPDATED) {
serviceManager.getService(ProfileService.class).save(event.getProfile());
@@ -169,6 +179,22 @@ public class ProcessEventsCommand extends
BaseCommand<Integer> {
processedEventsQty.incrementAndGet();
}
+ /**
+ * Same restricted-event-type check as REST {@code EventService#send}
callers. GraphQL does not
+ * currently thread a client IP through this command, so the source IP is
{@code null}: a
+ * restricted type is refused when the tenant has an authorized-IP list,
and unrestricted types
+ * still pass.
+ */
+ static boolean isEventAllowedForCurrentTenant(final Event event,
+ final EventService
eventService,
+ final
ExecutionContextManager executionContextManager) {
+ final ExecutionContext executionContext = executionContextManager !=
null
+ ? executionContextManager.getCurrentContext() : null;
+ final String tenantId = executionContext != null ?
executionContext.getTenantId() : null;
+
+ return eventService.isEventAllowedForTenant(event, tenantId, null);
+ }
+
public static Builder create(final List<CDPEventInput> eventInputs) {
return new Builder(eventInputs);
}
diff --git
a/graphql/cxs-impl/src/test/java/org/apache/unomi/graphql/commands/ProcessEventsCommandTest.java
b/graphql/cxs-impl/src/test/java/org/apache/unomi/graphql/commands/ProcessEventsCommandTest.java
new file mode 100644
index 000000000..a387637c9
--- /dev/null
+++
b/graphql/cxs-impl/src/test/java/org/apache/unomi/graphql/commands/ProcessEventsCommandTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.graphql.commands;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.ExecutionContext;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ExecutionContextManager;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class ProcessEventsCommandTest {
+
+ private static final String TENANT_ID = "tenant-1";
+
+ @Mock
+ private EventService eventService;
+ @Mock
+ private ExecutionContextManager executionContextManager;
+ @Mock
+ private ExecutionContext executionContext;
+
+ @Test
+ void allowsEventWhenTenantGatePasses() {
+ Event event = viewEvent();
+
when(executionContextManager.getCurrentContext()).thenReturn(executionContext);
+ when(executionContext.getTenantId()).thenReturn(TENANT_ID);
+ when(eventService.isEventAllowedForTenant(event, TENANT_ID,
null)).thenReturn(true);
+
+ assertTrue(ProcessEventsCommand.isEventAllowedForCurrentTenant(event,
eventService, executionContextManager));
+ verify(eventService).isEventAllowedForTenant(event, TENANT_ID, null);
+ }
+
+ @Test
+ void refusesEventWhenTenantGateFails() {
+ Event event = viewEvent();
+
when(executionContextManager.getCurrentContext()).thenReturn(executionContext);
+ when(executionContext.getTenantId()).thenReturn(TENANT_ID);
+ when(eventService.isEventAllowedForTenant(event, TENANT_ID,
null)).thenReturn(false);
+
+ assertFalse(ProcessEventsCommand.isEventAllowedForCurrentTenant(event,
eventService, executionContextManager));
+ verify(eventService).isEventAllowedForTenant(event, TENANT_ID, null);
+ }
+
+ @Test
+ void passesNullTenantWhenThereIsNoExecutionContext() {
+ Event event = viewEvent();
+ when(eventService.isEventAllowedForTenant(event, null,
null)).thenReturn(false);
+
+ assertFalse(ProcessEventsCommand.isEventAllowedForCurrentTenant(event,
eventService, null));
+ verify(eventService).isEventAllowedForTenant(event, null, null);
+ }
+
+ private static Event viewEvent() {
+ return new Event("view", null, new Profile(), null, null, null, new
Date());
+ }
+}