mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
change to enterprise + LIKE
This commit is contained in:
parent
75cd0ee407
commit
5dc1d0f950
@ -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 {
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
@ -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 {}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user