suvodeep-pyne commented on code in PR #16616:
URL: https://github.com/apache/pinot/pull/16616#discussion_r2286664388


##########
pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java:
##########
@@ -0,0 +1,317 @@
+/**
+ * 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.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Utility class for extracting audit information from Jersey HTTP requests.
+ * Handles all the complex logic for IP address extraction, user 
identification,
+ * and request payload capture for audit logging purposes.
+ * Uses dynamic configuration to control audit behavior.
+ */
+@Singleton
+public class AuditRequestProcessor {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(AuditRequestProcessor.class);
+  private static final String ANONYMOUS = "anonymous";
+
+  @Inject
+  private AuditConfigManager _configManager;
+
+  public AuditEvent processRequest(ContainerRequestContext requestContext, 
String remoteAddr) {
+    // Check if auditing is enabled (if config manager is available)
+    if (!isEnabled()) {
+      return null;
+    }
+
+    try {
+      UriInfo uriInfo = requestContext.getUriInfo();
+      String endpoint = uriInfo.getPath();
+
+      // Check endpoint exclusions
+      if (_configManager.isEndpointExcluded(endpoint)) {
+        return null;
+      }
+
+      String method = requestContext.getMethod();
+      String originIpAddress = extractClientIpAddress(requestContext, 
remoteAddr);
+      String userId = extractUserId(requestContext);
+
+      // Capture request payload based on configuration
+      Object requestPayload = captureRequestPayload(requestContext);
+
+      // Log the audit event (service ID will be extracted from headers, not 
config)
+      return new AuditEvent(extractServiceId(requestContext), endpoint, 
method, originIpAddress, userId,
+          requestPayload);
+    } catch (Exception e) {
+      // Graceful degradation: Never let audit logging failures affect the 
main request
+      LOG.warn("Failed to process audit logging for request", e);
+    }
+    return null;
+  }
+
+  public boolean isEnabled() {
+    return _configManager.isEnabled();
+  }
+
+  /**
+   * Extracts the client IP address from the request.
+   * Checks common proxy headers before falling back to remote address.
+   *
+   * @param requestContext the container request context
+   * @param remoteAddr the remote address from the underlying request
+   * @return the client IP address
+   */
+  private String extractClientIpAddress(ContainerRequestContext 
requestContext, String remoteAddr) {
+    try {
+      // Check for proxy headers first
+      String xForwardedFor = requestContext.getHeaderString("X-Forwarded-For");
+      if (StringUtils.isNotBlank(xForwardedFor)) {
+        // X-Forwarded-For can contain multiple IPs, take the first one
+        return xForwardedFor.split(",")[0].trim();
+      }
+
+      String xRealIp = requestContext.getHeaderString("X-Real-IP");
+      if (StringUtils.isNotBlank(xRealIp)) {
+        return xRealIp.trim();
+      }
+
+      // Fall back to remote address
+      return remoteAddr;
+    } catch (Exception e) {
+      LOG.debug("Failed to extract client IP address", e);
+      return "unknown";
+    }
+  }
+
+  /**
+   * Extracts user ID from request headers.
+   * Looks for common authentication headers.
+   *
+   * @param requestContext the container request context
+   * @return the user ID or "anonymous" if not found
+   */
+  private String extractUserId(ContainerRequestContext requestContext) {
+    try {
+      // Check for common user identification headers
+      String authHeader = requestContext.getHeaderString("Authorization");
+      if (StringUtils.isNotBlank(authHeader)) {
+        // For basic auth, extract username; for bearer tokens, use a 
placeholder
+        if (authHeader.startsWith("Basic ")) {
+          // Could decode basic auth to get username, but for security keep it 
as placeholder
+          return "basic-auth-user";
+        } else if (authHeader.startsWith("Bearer ")) {
+          return "bearer-token-user";
+        }
+      }
+
+      // Check for custom user headers
+      String userHeader = requestContext.getHeaderString("X-User-ID");
+      if (StringUtils.isNotBlank(userHeader)) {
+        return userHeader.trim();
+      }
+
+      userHeader = requestContext.getHeaderString("X-Username");
+      if (StringUtils.isNotBlank(userHeader)) {
+        return userHeader.trim();
+      }
+
+      return ANONYMOUS;
+    } catch (Exception e) {
+      LOG.debug("Failed to extract user ID", e);
+      return ANONYMOUS;
+    }
+  }
+
+  /**
+   * Extracts service ID from request headers.
+   * Service ID should be provided by the client in headers, not from 
configuration.
+   *
+   * @param requestContext the container request context
+   * @return the service ID or "unknown" if not found
+   */
+  private String extractServiceId(ContainerRequestContext requestContext) {
+    try {
+      // Check for custom service ID headers
+      String serviceId = requestContext.getHeaderString("X-Service-ID");
+      if (StringUtils.isNotBlank(serviceId)) {
+        return serviceId.trim();
+      }
+
+      serviceId = requestContext.getHeaderString("X-Service-Name");
+      if (StringUtils.isNotBlank(serviceId)) {
+        return serviceId.trim();
+      }
+
+      return "unknown";
+    } catch (Exception e) {
+      LOG.debug("Failed to extract service ID", e);
+      return "unknown";
+    }
+  }
+
+  /**
+   * Captures the request payload for audit logging based on configuration.
+   * Uses dynamic configuration to control what data is captured.
+   *
+   * @param requestContext the request context
+   * @return the captured request payload
+   */
+  private Object captureRequestPayload(ContainerRequestContext requestContext) 
{
+    // Get current configuration (fallback to defaults if no config manager)
+    AuditConfig config = _configManager != null ? 
_configManager.getCurrentConfig() : new AuditConfig();

Review Comment:
   No. fixed this. ConfigManager should be present always.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to