AI changes to reporting
All checks were successful
Build & Deploy Backend / build (push) Successful in 1m28s
Build & Deploy Backend / deploy (push) Successful in 34s

This commit is contained in:
2025-10-29 11:30:20 +08:00
parent 7797da78d0
commit afceca70d9
9 changed files with 89118 additions and 0 deletions

View File

@@ -60,6 +60,10 @@ dependencies {
// Email dependancy for SMTP
implementation 'org.springframework.boot:spring-boot-starter-mail'
// Caching with Caffeine
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'
// Lombok (optional for reducing boilerplate)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager(
"complianceSummary",
"topVulnerabilities",
"vulnerableSoftware"
);
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(15, TimeUnit.MINUTES));
return cacheManager;
}
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(15, TimeUnit.MINUTES);
}
}

View File

@@ -0,0 +1,92 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ComplianceSummaryDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.TopVulnerabilityDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.VulnerableSoftwareDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.service.ReportingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/reporting")
@RequiredArgsConstructor
@Slf4j
public class ReportingController {
private final ReportingService reportingService;
private final ClientRepository clientRepository;
/**
* GET /api/reporting/compliance-summary
* Provides high-level security compliance metrics for executive dashboard
*/
@PreAuthorize("isAuthenticated()")
@GetMapping("/compliance-summary")
public ResponseEntity<?> getComplianceSummary(@AuthenticationPrincipal CurrentUser user) {
try {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
ComplianceSummaryDTO summary = reportingService.getComplianceSummary(client.getClientId());
return ResponseEntity.ok(summary);
} catch (Exception e) {
log.error("Failed to generate compliance summary", e);
return ResponseEntity.internalServerError()
.body(Map.of("error", "Failed to generate compliance report", "code", 500));
}
}
/**
* GET /api/reporting/top-vulnerabilities
* Provides list of most critical vulnerabilities for detailed reporting
*/
@PreAuthorize("isAuthenticated()")
@GetMapping("/top-vulnerabilities")
public ResponseEntity<?> getTopVulnerabilities(@AuthenticationPrincipal CurrentUser user) {
try {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
List<TopVulnerabilityDTO> vulnerabilities = reportingService.getTopVulnerabilities(client.getClientId());
return ResponseEntity.ok(vulnerabilities);
} catch (Exception e) {
log.error("Failed to fetch top vulnerabilities", e);
return ResponseEntity.internalServerError()
.body(Map.of("error", "Failed to generate compliance report", "code", 500));
}
}
/**
* GET /api/reporting/vulnerable-software
* Identifies software packages posing highest security risk
*/
@PreAuthorize("isAuthenticated()")
@GetMapping("/vulnerable-software")
public ResponseEntity<?> getVulnerableSoftware(@AuthenticationPrincipal CurrentUser user) {
try {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
List<VulnerableSoftwareDTO> software = reportingService.getVulnerableSoftware(client.getClientId());
return ResponseEntity.ok(software);
} catch (Exception e) {
log.error("Failed to fetch vulnerable software", e);
return ResponseEntity.internalServerError()
.body(Map.of("error", "Failed to generate compliance report", "code", 500));
}
}
}

View File

@@ -0,0 +1,25 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComplianceSummaryDTO {
private Long totalDevices;
private Long vulnerableDevices;
private Long totalVulnerabilities;
private Long criticalVulns;
private Long highVulns;
private Long mediumVulns;
private Long lowVulns;
private Long totalSoftware;
private Long vulnerableSoftware;
private LocalDateTime lastUpdated;
}

View File

@@ -0,0 +1,18 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TopVulnerabilityDTO {
private String cveId;
private String title;
private String severity;
private Double score;
private Long affectedDevices;
}

View File

@@ -0,0 +1,17 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class VulnerableSoftwareDTO {
private String softwareName;
private Long totalInstances;
private Long vulnerableInstances;
private Long totalCves;
}

View File

