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

somandal pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new a58ec4c7522 [audit] Replace URL exclusion with PathMatcher glob 
patterns (#16723)
a58ec4c7522 is described below

commit a58ec4c7522e78e508f0060fcb31ff36d735e006
Author: Suvodeep Pyne <[email protected]>
AuthorDate: Wed Sep 3 09:35:22 2025 -0700

    [audit] Replace URL exclusion with PathMatcher glob patterns (#16723)
    
    * [audit] Add URL filter exclude patterns +implementation
    
    * [audit] Refactor URL exclusion logic to accept dynamic patterns and 
remove unused code
    
    * [audit] Rename UrlPathFilter to AuditUrlPathFilter and update references
    
    * Refactor AuditRequestProcessor and AuditUrlPathFilter for improved 
readability and maintainability
    
    * [audit] Refactor URL exclusion logic to use a helper method for pattern 
matching
    
    * [audit] Add unit tests for AuditUrlPathFilter to validate URL exclusion 
logic
    
    * [audit] Refactor AuditConfigManager to use a constant for audit config 
prefix and remove unused AuditLogConstants class
    
    * Refactor AuditUrlPathFilterTest for improved clarity and organization of 
test cases
    
    * [audit] Enhance AuditUrlPathFilter to support regex patterns and improve 
documentation
---
 .../org/apache/pinot/common/audit/AuditConfig.java |  14 +-
 .../pinot/common/audit/AuditConfigManager.java     |  97 +-----------
 .../pinot/common/audit/AuditRequestProcessor.java  |  26 ++--
 .../pinot/common/audit/AuditUrlPathFilter.java     | 107 +++++++++++++
 .../pinot/common/audit/AuditConfigManagerTest.java |  16 +-
 .../common/audit/AuditRequestProcessorTest.java    |   9 +-
 .../pinot/common/audit/AuditUrlPathFilterTest.java | 165 +++++++++++++++++++++
 .../controller/api/audit/AuditServiceBinder.java   |   2 +
 .../apache/pinot/spi/utils/CommonConstants.java    |  11 --
 9 files changed, 316 insertions(+), 131 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
index d1bb3093f99..36e07271409 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
@@ -42,8 +42,8 @@ public final class AuditConfig {
   @JsonProperty("payload.size.max.bytes")
   private int _maxPayloadSize = 10_240;
 
-  @JsonProperty("excluded.endpoints")
-  private String _excludedEndpoints = "";
+  @JsonProperty("url.filter.exclude.patterns")
+  private String _urlFilterExcludePatterns = "";
 
   @JsonProperty("userid.header")
   private String _useridHeader = "";
@@ -83,12 +83,12 @@ public final class AuditConfig {
     _maxPayloadSize = maxPayloadSize;
   }
 
-  public String getExcludedEndpoints() {
-    return _excludedEndpoints;
+  public String getUrlFilterExcludePatterns() {
+    return _urlFilterExcludePatterns;
   }
 
-  public void setExcludedEndpoints(String excludedEndpoints) {
-    _excludedEndpoints = excludedEndpoints;
+  public void setUrlFilterExcludePatterns(String urlFilterExcludePatterns) {
+    _urlFilterExcludePatterns = urlFilterExcludePatterns;
   }
 
   public String getUseridHeader() {
@@ -113,7 +113,7 @@ public final class AuditConfig {
         .add("_captureRequestPayload=" + _captureRequestPayload)
         .add("_captureRequestHeaders='" + _captureRequestHeaders + "'")
         .add("_maxPayloadSize=" + _maxPayloadSize)
-        .add("_excludedEndpoints='" + _excludedEndpoints + "'")
+        .add("_urlFilterExcludePatterns='" + _urlFilterExcludePatterns + "'")
         .add("_useridHeader='" + _useridHeader + "'")
         .add("_useridJwtClaimName='" + _useridJwtClaimName + "'")
         .toString();
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfigManager.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfigManager.java
index a3095caf1c6..4bfe365a048 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfigManager.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfigManager.java
@@ -19,94 +19,31 @@
 package org.apache.pinot.common.audit;
 
 import com.google.common.annotations.VisibleForTesting;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import javax.inject.Singleton;
 import org.apache.commons.configuration2.MapConfiguration;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.pinot.spi.config.provider.PinotClusterConfigChangeListener;
 import org.apache.pinot.spi.env.PinotConfiguration;
-import org.apache.pinot.spi.utils.CommonConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
 /**
- * Thread-safe configuration manager for audit logging settings.
+ * Configuration manager for audit logging settings.
  * Handles dynamic configuration updates from cluster configuration changes.
- * Self-registers with the provided cluster config provider.
+ * Note. Needs to be registered with the provided cluster config provider to 
listen to config changes
  */
 @Singleton
 public final class AuditConfigManager implements 
PinotClusterConfigChangeListener {
   private static final Logger LOG = 
LoggerFactory.getLogger(AuditConfigManager.class);
+  private static final String AUDIT_CONFIG_PREFIX = "pinot.audit";
 
   private AuditConfig _currentConfig = new AuditConfig();
 
-  /**
-   * Checks if the given endpoint should be excluded from audit logging.
-   * Supports simple wildcard matching with '*' character.
-   */
-  public static boolean isEndpointExcluded(String endpoint, String 
excludedEndpointsString) {
-    if (StringUtils.isBlank(endpoint) || 
StringUtils.isBlank(excludedEndpointsString)) {
-      return false;
-    }
-
-    Set<String> excludedEndpoints = 
parseExcludedEndpoints(excludedEndpointsString);
-    if (excludedEndpoints.isEmpty()) {
-      return false;
-    }
-
-    // Check for exact matches first
-    if (excludedEndpoints.contains(endpoint)) {
-      return true;
-    }
-
-    // Check for wildcard matches
-    for (String excluded : excludedEndpoints) {
-      if (excluded.contains("*")) {
-        if (matchesWildcard(endpoint, excluded)) {
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
-
-  private static Set<String> parseExcludedEndpoints(String 
excludedEndpointsString) {
-    Set<String> excludedEndpoints = new HashSet<>();
-    if (StringUtils.isNotBlank(excludedEndpointsString)) {
-      String[] endpoints = excludedEndpointsString.split(",");
-      for (String endpoint : endpoints) {
-        String trimmed = endpoint.trim();
-        if (StringUtils.isNotBlank(trimmed)) {
-          excludedEndpoints.add(trimmed);
-        }
-      }
-    }
-    return excludedEndpoints;
-  }
-
-  private static boolean matchesWildcard(String endpoint, String pattern) {
-    if (pattern.equals("*")) {
-      return true;
-    }
-    if (pattern.endsWith("/*")) {
-      String prefix = pattern.substring(0, pattern.length() - 2);
-      return endpoint.startsWith(prefix);
-    }
-    if (pattern.startsWith("*/")) {
-      String suffix = pattern.substring(2);
-      return endpoint.endsWith(suffix);
-    }
-    return false;
-  }
-
   @VisibleForTesting
   static AuditConfig buildFromClusterConfig(Map<String, String> 
clusterConfigs) {
-    return mapPrefixedConfigToObject(clusterConfigs,
-        CommonConstants.AuditLogConstants.PREFIX, AuditConfig.class);
+    return mapPrefixedConfigToObject(clusterConfigs, AUDIT_CONFIG_PREFIX, 
AuditConfig.class);
   }
 
   /**
@@ -121,40 +58,18 @@ public final class AuditConfigManager implements 
PinotClusterConfigChangeListene
     return AuditLogger.OBJECT_MAPPER.convertValue(subsetConfig.toMap(), 
configClass);
   }
 
-  /**
-   * Gets the current audit configuration.
-   * This method is thread-safe and lock-free.
-   *
-   * @return the current audit configuration
-   */
   public AuditConfig getCurrentConfig() {
     return _currentConfig;
   }
 
-  /**
-   * Checks if audit logging is currently enabled.
-   * Convenience method that delegates to the current configuration.
-   *
-   * @return true if audit logging is enabled
-   */
   public boolean isEnabled() {
     return _currentConfig.isEnabled();
   }
 
-  /**
-   * Checks if the given endpoint should be excluded from audit logging.
-   *
-   * @param endpoint the endpoint path to check
-   * @return true if the endpoint should be excluded
-   */
-  public boolean isEndpointExcluded(String endpoint) {
-    return isEndpointExcluded(endpoint, _currentConfig.getExcludedEndpoints());
-  }
-
   @Override
   public void onChange(Set<String> changedConfigs, Map<String, String> 
clusterConfigs) {
-    boolean hasAuditConfigChanges = changedConfigs.stream()
-        .anyMatch(configKey -> 
configKey.startsWith(CommonConstants.AuditLogConstants.PREFIX));
+    boolean hasAuditConfigChanges =
+        changedConfigs.stream().anyMatch(configKey -> 
configKey.startsWith(AUDIT_CONFIG_PREFIX));
 
     if (!hasAuditConfigChanges) {
       LOG.info("No audit-related configs changed, skipping configuration 
rebuild");
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
index 4e5b35675e2..271002489cd 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
@@ -49,11 +49,14 @@ public class AuditRequestProcessor {
 
   private final AuditConfigManager _configManager;
   private final AuditIdentityResolver _identityResolver;
+  private final AuditUrlPathFilter _auditUrlPathFilter;
 
   @Inject
-  public AuditRequestProcessor(AuditConfigManager configManager, 
AuditIdentityResolver identityResolver) {
+  public AuditRequestProcessor(AuditConfigManager configManager, 
AuditIdentityResolver identityResolver,
+      AuditUrlPathFilter auditUrlPathFilter) {
     _configManager = configManager;
     _identityResolver = identityResolver;
+    _auditUrlPathFilter = auditUrlPathFilter;
   }
 
   /**
@@ -109,7 +112,7 @@ public class AuditRequestProcessor {
       String endpoint = uriInfo.getPath();
 
       // Check endpoint exclusions
-      if (_configManager.isEndpointExcluded(endpoint)) {
+      if (_auditUrlPathFilter.isExcluded(endpoint, 
_configManager.getCurrentConfig().getUrlFilterExcludePatterns())) {
         return null;
       }
 
@@ -189,15 +192,7 @@ public class AuditRequestProcessor {
     }
   }
 
-  /**
-   * Reads the request body from the entity input stream.
-   * Restores the input stream for downstream processing.
-   * Limits the amount of data read based on configuration.
-   *
-   * @param requestContext the request context
-   * @param maxPayloadSize maximum bytes to read from the request body
-   * @return the request body as string (potentially truncated)
-   */
+
   /**
    * Parses a comma-separated list of headers into a Set of lowercase header 
names
    * for case-insensitive comparison.
@@ -222,6 +217,15 @@ public class AuditRequestProcessor {
     return headers;
   }
 
+  /**
+   * Reads the request body from the entity input stream.
+   * Restores the input stream for downstream processing.
+   * Limits the amount of data read based on configuration.
+   *
+   * @param requestContext the request context
+   * @param maxPayloadSize maximum bytes to read from the request body
+   * @return the request body as string (potentially truncated)
+   */
   private String readRequestBody(ContainerRequestContext requestContext, int 
maxPayloadSize) {
     // TODO spyne to be implemented
     return null;
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
new file mode 100644
index 00000000000..58db65cee1e
--- /dev/null
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
@@ -0,0 +1,107 @@
+/**
+ * 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.pinot.common.audit;
+
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import javax.inject.Singleton;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * URL path filter utility that uses Java's PathMatcher with glob and regex 
patterns
+ * to determine if a URL path should be excluded from processing.
+ *
+ * This class provides powerful pattern matching capabilities including:
+ * - Wildcards: * (within path segment), ** (across path segments), ? (single 
character)
+ * - Character sets: [abc], [a-z], [!abc]
+ * - Grouping: {api,v1,v2}
+ * - Regular expressions with regex: prefix
+ *
+ * Glob pattern examples:
+ * - health - exact match
+ * - api/* - matches api/users but not api/v1/users
+ * - api/** - matches api/users and api/v1/users
+ * - api/v[12]/users - matches api/v1/users and api/v2/users
+ * - api/{users,groups}/list - matches api/users/list and api/groups/list
+ *
+ * Regex pattern examples (must be prefixed with "regex:"):
+ * - regex:api/v[0-9]+/users - matches api/v1/users, api/v2/users, 
api/v123/users
+ * - regex:^health(check)?$ - matches only "health" or "healthcheck"
+ * - regex:.*\.(jpg|png|gif)$ - matches paths ending with image extensions
+ * - regex:api/(user|group|role)/[0-9]+ - matches api/user/123, api/group/456
+ * - regex:^(ping|status|healthz?)$ - matches ping, status, health, or healthz
+ */
+@Singleton
+public class AuditUrlPathFilter {
+  private static final Logger LOG = 
LoggerFactory.getLogger(AuditUrlPathFilter.class);
+  private static final String PREFIX_GLOB = "glob:";
+  private static final String PREFIX_REGEX = "regex:";
+
+  private static boolean matches(Path path, String pattern) {
+    try {
+      String globPattern = pattern;
+      if (!globPattern.startsWith(PREFIX_GLOB) && 
!globPattern.startsWith(PREFIX_REGEX)) {
+        globPattern = PREFIX_GLOB + globPattern;
+      }
+
+      PathMatcher matcher = 
FileSystems.getDefault().getPathMatcher(globPattern);
+      if (matcher.matches(path)) {
+        return true;
+      }
+    } catch (Exception e) {
+      LOG.warn("Invalid pattern '{}', skipping", pattern, e);
+    }
+    return false;
+  }
+
+  /**
+   * Checks if the given URL path should be excluded based on the provided 
patterns.
+   *
+   * @param urlPath The URL path to check (e.g., "api/v1/users")
+   * @param excludePatterns Comma-separated list of glob patterns
+   * @return true if the path matches any exclude pattern, false otherwise
+   */
+  public boolean isExcluded(String urlPath, String excludePatterns) {
+    if (StringUtils.isBlank(urlPath) || StringUtils.isBlank(excludePatterns)) {
+      return false;
+    }
+
+    try {
+      Path path = Paths.get(urlPath);
+      String[] patterns = excludePatterns.split(",");
+
+      if (Arrays.stream(patterns)
+          .map(String::trim)
+          .filter(StringUtils::isNotBlank)
+          .anyMatch(p -> matches(path, p))) {
+        return true;
+      }
+    } catch (Exception e) {
+      LOG.warn("Error checking URL path '{}' against exclude patterns", 
urlPath, e);
+    }
+
+    return false;
+  }
+}
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditConfigManagerTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditConfigManagerTest.java
index a3ebd0e3932..66ceffad4d9 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditConfigManagerTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditConfigManagerTest.java
@@ -40,7 +40,7 @@ public class AuditConfigManagerTest {
     properties.put("pinot.audit.capture.request.payload.enabled", "true");
     properties.put("pinot.audit.capture.request.headers", 
"Content-Type,X-Request-Id,Authorization");
     properties.put("pinot.audit.payload.size.max.bytes", "20480");
-    properties.put("pinot.audit.excluded.endpoints", "/health,/metrics");
+    properties.put("pinot.audit.url.filter.exclude.patterns", 
"/health,/metrics");
     properties.put("some.other.config", "value");
     properties.put("another.config", "123");
 
@@ -55,7 +55,7 @@ public class AuditConfigManagerTest {
     assertThat(config.isCaptureRequestPayload()).isTrue();
     
assertThat(config.getCaptureRequestHeaders()).isEqualTo("Content-Type,X-Request-Id,Authorization");
     assertThat(config.getMaxPayloadSize()).isEqualTo(20480);
-    assertThat(config.getExcludedEndpoints()).isEqualTo("/health,/metrics");
+    
assertThat(config.getUrlFilterExcludePatterns()).isEqualTo("/health,/metrics");
   }
 
   @Test
@@ -79,7 +79,7 @@ public class AuditConfigManagerTest {
     // Verify defaults for unspecified configs
     assertThat(config.isCaptureRequestPayload()).isFalse();
     assertThat(config.getCaptureRequestHeaders()).isEmpty();
-    assertThat(config.getExcludedEndpoints()).isEmpty();
+    assertThat(config.getUrlFilterExcludePatterns()).isEmpty();
   }
 
   @Test
@@ -99,7 +99,7 @@ public class AuditConfigManagerTest {
     assertThat(config.isCaptureRequestPayload()).isFalse();
     assertThat(config.getCaptureRequestHeaders()).isEmpty();
     assertThat(config.getMaxPayloadSize()).isEqualTo(10240);
-    assertThat(config.getExcludedEndpoints()).isEmpty();
+    assertThat(config.getUrlFilterExcludePatterns()).isEmpty();
   }
 
   @Test
@@ -145,7 +145,7 @@ public class AuditConfigManagerTest {
     
assertThat(config.getCaptureRequestHeaders()).isEqualTo("X-User-Id,X-Session-Token");
     // Verify defaults for unspecified fields
     assertThat(config.getMaxPayloadSize()).isEqualTo(10240);
-    assertThat(config.getExcludedEndpoints()).isEmpty();
+    assertThat(config.getUrlFilterExcludePatterns()).isEmpty();
   }
 
   @Test
@@ -213,7 +213,7 @@ public class AuditConfigManagerTest {
     customProperties.put("pinot.audit.capture.request.payload.enabled", 
"true");
     customProperties.put("pinot.audit.capture.request.headers", 
"X-Trace-Id,X-Correlation-Id");
     customProperties.put("pinot.audit.payload.size.max.bytes", "50000");
-    customProperties.put("pinot.audit.excluded.endpoints", "/test,/debug");
+    customProperties.put("pinot.audit.url.filter.exclude.patterns", 
"/test,/debug");
     manager.onChange(customProperties.keySet(), customProperties);
 
     // Verify custom configs are applied
@@ -222,7 +222,7 @@ public class AuditConfigManagerTest {
     assertThat(customConfig.isCaptureRequestPayload()).isTrue();
     
assertThat(customConfig.getCaptureRequestHeaders()).isEqualTo("X-Trace-Id,X-Correlation-Id");
     assertThat(customConfig.getMaxPayloadSize()).isEqualTo(50000);
-    assertThat(customConfig.getExcludedEndpoints()).isEqualTo("/test,/debug");
+    
assertThat(customConfig.getUrlFilterExcludePatterns()).isEqualTo("/test,/debug");
 
     // When - Simulate ZooKeeper config deletion with empty map
     // The changedConfigs should contain the keys that were deleted, but 
clusterConfigs should be empty
@@ -235,7 +235,7 @@ public class AuditConfigManagerTest {
     assertThat(defaultConfig.isCaptureRequestPayload()).isFalse();
     assertThat(defaultConfig.getCaptureRequestHeaders()).isEmpty();
     assertThat(defaultConfig.getMaxPayloadSize()).isEqualTo(10240);
-    assertThat(defaultConfig.getExcludedEndpoints()).isEmpty();
+    assertThat(defaultConfig.getUrlFilterExcludePatterns()).isEmpty();
   }
 
   @Test
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
index 23d8d767dfb..9ffc23d38d4 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
@@ -51,24 +51,27 @@ public class AuditRequestProcessorTest {
   @Mock
   private UriInfo _uriInfo;
 
+  @Mock
+  private AuditUrlPathFilter _auditUrlPathFilter;
+
   private AuditRequestProcessor _processor;
   private AuditConfig _defaultConfig;
 
   @BeforeMethod
   public void setUp() {
     MockitoAnnotations.openMocks(this);
-    _processor = new AuditRequestProcessor(_configManager, 
mock(AuditIdentityResolver.class));
+    _processor = new AuditRequestProcessor(_configManager, 
mock(AuditIdentityResolver.class), _auditUrlPathFilter);
 
     _defaultConfig = new AuditConfig();
     _defaultConfig.setEnabled(true);
     _defaultConfig.setCaptureRequestPayload(false);
     _defaultConfig.setCaptureRequestHeaders("");
     _defaultConfig.setMaxPayloadSize(10240);
-    _defaultConfig.setExcludedEndpoints("");
+    _defaultConfig.setUrlFilterExcludePatterns("");
 
     when(_configManager.isEnabled()).thenReturn(true);
     when(_configManager.getCurrentConfig()).thenReturn(_defaultConfig);
-    when(_configManager.isEndpointExcluded(any())).thenReturn(false);
+    when(_auditUrlPathFilter.isExcluded(any(), any())).thenReturn(false);
   }
 
   private MultivaluedMap<String, String> createHeaders(String... headerPairs) {
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
new file mode 100644
index 00000000000..22b6053a03e
--- /dev/null
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
@@ -0,0 +1,165 @@
+/**
+ * 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.pinot.common.audit;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/**
+ * Unit tests for {@link AuditUrlPathFilter}.
+ * Tests the filter's delegation to PathMatcher and its input handling logic.
+ */
+public class AuditUrlPathFilterTest {
+
+  private AuditUrlPathFilter _filter;
+
+  @BeforeMethod
+  public void setUp() {
+    _filter = new AuditUrlPathFilter();
+  }
+
+  // ===== Input Validation Tests =====
+
+  @Test
+  public void testNullUrlPath() {
+    assertThat(_filter.isExcluded(null, "health")).isFalse();
+  }
+
+  @Test
+  public void testEmptyUrlPath() {
+    assertThat(_filter.isExcluded("", "health")).isFalse();
+    assertThat(_filter.isExcluded("   ", "health")).isFalse();
+  }
+
+  @Test
+  public void testNullExcludePatterns() {
+    assertThat(_filter.isExcluded("api/users", null)).isFalse();
+  }
+
+  @Test
+  public void testEmptyExcludePatterns() {
+    assertThat(_filter.isExcluded("api/users", "")).isFalse();
+    assertThat(_filter.isExcluded("api/users", "   ")).isFalse();
+  }
+
+  @Test
+  public void testBothParametersBlank() {
+    assertThat(_filter.isExcluded(null, null)).isFalse();
+    assertThat(_filter.isExcluded("", "")).isFalse();
+  }
+
+  // ===== Multiple Pattern Processing Tests =====
+
+  @Test
+  public void testMultiplePatternsCommaSeparated() {
+    String patterns = "health,api/users,admin";
+
+    assertThat(_filter.isExcluded("health", patterns)).isTrue();
+    assertThat(_filter.isExcluded("api/users", patterns)).isTrue();
+    assertThat(_filter.isExcluded("admin", patterns)).isTrue();
+    assertThat(_filter.isExcluded("metrics", patterns)).isFalse();
+  }
+
+  @Test
+  public void testMultiplePatternsWithTrimmingAndEmptyElements() {
+    String patterns = " health , , api/users , , ";
+
+    assertThat(_filter.isExcluded("health", patterns)).isTrue();
+    assertThat(_filter.isExcluded("api/users", patterns)).isTrue();
+    assertThat(_filter.isExcluded("metrics", patterns)).isFalse();
+  }
+
+  @Test
+  public void testAnyPatternMatchesReturnsTrue() {
+    String patterns = "nonexistent1,health,nonexistent2";
+
+    assertThat(_filter.isExcluded("health", patterns)).isTrue();
+    assertThat(_filter.isExcluded("nonexistent1", patterns)).isTrue();
+    assertThat(_filter.isExcluded("other", patterns)).isFalse();
+  }
+
+  // ===== Prefix Handling Tests =====
+
+  @Test
+  public void testAutomaticGlobPrefixAddition() {
+    assertThat(_filter.isExcluded("health", "health")).isTrue();
+    assertThat(_filter.isExcluded("api/users", "api/*")).isTrue();
+  }
+
+  @Test
+  public void testExplicitGlobPrefix() {
+    assertThat(_filter.isExcluded("health", "glob:health")).isTrue();
+    assertThat(_filter.isExcluded("api/users", "glob:api/*")).isTrue();
+  }
+
+  @Test
+  public void testExplicitRegexPrefix() {
+    String pattern = "regex:api/v[0-9]+/.*";
+    assertThat(_filter.isExcluded("api/v1/users", pattern)).isTrue();
+    assertThat(_filter.isExcluded("api/v123/anything", pattern)).isTrue();
+    assertThat(_filter.isExcluded("api/va/users", pattern)).isFalse();
+  }
+
+  @Test
+  public void testMixedPrefixes() {
+    String patterns = "glob:health,regex:api/v[0-9]+/.*,admin";
+
+    assertThat(_filter.isExcluded("health", patterns)).isTrue();
+    assertThat(_filter.isExcluded("api/v1/users", patterns)).isTrue();
+    assertThat(_filter.isExcluded("admin", patterns)).isTrue();
+  }
+
+  // ===== Error Handling Tests =====
+
+  @Test
+  public void testInvalidPatternIsSkipped() {
+    String patterns = "api/v[123,health,{unclosed";
+
+    assertThat(_filter.isExcluded("health", patterns)).isTrue();
+    assertThat(_filter.isExcluded("api/v1", patterns)).isFalse();
+  }
+
+  @Test
+  public void testInvalidPathHandling() {
+    String invalidPath = "path\0with\0nulls";
+    assertThat(_filter.isExcluded(invalidPath, "health")).isFalse();
+  }
+
+  @Test
+  public void testAllInvalidPatternsReturnFalse() {
+    String patterns = "[unclosed,{unclosed,\\invalid";
+
+    assertThat(_filter.isExcluded("anything", patterns)).isFalse();
+  }
+
+  @Test
+  public void testBasicIntegrationWithPathMatcher() {
+    String patterns = "health,api/*,admin/**";
+
+    assertThat(_filter.isExcluded("health", patterns)).isTrue();
+    assertThat(_filter.isExcluded("api/users", patterns)).isTrue();
+    assertThat(_filter.isExcluded("api/v1/users", patterns)).isFalse();
+    assertThat(_filter.isExcluded("admin/config", patterns)).isTrue();
+    assertThat(_filter.isExcluded("admin/config/settings", patterns)).isTrue();
+    assertThat(_filter.isExcluded("metrics", patterns)).isFalse();
+  }
+}
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
index 976e267e0f3..8db54564ae3 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
@@ -21,6 +21,7 @@ package org.apache.pinot.controller.api.audit;
 import org.apache.pinot.common.audit.AuditConfigManager;
 import org.apache.pinot.common.audit.AuditIdentityResolver;
 import org.apache.pinot.common.audit.AuditRequestProcessor;
+import org.apache.pinot.common.audit.AuditUrlPathFilter;
 import org.apache.pinot.common.config.DefaultClusterConfigChangeHandler;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
 
@@ -47,5 +48,6 @@ public class AuditServiceBinder extends AbstractBinder {
 
     bindAsContract(AuditIdentityResolver.class);
     bindAsContract(AuditRequestProcessor.class);
+    bindAsContract(AuditUrlPathFilter.class);
   }
 }
diff --git 
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java 
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
index ea6b2885b4f..19fcf02e8c0 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
@@ -72,17 +72,6 @@ public class CommonConstants {
   public static final String CONFIG_OF_PINOT_TAR_COMPRESSION_CODEC_NAME = 
"pinot.tar.compression.codec.name";
   public static final String QUERY_WORKLOAD = "queryWorkload";
 
-  // Audit logging configuration constants
-  public static class AuditLogConstants {
-    public static final String PREFIX = "pinot.audit";
-    public static final String CONFIG_OF_AUDIT_LOG_ENABLED = PREFIX + 
".enabled";
-    public static final String CONFIG_OF_AUDIT_LOG_CAPTURE_REQUEST_PAYLOAD = 
PREFIX + ".capture.request.payload";
-    public static final String CONFIG_OF_AUDIT_LOG_EXCLUDED_ENDPOINTS = PREFIX 
+ ".excluded.endpoints";
-    public static final String CONFIG_OF_AUDIT_LOG_CAPTURE_REQUEST_HEADERS = 
PREFIX + ".capture.request.headers";
-    public static final String CONFIG_OF_AUDIT_LOG_MAX_PAYLOAD_SIZE = PREFIX + 
".max.payload.size";
-    public static final String CONFIG_OF_AUDIT_LOG_LOGGER_NAME = PREFIX + 
".logger.name";
-  }
-
   public static class Lucene {
     public static final String CONFIG_OF_LUCENE_MAX_CLAUSE_COUNT = 
"pinot.lucene.max.clause.count";
     public static final int DEFAULT_LUCENE_MAX_CLAUSE_COUNT = 1024;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to