change to enterprise + LIKE

This commit is contained in:
Anthony Stirling 2025-06-18 00:21:03 +01:00
parent 75cd0ee407
commit 5dc1d0f950
10 changed files with 124 additions and 60 deletions

View File

@ -442,14 +442,6 @@ public class ApplicationProperties {
private boolean database; private boolean database;
private CustomMetadata customMetadata = new CustomMetadata(); private CustomMetadata customMetadata = new CustomMetadata();
private GoogleDrive googleDrive = new GoogleDrive(); private GoogleDrive googleDrive = new GoogleDrive();
private Audit audit = new Audit();
@Data
public static class Audit {
private boolean enabled = true;
private int level = 2; // 0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE
private int retentionDays = 90;
}
@Data @Data
public static class CustomMetadata { public static class CustomMetadata {
@ -493,6 +485,14 @@ public class ApplicationProperties {
@Data @Data
public static class EnterpriseFeatures { public static class EnterpriseFeatures {
private PersistentMetrics persistentMetrics = new PersistentMetrics(); private PersistentMetrics persistentMetrics = new PersistentMetrics();
private Audit audit = new Audit();
@Data
public static class Audit {
private boolean enabled = true;
private int level = 2; // 0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE
private int retentionDays = 90;
}
@Data @Data
public static class PersistentMetrics { public static class PersistentMetrics {

View File

@ -11,7 +11,7 @@ import stirling.software.proprietary.audit.AuditLevel;
/** /**
* Configuration properties for the audit system. * Configuration properties for the audit system.
* Reads values from the ApplicationProperties under premium.proFeatures.audit * Reads values from the ApplicationProperties under premium.enterpriseFeatures.audit
*/ */
@Slf4j @Slf4j
@Getter @Getter
@ -24,8 +24,8 @@ public class AuditConfigurationProperties {
private final int retentionDays; private final int retentionDays;
public AuditConfigurationProperties(ApplicationProperties applicationProperties) { public AuditConfigurationProperties(ApplicationProperties applicationProperties) {
ApplicationProperties.Premium.ProFeatures.Audit auditConfig = ApplicationProperties.Premium.EnterpriseFeatures.Audit auditConfig =
applicationProperties.getPremium().getProFeatures().getAudit(); applicationProperties.getPremium().getEnterpriseFeatures().getAudit();
// Read values directly from configuration // Read values directly from configuration
this.enabled = auditConfig.isEnabled(); this.enabled = auditConfig.isEnabled();

View File

@ -51,17 +51,19 @@ public class CustomAuditEventRepository implements AuditEventRepository {
} }
String rid = MDC.get("requestId"); String rid = MDC.get("requestId");
log.info("AuditEvent clean data (JSON): {}",
mapper.writeValueAsString(clean));
if (rid != null) { if (rid != null) {
clean = new java.util.HashMap<>(clean); clean = new java.util.HashMap<>(clean);
clean.put("requestId", rid); clean.put("requestId", rid);
} }
String auditEventData = mapper.writeValueAsString(clean);
log.debug("AuditEvent data (JSON): {}",auditEventData);
PersistentAuditEvent ent = PersistentAuditEvent.builder() PersistentAuditEvent ent = PersistentAuditEvent.builder()
.principal(ev.getPrincipal()) .principal(ev.getPrincipal())
.type(ev.getType()) .type(ev.getType())
.data(mapper.writeValueAsString(clean)) .data(auditEventData)
.timestamp(ev.getTimestamp()) .timestamp(ev.getTimestamp())
.build(); .build();
repo.save(ent); repo.save(ent);

View File

@ -40,6 +40,7 @@ import stirling.software.proprietary.audit.AuditLevel;
import stirling.software.proprietary.config.AuditConfigurationProperties; import stirling.software.proprietary.config.AuditConfigurationProperties;
import stirling.software.proprietary.model.security.PersistentAuditEvent; import stirling.software.proprietary.model.security.PersistentAuditEvent;
import stirling.software.proprietary.repository.PersistentAuditEventRepository; import stirling.software.proprietary.repository.PersistentAuditEventRepository;
import stirling.software.proprietary.security.config.EnterpriseEndpoint;
/** /**
* Controller for the audit dashboard. * Controller for the audit dashboard.
@ -50,6 +51,7 @@ import stirling.software.proprietary.repository.PersistentAuditEventRepository;
@RequestMapping("/audit") @RequestMapping("/audit")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@RequiredArgsConstructor @RequiredArgsConstructor
@EnterpriseEndpoint
public class AuditDashboardController { public class AuditDashboardController {
private final PersistentAuditEventRepository auditRepository; private final PersistentAuditEventRepository auditRepository;
@ -186,10 +188,7 @@ public class AuditDashboardController {
@ResponseBody @ResponseBody
public List<String> getAuditTypes() { public List<String> getAuditTypes() {
// Get distinct event types from the database // Get distinct event types from the database
List<Object[]> results = auditRepository.findDistinctEventTypes(); List<String> dbTypes = auditRepository.findDistinctEventTypes();
List<String> dbTypes = results.stream()
.map(row -> (String) row[0])
.collect(Collectors.toList());
// Include standard enum types in case they're not in the database yet // Include standard enum types in case they're not in the database yet
List<String> enumTypes = Arrays.stream(AuditEventType.values()) List<String> enumTypes = Arrays.stream(AuditEventType.values())
@ -222,26 +221,26 @@ public class AuditDashboardController {
if (type != null && principal != null && startDate != null && endDate != null) { if (type != null && principal != null && startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByPrincipalAndTypeAndTimestampBetween( events = auditRepository.findAllByPrincipalAndTypeAndTimestampBetweenForExport(
principal, type, start, end); principal, type, start, end);
} else if (type != null && principal != null) { } else if (type != null && principal != null) {
events = auditRepository.findByPrincipalAndType(principal, type); events = auditRepository.findAllByPrincipalAndTypeForExport(principal, type);
} else if (type != null && startDate != null && endDate != null) { } else if (type != null && startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByTypeAndTimestampBetween(type, start, end); events = auditRepository.findAllByTypeAndTimestampBetweenForExport(type, start, end);
} else if (principal != null && startDate != null && endDate != null) { } else if (principal != null && startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByPrincipalAndTimestampBetween(principal, start, end); events = auditRepository.findAllByPrincipalAndTimestampBetweenForExport(principal, start, end);
} else if (startDate != null && endDate != null) { } else if (startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByTimestampBetween(start, end); events = auditRepository.findAllByTimestampBetweenForExport(start, end);
} else if (type != null) { } else if (type != null) {
events = auditRepository.findByTypeForExport(type); events = auditRepository.findByTypeForExport(type);
} else if (principal != null) { } else if (principal != null) {
events = auditRepository.findByPrincipal(principal); events = auditRepository.findAllByPrincipalForExport(principal);
} else { } else {
events = auditRepository.findAll(); events = auditRepository.findAll();
} }
@ -290,26 +289,26 @@ public class AuditDashboardController {
if (type != null && principal != null && startDate != null && endDate != null) { if (type != null && principal != null && startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByPrincipalAndTypeAndTimestampBetween( events = auditRepository.findAllByPrincipalAndTypeAndTimestampBetweenForExport(
principal, type, start, end); principal, type, start, end);
} else if (type != null && principal != null) { } else if (type != null && principal != null) {
events = auditRepository.findByPrincipalAndType(principal, type); events = auditRepository.findAllByPrincipalAndTypeForExport(principal, type);
} else if (type != null && startDate != null && endDate != null) { } else if (type != null && startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByTypeAndTimestampBetween(type, start, end); events = auditRepository.findAllByTypeAndTimestampBetweenForExport(type, start, end);
} else if (principal != null && startDate != null && endDate != null) { } else if (principal != null && startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByPrincipalAndTimestampBetween(principal, start, end); events = auditRepository.findAllByPrincipalAndTimestampBetweenForExport(principal, start, end);
} else if (startDate != null && endDate != null) { } else if (startDate != null && endDate != null) {
Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant start = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant(); Instant end = endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant();
events = auditRepository.findByTimestampBetween(start, end); events = auditRepository.findAllByTimestampBetweenForExport(start, end);
} else if (type != null) { } else if (type != null) {
events = auditRepository.findByTypeForExport(type); events = auditRepository.findByTypeForExport(type);
} else if (principal != null) { } else if (principal != null) {
events = auditRepository.findByPrincipal(principal); events = auditRepository.findAllByPrincipalForExport(principal);
} else { } else {
events = auditRepository.findAll(); events = auditRepository.findAll();
} }

View File

@ -6,9 +6,11 @@ import java.util.List;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import stirling.software.proprietary.model.security.PersistentAuditEvent; import stirling.software.proprietary.model.security.PersistentAuditEvent;
@ -17,29 +19,40 @@ public interface PersistentAuditEventRepository
extends JpaRepository<PersistentAuditEvent, Long> { extends JpaRepository<PersistentAuditEvent, Long> {
// Basic queries // Basic queries
Page<PersistentAuditEvent> findByPrincipal(String principal, Pageable pageable); @Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%'))")
Page<PersistentAuditEvent> findByPrincipal(@Param("principal") String principal, Pageable pageable);
Page<PersistentAuditEvent> findByType(String type, Pageable pageable); Page<PersistentAuditEvent> findByType(String type, Pageable pageable);
Page<PersistentAuditEvent> findByTimestampBetween(Instant startDate, Instant endDate, Pageable pageable); Page<PersistentAuditEvent> findByTimestampBetween(Instant startDate, Instant endDate, Pageable pageable);
Page<PersistentAuditEvent> findByPrincipalAndType(String principal, String type, Pageable pageable); @Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%')) AND e.type = :type")
Page<PersistentAuditEvent> findByPrincipalAndTimestampBetween(String principal, Instant startDate, Instant endDate, Pageable pageable); Page<PersistentAuditEvent> findByPrincipalAndType(@Param("principal") String principal, @Param("type") String type, Pageable pageable);
@Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%')) AND e.timestamp BETWEEN :startDate AND :endDate")
Page<PersistentAuditEvent> findByPrincipalAndTimestampBetween(@Param("principal") String principal, @Param("startDate") Instant startDate, @Param("endDate") Instant endDate, Pageable pageable);
Page<PersistentAuditEvent> findByTypeAndTimestampBetween(String type, Instant startDate, Instant endDate, Pageable pageable); Page<PersistentAuditEvent> findByTypeAndTimestampBetween(String type, Instant startDate, Instant endDate, Pageable pageable);
Page<PersistentAuditEvent> findByPrincipalAndTypeAndTimestampBetween(String principal, String type, Instant startDate, Instant endDate, Pageable pageable); @Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%')) AND e.type = :type AND e.timestamp BETWEEN :startDate AND :endDate")
Page<PersistentAuditEvent> findByPrincipalAndTypeAndTimestampBetween(@Param("principal") String principal, @Param("type") String type, @Param("startDate") Instant startDate, @Param("endDate") Instant endDate, Pageable pageable);
// Non-paged versions for export // Non-paged versions for export
List<PersistentAuditEvent> findByPrincipal(String principal); @Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%'))")
List<PersistentAuditEvent> findAllByPrincipalForExport(@Param("principal") String principal);
@Query("SELECT e FROM PersistentAuditEvent e WHERE e.type = :type") @Query("SELECT e FROM PersistentAuditEvent e WHERE e.type = :type")
List<PersistentAuditEvent> findByTypeForExport(@Param("type") String type); List<PersistentAuditEvent> findByTypeForExport(@Param("type") String type);
List<PersistentAuditEvent> findByTimestampBetween(Instant startDate, Instant endDate); @Query("SELECT e FROM PersistentAuditEvent e WHERE e.timestamp BETWEEN :startDate AND :endDate")
List<PersistentAuditEvent> findByTimestampAfter(Instant startDate); List<PersistentAuditEvent> findAllByTimestampBetweenForExport(@Param("startDate") Instant startDate, @Param("endDate") Instant endDate);
List<PersistentAuditEvent> findByPrincipalAndType(String principal, String type); @Query("SELECT e FROM PersistentAuditEvent e WHERE e.timestamp > :startDate")
List<PersistentAuditEvent> findByPrincipalAndTimestampBetween(String principal, Instant startDate, Instant endDate); List<PersistentAuditEvent> findByTimestampAfter(@Param("startDate") Instant startDate);
List<PersistentAuditEvent> findByTypeAndTimestampBetween(String type, Instant startDate, Instant endDate); @Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%')) AND e.type = :type")
List<PersistentAuditEvent> findByPrincipalAndTypeAndTimestampBetween(String principal, String type, Instant startDate, Instant endDate); List<PersistentAuditEvent> findAllByPrincipalAndTypeForExport(@Param("principal") String principal, @Param("type") String type);
@Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%')) AND e.timestamp BETWEEN :startDate AND :endDate")
List<PersistentAuditEvent> findAllByPrincipalAndTimestampBetweenForExport(@Param("principal") String principal, @Param("startDate") Instant startDate, @Param("endDate") Instant endDate);
@Query("SELECT e FROM PersistentAuditEvent e WHERE e.type = :type AND e.timestamp BETWEEN :startDate AND :endDate")
List<PersistentAuditEvent> findAllByTypeAndTimestampBetweenForExport(@Param("type") String type, @Param("startDate") Instant startDate, @Param("endDate") Instant endDate);
@Query("SELECT e FROM PersistentAuditEvent e WHERE UPPER(e.principal) LIKE UPPER(CONCAT('%', :principal, '%')) AND e.type = :type AND e.timestamp BETWEEN :startDate AND :endDate")
List<PersistentAuditEvent> findAllByPrincipalAndTypeAndTimestampBetweenForExport(@Param("principal") String principal, @Param("type") String type, @Param("startDate") Instant startDate, @Param("endDate") Instant endDate);
// Cleanup queries // Cleanup queries
@Query("DELETE FROM PersistentAuditEvent e WHERE e.timestamp < ?1") @Query("DELETE FROM PersistentAuditEvent e WHERE e.timestamp < ?1")
@org.springframework.data.jpa.repository.Modifying @Modifying
@org.springframework.transaction.annotation.Transactional @Transactional
int deleteByTimestampBefore(Instant cutoffDate); int deleteByTimestampBefore(Instant cutoffDate);
// Find IDs for batch deletion - using JPQL with setMaxResults instead of native query // Find IDs for batch deletion - using JPQL with setMaxResults instead of native query
@ -55,5 +68,5 @@ public interface PersistentAuditEventRepository
// Get distinct event types for filtering // Get distinct event types for filtering
@Query("SELECT DISTINCT e.type FROM PersistentAuditEvent e ORDER BY e.type") @Query("SELECT DISTINCT e.type FROM PersistentAuditEvent e ORDER BY e.type")
List<Object[]> findDistinctEventTypes(); List<String> findDistinctEventTypes();
} }

View File

@ -0,0 +1,11 @@
package stirling.software.proprietary.security.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to mark endpoints that require an Enterprise license. */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnterpriseEndpoint {}

View File

@ -0,0 +1,30 @@
package stirling.software.proprietary.security.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
@Aspect
@Component
public class EnterpriseEndpointAspect {
private final boolean runningEE;
public EnterpriseEndpointAspect(@Qualifier("runningEE") boolean runningEE) {
this.runningEE = runningEE;
}
@Around(
"@annotation(stirling.software.proprietary.security.config.EnterpriseEndpoint) || @within(stirling.software.proprietary.security.config.EnterpriseEndpoint)")
public Object checkEnterpriseAccess(ProceedingJoinPoint joinPoint) throws Throwable {
if (!runningEE) {
throw new ResponseStatusException(
HttpStatus.FORBIDDEN, "This endpoint requires an Enterprise license");
}
return joinPoint.proceed();
}
}

View File

@ -1,7 +1,7 @@
package stirling.software.proprietary.service; package stirling.software.proprietary.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -19,11 +19,19 @@ import java.util.Map;
*/ */
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor
public class AuditService { public class AuditService {
private final AuditEventRepository repository; private final AuditEventRepository repository;
private final AuditConfigurationProperties auditConfig; private final AuditConfigurationProperties auditConfig;
private final boolean runningEE;
public AuditService(AuditEventRepository repository,
AuditConfigurationProperties auditConfig,
@org.springframework.beans.factory.annotation.Qualifier("runningEE") boolean runningEE) {
this.repository = repository;
this.auditConfig = auditConfig;
this.runningEE = runningEE;
}
/** /**
* Record an audit event for the current authenticated user with a specific audit level * Record an audit event for the current authenticated user with a specific audit level
@ -34,8 +42,8 @@ public class AuditService {
* @param level The minimum audit level required for this event to be logged * @param level The minimum audit level required for this event to be logged
*/ */
public void audit(AuditEventType type, Map<String, Object> data, AuditLevel level) { public void audit(AuditEventType type, Map<String, Object> data, AuditLevel level) {
// Skip auditing if this level is not enabled - check first to avoid further processing // Skip auditing if this level is not enabled or if not Enterprise edition
if (!auditConfig.isEnabled() || !auditConfig.getAuditLevel().includes(level)) { if (!auditConfig.isEnabled() || !auditConfig.getAuditLevel().includes(level) || !runningEE) {
return; return;
} }
@ -65,8 +73,8 @@ public class AuditService {
* @param level The minimum audit level required for this event to be logged * @param level The minimum audit level required for this event to be logged
*/ */
public void audit(String principal, AuditEventType type, Map<String, Object> data, AuditLevel level) { public void audit(String principal, AuditEventType type, Map<String, Object> data, AuditLevel level) {
// Skip auditing if this level is not enabled // Skip auditing if this level is not enabled or if not Enterprise edition
if (!auditConfig.isLevelEnabled(level)) { if (!auditConfig.isLevelEnabled(level) || !runningEE) {
return; return;
} }
@ -95,8 +103,8 @@ public class AuditService {
* @param level The minimum audit level required for this event to be logged * @param level The minimum audit level required for this event to be logged
*/ */
public void audit(String type, Map<String, Object> data, AuditLevel level) { public void audit(String type, Map<String, Object> data, AuditLevel level) {
// Skip auditing if this level is not enabled // Skip auditing if this level is not enabled or if not Enterprise edition
if (!auditConfig.isLevelEnabled(level)) { if (!auditConfig.isLevelEnabled(level) || !runningEE) {
return; return;
} }
@ -126,8 +134,8 @@ public class AuditService {
* @param level The minimum audit level required for this event to be logged * @param level The minimum audit level required for this event to be logged
*/ */
public void audit(String principal, String type, Map<String, Object> data, AuditLevel level) { public void audit(String principal, String type, Map<String, Object> data, AuditLevel level) {
// Skip auditing if this level is not enabled // Skip auditing if this level is not enabled or if not Enterprise edition
if (!auditConfig.isLevelEnabled(level)) { if (!auditConfig.isLevelEnabled(level) || !runningEE) {
return; return;
} }

View File

@ -66,10 +66,6 @@ premium:
proFeatures: proFeatures:
database: true # Enable database features database: true # Enable database features
SSOAutoLogin: false SSOAutoLogin: false
audit:
enabled: true # Enable audit logging
level: 2 # Audit logging level: 0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE
retentionDays: 90 # Number of days to retain audit logs
CustomMetadata: CustomMetadata:
autoUpdateMetadata: false autoUpdateMetadata: false
author: username author: username
@ -80,6 +76,11 @@ premium:
clientId: '' clientId: ''
apiKey: '' apiKey: ''
appId: '' appId: ''
enterpriseFeatures:
audit:
enabled: true # Enable audit logging
level: 2 # Audit logging level: 0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE
retentionDays: 90 # Number of days to retain audit logs
mail: mail:
enabled: false # set to 'true' to enable sending emails enabled: false # set to 'true' to enable sending emails

View File

@ -113,7 +113,7 @@
<span th:text="#{adminUserSettings.usage}">Usage Statistics</span> <span th:text="#{adminUserSettings.usage}">Usage Statistics</span>
</a> </a>
<a href="/audit" th:if="${@runningProOrHigher}" class="data-btn data-btn-secondary" title="Audit Dashboard"> <a href="/audit" th:if="${@runningEE}" class="data-btn data-btn-secondary" title="Audit Dashboard">
<span class="material-symbols-rounded">security</span> <span class="material-symbols-rounded">security</span>
<span>Audit Dashboard</span> <span>Audit Dashboard</span>
</a> </a>