From bb04361c77559d28de4649caf94e8aea4bf054cd Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 16 Jun 2025 23:38:41 +0100 Subject: [PATCH] remove props, set ranges --- .../proprietary/audit/AuditAspect.java | 8 +- .../proprietary/audit/AuditLevel.java | 8 +- .../proprietary/audit/AuditUtils.java | 91 +++++++++++++++---- .../audit/ControllerAuditAspect.java | 6 +- .../config/AuditConfigurationProperties.java | 18 +++- .../proprietary/service/AuditService.java | 4 +- .../application-proprietary.properties | 12 --- 7 files changed, 107 insertions(+), 40 deletions(-) diff --git a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java index f31c0d318..519c901fd 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java @@ -37,12 +37,13 @@ public class AuditAspect { Method method = signature.getMethod(); Audited auditedAnnotation = method.getAnnotation(Audited.class); - // Use unified check to determine if we should audit + // Fast path: use unified check to determine if we should audit + // This avoids all data collection if auditing is disabled if (!AuditUtils.shouldAudit(method, auditConfig)) { return joinPoint.proceed(); } - // Use AuditUtils to create the base audit data + // Only create the map once we know we'll use it Map auditData = AuditUtils.createBaseAuditData(joinPoint, auditedAnnotation.level()); // Add HTTP information if we're in a web context @@ -80,7 +81,8 @@ public class AuditAspect { auditConfig.getAuditLevel() == AuditLevel.VERBOSE); if (includeResult && result != null) { - auditData.put("result", result.toString()); + // Use safe string conversion with size limiting + auditData.put("result", AuditUtils.safeToString(result, 1000)); } return result; diff --git a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditLevel.java b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditLevel.java index d6b61f488..79ca32922 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditLevel.java +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditLevel.java @@ -65,12 +65,16 @@ public enum AuditLevel { * @return The corresponding AuditLevel */ public static AuditLevel fromInt(int level) { + // Ensure level is within valid bounds + int boundedLevel = Math.min(Math.max(level, 0), 3); + for (AuditLevel auditLevel : values()) { - if (auditLevel.level == level) { + if (auditLevel.level == boundedLevel) { return auditLevel; } } - // Default to STANDARD if invalid level + + // Default to STANDARD if somehow we didn't match return STANDARD; } } \ No newline at end of file diff --git a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java index a966c0984..35153d956 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.apache.commons.lang3.StringUtils; + /** * Shared utilities for audit aspects to ensure consistent behavior * across different audit mechanisms. @@ -69,18 +71,25 @@ public class AuditUtils { * @param auditLevel The current audit level */ public static void addHttpData(Map data, String httpMethod, String path, AuditLevel auditLevel) { - ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (attrs == null) { - return; + if (httpMethod == null || path == null) { + return; // Skip if we don't have basic HTTP info } - HttpServletRequest req = attrs.getRequest(); - HttpServletResponse resp = attrs.getResponse(); - // BASIC level HTTP data data.put("httpMethod", httpMethod); data.put("path", path); + // Get request attributes safely + ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attrs == null) { + return; // No request context available + } + + HttpServletRequest req = attrs.getRequest(); + if (req == null) { + return; // No request available + } + // STANDARD level HTTP data if (auditLevel.includes(AuditLevel.STANDARD)) { data.put("clientIp", req.getRemoteAddr()); @@ -150,11 +159,56 @@ public class AuditUtils { Object[] vals = joinPoint.getArgs(); if (names != null && vals != null) { IntStream.range(0, names.length) - .forEach(i -> data.put("arg_" + names[i], vals[i])); + .forEach(i -> { + if (vals[i] != null) { + // Convert objects to safe string representation + data.put("arg_" + names[i], safeToString(vals[i], 500)); + } else { + data.put("arg_" + names[i], null); + } + }); } } } + /** + * Safely convert an object to string with size limiting + * + * @param obj The object to convert + * @param maxLength Maximum length of the resulting string + * @return A safe string representation, truncated if needed + */ + public static String safeToString(Object obj, int maxLength) { + if (obj == null) { + return "null"; + } + + String result; + try { + // Handle common types directly to avoid toString() overhead + if (obj instanceof String) { + result = (String) obj; + } else if (obj instanceof Number || obj instanceof Boolean) { + result = obj.toString(); + } else if (obj instanceof byte[]) { + result = "[binary data length=" + ((byte[]) obj).length + "]"; + } else { + // For complex objects, use toString but handle exceptions + result = obj.toString(); + } + + // Truncate if necessary + if (result != null && result.length() > maxLength) { + return StringUtils.truncate(result, maxLength - 3) + "..."; + } + + return result; + } catch (Exception e) { + // If toString() fails, return the class name + return "[" + obj.getClass().getName() + " - toString() failed]"; + } + } + /** * Determine if a method should be audited based on config and annotation * @@ -163,20 +217,19 @@ public class AuditUtils { * @return true if the method should be audited */ public static boolean shouldAudit(Method method, AuditConfigurationProperties auditConfig) { - // First check if audit is globally enabled + // First check if audit is globally enabled - fast path if (!auditConfig.isEnabled()) { return false; } // Check for annotation override Audited auditedAnnotation = method.getAnnotation(Audited.class); - if (auditedAnnotation != null) { - // Method has @Audited - check if the specific level is enabled - return auditConfig.isLevelEnabled(auditedAnnotation.level()); - } - - // No annotation - use global level for controllers - return auditConfig.isLevelEnabled(AuditLevel.BASIC); + AuditLevel requiredLevel = (auditedAnnotation != null) + ? auditedAnnotation.level() + : AuditLevel.BASIC; + + // Check if the required level is enabled + return auditConfig.getAuditLevel().includes(requiredLevel); } /** @@ -198,7 +251,11 @@ public class AuditUtils { // Add HTTP status code if available if (response != null) { - data.put("statusCode", response.getStatus()); + try { + data.put("statusCode", response.getStatus()); + } catch (Exception e) { + // Ignore - response might be in an inconsistent state + } } } } @@ -220,7 +277,7 @@ public class AuditUtils { } // For HTTP methods, infer based on controller and path - if (httpMethod != null) { + if (httpMethod != null && path != null) { String cls = controller.getSimpleName().toLowerCase(); String pkg = controller.getPackage().getName().toLowerCase(); diff --git a/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java b/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java index 202872d63..6f3990a68 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java @@ -89,7 +89,8 @@ public class ControllerAuditAspect { MethodSignature sig = (MethodSignature) joinPoint.getSignature(); Method method = sig.getMethod(); - // Use unified check to determine if we should audit + // Fast path: check if auditing is enabled before doing any work + // This avoids all data collection if auditing is disabled if (!AuditUtils.shouldAudit(method, auditConfig)) { return joinPoint.proceed(); } @@ -155,7 +156,8 @@ public class ControllerAuditAspect { // Add result for VERBOSE level if (level.includes(AuditLevel.VERBOSE) && result != null) { - data.put("result", result.toString()); + // Use safe string conversion with size limiting + data.put("result", AuditUtils.safeToString(result, 1000)); } // Resolve the event type using the unified method diff --git a/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java b/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java index 57871b703..6ff420ffe 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java +++ b/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java @@ -28,10 +28,15 @@ public class AuditConfigurationProperties { applicationProperties.getPremium().getProFeatures().getAudit(); // Read values directly from configuration this.enabled = auditConfig.isEnabled(); - this.level = auditConfig.getLevel(); + + // Ensure level is within valid bounds (0-3) + int configLevel = auditConfig.getLevel(); + this.level = Math.min(Math.max(configLevel, 0), 3); + + // Retention days (0 means infinite) this.retentionDays = auditConfig.getRetentionDays(); - log.debug("Initialized audit configuration: enabled={}, level={}, retentionDays={}", + log.debug("Initialized audit configuration: enabled={}, level={}, retentionDays={} (0=infinite)", this.enabled, this.level, this.retentionDays); } @@ -51,4 +56,13 @@ public class AuditConfigurationProperties { public boolean isLevelEnabled(AuditLevel requiredLevel) { return enabled && getAuditLevel().includes(requiredLevel); } + + /** + * Get the effective retention period in days + * @return The number of days to retain audit records, or -1 for infinite retention + */ + public int getEffectiveRetentionDays() { + // 0 means infinite retention + return retentionDays <= 0 ? -1 : retentionDays; + } } \ No newline at end of file diff --git a/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java b/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java index 0df667d4c..048a424ea 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java +++ b/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java @@ -34,8 +34,8 @@ public class AuditService { * @param level The minimum audit level required for this event to be logged */ public void audit(AuditEventType type, Map data, AuditLevel level) { - // Skip auditing if this level is not enabled - if (!auditConfig.isLevelEnabled(level)) { + // Skip auditing if this level is not enabled - check first to avoid further processing + if (!auditConfig.isEnabled() || !auditConfig.getAuditLevel().includes(level)) { return; } diff --git a/proprietary/src/main/resources/application-proprietary.properties b/proprietary/src/main/resources/application-proprietary.properties index 76309521c..e69de29bb 100644 --- a/proprietary/src/main/resources/application-proprietary.properties +++ b/proprietary/src/main/resources/application-proprietary.properties @@ -1,12 +0,0 @@ -# ── Actuator surface-area hardening ─────────────────────── -# Enable Prometheus metrics endpoint -management.endpoints.web.exposure.include=health,info,metrics,prometheus -# Exclude auditevents from exposure -management.endpoints.web.exposure.exclude=auditevents -# Disable the audit events endpoint completely -management.endpoint.auditevents.enabled=false -# Configure endpoints -management.endpoints.web.base-path=/actuator -management.info.env.enabled=true -management.endpoint.health.show-details=when_authorized -management.endpoint.health.roles=ADMIN \ No newline at end of file