@@ -0,0 +1,96 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.TopVulnerabilityDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.VulnerableSoftwareDTO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface ReportingRepository extends JpaRepository<com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices, Long> {
// Compliance Summary Queries
@Query("SELECT COUNT(DISTINCT d.deviceId) FROM Devices d WHERE d.client.clientId = :clientId")
Long countTotalDevices(@Param("clientId") Long clientId);
@Query("SELECT COUNT(DISTINCT cdv.deviceId) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId)")
Long countVulnerableDevices(@Param("clientId") Long clientId);
@Query("SELECT COUNT(cdv) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId)")
Long countTotalVulnerabilities(@Param("clientId") Long clientId);
@Query("SELECT COUNT(cdv) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"AND UPPER(cdv.severity) = 'CRITICAL'")
Long countCriticalVulnerabilities(@Param("clientId") Long clientId);
@Query("SELECT COUNT(cdv) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"AND UPPER(cdv.severity) = 'HIGH'")
Long countHighVulnerabilities(@Param("clientId") Long clientId);
@Query("SELECT COUNT(cdv) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"AND UPPER(cdv.severity) = 'MEDIUM'")
Long countMediumVulnerabilities(@Param("clientId") Long clientId);
@Query("SELECT COUNT(cdv) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"AND UPPER(cdv.severity) = 'LOW'")
Long countLowVulnerabilities(@Param("clientId") Long clientId);
@Query("SELECT COUNT(DISTINCT cis.softwareName) FROM CachedInstalledSoftware cis " +
"WHERE cis.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId)")
Long countTotalSoftware(@Param("clientId") Long clientId);
@Query("SELECT COUNT(DISTINCT cis.softwareName) FROM CachedInstalledSoftware cis " +
"WHERE cis.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"AND cis.totalCves > 0")
Long countVulnerableSoftware(@Param("clientId") Long clientId);
@Query("SELECT MAX(cdv.lastUpdated) FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId)")
LocalDateTime findLastVulnerabilityScanDate(@Param("clientId") Long clientId);
// Top Vulnerabilities Query
@Query("SELECT new com.psg.dlsysinfo.dl_sysinfo_server.dto.TopVulnerabilityDTO(" +
"cdv.cveId, " +
"cdv.description, " +
"cdv.severity, " +
"cdv.score, " +
"COUNT(DISTINCT cdv.deviceId)) " +
"FROM CachedDeviceVuln cdv " +
"WHERE cdv.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"GROUP BY cdv.cveId, cdv.description, cdv.severity, cdv.score " +
"ORDER BY " +
"CASE WHEN UPPER(cdv.severity) = 'CRITICAL' THEN 0 " +
" WHEN UPPER(cdv.severity) = 'HIGH' THEN 1 " +
" WHEN UPPER(cdv.severity) = 'MEDIUM' THEN 2 " +
" WHEN UPPER(cdv.severity) = 'LOW' THEN 3 " +
" ELSE 4 END, " +
"COUNT(DISTINCT cdv.deviceId) DESC")
List<TopVulnerabilityDTO> findTopVulnerabilities(@Param("clientId") Long clientId,
@Param("limit") int limit);
// Vulnerable Software Query
@Query("SELECT new com.psg.dlsysinfo.dl_sysinfo_server.dto.VulnerableSoftwareDTO(" +
"cis.softwareName, " +
"COUNT(cis.id), " +
"SUM(CASE WHEN cis.totalCves > 0 THEN 1 ELSE 0 END), " +
"MAX(COALESCE(cis.totalCves, 0))) " +
"FROM CachedInstalledSoftware cis " +
"WHERE cis.deviceId IN (SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId) " +
"GROUP BY cis.softwareName " +
"ORDER BY (SUM(CASE WHEN cis.totalCves > 0 THEN 1 ELSE 0 END) * 1.0 / COUNT(cis.id) * MAX(COALESCE(cis.totalCves, 0))) DESC")
List<VulnerableSoftwareDTO> findVulnerableSoftware(@Param("clientId") Long clientId,
@Param("limit") int limit);
}

View File

@@ -0,0 +1,78 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ComplianceSummaryDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.TopVulnerabilityDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.VulnerableSoftwareDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ReportingRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Slf4j
public class ReportingService {
private final ReportingRepository reportingRepository;
/**
* Get compliance summary with high-level security metrics
* Cached for 15 minutes due to high computation cost
*/
@Cacheable(value = "complianceSummary", key = "#clientId")
@Transactional(readOnly = true)
public ComplianceSummaryDTO getComplianceSummary(Long clientId) {
log.info("Generating compliance summary for client: {}", clientId);
Long totalDevices = reportingRepository.countTotalDevices(clientId);
Long vulnerableDevices = reportingRepository.countVulnerableDevices(clientId);
Long totalVulnerabilities = reportingRepository.countTotalVulnerabilities(clientId);
Long criticalVulns = reportingRepository.countCriticalVulnerabilities(clientId);
Long highVulns = reportingRepository.countHighVulnerabilities(clientId);
Long mediumVulns = reportingRepository.countMediumVulnerabilities(clientId);
Long lowVulns = reportingRepository.countLowVulnerabilities(clientId);
Long totalSoftware = reportingRepository.countTotalSoftware(clientId);
Long vulnerableSoftware = reportingRepository.countVulnerableSoftware(clientId);
LocalDateTime lastUpdated = reportingRepository.findLastVulnerabilityScanDate(clientId);
return ComplianceSummaryDTO.builder()
.totalDevices(totalDevices != null ? totalDevices : 0L)
.vulnerableDevices(vulnerableDevices != null ? vulnerableDevices : 0L)
.totalVulnerabilities(totalVulnerabilities != null ? totalVulnerabilities : 0L)
.criticalVulns(criticalVulns != null ? criticalVulns : 0L)
.highVulns(highVulns != null ? highVulns : 0L)
.mediumVulns(mediumVulns != null ? mediumVulns : 0L)
.lowVulns(lowVulns != null ? lowVulns : 0L)
.totalSoftware(totalSoftware != null ? totalSoftware : 0L)
.vulnerableSoftware(vulnerableSoftware != null ? vulnerableSoftware : 0L)
.lastUpdated(lastUpdated != null ? lastUpdated : LocalDateTime.now())
.build();
}
/**
* Get top vulnerabilities by severity and affected device count
* Cached for 30 minutes due to moderate update frequency
*/
@Cacheable(value = "topVulnerabilities", key = "#clientId")
@Transactional(readOnly = true)
public List<TopVulnerabilityDTO> getTopVulnerabilities(Long clientId) {
log.info("Fetching top vulnerabilities for client: {}", clientId);
return reportingRepository.findTopVulnerabilities(clientId, 20);
}
/**
* Get vulnerable software packages by risk score
* Cached for 1 hour due to less frequent changes
*/
@Cacheable(value = "vulnerableSoftware", key = "#clientId")
@Transactional(readOnly = true)
public List<VulnerableSoftwareDTO> getVulnerableSoftware(Long clientId) {
log.info("Fetching vulnerable software for client: {}", clientId);
return reportingRepository.findVulnerableSoftware(clientId, 20);
}
}