From 561003f9af7bed34220c51c6010dbddc86111fca Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 16 Jun 2025 23:03:04 +0100 Subject: [PATCH] extra stuff --- .../proprietary/audit/AuditAspect.java | 72 ++-- .../proprietary/audit/AuditUtils.java | 318 ++++++++++++++++++ .../audit/ControllerAuditAspect.java | 164 ++++----- .../config/AuditConfigurationProperties.java | 15 +- .../controller/AuditDashboardController.java | 18 +- .../proprietary/util/SecretMasker.java | 44 ++- 6 files changed, 464 insertions(+), 167 deletions(-) create mode 100644 proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java 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 aadfe15d0..f31c0d318 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java @@ -8,13 +8,16 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import stirling.software.proprietary.config.AuditConfigurationProperties; import stirling.software.proprietary.service.AuditService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import java.util.stream.IntStream; /** * Aspect for processing {@link Audited} annotations. @@ -34,14 +37,23 @@ public class AuditAspect { Method method = signature.getMethod(); Audited auditedAnnotation = method.getAnnotation(Audited.class); - // Skip if this audit level is not enabled - if (!auditConfig.isLevelEnabled(auditedAnnotation.level())) { + // Use unified check to determine if we should audit + if (!AuditUtils.shouldAudit(method, auditConfig)) { return joinPoint.proceed(); } - Map auditData = new HashMap<>(); - auditData.put("className", joinPoint.getTarget().getClass().getName()); - auditData.put("methodName", method.getName()); + // Use AuditUtils to create the base audit data + Map auditData = AuditUtils.createBaseAuditData(joinPoint, auditedAnnotation.level()); + + // Add HTTP information if we're in a web context + ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attrs != null) { + HttpServletRequest req = attrs.getRequest(); + String path = req.getRequestURI(); + String httpMethod = req.getMethod(); + AuditUtils.addHttpData(auditData, httpMethod, path, auditedAnnotation.level()); + AuditUtils.addFileData(auditData, joinPoint, auditedAnnotation.level()); + } // Add arguments if requested and if at VERBOSE level, or if specifically requested boolean includeArgs = auditedAnnotation.includeArgs() && @@ -49,18 +61,11 @@ public class AuditAspect { auditConfig.getAuditLevel() == AuditLevel.VERBOSE); if (includeArgs) { - Object[] args = joinPoint.getArgs(); - String[] parameterNames = signature.getParameterNames(); - - if (args != null && parameterNames != null) { - IntStream.range(0, args.length) - .forEach(i -> { - String paramName = i < parameterNames.length ? parameterNames[i] : "arg" + i; - auditData.put("arg_" + paramName, args[i]); - }); - } + AuditUtils.addMethodArguments(auditData, joinPoint, AuditLevel.VERBOSE); } + // Record start time for latency calculation + long startTime = System.currentTimeMillis(); Object result; try { // Execute the method @@ -88,17 +93,36 @@ public class AuditAspect { // Re-throw the exception throw ex; } finally { - // Create the audit entry with the specified level - // Determine which type of event identifier to use (enum or string) - AuditEventType eventType = auditedAnnotation.type(); - String typeString = auditedAnnotation.typeString(); + // Add timing information - use isHttpRequest=false to ensure we get timing for non-HTTP methods + HttpServletResponse resp = attrs != null ? attrs.getResponse() : null; + boolean isHttpRequest = attrs != null; + AuditUtils.addTimingData(auditData, startTime, resp, auditedAnnotation.level(), isHttpRequest); - if (eventType != AuditEventType.HTTP_REQUEST || !StringUtils.isNotEmpty(typeString)) { - // Use the enum type (preferred) - auditService.audit(eventType, auditData, auditedAnnotation.level()); - } else { + // Resolve the event type based on annotation and context + String httpMethod = null; + String path = null; + if (attrs != null) { + HttpServletRequest req = attrs.getRequest(); + httpMethod = req.getMethod(); + path = req.getRequestURI(); + } + + AuditEventType eventType = AuditUtils.resolveEventType( + method, + joinPoint.getTarget().getClass(), + path, + httpMethod, + auditedAnnotation + ); + + // Check if we should use string type instead + String typeString = auditedAnnotation.typeString(); + if (eventType == AuditEventType.HTTP_REQUEST && StringUtils.isNotEmpty(typeString)) { // Use the string type (for backward compatibility) auditService.audit(typeString, auditData, auditedAnnotation.level()); + } else { + // Use the enum type (preferred) + auditService.audit(eventType, auditData, auditedAnnotation.level()); } } } diff --git a/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java new file mode 100644 index 000000000..a966c0984 --- /dev/null +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java @@ -0,0 +1,318 @@ +package stirling.software.proprietary.audit; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.MDC; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import stirling.software.common.util.RequestUriUtils; +import stirling.software.proprietary.config.AuditConfigurationProperties; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Shared utilities for audit aspects to ensure consistent behavior + * across different audit mechanisms. + */ +@Slf4j +public class AuditUtils { + + /** + * Create a standard audit data map with common attributes based on the current audit level + * + * @param joinPoint The AspectJ join point + * @param auditLevel The current audit level + * @return A map with standard audit data + */ + public static Map createBaseAuditData(ProceedingJoinPoint joinPoint, AuditLevel auditLevel) { + Map data = new HashMap<>(); + + // Common data for all levels + data.put("timestamp", Instant.now().toString()); + + // Add principal if available + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.getName() != null) { + data.put("principal", auth.getName()); + } else { + data.put("principal", "system"); + } + + // Add class name and method name only at VERBOSE level + if (auditLevel.includes(AuditLevel.VERBOSE)) { + data.put("className", joinPoint.getTarget().getClass().getName()); + data.put("methodName", ((MethodSignature) joinPoint.getSignature()).getMethod().getName()); + } + + return data; + } + + /** + * Add HTTP-specific information to the audit data if available + * + * @param data The existing audit data map + * @param httpMethod The HTTP method (GET, POST, etc.) + * @param path The request path + * @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; + } + + HttpServletRequest req = attrs.getRequest(); + HttpServletResponse resp = attrs.getResponse(); + + // BASIC level HTTP data + data.put("httpMethod", httpMethod); + data.put("path", path); + + // STANDARD level HTTP data + if (auditLevel.includes(AuditLevel.STANDARD)) { + data.put("clientIp", req.getRemoteAddr()); + data.put("sessionId", req.getSession(false) != null ? req.getSession(false).getId() : null); + data.put("requestId", MDC.get("requestId")); + + // Form data for POST/PUT/PATCH + if (("POST".equalsIgnoreCase(httpMethod) || + "PUT".equalsIgnoreCase(httpMethod) || + "PATCH".equalsIgnoreCase(httpMethod)) && req.getContentType() != null) { + + String contentType = req.getContentType(); + if (contentType.contains("application/x-www-form-urlencoded") || + contentType.contains("multipart/form-data")) { + + Map params = new HashMap<>(req.getParameterMap()); + // Remove CSRF token from logged parameters + params.remove("_csrf"); + + if (!params.isEmpty()) { + data.put("formParams", params); + } + } + } + } + } + + /** + * Add file information to the audit data if available + * + * @param data The existing audit data map + * @param joinPoint The AspectJ join point + * @param auditLevel The current audit level + */ + public static void addFileData(Map data, ProceedingJoinPoint joinPoint, AuditLevel auditLevel) { + if (auditLevel.includes(AuditLevel.STANDARD)) { + List files = Arrays.stream(joinPoint.getArgs()) + .filter(a -> a instanceof MultipartFile) + .map(a -> (MultipartFile)a) + .collect(Collectors.toList()); + + if (!files.isEmpty()) { + List> fileInfos = files.stream().map(f -> { + Map m = new HashMap<>(); + m.put("name", f.getOriginalFilename()); + m.put("size", f.getSize()); + m.put("type", f.getContentType()); + return m; + }).collect(Collectors.toList()); + + data.put("files", fileInfos); + } + } + } + + /** + * Add method arguments to the audit data + * + * @param data The existing audit data map + * @param joinPoint The AspectJ join point + * @param auditLevel The current audit level + */ + public static void addMethodArguments(Map data, ProceedingJoinPoint joinPoint, AuditLevel auditLevel) { + if (auditLevel.includes(AuditLevel.VERBOSE)) { + MethodSignature sig = (MethodSignature) joinPoint.getSignature(); + String[] names = sig.getParameterNames(); + Object[] vals = joinPoint.getArgs(); + if (names != null && vals != null) { + IntStream.range(0, names.length) + .forEach(i -> data.put("arg_" + names[i], vals[i])); + } + } + } + + /** + * Determine if a method should be audited based on config and annotation + * + * @param method The method to check + * @param auditConfig The audit configuration + * @return true if the method should be audited + */ + public static boolean shouldAudit(Method method, AuditConfigurationProperties auditConfig) { + // First check if audit is globally enabled + 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); + } + + /** + * Add timing and response status data to the audit record + * + * @param data The audit data to add to + * @param startTime The start time in milliseconds + * @param response The HTTP response (may be null for non-HTTP methods) + * @param level The current audit level + * @param isHttpRequest Whether this is an HTTP request (controller) or a regular method call + */ + public static void addTimingData(Map data, long startTime, HttpServletResponse response, AuditLevel level, boolean isHttpRequest) { + if (level.includes(AuditLevel.STANDARD)) { + // For HTTP requests, let ControllerAuditAspect handle timing separately + // For non-HTTP methods, add execution time here + if (!isHttpRequest) { + data.put("latencyMs", System.currentTimeMillis() - startTime); + } + + // Add HTTP status code if available + if (response != null) { + data.put("statusCode", response.getStatus()); + } + } + } + + /** + * Resolve the event type to use for auditing, considering annotations and context + * + * @param method The method being audited + * @param controller The controller class + * @param path The request path (may be null for non-HTTP methods) + * @param httpMethod The HTTP method (may be null for non-HTTP methods) + * @param annotation The @Audited annotation (may be null) + * @return The resolved event type (never null) + */ + public static AuditEventType resolveEventType(Method method, Class controller, String path, String httpMethod, Audited annotation) { + // First check if we have an explicit annotation + if (annotation != null && annotation.type() != AuditEventType.HTTP_REQUEST) { + return annotation.type(); + } + + // For HTTP methods, infer based on controller and path + if (httpMethod != null) { + String cls = controller.getSimpleName().toLowerCase(); + String pkg = controller.getPackage().getName().toLowerCase(); + + if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST; + + if (cls.contains("user") || cls.contains("auth") || pkg.contains("auth") + || path.startsWith("/user") || path.startsWith("/login")) { + return AuditEventType.USER_PROFILE_UPDATE; + } else if (cls.contains("admin") || path.startsWith("/admin") || path.startsWith("/settings")) { + return AuditEventType.SETTINGS_CHANGED; + } else if (cls.contains("file") || path.startsWith("/file") + || path.matches("(?i).*/(upload|download)/.*")) { + return AuditEventType.FILE_OPERATION; + } + } + + // Default for non-HTTP methods or when no specific match + return AuditEventType.PDF_PROCESS; + } + + /** + * Determine the appropriate audit level to use + * + * @param method The method to check + * @param defaultLevel The default level to use if no annotation present + * @param auditConfig The audit configuration + * @return The audit level to use + */ + public static AuditLevel getEffectiveAuditLevel(Method method, AuditLevel defaultLevel, AuditConfigurationProperties auditConfig) { + Audited auditedAnnotation = method.getAnnotation(Audited.class); + if (auditedAnnotation != null) { + // Method has @Audited - use its level + return auditedAnnotation.level(); + } + + // Use default level (typically from global config) + return defaultLevel; + } + + /** + * Determine the appropriate audit event type to use + * + * @param method The method being audited + * @param controller The controller class + * @param path The request path + * @param httpMethod The HTTP method + * @return The determined audit event type + */ + public static AuditEventType determineAuditEventType(Method method, Class controller, String path, String httpMethod) { + // First check for explicit annotation + Audited auditedAnnotation = method.getAnnotation(Audited.class); + if (auditedAnnotation != null) { + return auditedAnnotation.type(); + } + + // Otherwise infer from controller and path + String cls = controller.getSimpleName().toLowerCase(); + String pkg = controller.getPackage().getName().toLowerCase(); + + if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST; + + if (cls.contains("user") || cls.contains("auth") || pkg.contains("auth") + || path.startsWith("/user") || path.startsWith("/login")) { + return AuditEventType.USER_PROFILE_UPDATE; + } else if (cls.contains("admin") || path.startsWith("/admin") || path.startsWith("/settings")) { + return AuditEventType.SETTINGS_CHANGED; + } else if (cls.contains("file") || path.startsWith("/file") + || path.matches("(?i).*/(upload|download)/.*")) { + return AuditEventType.FILE_OPERATION; + } else { + return AuditEventType.PDF_PROCESS; + } + } + + /** + * Get the current HTTP request if available + * + * @return The current request or null if not in a request context + */ + public static HttpServletRequest getCurrentRequest() { + ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return attrs != null ? attrs.getRequest() : null; + } + + /** + * Check if a GET request is for a static resource + * + * @param request The HTTP request + * @return true if this is a static resource request + */ + public static boolean isStaticResourceRequest(HttpServletRequest request) { + return request != null && !RequestUriUtils.isTrackableResource( + request.getContextPath(), request.getRequestURI()); + } +} \ No newline at end of file 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 edf39a080..202872d63 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java +++ b/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java @@ -2,12 +2,11 @@ package stirling.software.proprietary.audit; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.MDC; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -17,23 +16,16 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import stirling.software.common.util.RequestUriUtils; import stirling.software.proprietary.config.AuditConfigurationProperties; import stirling.software.proprietary.service.AuditService; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.time.Instant; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * Aspect for automatically auditing controller methods with web mappings @@ -50,11 +42,8 @@ public class ControllerAuditAspect { @Around("execution(* org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(..))") - public Object auditStaticResource(ProceedingJoinPoint jp) throws Throwable { - log.info("HELLOOOOOOOOOOOOOOOO"); - return auditController(jp, "GET"); - - + public Object auditStaticResource(ProceedingJoinPoint jp) throws Throwable { + return auditController(jp, "GET"); } /** * Intercept all methods with GetMapping annotation @@ -99,23 +88,28 @@ public class ControllerAuditAspect { private Object auditController(ProceedingJoinPoint joinPoint, String httpMethod) throws Throwable { MethodSignature sig = (MethodSignature) joinPoint.getSignature(); Method method = sig.getMethod(); - AuditLevel level = auditConfig.getAuditLevel(); - // OFF below BASIC? - if (!auditConfig.isLevelEnabled(AuditLevel.BASIC)) { + + // Use unified check to determine if we should audit + if (!AuditUtils.shouldAudit(method, auditConfig)) { return joinPoint.proceed(); } - -// // Opt-out -// if (method.isAnnotationPresent(Audited.class)) { -// return joinPoint.proceed(); -// } + + // Check if method is explicitly annotated with @Audited + Audited auditedAnnotation = method.getAnnotation(Audited.class); + AuditLevel level = auditConfig.getAuditLevel(); + + // If @Audited annotation is present, respect its level setting + if (auditedAnnotation != null) { + // Use the level from annotation if it's stricter than global level + level = auditedAnnotation.level(); + } String path = getRequestPath(method, httpMethod); // Skip static GET resources if ("GET".equals(httpMethod)) { - HttpServletRequest maybe = getCurrentRequest(); - if (maybe != null && !RequestUriUtils.isTrackableResource(maybe.getContextPath(), maybe.getRequestURI())) { + HttpServletRequest maybe = AuditUtils.getCurrentRequest(); + if (maybe != null && AuditUtils.isStaticResourceRequest(maybe)) { return joinPoint.proceed(); } } @@ -125,64 +119,19 @@ public class ControllerAuditAspect { HttpServletResponse resp = attrs != null ? attrs.getResponse() : null; long start = System.currentTimeMillis(); - Map data = new HashMap<>(); - - // BASIC - if (level.includes(AuditLevel.BASIC)) { - data.put("timestamp", Instant.now().toString()); - data.put("principal", SecurityContextHolder.getContext().getAuthentication().getName()); - data.put("path", path); - data.put("httpMethod", httpMethod); - } - - // STANDARD - if (level.includes(AuditLevel.STANDARD) && req != null) { - data.put("clientIp", req.getRemoteAddr()); - data.put("sessionId", req.getSession(false) != null ? req.getSession(false).getId() : null); - data.put("requestId", MDC.get("requestId")); - - if ("POST".equalsIgnoreCase(httpMethod) - || "PUT".equalsIgnoreCase(httpMethod) - || "PATCH".equalsIgnoreCase(httpMethod)) { - String ct = req.getContentType(); - if (ct != null && ( - ct.contains("application/x-www-form-urlencoded") || - ct.contains("multipart/form-data") - )) { - Map params = req.getParameterMap(); - if (!params.isEmpty()) { - data.put("formParams", params); - } - } - - List files = Arrays.stream(joinPoint.getArgs()) - .filter(a -> a instanceof MultipartFile) - .map(a -> (MultipartFile)a) - .collect(Collectors.toList()); - - if (!files.isEmpty()) { - List> fileInfos = files.stream().map(f -> { - Map m = new HashMap<>(); - m.put("name", f.getOriginalFilename()); - m.put("size", f.getSize()); - m.put("type", f.getContentType()); - return m; - }).collect(Collectors.toList()); - - data.put("files", fileInfos); - } - - } - - } - - // VERBOSE args + + // Use AuditUtils to create the base audit data + Map data = AuditUtils.createBaseAuditData(joinPoint, level); + + // Add HTTP-specific information + AuditUtils.addHttpData(data, httpMethod, path, level); + + // Add file information if present + AuditUtils.addFileData(data, joinPoint, level); + + // Add method arguments if at VERBOSE level if (level.includes(AuditLevel.VERBOSE)) { - String[] names = sig.getParameterNames(); - Object[] vals = joinPoint.getArgs(); - if (names != null && vals != null) { - IntStream.range(0, names.length).forEach(i -> data.put("arg_" + names[i], vals[i])); - } + AuditUtils.addMethodArguments(data, joinPoint, level); } Object result = null; @@ -195,37 +144,45 @@ public class ControllerAuditAspect { data.put("errorMessage", ex.getMessage()); throw ex; } finally { - // finalize STANDARD + // Handle timing directly for HTTP requests if (level.includes(AuditLevel.STANDARD)) { data.put("latencyMs", System.currentTimeMillis() - start); if (resp != null) data.put("statusCode", resp.getStatus()); } - // finalize VERBOSE result + + // Call AuditUtils but with isHttpRequest=true to skip additional timing + AuditUtils.addTimingData(data, start, resp, level, true); + + // Add result for VERBOSE level if (level.includes(AuditLevel.VERBOSE) && result != null) { data.put("result", result.toString()); } - AuditEventType type = determineAuditEventType(joinPoint.getTarget().getClass(), path, httpMethod); - auditService.audit(type, data, level); + + // Resolve the event type using the unified method + AuditEventType eventType = AuditUtils.resolveEventType( + method, + joinPoint.getTarget().getClass(), + path, + httpMethod, + auditedAnnotation + ); + + // Check if we should use string type instead (for backward compatibility) + if (auditedAnnotation != null) { + String typeString = auditedAnnotation.typeString(); + if (eventType == AuditEventType.HTTP_REQUEST && StringUtils.isNotEmpty(typeString)) { + auditService.audit(typeString, data, level); + return result; + } + } + + // Use the enum type + auditService.audit(eventType, data, level); } return result; } - private AuditEventType determineAuditEventType(Class controller, String path, String httpMethod) { - String cls = controller.getSimpleName().toLowerCase(); - String pkg = controller.getPackage().getName().toLowerCase(); - if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST; - if (cls.contains("user") || cls.contains("auth") || pkg.contains("auth") - || path.startsWith("/user") || path.startsWith("/login")) { - return AuditEventType.USER_PROFILE_UPDATE; - } else if (cls.contains("admin") || path.startsWith("/admin") || path.startsWith("/settings")) { - return AuditEventType.SETTINGS_CHANGED; - } else if (cls.contains("file") || path.startsWith("/file") - || path.matches("(?i).*/(upload|download)/.*")) { - return AuditEventType.FILE_OPERATION; - } else { - return AuditEventType.PDF_PROCESS; - } - } + // Using AuditUtils.determineAuditEventType instead private String getRequestPath(Method method, String httpMethod) { String base = ""; @@ -248,8 +205,5 @@ public class ControllerAuditAspect { return base + mp; } - private HttpServletRequest getCurrentRequest() { - ServletRequestAttributes a = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - return a != null ? a.getRequest() : null; - } + // Using AuditUtils.getCurrentRequest instead } 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 2e3e04ab7..57871b703 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java +++ b/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java @@ -3,8 +3,6 @@ package stirling.software.proprietary.config; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @@ -18,25 +16,22 @@ import stirling.software.proprietary.audit.AuditLevel; @Slf4j @Getter @Component -@Order(Ordered.HIGHEST_PRECEDENCE+ 10) +@Order(Ordered.HIGHEST_PRECEDENCE + 10) public class AuditConfigurationProperties { private final boolean enabled; private final int level; private final int retentionDays; - private final String licenseType; - - public AuditConfigurationProperties(ApplicationProperties applicationProperties, @Qualifier("license") String licenseType) { + public AuditConfigurationProperties(ApplicationProperties applicationProperties) { ApplicationProperties.Premium.ProFeatures.Audit auditConfig = - applicationProperties.getPremium().getProFeatures().getAudit(); - + applicationProperties.getPremium().getProFeatures().getAudit(); + // Read values directly from configuration this.enabled = auditConfig.isEnabled(); this.level = auditConfig.getLevel(); this.retentionDays = auditConfig.getRetentionDays(); - this.licenseType = licenseType; - log.info("Initialized audit configuration: enabled={}, level={}, retentionDays={}", + log.debug("Initialized audit configuration: enabled={}, level={}, retentionDays={}", this.enabled, this.level, this.retentionDays); } diff --git a/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java b/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java index 3df0da54e..6616fcd47 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/controller/AuditDashboardController.java @@ -81,8 +81,8 @@ public class AuditDashboardController { @GetMapping("/data") @ResponseBody public Map getAuditData( - @RequestParam(value = "page", defaultValue = "0") Long page, - @RequestParam(value = "size", defaultValue = "30") Long size, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "30") int size, @RequestParam(value = "type", required = false) String type, @RequestParam(value = "principal", required = false) String principal, @RequestParam(value = "startDate", required = false) @@ -90,12 +90,11 @@ public class AuditDashboardController { @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate, HttpServletRequest request) { - log.info("Raw query string: {}", request.getQueryString()); - - Pageable pageable = PageRequest.of(page.intValue(), size.intValue(), Sort.by("timestamp").descending()); + + Pageable pageable = PageRequest.of(page, size, Sort.by("timestamp").descending()); Page events; - String mode = "unknown"; + String mode; if (type != null && principal != null && startDate != null && endDate != null) { mode = "principal + type + startDate + endDate"; @@ -133,13 +132,6 @@ public class AuditDashboardController { // Logging List content = events.getContent(); - Long firstId = content.isEmpty() ? null : content.get(0).getId(); - Long lastId = content.isEmpty() ? null : content.get(content.size() - 1).getId(); - - log.info("Audit request: page={} size={} mode='{}' → result page={} totalElements={} totalPages={} contentSize={}", - page, size, mode, events.getNumber(), events.getTotalElements(), events.getTotalPages(), content.size()); - - log.info("Audit content ID range: firstId={} lastId={} (descending timestamp)", firstId, lastId); Map response = new HashMap<>(); response.put("content", content); diff --git a/proprietary/src/main/java/stirling/software/proprietary/util/SecretMasker.java b/proprietary/src/main/java/stirling/software/proprietary/util/SecretMasker.java index 56d46710d..82a0f7a52 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/util/SecretMasker.java +++ b/proprietary/src/main/java/stirling/software/proprietary/util/SecretMasker.java @@ -6,7 +6,14 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import lombok.extern.slf4j.Slf4j; + /** Redacts any map values whose keys match common secret/token patterns. */ +@Slf4j public final class SecretMasker { private static final Pattern SENSITIVE = @@ -14,21 +21,34 @@ public final class SecretMasker { private SecretMasker() {} - public static Object deepMask(Object value) { + public static Map mask(Map in) { + if (in == null) return null; + + return in.entrySet().stream() + .filter(e -> e.getValue() != null) + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> deepMaskValue(e.getKey(), e.getValue()) + )); + } + + private static Object deepMask(Object value) { if (value instanceof Map m) { - return m.entrySet().stream().collect(Collectors.toMap( - Map.Entry::getKey, - e -> deepMaskValue((String)e.getKey(), e.getValue()) - )); + return m.entrySet().stream() + .filter(e -> e.getValue() != null) + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> deepMaskValue((String)e.getKey(), e.getValue()) + )); } else if (value instanceof List list) { return list.stream() - .map(SecretMasker::deepMask) - .collect(Collectors.toList()); + .map(SecretMasker::deepMask).toList(); } else { return value; } } + private static Object deepMaskValue(String key, Object value) { if (key != null && SENSITIVE.matcher(key).find()) { return "***REDACTED***"; @@ -36,12 +56,6 @@ public final class SecretMasker { return deepMask(value); } - public static Map mask(Map in) { - if (in == null) return null; - return in.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - e -> deepMaskValue(e.getKey(), e.getValue()) - )); - } + + } \ No newline at end of file