Initial commit of ld-sysinfo-server backend

This commit is contained in:
2025-09-19 02:09:05 +00:00
commit c0349c106b
1760 changed files with 216243 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
package com.psg.dlsysinfo.dl_sysinfo_server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // ✅ Enables cron-style scheduled tasks across the app
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,12 @@
package com.psg.dlsysinfo.dl_sysinfo_server.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated {
// You can define an attribute here if needed, but not a method
}

View File

@@ -0,0 +1,22 @@
package com.psg.dlsysinfo.dl_sysinfo_server.auth;
import org.springframework.security.core.Authentication;
public class AuthenticatedData {
private final Long groupId;
private final Authentication authentication;
// Constructor
public AuthenticatedData(Long groupId, Authentication authentication) {
this.groupId = groupId;
this.authentication = authentication;
}
public Long getGroupId() {
return groupId;
}
public Authentication getAuthentication() {
return authentication;
}
}

View File

@@ -0,0 +1,34 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hcaptcha")
public class CaptchaConfig {
private boolean enabled;
private String sitekey;
private String secret; // This will not be serialized
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getSitekey() {
return sitekey;
}
public void setSitekey(String sitekey) {
this.sitekey = sitekey;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}

View File

@@ -0,0 +1,36 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class Config {
private MySQLConfig mysql;
private LoggerConfig logger = new LoggerConfig();
private CaptchaConfig hcaptcha = new CaptchaConfig(); // Default values
public MySQLConfig getMysql() {
return mysql;
}
public void setMysql(MySQLConfig mysql) {
this.mysql = mysql;
}
public LoggerConfig getLogger() {
return logger;
}
public void setLogger(LoggerConfig logger) {
this.logger = logger;
}
public CaptchaConfig getHcaptcha() {
return hcaptcha;
}
public void setHcaptcha(CaptchaConfig hcaptcha) {
this.hcaptcha = hcaptcha;
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
public enum LogLevel {
INFO,
WARN,
ERROR;
public String toString() {
switch (this) {
case INFO:
return "info";
case WARN:
return "warn";
case ERROR:
return "error";
default:
return "info"; // Default case
}
}
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "logger")
public class LoggerConfig {
private LogLevel level;
public LogLevel getLevel() {
return level;
}
public void setLevel(LogLevel level) {
this.level = level;
}
}

View File

@@ -0,0 +1,52 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "mysql")
public class MySQLConfig {
private String user;
private String password;
private String database;
private String host;
private int port;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.config;
import org.springframework.stereotype.Component;
@Component
public class PingToggle {
private boolean acceptPings = true;
public boolean isAcceptPings() {
return acceptPings;
}
public void setAcceptPings(boolean acceptPings) {
this.acceptPings = acceptPings;
}
}

View File

@@ -0,0 +1,6 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
public enum ActionType {
ADD,
DELETE
}

View File

@@ -0,0 +1,124 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DeviceDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.service.*;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {
@Autowired
private DeviceService deviceService;
@Autowired
private UserService userService;
@Autowired
private CachedVulnService cachedVulnService;
@Autowired
private CachedSoftwareService cachedSoftwareService;
@Autowired
private VulnerabilityScannerService scannerService;
@Autowired
private NormalizationService normalizationService;
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private CveStatisticsService cveStatisticsService;
@GetMapping("/devices")
public ResponseEntity<List<DeviceDTO>> getAllDevices(@AuthenticationPrincipal CurrentUser user) {
return ResponseEntity.ok(deviceService.getAllDevices());
}
@GetMapping("/devices/client/{clientId}")
public ResponseEntity<List<DeviceDTO>> getDevicesForClient(@PathVariable Long clientId, @AuthenticationPrincipal CurrentUser user) {
return ResponseEntity.ok(deviceService.getDevicesForClient(clientId));
}
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getAllUsersDecrypted(@AuthenticationPrincipal CurrentUser user) {
return ResponseEntity.ok(userService.getAllDecryptedUsers());
}
@PutMapping("/users/{userId}/enabled")
public ResponseEntity<Void> setUserEnabled(
@PathVariable Long userId,
@RequestParam boolean enabled,
@AuthenticationPrincipal CurrentUser user
) {
userService.setUserEnabledState(userId, enabled);
return ResponseEntity.ok().build();
}
@Transactional
@PostMapping("/vulns/refresh-cache")
public ResponseEntity<String> triggerVulnRefresh(@AuthenticationPrincipal CurrentUser user) {
String summary = cachedVulnService.refreshVulnerabilityCacheWithSummary();
return ResponseEntity.ok("✅ Scan complete:\n" + summary);
}
@PostMapping("/software/refresh-cache")
public ResponseEntity<String> triggerSoftwareCacheRefresh(@AuthenticationPrincipal CurrentUser user) {
String summary = cachedSoftwareService.refreshSoftwareCacheWithSummary();
return ResponseEntity.ok("✅ Installed software cache refresh complete:\n" + summary);
}
@PostMapping("/software/normalize")
public ResponseEntity<String> normalizeSoftwareEntries(@AuthenticationPrincipal CurrentUser user) {
int updated = normalizationService.normalizeInstalledSoftware();
return ResponseEntity.ok("✅ Normalized " + updated + " installed software records.");
}
@GetMapping("/statistics")
public ResponseEntity<Map<String, Object>> getCveStats() {
try {
Map<String, Object> stats = jdbcTemplate.queryForMap("SELECT * FROM cve_statistics ORDER BY last_updated DESC LIMIT 1");
return ResponseEntity.ok(stats);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
@PostMapping("/statistics/refresh")
public ResponseEntity<String> refreshCveStatistics() {
try {
cveStatisticsService.refreshStatistics();
return ResponseEntity.ok("✅ CVE statistics refreshed successfully.");
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(500).body("❌ Failed to refresh CVE statistics.");
}
}
}

View File

@@ -0,0 +1,167 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.AuthRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ChangePasswordRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.LoginResponse;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;
private final UserService userAuthService;
private final EncryptionService encryptionService;
@Autowired
private PasswordEncoder passwordEncoder;
public AuthController(AuthenticationManager authenticationManager,
JwtUtil jwtUtil,
UserService userAuthService,
EncryptionService encryptionService,
Environment environment) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
this.userAuthService = userAuthService;
this.encryptionService = encryptionService;
}
private static String stripBOM(String s) {
if (s.startsWith("\uFEFF")) {
return s.substring(1);
}
return s;
}
private String resolveCookieDomain(HttpServletRequest request) {
String host = request.getHeader("Host");
System.out.println("🔍 Host: " + host);
if (host != null && host.contains("localhost")) {
return "localhost";
}
return ".psg.net.au";
}
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@RequestBody AuthRequest encryptedRequest,
HttpServletRequest request,
HttpServletResponse response) {
try {
String username = encryptionService.decryptData(encryptedRequest.getUsername()).trim();
String password = encryptionService.decryptData(encryptedRequest.getPassword()).trim();
password = stripBOM(password).replaceAll("[^\\x20-\\x7E]", "").trim();
UserAuth user = userAuthService.findByUsername(username);
if (user == null) {
return ResponseEntity.status(401).body(Map.of("error", "User not found"));
}
boolean passwordMatches = passwordEncoder.matches(password, user.getPasswordHash());
if (!passwordMatches) {
return ResponseEntity.status(401).body(Map.of("error", "Invalid password"));
}
if (user.getClient() == null) {
return ResponseEntity.status(401).body(Map.of("error", "User is not linked to any client"));
}
String decryptedDisplayName = user.getDisplayNameHash() != null
? encryptionService.decryptData(user.getDisplayNameHash())
: "";
List<String> roles = List.of(user.getRole());
System.out.printf("✅ Assigning roles %s to user %s%n", roles, username);
String token = jwtUtil.generateToken(
username,
decryptedDisplayName,
user.getClient().getClientIdentifier(),
user.getId(),
roles
);
// Resolve domain and use SameSite=None always now that were on HTTPS real origins
String cookieDomain = resolveCookieDomain(request);
ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from("authToken", token)
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("None") // ✅ Always set to None for frontend-to-backend fetches
.maxAge(60 * 60); // 1 hour
if (!"localhost".equals(cookieDomain)) {
cookieBuilder.domain(cookieDomain);
}
ResponseCookie cookie = cookieBuilder.build();
System.out.println("🧁 Set-Cookie: " + cookie.toString());
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
return ResponseEntity.ok(new LoginResponse(token, user.getUsername(), user.getId()));
} catch (Exception e) {
return ResponseEntity.status(401).body(Map.of("error", "Invalid credentials or decryption error"));
}
}
@PostMapping("/logout")
public ResponseEntity<Map<String, String>> logout(HttpServletRequest request,
HttpServletResponse response) {
SecurityContextHolder.clearContext();
ResponseCookie expiredCookie = ResponseCookie.from("authToken", "")
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("None")
.maxAge(0)
.domain(resolveCookieDomain(request))
.build();
response.addHeader(HttpHeaders.SET_COOKIE, expiredCookie.toString());
return ResponseEntity.ok(Map.of("message", "Logged out successfully"));
}
@PutMapping("/change-password")
public ResponseEntity<?> changePassword(@RequestBody ChangePasswordRequest request,
@AuthenticationPrincipal CurrentUser user) {
boolean success = userAuthService.changePassword(user.getUsername(), request);
if (success) {
return ResponseEntity.ok("Password changed successfully");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Incorrect current password");
}
}
}

View File

@@ -0,0 +1,169 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.*;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedInstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CachedDeviceVulnRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CachedInstalledSoftwareRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.DevicesRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.service.CachedVulnService;
import lombok.RequiredArgsConstructor;
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.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/cached")
@RequiredArgsConstructor
public class CacheController {
private final DevicesRepository devicesRepository;
private final CachedDeviceVulnRepository cachedRepo;
private final EncryptionService encryptionService;
private final CachedVulnService cachedVulnService;
private final CachedInstalledSoftwareRepository cachedSoftwareRepo;
@GetMapping("/devices/with-vulns")
public DeviceWithCveResponse getDevicesWithCachedVulns(@AuthenticationPrincipal CurrentUser user) {
if (user == null) {
throw new IllegalArgumentException("Authenticated user context is missing");
}
List<Devices> devices = devicesRepository.findByClient_ClientIdentifier(user.getClientIdentifier());
return buildDeviceWithCveResponse(devices);
}
@GetMapping("/devices/with-vulns/all")
public DeviceWithCveResponse getAllDevicesWithCachedVulns(@AuthenticationPrincipal CurrentUser user) {
if (user == null) {
throw new IllegalArgumentException("Authenticated user context is missing");
}
if (!user.hasRole("ADMIN")) {
throw new SecurityException("Access denied: ADMIN role required");
}
List<Devices> devices = devicesRepository.findAll();
return buildDeviceWithCveResponse(devices);
}
private DeviceWithCveResponse buildDeviceWithCveResponse(List<Devices> devices) {
List<DetailedDeviceDTO> detailedDevices = devices.stream().map(device -> {
DetailedDeviceDTO dto = new DetailedDeviceDTO();
dto.setDeviceId(device.getDeviceId());
dto.setLastCheckedIn(device.getLastCheckedIn());
dto.setOsName(device.getOsName());
dto.setOsVersion(device.getOsVersion());
dto.setWindowsVersion(device.getWindowsVersion());
dto.setWindowsBuild(device.getWindowsBuild());
dto.setOsArchitecture(device.getOsArchitecture());
dto.setProcessorName(device.getProcessorName());
dto.setProcessorArchitecture(device.getProcessorArchitecture());
dto.setGpuNames(device.getGpuName() != null
? Arrays.stream(device.getGpuName().split(",")).map(String::trim).toList()
: Collections.emptyList());
dto.setTotalMemory(device.getTotalMemory());
dto.setLastBootTime(device.getLastBootTime());
try {
dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
if (device.getClient() != null && device.getClient().getClientNameEncrypted() != null) {
dto.setClientName(encryptionService.decryptData(device.getClient().getClientNameEncrypted()));
}
} catch (Exception e) {
dto.setHostname("DECRYPTION_FAILED");
dto.setClientName("DECRYPTION_FAILED");
}
dto.setDrives(device.getDrives().stream().map(d ->
new DriveInfoDTO(d.getName(), d.getDriveType(), d.getTotalSizeGB(), d.getFreeSpaceGB()))
.toList());
dto.setInstalledApplications(device.getInstalledApplications().stream().map(app ->
new InstalledAppDTO(app.getAppName(), app.getAppVersion(), app.getPublisher()))
.toList());
dto.setIpAddresses(device.getIpAddresses().stream().map(ip ->
new IpAddressDTO(ip.getInterfaceName(), ip.getIpAddress(), ip.getMacAddress()))
.toList());
return dto;
}).toList();
Map<Long, List<DeviceVulnerabilityDTO>> vulnMap = new HashMap<>();
for (DetailedDeviceDTO device : detailedDevices) {
List<DeviceVulnerabilityDTO> vulns = cachedRepo.findByDeviceId(device.getDeviceId()).stream()
.map(v -> new DeviceVulnerabilityDTO(
v.getCveId(), // ✅ CVE ID
v.getDescription(), // ✅ Description
v.getSeverity(), // ✅ Severity
v.getScore(), // ✅ Score
v.getLastModified() != null ? v.getLastModified().toString() : null, // ✅ Last Modified
v.getLastModified() != null ? v.getLastModified().toString() : null // ✅ Also use for Published
))
.toList();
vulnMap.put(device.getDeviceId(), vulns);
}
return new DeviceWithCveResponse(detailedDevices, vulnMap);
}
@GetMapping("/software/summary")
public List<CachedInstalledSoftware> getSoftwareSummaryForClient(@AuthenticationPrincipal CurrentUser user) {
if (user == null) {
throw new IllegalArgumentException("Authenticated user context is missing");
}
List<CachedInstalledSoftware> entries;
if (user.hasRole("ADMIN")) {
System.out.println("🔓 Admin fetching all cached installed software");
entries = cachedSoftwareRepo.findAll();
} else {
List<Long> deviceIds = devicesRepository.findByClient_ClientIdentifier(user.getClientIdentifier())
.stream()
.map(Devices::getDeviceId)
.toList();
System.out.println("🔐 User fetching cached software for devices: " + deviceIds);
entries = cachedSoftwareRepo.findAll().stream()
.filter(entry -> deviceIds.contains(entry.getDeviceId()))
.toList();
}
// ✨ Decrypt using encryptedHostname instead of trying to decrypt the hash
List<CachedInstalledSoftware> decryptedEntries = entries.stream()
.map(entry -> {
try {
// Decrypt using encryptedHostname
if (entry.getEncryptedHostname() != null) {
entry.setHostname(encryptionService.decryptData(entry.getEncryptedHostname()));
} else {
entry.setHostname("MISSING_ENCRYPTED_HOSTNAME");
}
} catch (Exception e) {
entry.setHostname("DECRYPTION_FAILED");
}
return entry;
})
.toList();
return decryptedEntries;
}
}

View File

@@ -0,0 +1,35 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DemoDeviceRequestDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.service.DemoDeviceGeneratorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
@RestController
@RequestMapping("/api/system/devices/demo")
public class DemoController {
@Autowired
private DemoDeviceGeneratorService demoDeviceGeneratorService;
@PostMapping
public ResponseEntity<?> createDemoDevice(@RequestBody DemoDeviceRequestDTO request) {
try {
Devices demoDevice = demoDeviceGeneratorService.generateDemoDeviceForClient(request.getClientId());
return ResponseEntity.ok(demoDevice);
} catch (Exception e) {
System.err.println("❌ Failed to generate demo device: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.status(500).body("Failed to generate demo device");
}
}
}

View File

@@ -0,0 +1,33 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.config.PingToggle;
import org.springframework.http.HttpStatus;
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.*;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class HealthCheckController {
private final PingToggle pingToggle;
public HealthCheckController(PingToggle pingToggle) {
this.pingToggle = pingToggle;
}
@GetMapping("/system/ping-status")
public ResponseEntity<Map<String, Boolean>> pingStatus() {
return ResponseEntity.ok(Map.of("acceptPings", pingToggle.isAcceptPings()));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/admin/toggle-ping")
public ResponseEntity<String> togglePing(@RequestParam boolean enabled, @AuthenticationPrincipal Object user) {
pingToggle.setAcceptPings(enabled);
return ResponseEntity.ok("Ping is now " + (enabled ? "ENABLED" : "DISABLED"));
}
}

View File

@@ -0,0 +1,40 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.service.DeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/admin/manage")
@PreAuthorize("hasRole('ADMIN')")
public class ManagementController {
@Autowired
private DeviceService deviceService;
@DeleteMapping("/devices/{deviceId}")
public ResponseEntity<?> deleteDevice(@PathVariable Long deviceId) {
try {
deviceService.deleteDeviceAndChildren(deviceId); // Implement this in your service
return ResponseEntity.ok("✅ Device deleted successfully");
} catch (Exception e) {
return ResponseEntity.status(500).body("❌ Failed to delete device: " + e.getMessage());
}
}
@PutMapping("/devices/{deviceId}/client")
public ResponseEntity<?> updateDeviceClient(
@PathVariable Long deviceId,
@RequestBody Map<String, Long> body) {
Long newClientId = body.get("clientId");
deviceService.reassignClient(deviceId, newClientId);
return ResponseEntity.ok("Client updated");
}
// Other create/delete endpoints can go here
}

View File

@@ -0,0 +1,52 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Cve;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.CveDTO;
import org.springframework.data.domain.PageImpl;
import java.util.stream.Collectors;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CveRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.service.VulnerabilityScannerService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/public/vulnerabilities")
public class PublicVulnerabilityController {
private final CveRepository cveRepository;
public PublicVulnerabilityController(CveRepository cveRepository) {
this.cveRepository = cveRepository;
}
@GetMapping
public ResponseEntity<Page<CveDTO>> searchPublicCVEs(
@RequestParam(required = false) String search,
@PageableDefault(size = 25, sort = "lastModifiedDate", direction = Sort.Direction.DESC)
Pageable pageable
) {
Page<Cve> cvePage = cveRepository.findFiltered(search, pageable);
Page<CveDTO> dtoPage = new PageImpl<>(
cvePage.getContent().stream()
.filter(cve -> {
String desc = cve.getDescription();
return desc == null || !desc.trim().equalsIgnoreCase("Rejected reason: Not used");
})
.map(CveDTO::new)
.collect(Collectors.toList()),
pageable,
cvePage.getTotalElements()
);
return ResponseEntity.ok(dtoPage);
}
}

View File

@@ -0,0 +1,73 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ClientDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ClientRegisterRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserRegisterRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.service.ClientService;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class RegisterController {
@Autowired
private ClientService clientService;
@Autowired
private UserService userService;
@Autowired
private ClientRepository clientRepository;
@Autowired
private EncryptionService encryptionService;
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/clients")
public ResponseEntity<List<ClientDTO>> getAllClients() {
List<ClientDTO> clients = clientRepository.findAll().stream()
.map(client -> {
String decryptedName;
try {
decryptedName = encryptionService.decryptData(client.getClientNameEncrypted());
} catch (Exception e) {
decryptedName = "[Decryption failed]";
}
return new ClientDTO(client.getClientId(), client.getClientIdentifier(), decryptedName);
})
.toList();
return ResponseEntity.ok(clients);
}
@PostMapping("/register/client")
public ResponseEntity<?> registerClient(@RequestBody ClientRegisterRequest request) {
Client client = clientService.registerClient(request.getClientName());
return ResponseEntity.ok(Map.of(
"clientId", client.getClientId(),
"clientIdentifier", client.getClientIdentifier()
));
}
@PostMapping("/register/user")
public ResponseEntity<?> registerUser(@RequestBody UserRegisterRequest request) {
UserAuth user = userService.registerUser(
request.getUsername(),
request.getPassword(),
request.getRole(),
request.getClientId()
);
return ResponseEntity.ok(Map.of("userId", user.getId()));
}
}

View File

@@ -0,0 +1,287 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import org.springframework.beans.factory.annotation.Value;
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.*;
import org.springframework.http.MediaType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Map;
@RestController
@RequestMapping("/api/admin/scripts")
public class ScriptController {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String dbUser;
@Value("${spring.datasource.password}")
private String dbPass;
@Value("${nvd.api.key}")
private String apiKey;
@Value("${nvd.max-range-days:7}")
private String nvdMaxRangeDays;
private final File cveLogFile = new File("scripts/cve-sync.log");
private final File kevLogFile = new File("scripts/kev-sync.log");
private final File msrcLogFile = new File("scripts/msrc-sync.log");
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-cve")
public ResponseEntity<String> runCveScript(@AuthenticationPrincipal Object user) {
return triggerScript("fetchCVE.js", "📡 CVE sync launched in background.", cveLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-kev")
public ResponseEntity<String> runKevScript(@AuthenticationPrincipal Object user) {
return triggerScript("fetchKEV.js", "📡 KEV sync launched in background.", kevLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-msrc")
public ResponseEntity<String> runMsrcScript(@AuthenticationPrincipal Object user) {
return triggerScript("enrichCVE_MSRC.js", "📡 MSRC sync launched in background.", msrcLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/fetch-cve/logs")
public ResponseEntity<String> fetchLogs(@AuthenticationPrincipal Object user) {
return readLogs(cveLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-cve/clear-logs")
public ResponseEntity<String> clearLogs(@AuthenticationPrincipal Object user) {
return clearLogs(cveLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-kev/clear-logs")
public ResponseEntity<String> clearKevLogs(@AuthenticationPrincipal Object user) {
return clearLogs(kevLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/fetch-msrc/clear-logs")
public ResponseEntity<String> clearMsrcLogs(@AuthenticationPrincipal Object user) {
return clearLogs(msrcLogFile);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping(value = "/fetch-cve/logs/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamLogs(@AuthenticationPrincipal Object user) {
Path logFile = Paths.get("scripts/cve-sync.log");
return Flux.interval(Duration.ofSeconds(1))
.map(tick -> {
try {
byte[] rawBytes = Files.readAllBytes(logFile);
String content = new String(rawBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
return content.isEmpty() ? "📭 No log output yet." : content;
} catch (IOException e) {
return "❌ Failed to read logs: " + e.getMessage();
}
});
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping(value = "/fetch-kev/logs/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamKevLogs(@AuthenticationPrincipal Object user) {
Path logFile = Paths.get("scripts/kev-sync.log");
return Flux.<String>create(emitter -> {
final long[] lastKnownPosition = {0};
emitter.onRequest(n -> {
try {
if (!Files.exists(logFile)) {
emitter.next("📭 Log file not found yet.");
return;
}
long fileSize = Files.size(logFile);
if (fileSize > lastKnownPosition[0]) {
try (RandomAccessFile raf = new RandomAccessFile(logFile.toFile(), "r")) {
raf.seek(lastKnownPosition[0]);
byte[] newBytes = new byte[(int) (fileSize - lastKnownPosition[0])];
raf.readFully(newBytes);
String newContent = new String(newBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
emitter.next(newContent); // 👈 perfectly legal now
lastKnownPosition[0] = fileSize;
}
}
} catch (Exception e) {
emitter.error(e);
}
});
emitter.onDispose(() -> {
System.out.println("Client disconnected from KEV stream.");
});
}).delayElements(Duration.ofSeconds(1));
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping(value = "/fetch-msrc/logs/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamMsrcLogs(@AuthenticationPrincipal Object user) {
Path logFile = Paths.get("scripts/msrc-sync.log");
return Flux.interval(Duration.ofSeconds(1))
.map(tick -> {
try {
byte[] rawBytes = Files.readAllBytes(logFile);
String content = new String(rawBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
return content.isEmpty() ? "📭 No log output yet." : content;
} catch (IOException e) {
return "❌ Failed to read logs: " + e.getMessage();
}
});
}
private ResponseEntity<String> triggerScript(String scriptName, String message, File targetLogFile) {
File scriptFile = new File("scripts", scriptName);
if (!scriptFile.exists()) {
return ResponseEntity.status(404).body("" + scriptName + " not found at: " + scriptFile.getAbsolutePath());
}
runNodeScript(scriptName, message, targetLogFile);
return ResponseEntity.ok("🚀 " + message);
}
private void runNodeScript(String scriptName, String startMessage, File logTarget) {
File scriptDir = new File("scripts");
File scriptFile = new File(scriptDir, scriptName);
if (!scriptFile.exists()) {
appendLog("" + scriptName + " not found at: " + scriptFile.getAbsolutePath(), logTarget);
return;
}
new Thread(() -> {
try (PrintWriter logWriter = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(logTarget, true), StandardCharsets.UTF_8), true)) {
logWriter.println(formatNow() + " 🚀 " + startMessage);
ProcessBuilder builder = new ProcessBuilder("node", scriptName);
builder.directory(scriptDir);
builder.redirectErrorStream(true);
Map<String, String> env = builder.environment();
env.put("DB_HOST", extractHost(dbUrl));
env.put("DB_NAME", extractDbName(dbUrl));
env.put("DB_USER", dbUser);
env.put("DB_PASSWORD", dbPass);
env.put("NVD_API_KEY", apiKey);
env.put("NVD_MAX_RANGE_DAYS", nvdMaxRangeDays);
env.put("NODE_OPTIONS", "--no-warnings --enable-source-maps");
env.put("LC_ALL", "en_US.UTF-8");
env.put("LANG", "en_US.UTF-8");
env.put("LANGUAGE", "en_US:en");
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
logWriter.println(line);
}
}
int exitCode = process.waitFor();
logWriter.println(formatNow() + "" + scriptName + " finished with exit code: " + exitCode);
} catch (Exception e) {
appendLog("" + scriptName + " error: " + e.getMessage(), logTarget);
}
}).start();
}
private String extractHost(String url) {
return url.replace("jdbc:mysql://", "").split(":")[0].split("/")[0];
}
private String extractDbName(String url) {
return url.substring(url.lastIndexOf("/") + 1).split("\\?")[0];
}
private void appendLog(String message, File logTarget) {
try (PrintWriter logWriter = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(logTarget, true), StandardCharsets.UTF_8), true)) {
logWriter.println(formatNow() + " " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
private ResponseEntity<String> readLogs(File logTarget) {
if (!logTarget.exists()) {
return ResponseEntity.ok("📭 No log file found yet.");
}
try {
byte[] rawBytes = Files.readAllBytes(logTarget.toPath());
String content = new String(rawBytes, StandardCharsets.UTF_8)
.replace("\r\n", "\n")
.replace("\r", "\n")
.replace("\uFEFF", "");
return ResponseEntity.ok(content.isEmpty() ? "📭 Log is empty." : content);
} catch (IOException e) {
return ResponseEntity.status(500).body("❌ Error reading logs: " + e.getMessage());
}
}
private ResponseEntity<String> clearLogs(File logTarget) {
try {
if (logTarget.exists()) {
Files.delete(logTarget.toPath());
}
return ResponseEntity.ok("🧹 Logs cleared.");
} catch (IOException e) {
return ResponseEntity.status(500).body("❌ Failed to clear logs: " + e.getMessage());
}
}
private String formatNow() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd LLL yyyy, hh:mm:ss a", Locale.ENGLISH)
.withZone(ZoneId.systemDefault());
return "[" + formatter.format(java.time.ZonedDateTime.now()).replace("AM", "am").replace("PM", "pm") + "]";
}
}

View File

@@ -0,0 +1,45 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.SoftwareSummaryDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.service.SoftwareService;
import org.springframework.beans.factory.annotation.Autowired;
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 com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/software")
public class SoftwareController {
@Autowired
private SoftwareService softwareService;
@Autowired
private ClientRepository clientRepository;
@GetMapping("/summary")
public List<SoftwareSummaryDTO> getSoftwareOverview(@AuthenticationPrincipal CurrentUser user) {
if (user.hasRole("ADMIN")) {
System.out.println("🔓 Admin override: fetching software for all clients");
return softwareService.getSoftwareOverviewForAllClients(); // ⬅️ implement this
}
Client client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
return softwareService.getSoftwareOverviewForClient(client.getClientId());
}
}

View File

@@ -0,0 +1,140 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.InstalledAppDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.SystemInfoDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil;
import com.psg.dlsysinfo.dl_sysinfo_server.security.TokenResolver;
import com.psg.dlsysinfo.dl_sysinfo_server.service.DeviceService;
import com.psg.dlsysinfo.dl_sysinfo_server.util.NormalizationUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
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.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class SystemInfoController {
private final JwtUtil jwtUtil;
private final EncryptionService encryptionService;
private final TokenResolver tokenResolver;
public SystemInfoController(JwtUtil jwtUtil, EncryptionService encryptionService, TokenResolver tokenResolver) {
this.jwtUtil = jwtUtil;
this.encryptionService = encryptionService;
this.tokenResolver = tokenResolver;
System.out.println("🛠️ SystemInfoController created");
}
@Autowired private ClientRepository clientRepository;
@Autowired private InstalledSoftwareRepository installedSoftwareRepository;
@Autowired private DeviceService deviceService;
@Autowired private ObjectMapper objectMapper;
@PreAuthorize("isAuthenticated()")
@PostMapping("/system-info")
public ResponseEntity<Map<String, String>> receiveEncryptedData(
@AuthenticationPrincipal Object currentUser,
HttpServletRequest request,
@RequestBody Map<String, String> payload) {
Map<String, String> response = new HashMap<>();
try {
String token = tokenResolver.resolveToken(request);
if (!jwtUtil.validateToken(token)) {
return errorResponse(response, "Invalid or expired token.", 403);
}
String encryptedData = payload.get("data");
if (encryptedData == null || encryptedData.isEmpty()) {
return errorResponse(response, "No data found in request body.", 400);
}
String decryptedData = encryptionService.decryptData(encryptedData);
System.out.println("🔓 Decrypted payload size: " + decryptedData.length());
SystemInfoDTO dto;
try {
dto = objectMapper.readValue(decryptedData, SystemInfoDTO.class);
} catch (Exception ex) {
System.out.println("❌ Failed to deserialize SystemInfoDTO: " + ex.getMessage());
ex.printStackTrace();
return errorResponse(response, "Failed to parse payload.", 400);
}
if (dto.getClientIdentifier() == null || dto.getHostname() == null) {
return errorResponse(response, "Missing clientIdentifier or hostname in payload.", 400);
}
String clientIdentifier = dto.getClientIdentifier();
String hostname = dto.getHostname();
String hashedHostname = encryptionService.hashString(hostname);
Client client = clientRepository.findByClientIdentifier(clientIdentifier)
.orElseThrow(() -> new IllegalArgumentException("Client not registered"));
Devices device = deviceService.findOrCreateDevice(hostname, hashedHostname, client);
System.out.println("🧩 System Info: OS = " + dto.getOsName() + ", GPU = " + dto.getGpuNames());
deviceService.populateDeviceFromDTO(device, dto);
System.out.println("💾 Saving device: " + objectMapper.writeValueAsString(device));
deviceService.saveDevice(device);
List<InstalledAppDTO> installedApplications = dto.getInstalledApplications();
if (installedApplications != null) {
for (InstalledAppDTO app : installedApplications) {
if (app.getApp_name() == null) continue;
InstalledSoftware software = installedSoftwareRepository
.findByDeviceAndAppName(device, app.getApp_name())
.orElseGet(() -> {
InstalledSoftware newSoftware = new InstalledSoftware();
newSoftware.setDevice(device);
newSoftware.setAppName(app.getApp_name());
return newSoftware;
});
software.setAppVersion(app.getApp_version());
software.setPublisher(app.getPublisher());
software.setNormalizedAppName(NormalizationUtils.normalize(app.getApp_name()));
software.setNormalizedPublisher(NormalizationUtils.simplifyPublisher(app.getPublisher()));
software.setNormalizedProduct(NormalizationUtils.normalizeProduct(app.getApp_name()));
software.setLastUpdated(LocalDateTime.now());
installedSoftwareRepository.save(software);
}
}
response.put("status", "success");
response.put("message", "Data received and stored successfully.");
return ResponseEntity.ok(response);
} catch (Exception e) {
System.out.println("❌ Unexpected exception: " + e.getMessage());
e.printStackTrace();
return errorResponse(response, "Server error occurred: " + e.getMessage(), 500);
}
}
private ResponseEntity<Map<String, String>> errorResponse(Map<String, String> response, String message, int statusCode) {
response.put("status", "error");
response.put("message", message);
return ResponseEntity.status(statusCode).body(response);
}
}

View File

@@ -0,0 +1,88 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DetailedDeviceDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DeviceDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DeviceVulnerabilityDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DeviceWithCveResponse;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.DevicesRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.service.DeviceService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
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.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/user/devices")
@RequiredArgsConstructor
public class UserDeviceController {
private final DeviceService deviceService;
private final ClientRepository clientRepository;
private final DevicesRepository devicesRepository;
@PreAuthorize("isAuthenticated()")
@GetMapping
public ResponseEntity<List<DeviceDTO>> getDevicesForAuthenticatedClient(@AuthenticationPrincipal CurrentUser user) {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
List<DeviceDTO> devices = deviceService.getDevicesForClient(client.getClientId());
return ResponseEntity.ok(devices);
}
@PreAuthorize("isAuthenticated()")
@GetMapping("/{deviceId}")
public ResponseEntity<DetailedDeviceDTO> getDeviceDetailsForClient(
@PathVariable Long deviceId,
@AuthenticationPrincipal CurrentUser user) {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
var device = devicesRepository.findById(deviceId)
.orElseThrow(() -> new RuntimeException("Device not found"));
if (!device.getClient().getClientId().equals(client.getClientId())) {
throw new RuntimeException("Unauthorized access to device.");
}
var detailedDevice = deviceService.getDeviceDetails(deviceId);
return ResponseEntity.ok(detailedDevice);
}
@PreAuthorize("isAuthenticated()")
@GetMapping("/with-vulns")
public ResponseEntity<DeviceWithCveResponse> getDevicesWithVulnerabilities(@AuthenticationPrincipal CurrentUser user) {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
List<DeviceDTO> devices = deviceService.getDevicesForClient(client.getClientId());
Map<Long, List<DeviceVulnerabilityDTO>> vulnMap = deviceService
.getVulnerabilitiesGroupedByDevice(client.getClientId())
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().stream()
.map(v -> new DeviceVulnerabilityDTO(
v.getCveId(), // ✅ correct method name
v.getDescription(), // ✅
v.getSeverity(), // ✅
v.getScore(), // ✅ instead of getCvssScore()
null, // ❗ Published date: not available in CveMatchResult, so set null
v.getLastModifiedDate() != null ? v.getLastModifiedDate().toString() : null // ✅ lastModifiedDate
))
.toList()
));
return ResponseEntity.ok(new DeviceWithCveResponse(devices, vulnMap));
}
}

View File

@@ -0,0 +1,84 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserProfileDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
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.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserProfileController {
private final UserService userAuthService;
private final EncryptionService encryptionService;
private final JwtUtil jwtUtil;
@PreAuthorize("isAuthenticated()")
@GetMapping("/profile")
public ResponseEntity<?> getUserProfile(@AuthenticationPrincipal CurrentUser user) {
try {
UserAuth userAuth = userAuthService.findByUsername(user.getUsername());
UserProfileDTO profileDto = new UserProfileDTO(
userAuth.getUsername(),
encryptionService.decryptData(userAuth.getDisplayNameHash()),
encryptionService.decryptData(userAuth.getFirstNameHash()),
encryptionService.decryptData(userAuth.getLastNameHash()),
encryptionService.decryptData(userAuth.getEmailHash())
);
return ResponseEntity.ok(profileDto);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to decrypt user data");
}
}
@PreAuthorize("isAuthenticated()")
@PostMapping("/profile")
public ResponseEntity<?> updateUserProfile(@AuthenticationPrincipal CurrentUser user,
@RequestBody UserProfileDTO profileDto) {
try {
UserAuth userAuth = userAuthService.findByUsername(user.getUsername());
userAuth.setDisplayNameHash(encryptionService.encryptData(profileDto.getDisplayName()));
userAuth.setFirstNameHash(encryptionService.encryptData(profileDto.getFirstName()));
userAuth.setLastNameHash(encryptionService.encryptData(profileDto.getLastName()));
userAuth.setEmailHash(encryptionService.encryptData(profileDto.getEmail()));
userAuthService.save(userAuth);
// ✅ Rebuild token including roles
String decryptedDisplayName = encryptionService.decryptData(userAuth.getDisplayNameHash());
List<String> roles = List.of(userAuth.getRole());
String newToken = jwtUtil.generateToken(
userAuth.getUsername(),
decryptedDisplayName,
userAuth.getClient().getClientIdentifier(),
userAuth.getId(),
roles
);
return ResponseEntity.ok(Map.of(
"message", "Profile updated successfully",
"token", newToken
));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to encrypt or update user data");
}
}
}

View File

@@ -0,0 +1,67 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Cve;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CveRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.DevicesRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.CurrentUser;
import com.psg.dlsysinfo.dl_sysinfo_server.service.VulnerabilityScannerService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
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.*;
import java.util.List;
@RestController
@RequestMapping("/api/user/vulnerabilities")
@RequiredArgsConstructor
public class UserVulnerabilityController {
private final VulnerabilityScannerService vulnerabilityScannerService;
private final CveRepository cveRepository;
private final DevicesRepository devicesRepository;
private final ClientRepository clientRepository;
@PreAuthorize("isAuthenticated()")
@GetMapping("/{deviceId}")
public ResponseEntity<Page<CveMatchResult>> getDeviceVulnerabilities(
@PathVariable Long deviceId,
@AuthenticationPrincipal CurrentUser user,
@PageableDefault(size = 25, sort = "score", direction = Sort.Direction.DESC) Pageable pageable
) {
var client = clientRepository.findByClientIdentifier(user.getClientIdentifier())
.orElseThrow(() -> new RuntimeException("Client not found"));
var device = devicesRepository.findById(deviceId)
.orElseThrow(() -> new RuntimeException("Device not found"));
if (!device.getClient().getClientId().equals(client.getClientId())) {
throw new RuntimeException("Unauthorized access to device.");
}
List<CveMatchResult> all = vulnerabilityScannerService.getVulnerabilitiesForDevice(deviceId);
int start = (int) pageable.getOffset();
int end = Math.min((start + pageable.getPageSize()), all.size());
List<CveMatchResult> paged = all.subList(start, end);
return ResponseEntity.ok(new PageImpl<>(paged, pageable, all.size()));
}
@PreAuthorize("isAuthenticated()")
@GetMapping
public ResponseEntity<Page<Cve>> getPaginatedCves(
@RequestParam(required = false) String severity,
@RequestParam(required = false) String search,
@PageableDefault(size = 25, sort = "lastModifiedDate", direction = Sort.Direction.DESC) Pageable pageable
) {
return ResponseEntity.ok(cveRepository.findFiltered(search, pageable));
}
}

View File

@@ -0,0 +1,50 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.DeviceVulnerabilityDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Cve;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CveRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/vuln/cves")
@RequiredArgsConstructor
public class VulnerabilityController {
private final CveRepository cveRepository;
@GetMapping("/lookup")
public ResponseEntity<List<DeviceVulnerabilityDTO>> lookupCves(
@RequestParam List<String> cveIds
) {
List<Cve> cves = cveRepository.findAllById(cveIds);
List<DeviceVulnerabilityDTO> results = cves.stream()
.map(cve -> new DeviceVulnerabilityDTO(
cve.getId(),
cve.getDescription(), // 👉 using description as title
cve.getSeverity(),
cve.getCvssScore(),
cve.getPublishedDate(), // 📅 pull publishedDate
cve.getLastModifiedDate() // 🔥 pull lastModifiedDate
))
.toList();
return ResponseEntity.ok(results);
}
@GetMapping("/all")
public ResponseEntity<Page<Cve>> getAllCves(
@RequestParam(required = false) String search,
@PageableDefault(size = 25) Pageable pageable
) {
return ResponseEntity.ok(cveRepository.findFiltered(search, pageable));
}
}

View File

@@ -0,0 +1,26 @@
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.service.VulnerabilityScannerService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
public class VulnerabilityTestController {
private final VulnerabilityScannerService vulnerabilityScannerService;
@GetMapping("/vulnerabilities/{deviceId}")
public ResponseEntity<List<CveMatchResult>> testVulns(@PathVariable Long deviceId) {
List<CveMatchResult> results = vulnerabilityScannerService.getVulnerabilitiesForDevice(deviceId);
return ResponseEntity.ok(results);
}
}

View File

@@ -0,0 +1,41 @@
package com.psg.dlsysinfo.dl_sysinfo_server.db;
import com.psg.dlsysinfo.dl_sysinfo_server.error.ApiError;
import org.springframework.jdbc.core.JdbcTemplate;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class DatabaseService {
private final JdbcTemplate jdbcTemplate;
private static final int CURRENT_GROUP_VERSION = 2;
private static final String SHARED_MEMBER = "SharedMember"; // Define your shared member constant
public DatabaseService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private String hashToken(String token, String groupName) throws ApiError {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
String input = token + groupName; // Combine token and group name for hashing
byte[] hashBytes = digest.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString(); // Return the hashed token as a hex string
} catch (NoSuchAlgorithmException e) {
throw new ApiError("Hashing algorithm not found", e);
}
}
// ... (rest of your methods remain unchanged)
}

View File

@@ -0,0 +1,23 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class AuthRequest {
private String username;
private String password;
// Getters and Setters
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,25 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class ChangePasswordRequest {
private String currentPassword;
private String newPassword;
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}

View File

@@ -0,0 +1,17 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class ClientDTO {
private Long clientId;
private String clientIdentifier;
private String clientName; // decrypted name
public ClientDTO(Long clientId, String clientIdentifier, String clientName) {
this.clientId = clientId;
this.clientIdentifier = clientIdentifier;
this.clientName = clientName;
}
public Long getClientId() { return clientId; }
public String getClientIdentifier() { return clientIdentifier; }
public String getClientName() { return clientName; }
}

View File

@@ -0,0 +1,11 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ClientRegisterRequest {
private String clientName;
}

View File

@@ -0,0 +1,83 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Cve;
public class CveDTO {
private String id;
private String severity;
private String description;
private Double cvssScore;
private String publishedDate;
private String lastModifiedDate;
// Constructor to map Cve entity with v4 -> v3 -> v2 fallback
public CveDTO(Cve cve) {
this.id = cve.getId();
this.description = cve.getDescription();
this.publishedDate = cve.getPublishedDate();
this.lastModifiedDate = cve.getLastModifiedDate();
if (cve.getSeverityV4() != null && cve.getCvssScoreV4() != null) {
this.severity = cve.getSeverityV4();
this.cvssScore = cve.getCvssScoreV4();
} else if (cve.getSeverityV3() != null && cve.getCvssScoreV3() != null) {
this.severity = cve.getSeverityV3();
this.cvssScore = cve.getCvssScoreV3();
} else if (cve.getSeverityV2() != null && cve.getCvssScoreV2() != null) {
this.severity = cve.getSeverityV2();
this.cvssScore = cve.getCvssScoreV2();
} else {
this.severity = "UNKNOWN";
this.cvssScore = 0.0;
}
}
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSeverity() {
return severity;
}
public void setSeverity(String severity) {
this.severity = severity;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getCvssScore() {
return cvssScore;
}
public void setCvssScore(Double cvssScore) {
this.cvssScore = cvssScore;
}
public String getPublishedDate() {
return publishedDate;
}
public void setPublishedDate(String publishedDate) {
this.publishedDate = publishedDate;
}
public String getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(String lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}

View File

@@ -0,0 +1,8 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class DemoDeviceRequestDTO {
private Long clientId;
public Long getClientId() { return clientId; }
public void setClientId(Long clientId) { this.clientId = clientId; }
}

View File

@@ -0,0 +1,164 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import java.time.LocalDateTime;
import java.util.List;
public class DetailedDeviceDTO extends DeviceDTO {
private Long deviceId;
private String hostname;
private String osName;
private String osVersion;
private String windowsVersion;
private String windowsBuild;
private String osArchitecture;
private String processorName;
private String processorArchitecture;
private List<String> gpuNames;
private String totalMemory;
private String lastBootTime;
private LocalDateTime lastCheckedIn;
private List<DriveInfoDTO> drives;
private List<IpAddressDTO> ipAddresses;
private List<InstalledAppDTO> installedApplications;
private String clientName; // ✅ Added field
// ✅ Getter
public String getClientName() {
return clientName;
}
// ✅ Setter
public void setClientName(String clientName) {
this.clientName = clientName;
}
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getOsName() {
return osName;
}
public void setOsName(String osName) {
this.osName = osName;
}
public String getOsVersion() {
return osVersion;
}
public void setOsVersion(String osVersion) {
this.osVersion = osVersion;
}
public String getWindowsVersion() {
return windowsVersion;
}
public void setWindowsVersion(String windowsVersion) {
this.windowsVersion = windowsVersion;
}
public String getWindowsBuild() {
return windowsBuild;
}
public void setWindowsBuild(String windowsBuild) {
this.windowsBuild = windowsBuild;
}
public String getOsArchitecture() {
return osArchitecture;
}
public void setOsArchitecture(String osArchitecture) {
this.osArchitecture = osArchitecture;
}
public String getProcessorName() {
return processorName;
}
public void setProcessorName(String processorName) {
this.processorName = processorName;
}
public String getProcessorArchitecture() {
return processorArchitecture;
}
public void setProcessorArchitecture(String processorArchitecture) {
this.processorArchitecture = processorArchitecture;
}
public List<String> getGpuNames() {
return gpuNames;
}
public void setGpuNames(List<String> gpuNames) {
this.gpuNames = gpuNames;
}
public String getTotalMemory() {
return totalMemory;
}
public void setTotalMemory(String totalMemory) {
this.totalMemory = totalMemory;
}
public String getLastBootTime() {
return lastBootTime;
}
public void setLastBootTime(String lastBootTime) {
this.lastBootTime = lastBootTime;
}
public LocalDateTime getLastCheckedIn() {
return lastCheckedIn;
}
public void setLastCheckedIn(LocalDateTime lastCheckedIn) {
this.lastCheckedIn = lastCheckedIn;
}
public List<DriveInfoDTO> getDrives() {
return drives;
}
public void setDrives(List<DriveInfoDTO> drives) {
this.drives = drives;
}
public List<IpAddressDTO> getIpAddresses() {
return ipAddresses;
}
public void setIpAddresses(List<IpAddressDTO> ipAddresses) {
this.ipAddresses = ipAddresses;
}
public List<InstalledAppDTO> getInstalledApplications() {
return installedApplications;
}
public void setInstalledApplications(List<InstalledAppDTO> installedApplications) {
this.installedApplications = installedApplications;
}
}

View File

@@ -0,0 +1,32 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import java.time.LocalDateTime;
public class DeviceDTO {
private Long deviceId;
private String hostname;
private LocalDateTime lastCheckedIn;
private Long clientId;
private String clientIdentifier;
private String clientName;
// Getters and setters
public Long getDeviceId() { return deviceId; }
public void setDeviceId(Long deviceId) { this.deviceId = deviceId; }
public String getHostname() { return hostname; }
public void setHostname(String hostname) { this.hostname = hostname; }
public LocalDateTime getLastCheckedIn() { return lastCheckedIn; }
public void setLastCheckedIn(LocalDateTime lastCheckedIn) { this.lastCheckedIn = lastCheckedIn; }
public Long getClientId() { return clientId; }
public void setClientId(Long clientId) { this.clientId = clientId; }
public String getClientIdentifier() { return clientIdentifier; }
public void setClientIdentifier(String clientIdentifier) { this.clientIdentifier = clientIdentifier; }
public String getClientName() { return clientName; }
public void setClientName(String clientName) { this.clientName = clientName; }
}

View File

@@ -0,0 +1,11 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public record DeviceVulnerabilityDTO(
String cveId,
String title, // map from description or another title source
String severity,
Double score,
String publishedDate, // ISO 8601 or formatted string
String lastModifiedDate
) {}

View File

@@ -0,0 +1,23 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import java.util.List;
import java.util.Map;
public class DeviceWithCveResponse {
private final List<? extends DeviceDTO> devices;
private final Map<Long, List<DeviceVulnerabilityDTO>> vulnerabilitiesByDevice;
public DeviceWithCveResponse(List<? extends DeviceDTO> devices, Map<Long, List<DeviceVulnerabilityDTO>> vulnerabilitiesByDevice) {
this.devices = devices;
this.vulnerabilitiesByDevice = vulnerabilitiesByDevice;
}
public List<? extends DeviceDTO> getDevices() {
return devices;
}
public Map<Long, List<DeviceVulnerabilityDTO>> getVulnerabilitiesByDevice() {
return vulnerabilitiesByDevice;
}
}

View File

@@ -0,0 +1,23 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DriveInfoDTO {
@JsonProperty("name")
private String name;
@JsonProperty("driveType")
private String driveType;
@JsonProperty("totalSizeGB")
private double totalSizeGB;
@JsonProperty("freeSpaceGB")
private double freeSpaceGB;
}

View File

@@ -0,0 +1,21 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InstalledAppDTO {
@JsonProperty("app_name")
private String app_name;
@JsonProperty("app_version")
private String app_version;
@JsonProperty("publisher")
private String publisher;
}

View File

@@ -0,0 +1,21 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IpAddressDTO {
@JsonProperty("interfaceName")
private String interfaceName;
@JsonProperty("ipAddress")
private String ipAddress;
@JsonProperty("macAddress") // ✅ Required for deserialization
private String macAddress;
}

View File

@@ -0,0 +1,26 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class LoginResponse {
private String token;
private String username;
private Long userId;
// Constructor, Getters, and Setters
public LoginResponse(String token, String username, Long userId) {
this.token = token;
this.username = username;
this.userId = userId;
}
public String getToken() {
return token;
}
public String getUsername() {
return username;
}
public Long getUserId() {
return userId;
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class ProductCveDTO {
private String cveId;
private String severity;
private String description;
private String product;
public ProductCveDTO(String cveId, String severity, String description, String product) {
this.cveId = cveId;
this.severity = severity;
this.description = description;
this.product = product;
}
public String getCveId() { return cveId; }
public String getSeverity() { return severity; }
public String getDescription() { return description; }
public String getProduct() { return product; }
}

View File

@@ -0,0 +1,35 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Cve;
import lombok.Getter;
@Getter
public class PublicCveDTO {
private final String id;
private final String description;
private final String publishedDate;
private final String lastModifiedDate;
private final String severity;
private final Double cvssScore;
public PublicCveDTO(Cve cve) {
this.id = cve.getId();
this.description = cve.getDescription();
this.publishedDate = cve.getPublishedDate();
this.lastModifiedDate = cve.getLastModifiedDate();
if (cve.getSeverityV4() != null && cve.getCvssScoreV4() != null) {
this.severity = cve.getSeverityV4();
this.cvssScore = cve.getCvssScoreV4();
} else if (cve.getSeverityV3() != null && cve.getCvssScoreV3() != null) {
this.severity = cve.getSeverityV3();
this.cvssScore = cve.getCvssScoreV3();
} else if (cve.getSeverityV2() != null && cve.getCvssScoreV2() != null) {
this.severity = cve.getSeverityV2();
this.cvssScore = cve.getCvssScoreV2();
} else {
this.severity = "UNKNOWN";
this.cvssScore = 0.0;
}
}
}

View File

@@ -0,0 +1,18 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import java.time.LocalDateTime;
public record RawCveMatch(
String appName,
String appVersion,
String versionStart,
String versionEnd,
String version,
String cveId,
String description,
String severity,
Double cvssScore,
LocalDateTime lastModifiedDate,
boolean lenient // ✅
) {}

View File

@@ -0,0 +1,73 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import java.time.LocalDateTime;
import java.util.List;
public class SoftwareInstanceDTO {
private Long id; // cached_installed_software.id
private Long deviceId;
private String hostname;
private String version;
private String publisher;
private LocalDateTime lastUpdated;
private List<DeviceVulnerabilityDTO> vulnerabilities; // ✅ correct
// --- GETTERS and SETTERS ---
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
public LocalDateTime getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(LocalDateTime lastUpdated) {
this.lastUpdated = lastUpdated;
}
public List<DeviceVulnerabilityDTO> getVulnerabilities() { // ✅ correct
return vulnerabilities;
}
public void setVulnerabilities(List<DeviceVulnerabilityDTO> vulnerabilities) { // ✅ correct
this.vulnerabilities = vulnerabilities;
}
}

View File

@@ -0,0 +1,37 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import java.util.List;
public class SoftwareSummaryDTO {
private String softwareName;
private int totalDevices;
private List<SoftwareInstanceDTO> instances;
public String getSoftwareName() {
return softwareName;
}
public void setSoftwareName(String softwareName) {
this.softwareName = softwareName;
}
public int getTotalDevices() {
return totalDevices;
}
public void setTotalDevices(int totalDevices) {
this.totalDevices = totalDevices;
}
public List<SoftwareInstanceDTO> getInstances() {
return instances;
}
public void setInstances(List<SoftwareInstanceDTO> instances) {
this.instances = instances;
}
}

View File

@@ -0,0 +1,183 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
public class SystemInfoDTO {
@JsonProperty("clientIdentifier")
private String clientIdentifier;
@JsonProperty("hostname")
private String hostname;
@JsonProperty("osName")
private String osName;
@JsonProperty("osVersion")
private String osVersion;
@JsonProperty("windowsVersion")
private String windowsVersion;
@JsonProperty("windowsBuild")
private String windowsBuild;
@JsonProperty("osArchitecture")
private String osArchitecture;
@JsonProperty("processorName")
private String processorName;
@JsonProperty("processorArchitecture")
private String processorArchitecture;
@JsonProperty("gpuName")
private List<String> gpuNames;
@JsonProperty("totalMemory")
private String totalMemory;
@JsonProperty("ipAddress")
private String ipAddress;
@JsonProperty("lastBootTime")
private String lastBootTime;
@JsonProperty("installedApplications")
private List<InstalledAppDTO> installedApplications;
@JsonProperty("drives")
private List<DriveInfoDTO> drives = new ArrayList<>();
@JsonProperty("ipAddresses")
private List<IpAddressDTO> ipAddresses;
public List<DriveInfoDTO> getDrives() {
return drives;
}
public void setDrives(List<DriveInfoDTO> drives) {
this.drives = drives;
}
public String getClientIdentifier() {
return clientIdentifier;
}
public void setClientIdentifier(String clientIdentifier) {
this.clientIdentifier = clientIdentifier;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getOsName() {
return osName;
}
public void setOsName(String osName) {
this.osName = osName;
}
public String getOsVersion() {
return osVersion;
}
public void setOsVersion(String osVersion) {
this.osVersion = osVersion;
}
public String getWindowsVersion() {
return windowsVersion;
}
public void setWindowsVersion(String windowsVersion) {
this.windowsVersion = windowsVersion;
}
public String getWindowsBuild() {
return windowsBuild;
}
public void setWindowsBuild(String windowsBuild) {
this.windowsBuild = windowsBuild;
}
public String getOsArchitecture() {
return osArchitecture;
}
public void setOsArchitecture(String osArchitecture) {
this.osArchitecture = osArchitecture;
}
public String getProcessorName() {
return processorName;
}
public void setProcessorName(String processorName) {
this.processorName = processorName;
}
public String getProcessorArchitecture() {
return processorArchitecture;
}
public void setProcessorArchitecture(String processorArchitecture) {
this.processorArchitecture = processorArchitecture;
}
public List<String> getGpuNames() {
return gpuNames;
}
public void setGpuNames(List<String> gpuNames) {
this.gpuNames = gpuNames;
}
public String getTotalMemory() {
return totalMemory;
}
public void setTotalMemory(String totalMemory) {
this.totalMemory = totalMemory;
}
public List<IpAddressDTO> getIpAddresses() {
return ipAddresses;
}
public void setIpAddresses(List<IpAddressDTO> ipAddresses) {
this.ipAddresses = ipAddresses;
}
public String getLastBootTime() {
return lastBootTime;
}
public void setLastBootTime(String lastBootTime) {
this.lastBootTime = lastBootTime;
}
public List<InstalledAppDTO> getInstalledApplications() {
return installedApplications;
}
public void setInstalledApplications(List<InstalledAppDTO> installedApplications) {
this.installedApplications = installedApplications;
}
}

View File

@@ -0,0 +1,54 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class UserDTO {
private Long id;
private String username;
private String displayName;
private String firstName;
private String lastName;
private String email;
private String role;
private String clientIdentifier;
private String clientName;
private boolean enabled;
public UserDTO() {}
public UserDTO(Long id, String username, String displayName, String firstName, String lastName,
String email, String role, String clientIdentifier,String clientName, boolean enabled) {
this.id = id;
this.username = username;
this.displayName = displayName;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.role = role;
this.clientIdentifier = clientIdentifier;
this.clientName = clientName;
this.enabled = enabled;
}
// Getters
public Long getId() { return id; }
public String getUsername() { return username; }
public String getDisplayName() { return displayName; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getEmail() { return email; }
public String getRole() { return role; }
public String getClientIdentifier() { return clientIdentifier; }
public boolean isEnabled() { return enabled; }
public String getClientName() { return clientName; }
// Setters
public void setId(Long id) { this.id = id; }
public void setUsername(String username) { this.username = username; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public void setEmail(String email) { this.email = email; }
public void setRole(String role) { this.role = role; }
public void setClientIdentifier(String clientIdentifier) { this.clientIdentifier = clientIdentifier; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public void setClientName(String clientName) { this.clientName = clientName; }
}

View File

@@ -0,0 +1,61 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
public class UserProfileDTO {
private String username;
private String displayName;
private String firstName;
private String lastName;
private String email;
public UserProfileDTO() {}
public UserProfileDTO(String username, String displayName, String firstName, String lastName, String email) {
this.username = username;
this.displayName = displayName;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
// Getters
public String getUsername() {
return username;
}
public String getDisplayName() {
return displayName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getEmail() {
return email;
}
// Setters
public void setUsername(String username) {
this.username = username;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@@ -0,0 +1,15 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserRegisterRequest {
private String username;
private String password;
private String role;
private Long clientId;
}

View File

@@ -0,0 +1,51 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "cached_device_vulns")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CachedDeviceVuln {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "device_id", nullable = false)
private Long deviceId;
@Column(name = "cve_id", nullable = false)
private String cveId;
@Column(name = "severity")
private String severity;
@Column(name = "score")
private Double score;
@Column(name = "description", columnDefinition = "TEXT")
private String description;
@Column(name = "affected_app")
private String affectedApp;
@Column(name = "installed_version")
private String installedVersion;
@Column(name = "last_modified")
private LocalDateTime lastModified;
@Column(name = "last_updated")
private LocalDateTime lastUpdated;
@Column(name = "lenient_match")
private boolean lenientMatch;
}

View File

@@ -0,0 +1,47 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "cached_installed_software")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CachedInstalledSoftware {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "device_id") // 👈 matches database
private Long deviceId;
private String hostname;
@Column(name = "encrypted_hostname")
private String encryptedHostname;
@Column(name = "software_name") // 👈 matches database
private String softwareName;
@Column(name = "app_version") // 👈 matches database
private String appVersion;
private String publisher;
@Column(name = "total_cves")
private Integer totalCves;
@Column(name = "cve_list", columnDefinition = "TEXT")
private String cveList;
@Column(name = "last_updated") // 👈 matches database
private LocalDateTime lastUpdated;
}

View File

@@ -0,0 +1,34 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "clients")
@Getter
@Setter
@NoArgsConstructor
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "clientId")
private Long clientId;
@Column(unique = true, nullable = false)
private String clientIdentifier;
@Column(name = "client_name_hash", nullable = false)
private String clientNameHash;
public Client(String clientIdentifier, String clientNameHash) {
this.clientIdentifier = clientIdentifier;
this.clientNameHash = clientNameHash;
}
@Column(name = "client_name_encrypted")
private String clientNameEncrypted;
}

View File

@@ -0,0 +1,37 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "cpe_matches")
@Getter
@Setter
@NoArgsConstructor
public class CpeMatch {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "cve_id", referencedColumnName = "id")
private Cve cve;
@Column(name = "cpe_uri", columnDefinition = "TEXT")
private String cpeUri;
@Column(name = "version_start")
private String versionStart;
@Column(name = "version_end")
private String versionEnd;
private Boolean vulnerable;
private String vendor;
private String product;
private String version;
}

View File

@@ -0,0 +1,78 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "cves")
@Getter
@Setter
@NoArgsConstructor
public class Cve {
@Id
private String id; // CVE-2024-10556
@Column(columnDefinition = "TEXT")
private String description;
@Column(name = "published_date")
private String publishedDate;
@Column(name = "last_modified_date")
private String lastModifiedDate;
private String severity; // (Old "primary severity" field)
@Column(name = "cvss_score")
private Double cvssScore; // (Old "primary score" field)
// === New Enrichment Fields ===
@Column(name = "severity_v2")
private String severityV2;
@Column(name = "cvss_score_v2")
private Double cvssScoreV2;
@Column(name = "cvss_vector_v2")
private String cvssVectorV2;
@Column(name = "severity_v3")
private String severityV3;
@Column(name = "cvss_score_v3")
private Double cvssScoreV3;
@Column(name = "cvss_vector_v3")
private String cvssVectorV3;
@Column(name = "severity_v4")
private String severityV4;
@Column(name = "cvss_score_v4")
private Double cvssScoreV4;
@Column(name = "cvss_vector_v4")
private String cvssVectorV4;
@Column(name = "cwe_ids", columnDefinition = "TEXT")
private String cweIds;
@Column(name = "references", columnDefinition = "TEXT")
private String referencesField; // ⭐ You can't name a field `references` in Java easily, so slight rename.
@Column(name = "hasKev")
private Boolean hasKev = false;
@Column(name = "hasCertNotes")
private Boolean hasCertNotes = false;
@Column(name = "hasCertAlerts")
private Boolean hasCertAlerts = false;
@Column(name = "source")
private String source = "NVD";
}

View File

@@ -0,0 +1,13 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import java.time.LocalDateTime;
public interface CveMatchResult {
String getCveId();
String getSeverity();
Double getScore();
String getDescription();
String getAffectedApp();
String getInstalledVersion();
LocalDateTime getLastModifiedDate();
}

View File

@@ -0,0 +1,21 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
@AllArgsConstructor
public class CveMatchResultImpl implements CveMatchResult {
private final String cveId;
private final String severity;
private final Double score;
private final String description;
private final String affectedApp;
private final String installedVersion;
private final LocalDateTime lastModifiedDate; // or null for now
private final boolean lenientMatch;
public boolean isLenientMatch() { return lenientMatch; }
}

View File

@@ -0,0 +1,91 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.DynamicUpdate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "devices")
@DynamicUpdate
@Getter
@Setter
@NoArgsConstructor
public class Devices {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long deviceId;
@ManyToOne
@JoinColumn(name = "clientId", nullable = false)
private Client client;
@Column(unique = true, nullable = false)
private String hashedHostname;
@Column(name = "encrypted_hostname", nullable = false)
private String encryptedHostname;
@Column(name = "os_name")
private String osName;
@Column(name = "os_version")
private String osVersion;
@Column(name = "windows_version")
private String windowsVersion;
@Column(name = "windows_build")
private String windowsBuild;
@Column(name = "os_architecture")
private String osArchitecture;
@Column(name = "processor_name")
private String processorName;
@Column(name = "processor_architecture")
private String processorArchitecture;
@Column(length = 1000, name = "gpu_name")
private String gpuName;
@Column(name = "total_memory")
private String totalMemory;
@Column(name = "free_disk_space")
private String freeDiskSpace;
@Column(name = "ip_address")
private String ipAddress;
@Column(name = "mac_address")
private String macAddress;
@Column(name = "last_boot_time")
private String lastBootTime;
@Column(name = "last_checked_in", nullable = false)
private LocalDateTime lastCheckedIn = LocalDateTime.now();
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference
private List<Drive> drives;
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference
private List<InstalledSoftware> installedApplications = new ArrayList<>();
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference
private List<IpAddress> ipAddresses = new ArrayList<>();
}

View File

@@ -0,0 +1,76 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
@Entity
@Table(name = "drives")
public class Drive {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "device_id", nullable = false)
@JsonBackReference
private Devices device;
@Column(name = "name")
private String name;
@Column(name = "total_size_gb")
private Double totalSizeGB;
@Column(name = "free_space_gb")
private Double freeSpaceGB;
@Column(name = "drive_type")
private String driveType;
// Getters and Setters
public Long getId() {
return id;
}
public Devices getDevice() {
return device;
}
public void setDevice(Devices device) {
this.device = device;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getTotalSizeGB() {
return totalSizeGB;
}
public void setTotalSizeGB(Double totalSizeGB) {
this.totalSizeGB = totalSizeGB;
}
public Double getFreeSpaceGB() {
return freeSpaceGB;
}
public void setFreeSpaceGB(Double freeSpaceGB) {
this.freeSpaceGB = freeSpaceGB;
}
public String getDriveType() {
return driveType;
}
public void setDriveType(String driveType) {
this.driveType = driveType;
}
}

View File

@@ -0,0 +1,49 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
// InstalledSoftware.java
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "installed_software")
public class InstalledSoftware {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long softwareId;
@ManyToOne
@JoinColumn(name = "deviceId", nullable = false)
@JsonIgnore
private Devices device;
private String appName;
private String appVersion;
private String publisher;
@Column(name = "normalized_product")
private String normalizedProduct;
@Column(name = "normalized_app_name")
private String normalizedAppName;
@Column(name = "normalized_publisher")
private String normalizedPublisher;
@Column(name = "last_updated", nullable = false)
private LocalDateTime lastUpdated = LocalDateTime.now();
// ✅ Manual alias getter
public Long getId() {
return softwareId;
}
}

View File

@@ -0,0 +1,68 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
@Entity
@Table(name = "ip_addresses")
public class IpAddress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "ip_address")
private String ipAddress;
@Column(name = "interface_name")
private String interfaceName;
@Column(name = "mac_address")
private String macAddress; // ✅ NEW
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "device_id", nullable = false)
@JsonBackReference
private Devices device;
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getMacAddress() {
return macAddress;
}
public void setMacAddress(String macAddress) {
this.macAddress = macAddress;
}
public Devices getDevice() {
return device;
}
public void setDevice(Devices device) {
this.device = device;
}
}

View File

@@ -0,0 +1,58 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
@Entity
@Table(name = "mac_addresses")
public class MacAddress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "mac_address")
private String macAddress;
@Column(name = "interface_name")
private String interfaceName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "device_id", nullable = false)
@JsonBackReference
private Devices device;
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMacAddress() {
return macAddress;
}
public void setMacAddress(String macAddress) {
this.macAddress = macAddress;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public Devices getDevice() {
return device;
}
public void setDevice(Devices device) {
this.device = device;
}
}

View File

@@ -0,0 +1,120 @@
package com.psg.dlsysinfo.dl_sysinfo_server.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class UserAuth {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "username", nullable = false, length = 50)
private String username;
@Column(name = "password_hash", nullable = false, length = 60)
private String passwordHash;
@ManyToOne
@JoinColumn(name = "clientId", nullable = false)
private Client client;
@Column(nullable = false)
private String role;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "password_changed_at")
private LocalDateTime passwordChangedAt;
@Column(name = "display_name_hash", length = 100)
private String displayNameHash;
@Column(name = "first_name_hash")
private String firstNameHash;
@Column(name = "last_name_hash")
private String lastNameHash;
@Column(name = "email_hash", length = 256)
private String emailHash;
@Column(nullable = false)
private boolean enabled = true;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
public Client getClient() { return client; }
public void setClient(Client client) { this.client = client; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getPasswordChangedAt() {
return passwordChangedAt;
}
public void setPasswordChangedAt(LocalDateTime passwordChangedAt) {
this.passwordChangedAt = passwordChangedAt;
}
public String getDisplayNameHash() {return displayNameHash; }
public void setDisplayNameHash(String displayNameHash) {this.displayNameHash = displayNameHash; }
public String getEmailHash() { return emailHash; }
public void setEmailHash(String emailHash) { this.emailHash = emailHash; }
public String getFirstNameHash() {
return firstNameHash;
}
public void setFirstNameHash(String firstNameHash) {
this.firstNameHash = firstNameHash;
}
public String getLastNameHash() {
return lastNameHash;
}
public void setLastNameHash(String lastNameHash) {
this.lastNameHash = lastNameHash;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getClientNameEncrypted() {
return client != null ? client.getClientNameEncrypted() : null;
}
}

View File

@@ -0,0 +1,19 @@
package com.psg.dlsysinfo.dl_sysinfo_server.error;
public class ApiError extends Throwable {
private String message;
private Throwable cause; // Optional
// Existing constructor
public ApiError(String message) {
this.message = message;
}
// New constructor
public ApiError(String message, Throwable cause) {
this.message = message;
this.cause = cause;
}
// Getters and setters...
}

View File

@@ -0,0 +1,14 @@
package com.psg.dlsysinfo.dl_sysinfo_server.model;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class LoginRequest {
// Getters and setters
private String username;
private String password;
}

View File

@@ -0,0 +1,9 @@
package com.psg.dlsysinfo.dl_sysinfo_server.projection;
public interface CveSummaryProjection {
String getId();
String getTitle();
String getSeverityV3();
Double getCvssScoreV3();
}

View File

@@ -0,0 +1,22 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedDeviceVuln;
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 org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public interface CachedDeviceVulnRepository extends JpaRepository<CachedDeviceVuln, Long> {
List<CachedDeviceVuln> findByDeviceId(Long deviceId);
@Modifying
@Transactional
@Query("DELETE FROM CachedDeviceVuln c WHERE c.deviceId = :deviceId")
void deleteByDeviceId(@Param("deviceId") Long deviceId);
}

View File

@@ -0,0 +1,22 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedInstalledSoftware;
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.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface CachedInstalledSoftwareRepository extends JpaRepository<CachedInstalledSoftware, Long> {
List<CachedInstalledSoftware> findByDeviceId(Long deviceId);
List<CachedInstalledSoftware> findBySoftwareName(String softwareName);
@Transactional
@Modifying
@Query(value = "DELETE FROM cached_installed_software WHERE device_id = :deviceId", nativeQuery = true)
void deleteByDeviceId(@Param("deviceId") Long deviceId);
}

View File

@@ -0,0 +1,12 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ClientRepository extends JpaRepository<Client, Long> {
Optional<Client> findByClientIdentifier(String clientIdentifier);
}

View File

@@ -0,0 +1,7 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CpeMatch;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CpeMatchRepository extends JpaRepository<CpeMatch, Long> {
}

View File

@@ -0,0 +1,118 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ProductCveDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Cve;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface CveRepository extends JpaRepository<Cve, String> {
// 🆕 Add this for the admin page
@Query("""
SELECT c FROM Cve c
WHERE (:search IS NULL OR :search = ''
OR LOWER(c.id) LIKE LOWER(CONCAT('%', :search, '%'))
OR LOWER(c.description) LIKE LOWER(CONCAT('%', :search, '%'))
OR LOWER(c.severity) LIKE LOWER(CONCAT('%', :search, '%')))
""")
Page<Cve> findFiltered(@Param("search") String search, Pageable pageable);
@Query(value = """
SELECT DISTINCT
v.id AS cveId,
v.severity AS severity,
v.cvss_score AS score,
v.description AS description,
i.appName AS affectedApp,
i.appVersion AS installedVersion,
v.last_modified_date AS lastModifiedDate
FROM installed_software i
JOIN cpe_matches c ON
i.normalized_product = c.product
AND i.normalized_publisher = c.vendor
AND (
(
(c.version_start IS NULL OR i.appVersion >= c.version_start)
AND (c.version_end IS NULL OR i.appVersion <= c.version_end)
)
OR i.appVersion = c.version
)
JOIN cves v ON c.cve_id = v.id
WHERE i.deviceId = :deviceId
""",
countQuery = """
SELECT COUNT(DISTINCT v.id)
FROM installed_software i
JOIN cpe_matches c ON
i.normalized_product = c.product
AND i.normalized_publisher = c.vendor
AND (
(
(c.version_start IS NULL OR i.appVersion >= c.version_start)
AND (c.version_end IS NULL OR i.appVersion <= c.version_end)
)
OR i.appVersion = c.version
)
JOIN cves v ON c.cve_id = v.id
WHERE i.deviceId = :deviceId
""",
nativeQuery = true)
Page<CveMatchResult> findPaginatedVulnerabilitiesForDevice(@Param("deviceId") Long deviceId, Pageable pageable);
@Query(value = """
SELECT
v.id AS cve_id, -- 0
v.description, -- 1
v.severity, -- 2 ✅ NEW: severity
c.version_start, -- 3
c.version_end, -- 4
c.version, -- 5
i.appVersion, -- 6
i.appName, -- 7
i.normalized_app_name, -- 8
v.cvss_score, -- 9
v.last_modified_date -- 10
FROM installed_software i
JOIN cpe_matches c ON
(i.normalized_product = c.product OR i.normalized_app_name = c.product)
AND i.normalized_publisher = c.vendor
JOIN cves v ON c.cve_id = v.id
WHERE i.deviceId = :deviceId
""", nativeQuery = true)
List<Object[]> findUnfilteredMatches(@Param("deviceId") Long deviceId);
@Query(value = """
SELECT DISTINCT v.*
FROM cves v
JOIN cpe_matches c ON c.cve_id = v.id
WHERE LOWER(c.product) = LOWER(:productName)
""", nativeQuery = true)
List<Cve> findByProductName(@Param("productName") String productName);
@Query(value = """
SELECT DISTINCT v.*
FROM cves v
JOIN cpe_matches c ON c.cve_id = v.id
WHERE LOWER(c.product) IN :products
""", nativeQuery = true)
List<Cve> findAllByProductNames(@Param("products") List<String> products);
@Query("""
SELECT DISTINCT new com.psg.dlsysinfo.dl_sysinfo_server.dto.ProductCveDTO(
c.cve.id, c.cve.severity, c.cve.description, c.product
)
FROM CpeMatch c
WHERE LOWER(c.product) IN :products
""")
List<ProductCveDTO> findAllProductMatches(@Param("products") List<String> products);
}

View File

@@ -0,0 +1,22 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
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.util.List;
import java.util.Optional;
@Repository
public interface DevicesRepository extends JpaRepository<Devices, Long> {
Optional<Devices> findByHashedHostname(String hashedHostname);
List<Devices> findByClient_ClientId(Long clientId); // ← by Long ID (DB FK)
List<Devices> findByClient_ClientIdentifier(String clientIdentifier); // ← ✅ by idauth token
@Query("SELECT d.deviceId FROM Devices d WHERE d.client.clientId = :clientId")
List<Long> findDeviceIdsByClientId(@Param("clientId") Long clientId);
}

View File

@@ -0,0 +1,8 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Drive;
import org.springframework.data.jpa.repository.JpaRepository;
public interface DriveRepository extends JpaRepository<Drive, Long> {
}

View File

@@ -0,0 +1,47 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.InstalledAppDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware;
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.util.List;
import java.util.Optional;
@Repository
public interface InstalledSoftwareRepository extends JpaRepository<InstalledSoftware, Long> {
Optional<InstalledSoftware> findByDeviceAndAppName(Devices device, String appName);
List<InstalledSoftware> findByDevice(Devices device);
@Query("SELECT DISTINCT new com.psg.dlsysinfo.dl_sysinfo_server.dto.InstalledAppDTO(" +
"s.appName, s.appVersion, s.publisher) " +
"FROM InstalledSoftware s WHERE s.appName IS NOT NULL")
List<InstalledAppDTO> findDistinctInstalledAppDTOs();
@Query("""
SELECT s FROM InstalledSoftware s
JOIN s.device d
WHERE d.client.clientId = :clientId
""")
List<InstalledSoftware> findAllByClientId(Long clientId);
@Query("""
SELECT s FROM InstalledSoftware s
JOIN FETCH s.device d
JOIN FETCH d.client
""")
List<InstalledSoftware> findAllWithDeviceAndClient();
@Query("""
SELECT s FROM InstalledSoftware s
JOIN FETCH s.device d
JOIN FETCH d.client c
WHERE c.clientId = :clientId
""")
List<InstalledSoftware> findAllByClientIdWithDevice(@Param("clientId") Long clientId);
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.IpAddress;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface IpAddressRepository extends JpaRepository<IpAddress, Long> {
List<IpAddress> findByDeviceDeviceId(Long deviceId);
// 🔧 Add this to enable deletion by device
void deleteByDevice(Devices device);
}

View File

@@ -0,0 +1,16 @@
package com.psg.dlsysinfo.dl_sysinfo_server.repository;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface UserAuthRepository extends JpaRepository<UserAuth, Long> {
@Query("SELECT u FROM UserAuth u JOIN FETCH u.client WHERE u.username = :username")
Optional<UserAuth> findByUsernameWithClient(@Param("username") String username);
Optional<UserAuth> findByUsername(String username); // still used by Spring Security
}

View File

@@ -0,0 +1,98 @@
package com.psg.dlsysinfo.dl_sysinfo_server.scheduling;
import com.psg.dlsysinfo.dl_sysinfo_server.service.CveStatisticsService;
import com.psg.dlsysinfo.dl_sysinfo_server.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Map;
@Component
public class CveSyncScheduler {
private final File logFile = new File("cve-sync.log");
@Autowired
private EmailService emailService;
@Autowired
private CveStatisticsService cveStatisticsService;
@Scheduled(cron = "0 0 */8 * * *") // ⏰ Every 8 hours
public void runCveSyncScript() {
File scriptFile = new File("scripts/fetchCVE.js");
if (!scriptFile.exists()) {
String msg = "❌ Script not found: " + scriptFile.getAbsolutePath();
log(msg);
emailService.sendHtmlEmail("⚠️ CVE Sync Failed", wrapHtml(msg));
return;
}
try {
ProcessBuilder pb = new ProcessBuilder("node", scriptFile.getAbsolutePath());
Map<String, String> env = pb.environment();
env.put("DB_HOST", System.getenv("DB_HOST"));
env.put("DB_USER", System.getenv("DB_USER"));
env.put("DB_PASSWORD", System.getenv("DB_PASSWORD"));
env.put("DB_NAME", System.getenv("DB_NAME"));
env.put("NVD_API_KEY", System.getenv("NVD_API_KEY"));
env.put("NVD_MAX_RANGE_DAYS", System.getenv("NVD_MAX_RANGE_DAYS"));
pb.redirectErrorStream(true);
Process process = pb.start();
StringBuilder output = new StringBuilder();
output.append("=== CVE Sync Run at ").append(LocalDateTime.now()).append(" ===\n");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
FileWriter fw = new FileWriter(logFile, true)) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
fw.write(line + "\n");
}
int exitCode = process.waitFor();
output.append(">>> Exit Code: ").append(exitCode).append("\n");
fw.write(">>> Exit Code: " + exitCode + "\n");
String subject = (exitCode == 0 ? "✅ CVE Sync Complete" : "⚠️ CVE Sync Completed with Errors");
emailService.sendHtmlEmail(subject, wrapHtml(output.toString()));
if (exitCode == 0) {
cveStatisticsService.refreshStatistics();
}
}
} catch (Exception e) {
String err = "❌ Exception during CVE sync: " + e.getMessage();
log(err);
emailService.sendHtmlEmail("❌ CVE Sync Error", wrapHtml(err));
}
}
private void log(String message) {
try (FileWriter fw = new FileWriter(logFile, true)) {
fw.write(LocalDateTime.now() + "" + message + "\n");
} catch (IOException ignored) {}
}
private String wrapHtml(String content) {
return """
<html>
<body style="font-family: sans-serif; font-size: 14px;">
<h2 style="color: #2E86C1;">CVE Sync Report</h2>
<pre style="background: #f4f4f4; padding: 1em; border-radius: 4px;">%s</pre>
</body>
</html>
""".formatted(content.replace("<", "&lt;").replace(">", "&gt;"));
}
}

View File

@@ -0,0 +1,29 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
public class AuthenticationInfo {
private Long groupId; // Keep this as Long
private String version; // This should be String
// Constructor
public AuthenticationInfo(Long groupId, String version) {
this.groupId = groupId; // Should match Long type
this.version = version; // Should match String type
}
// Getters and setters
public Long getGroupId() {
return groupId;
}
public void setGroupId(Long groupId) {
this.groupId = groupId;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@@ -0,0 +1,172 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.converter.Converter;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.util.Base64;
import java.util.List;
@Configuration
@EnableWebSecurity
public class BasicConfiguration implements WebMvcConfigurer {
private final UserService userAuthService;
private final JwtToCurrentUserConverter jwtToCurrentUserConverter;
@Autowired
public BasicConfiguration(@Lazy UserService userAuthService, JwtToCurrentUserConverter jwtToCurrentUserConverter) {
this.userAuthService = userAuthService;
this.jwtToCurrentUserConverter = jwtToCurrentUserConverter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public JwtUtil jwtUtil() {
return new JwtUtil(); // Ensure you have a JwtUtil bean
}
@Bean
public JwtDecoder jwtDecoder(@Value("${jwt.secret}") String base64Secret) {
byte[] secret = Base64.getDecoder().decode(base64Secret);
SecretKey secretKey = new SecretKeySpec(secret, 0, secret.length, "HmacSHA256");
return NimbusJwtDecoder.withSecretKey(secretKey).build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JwtUtil jwtUtil) {
return new JwtAuthenticationFilter(userAuthService, jwtUtil); // Pass GroupService
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
// Public endpoints
.requestMatchers("/register", "/login", "/error").permitAll()
.requestMatchers("/api/auth/login", "/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/scripts/run-scheduled-sync").permitAll()
.requestMatchers("/admin/vulnerabilities").permitAll()
// Internal system health checks, available without auth
.requestMatchers("/api/system/**").permitAll()
// Pre-fetched cached information to prevent API abuse.
.requestMatchers("/api/cached/**").authenticated()
// Authenticated API endpoints
.requestMatchers("/api/ping", "/api/system-info").authenticated()
.requestMatchers("/api/devices", "/api/devices/**").authenticated()
.requestMatchers("/api/user/**").authenticated()
.requestMatchers("/api/vuln/**").authenticated()
// Special: Allow stream endpoints for authenticated users (not strict role)
.requestMatchers("/api/admin/scripts/*/logs/stream").authenticated()
// Admin-only API and frontend views
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/admin/**").hasRole("ADMIN")
// All other endpoints
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(jwtUtil()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CustomCacheControlFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userAuthService).passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:3000","https://localhost:3000", "https://sys.psg.net.au", "http://172.16.10.180:3000", "https://dev.psg.net.au:3000"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setExposedHeaders(List.of("Set-Cookie", "Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Component
public class CorsDebugFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("Origin: " + request.getHeader("Origin"));
System.out.println("Cookie: " + request.getHeader("Cookie"));
filterChain.doFilter(request, response);
}
}
@Autowired
private CurrentUserArgumentResolver currentUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserArgumentResolver);
}
}

View File

@@ -0,0 +1,20 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class CurrentUser {
private final String username;
private final String displayName;
private final String clientIdentifier;
private final Long userId;
private final List<String> roles;
public boolean hasRole(String role) {
return roles != null && roles.contains(role);
}
}

View File

@@ -0,0 +1,30 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.stereotype.Component;
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof CurrentUser currentUser) {
return currentUser;
}
throw new IllegalArgumentException("Authenticated user context is missing");
}
}

View File

@@ -0,0 +1,32 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class CustomCacheControlFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (response != null) {
// Prevent caching of sensitive data
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// Harden browser security
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("Referrer-Policy", "no-referrer");
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,80 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
@Service // ✅ Mark this as a Spring Service so it can be injected
public class EncryptionService {
@Value("${encryption.aes.key}") // Load AES Key from properties
private String aesKey;
@Value("${encryption.aes.iv}") // Load AES IV from properties
private String aesIv;
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public String decryptData(String encryptedData) throws Exception {
if (encryptedData == null || encryptedData.trim().isEmpty()) {
return "";
}
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(), "AES");
byte[] ivBytes = aesIv.getBytes();
if (ivBytes.length != 16) {
throw new IllegalArgumentException("Invalid IV length: " + ivBytes.length + " bytes (must be 16 bytes)");
}
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
if (encryptedBytes.length % 16 != 0) {
throw new IllegalArgumentException("Invalid encrypted data length: must be multiple of 16 bytes.");
}
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public String encryptData(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(StandardCharsets.UTF_8), "AES");
byte[] ivBytes = aesIv.getBytes(StandardCharsets.UTF_8);
if (ivBytes.length != 16) {
throw new IllegalArgumentException("Invalid IV length: " + ivBytes.length + " bytes (must be 16 bytes)");
}
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String hashString(String input) {
try {
// 🔑 Using SHA-256 hashing algorithm
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
// 🔑 Encode the hash as Base64 for storage
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error generating hash: " + e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,138 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import com.psg.dlsysinfo.dl_sysinfo_server.service.UserService;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.io.IOException;
import java.util.List;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserService userService;
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String clientIp = extractClientIp(request);
String uri = request.getRequestURI();
System.out.println("🌐 Incoming request to " + uri + " from IP: " + clientIp);
String jwt = null;
// Try to get token from Authorization header
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7);
System.out.println("🔐 JWT found in Authorization header");
}
// If not in header, try cookie
if (jwt == null && request.getCookies() != null) {
for (jakarta.servlet.http.Cookie cookie : request.getCookies()) {
if ("authToken".equals(cookie.getName())) {
jwt = cookie.getValue();
System.out.println("🍪 JWT extracted from authToken cookie");
break;
}
}
}
if (jwt == null) {
System.out.println("🔓 No JWT found in header or cookie");
chain.doFilter(request, response);
return;
}
try {
String username = jwtUtil.extractUsername(jwt);
System.out.println("👤 Extracted username: " + username + " from IP: " + clientIp);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, username)) {
String displayName = jwtUtil.extractDisplayName(jwt);
String clientIdentifier = jwtUtil.extractClientIdentifier(jwt);
Long userId = jwtUtil.extractUserId(jwt);
List<String> roles = jwtUtil.extractRoles(jwt); // youll define this next
CurrentUser currentUser = new CurrentUser(
username,
displayName,
clientIdentifier,
userId,
roles
);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
currentUser, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
System.out.println("✅ Authenticated user: " + username + " from IP: " + clientIp);
}
}
} catch (ExpiredJwtException e) {
System.out.println("⏰ JWT expired from IP " + clientIp + ": " + e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"Token expired\"}");
return;
} catch (JwtException | IllegalArgumentException e) {
System.out.println("❌ Invalid JWT from IP " + clientIp + ": " + e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"Invalid token\"}");
return;
} catch (UsernameNotFoundException e) {
System.out.println("❌ User not found from IP " + clientIp + ": " + e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"User not found\"}");
return;
}
chain.doFilter(request, response);
}
private String extractClientIp(HttpServletRequest request) {
String cfIp = request.getHeader("CF-Connecting-IP");
if (cfIp != null && !cfIp.isEmpty()) return cfIp;
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader != null && !xfHeader.isEmpty()) {
return xfHeader.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}

View File

@@ -0,0 +1,26 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class JwtToCurrentUserConverter implements Converter<Jwt, CurrentUser> {
@Override
public CurrentUser convert(Jwt jwt) {
// 👇 Try to get the "roles" claim as a list of strings
var roles = jwt.getClaimAsStringList("roles");
return new CurrentUser(
jwt.getSubject(), // username
jwt.getClaim("displayname"), // display name
jwt.getClaim("idauth"), // clientIdentifier
jwt.getClaim("userId"), // userId
roles != null ? roles : List.of() // roles fallback to empty list
);
}
}

View File

@@ -0,0 +1,146 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secretKeyString;
@Value("${jwt.expiration}")
private long jwtExpirationMs;
/**
* Converts Base64-encoded key from properties to SecretKey.
*/
private SecretKey getSigningKey() {
byte[] keyBytes = Base64.getDecoder().decode(secretKeyString);
return Keys.hmacShaKeyFor(keyBytes);
}
/**
* Generates a JWT token with a username and idauth.
*/
public String generateToken(String username, String displayName, String idauth, Long userId, List<String> roles) {
return Jwts.builder()
.setSubject(username)
.claim("displayname", displayName)
.claim("idauth", idauth)
.claim("userId", userId)
.claim("roles", roles)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS256, getSigningKey())
.compact();
}
/**
* Extracts claims from a token.
*/
private Claims extractAllClaims(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
System.err.println("⏰ JWT expired: " + e.getMessage());
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "JWT expired", e); // re-throw
} catch (JwtException e) {
System.err.println("❌ Invalid JWT: " + e.getMessage());
throw new IllegalArgumentException("Invalid JWT Token", e);
}
}
public String extractDisplayName(String token) {
return extractClaim(token, claims -> claims.get("displayname", String.class));
}
public Long extractUserId(String token) {
return extractClaim(token, claims -> claims.get("userId", Long.class));
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public String extractIdAuth(String token) {
return extractClaim(token, claims -> claims.get("idauth", String.class));
}
public String extractClientIdentifier(String token) {
return extractIdAuth(token);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
return claimsResolver.apply(extractAllClaims(token));
}
public boolean isTokenExpired(String token) {
return extractClaim(token, Claims::getExpiration).before(new Date());
}
/**
* Validates the token and checks if it matches the provided username.
*/
public boolean validateToken(String token, String username) {
try {
return extractUsername(token).equals(username) && !isTokenExpired(token);
} catch (JwtException | IllegalArgumentException e) {
System.err.println("Token validation failed: " + e.getMessage());
return false;
}
}
/**
* Overloaded method for validating token without username check.
*/
public boolean validateToken(String token) {
try {
return !isTokenExpired(token);
} catch (JwtException | IllegalArgumentException e) {
System.err.println("Token validation failed: " + e.getMessage());
return false;
}
}
/**
* Extracts token from Authorization header.
*/
public String extractToken(String authHeader) {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7); // Remove "Bearer " prefix
}
return authHeader; // If it's a direct token, return as is
}
public List<String> extractRoles(String token) {
Claims claims = extractAllClaims(token);
Object rolesClaim = claims.get("roles");
if (rolesClaim instanceof List<?>) {
return ((List<?>) rolesClaim).stream()
.filter(String.class::isInstance)
.map(String.class::cast)
.toList();
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,28 @@
package com.psg.dlsysinfo.dl_sysinfo_server.security;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
@Component
public class TokenResolver {
public String resolveToken(HttpServletRequest request) {
// Check Authorization header first
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
// Fallback to Cookie
if (request.getCookies() != null) {
for (var cookie : request.getCookies()) {
if ("authToken".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,86 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedInstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CachedInstalledSoftwareRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.DevicesRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class CachedSoftwareService {
private final DevicesRepository devicesRepository;
private final CachedInstalledSoftwareRepository cachedSoftwareRepo;
private final VulnerabilityScannerService vulnerabilityScannerService;
@Scheduled(cron = "0 30 * * * *") // Every hour at minute 30
@Transactional
public void refreshSoftwareCache() {
System.out.println("♻️ Refreshing cached installed software...");
refreshSoftwareCacheWithSummary(); // 🔥 Call the real logic
}
@Transactional
public String refreshSoftwareCacheWithSummary() {
StringBuilder sb = new StringBuilder();
List<Devices> devices = devicesRepository.findAll();
for (Devices device : devices) {
sb.append(refreshDeviceSoftwareCache(device));
}
System.out.println("✅ Software cache refresh complete.");
return sb.toString();
}
/**
* Refreshes the cached software entries for a single device.
*/
private String refreshDeviceSoftwareCache(Devices device) {
Long deviceId = device.getDeviceId();
String hashedHostname = device.getHashedHostname();
String encryptedHostname = device.getEncryptedHostname();
cachedSoftwareRepo.deleteByDeviceId(deviceId);
cachedSoftwareRepo.flush();
List<InstalledSoftware> installedApps = device.getInstalledApplications();
List<CachedInstalledSoftware> toSave = installedApps.stream()
.map(app -> CachedInstalledSoftware.builder()
.deviceId(deviceId)
.hostname(hashedHostname)
.encryptedHostname(encryptedHostname)
.softwareName(app.getAppName())
.appVersion(app.getAppVersion())
.publisher(app.getPublisher())
.lastUpdated(LocalDateTime.now())
.build())
.toList();
List<CveMatchResult> deviceVulns = vulnerabilityScannerService.getVulnerabilitiesForDevice(deviceId);
for (CachedInstalledSoftware entry : toSave) {
List<String> matchingCveIds = deviceVulns.stream()
.filter(vuln -> vuln.getAffectedApp().equalsIgnoreCase(entry.getSoftwareName()))
.map(CveMatchResult::getCveId)
.toList();
entry.setTotalCves(matchingCveIds.size());
entry.setCveList(String.join(",", matchingCveIds));
}
cachedSoftwareRepo.saveAll(toSave);
return String.format("📦 Device %d: %d software entries cached\n", deviceId, installedApps.size());
}
}

View File

@@ -0,0 +1,127 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedDeviceVuln;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResultImpl;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CachedDeviceVulnRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.DevicesRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class CachedVulnService {
private final VulnerabilityScannerService scannerService;
private final DevicesRepository devicesRepository;
private final CachedDeviceVulnRepository cachedRepo;
@Scheduled(cron = "0 0 * * * *") // Every hour
@Transactional
public void refreshVulnerabilityCache() {
System.out.println("♻️ Refreshing cached device vulnerabilities...");
List<Devices> devices = devicesRepository.findAll();
for (Devices device : devices) {
Long deviceId = device.getDeviceId();
// ✅ Get fresh matches
List<CveMatchResult> matches = scannerService.getVulnerabilitiesForDevice(deviceId);
// ✅ Deduplicate by CVE ID
List<CveMatchResult> uniqueMatches = matches.stream()
.collect(Collectors.toMap(
CveMatchResult::getCveId,
v -> v,
(existing, replacement) -> existing // Keep first
))
.values()
.stream()
.toList();
// ✅ Delete old entries first, then flush to commit
cachedRepo.deleteByDeviceId(deviceId);
cachedRepo.flush();
// ✅ Save deduplicated matches
List<CachedDeviceVuln> toSave = uniqueMatches.stream()
.map(v -> CachedDeviceVuln.builder()
.deviceId(deviceId)
.cveId(v.getCveId())
.severity(v.getSeverity())
.score(v.getScore())
.description(v.getDescription())
.affectedApp(v.getAffectedApp())
.installedVersion(v.getInstalledVersion())
.lastModified(v.getLastModifiedDate())
.lastUpdated(LocalDateTime.now())
.lenientMatch(v instanceof CveMatchResultImpl impl && impl.isLenientMatch())
.build())
.toList();
cachedRepo.saveAll(toSave);
System.out.printf("📦 Device ID %d matched %d vulnerabilities (saved %d after dedup)%n",
deviceId, matches.size(), toSave.size());
}
System.out.println("✅ Vulnerability cache refreshed.");
}
public String refreshVulnerabilityCacheWithSummary() {
StringBuilder sb = new StringBuilder();
List<Devices> devices = devicesRepository.findAll();
for (Devices device : devices) {
List<CveMatchResult> vulns = scannerService.getVulnerabilitiesForDevice(device.getDeviceId());
cachedRepo.deleteByDeviceId(device.getDeviceId());
List<CveMatchResult> uniqueMatches = vulns.stream()
.collect(Collectors.toMap(
CveMatchResult::getCveId,
v -> v,
(existing, replacement) -> existing
))
.values()
.stream()
.toList();
List<CachedDeviceVuln> toSave = uniqueMatches.stream()
.map(v -> CachedDeviceVuln.builder()
.deviceId(device.getDeviceId())
.cveId(v.getCveId())
.severity(v.getSeverity())
.score(v.getScore())
.description(v.getDescription())
.affectedApp(v.getAffectedApp())
.installedVersion(v.getInstalledVersion())
.lastModified(v.getLastModifiedDate())
.lastUpdated(LocalDateTime.now())
.lenientMatch(v instanceof CveMatchResultImpl impl && impl.isLenientMatch())
.build())
.toList();
System.out.printf("📦 Saving %d vulns for device %d\n", toSave.size(), device.getDeviceId());
cachedRepo.saveAll(toSave);
System.out.println("✅ Save call completed.");
sb.append(String.format("📦 Device %d: %d vulns\n", device.getDeviceId(), vulns.size()));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,37 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.util.HashUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class ClientService {
@Autowired
private ClientRepository clientRepo;
@Autowired
private EncryptionService encryptionService;
public Client registerClient(String clientName) {
try {
String identifier = UUID.randomUUID().toString();
String encryptedName = encryptionService.encryptData(clientName);
String hashedName = HashUtil.sha256(clientName);
Client client = new Client(identifier, hashedName);
client.setClientNameEncrypted(encryptedName);
return clientRepo.save(client);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt client name", e);
}
}
}

View File

@@ -0,0 +1,7 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface CustomUserDetailsService extends UserDetailsService {
// You can add custom methods here if needed
}

View File

@@ -0,0 +1,39 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class CveStatisticsService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void refreshStatistics() {
String sql = """
INSERT INTO cve_statistics (
total, missing_title, missing_severity, missing_cvss_score,
missing_cvss_vector, missing_references, missing_published_date,
missing_description, missing_cwe, missing_cert_notes, missing_cert_alerts, missing_cisa_kev
)
SELECT
(SELECT COUNT(*) FROM cves),
(SELECT COUNT(*) FROM cves WHERE title IS NULL OR title = ''),
(SELECT COUNT(*) FROM cves WHERE severity IS NULL OR severity = ''),
(SELECT COUNT(*) FROM cves WHERE cvss_score IS NULL AND cvss_score_v2 IS NULL AND cvss_score_v3 IS NULL AND cvss_score_v4 IS NULL),
(SELECT COUNT(*) FROM cves WHERE cvss_vector IS NULL AND cvss_vector_v2 IS NULL AND cvss_vector_v3 IS NULL AND cvss_vector_v4 IS NULL),
(SELECT COUNT(*) FROM cves WHERE `references` IS NULL OR `references` = ''),
(SELECT COUNT(*) FROM cves WHERE published_date IS NULL),
(SELECT COUNT(*) FROM cves WHERE description IS NULL OR description = ''),
(SELECT COUNT(*) FROM cves WHERE cwe_ids IS NULL OR cwe_ids = ''),
(SELECT COUNT(*) FROM cves WHERE hasCertNotes = 0),
(SELECT COUNT(*) FROM cves WHERE hasCertAlerts = 0),
(SELECT COUNT(*) FROM cves WHERE hasKev = 0)
""";
jdbcTemplate.execute("DELETE FROM cve_statistics"); // clear existing row(s)
jdbcTemplate.execute(sql);
}
}

View File

@@ -0,0 +1,224 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.InstalledAppDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Drive;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.IpAddress;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.*;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@Service
public class DemoDeviceGeneratorService {
@Autowired
private DevicesRepository devicesRepository;
@Autowired
private EncryptionService encryptionService;
@Autowired
private IpAddressRepository ipAddressRepository;
@Autowired
private CveRepository cveRepository;
@Autowired
private InstalledSoftwareRepository installedSoftwareRepository;
@Autowired
private VulnerabilityScannerService vulnScanner;
@Autowired
private DeviceService deviceService;
private static final List<String> CPU_NAMES = List.of("Intel i7-12700K", "Ryzen 5 5600X", "Apple M1");
private static final List<String> OS_VERSIONS = List.of("Windows 11 Pro", "Windows 10 Enterprise", "Ubuntu 22.04");
private static final List<String> OS_NAMES = List.of("Windows", "Ubuntu", "macOS");
private <T> T randomFrom(List<T> list) {
return list.get((int) (Math.random() * list.size()));
}
private int randomInt(int min, int max) {
return (int) (Math.random() * (max - min + 1)) + min;
}
// Add more preset data pools
@Autowired private ClientRepository clientRepository;
public Devices generateDemoDeviceForClient(Long clientId) {
Client client = clientRepository.findById(clientId)
.orElseThrow(() -> new IllegalArgumentException("Invalid client ID"));
// Step 1: Decrypt the client name
String decryptedName;
try {
decryptedName = encryptionService.decryptData(client.getClientNameEncrypted());
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt client name", e);
}
// Step 2: Build client code from first 4 alphanumeric chars of name
String clientCode = decryptedName
.replaceAll("[^A-Za-z0-9]", "")
.toUpperCase()
.substring(0, Math.min(4, decryptedName.length()));
// Step 3: Generate type and number
String deviceType = Math.random() < 0.5 ? "SVR" : "WKS";
String deviceNumber = String.format("%02d", randomInt(1, 99));
// Step 4: Build final hostname
String demoHost = String.format("DEMO-%s%s%s", clientCode, deviceType, deviceNumber);
// Step 5: Hash and encrypt
String hashedHostname = encryptionService.hashString(demoHost);
// Step 6: Find or create device
Devices device = devicesRepository.findByHashedHostname(hashedHostname).orElse(null);
boolean isNew = false;
if (device == null) {
device = new Devices();
device.setHashedHostname(hashedHostname);
isNew = true;
}
device.setClient(client);
device.setLastCheckedIn(LocalDateTime.now());
try {
device.setEncryptedHostname(encryptionService.encryptData(demoHost));
} catch (Exception e) {
throw new RuntimeException("Encryption failed", e);
}
// Random system info
device.setOsName(randomFrom(OS_NAMES));
device.setOsVersion(randomFrom(OS_VERSIONS));
device.setWindowsVersion("22H2");
device.setWindowsBuild("22631.3374");
device.setOsArchitecture("x64");
device.setProcessorName(randomFrom(CPU_NAMES));
device.setProcessorArchitecture("x64");
device.setGpuName("NVIDIA GeForce RTX 3060");
int totalGB = randomInt(8, 64);
device.setTotalMemory(String.valueOf(totalGB * 1024)); // in MB
device.setLastBootTime(generateRandomBootTime());
// Related tables
device.setDrives(generateRandomDrives(device));
device.setIpAddresses(generateRandomIps(device));
device.setInstalledApplications(generateRandomSoftware(device));
Devices saved = devicesRepository.save(device);
System.out.println((isNew ? "🆕 Created" : "🔁 Updated") + " demo device with ID: " + saved.getDeviceId());
return saved;
}
private List<Drive> generateRandomDrives(Devices device) {
double totalC = randomDouble(256, 1024);
double totalD = randomDouble(512, 2048);
Drive cDrive = new Drive();
cDrive.setName("C:");
cDrive.setDriveType("NTFS");
cDrive.setTotalSizeGB(roundToTwoDecimalPlaces(totalC));
cDrive.setFreeSpaceGB(roundToTwoDecimalPlaces(totalC * 0.75));
cDrive.setDevice(device);
Drive dDrive = new Drive();
dDrive.setName("D:");
dDrive.setDriveType("NTFS");
dDrive.setTotalSizeGB(roundToTwoDecimalPlaces(totalD));
dDrive.setFreeSpaceGB(roundToTwoDecimalPlaces(totalD * 0.9));
dDrive.setDevice(device);
return List.of(cDrive, dDrive);
}
private double roundToTwoDecimalPlaces(double value) {
return Math.round(value * 100.0) / 100.0;
}
private double randomDouble(int min, int max) {
return min + (Math.random() * (max - min));
}
private String generateRandomBootTime() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime randomBoot = now.minusDays(randomInt(0, 30)).withHour(randomInt(0, 23)).withMinute(randomInt(0, 59));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return randomBoot.format(formatter);
}
private List<IpAddress> generateRandomIps(Devices device) {
IpAddress ip1 = new IpAddress();
ip1.setInterfaceName("Ethernet 0");
ip1.setIpAddress("192.168.1." + randomInt(2, 254));
ip1.setMacAddress(randomMac());
ip1.setDevice(device);
IpAddress ip2 = new IpAddress();
ip2.setInterfaceName("Wi-Fi 1");
ip2.setIpAddress("192.168.1." + randomInt(100, 200));
ip2.setMacAddress(randomMac());
ip2.setDevice(device);
return List.of(ip1, ip2);
}
private String randomMac() {
return "AA:BB:" + UUID.randomUUID().toString().substring(0, 2) + ":" +
UUID.randomUUID().toString().substring(0, 2) + ":00:FF";
}
private List<InstalledSoftware> generateRandomSoftware(Devices device) {
List<InstalledAppDTO> samples = installedSoftwareRepository.findDistinctInstalledAppDTOs();
Collections.shuffle(samples);
return samples.stream().limit(5).map(dto -> {
InstalledSoftware software = new InstalledSoftware();
software.setDevice(device);
software.setAppName(dto.getApp_name());
software.setAppVersion(dto.getApp_version());
software.setPublisher(dto.getPublisher());
software.setNormalizedAppName(null);
software.setNormalizedProduct(null);
software.setNormalizedPublisher(null);
software.setLastUpdated(LocalDateTime.now());
return software;
}).toList();
}
}

View File

@@ -0,0 +1,299 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.*;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.*;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.*;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class DeviceService {
@Autowired
private DevicesRepository devicesRepository;
@Autowired
private EncryptionService encryptionService;
@Autowired
private IpAddressRepository ipAddressRepository;
@Autowired
private CveRepository cveRepository;
@Autowired
private InstalledSoftwareRepository installedSoftwareRepository;
@Autowired
private VulnerabilityScannerService vulnScanner;
@Autowired
private ClientRepository clientRepository;
public List<DeviceDTO> getDevicesForClient(Long clientId) {
System.out.println("🔍 Fetching devices for client ID: " + clientId);
List<Devices> devices = devicesRepository.findByClient_ClientId(clientId);
return devices.stream().map(device -> {
DeviceDTO dto = new DeviceDTO();
dto.setDeviceId(device.getDeviceId());
dto.setLastCheckedIn(device.getLastCheckedIn());
try {
dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
} catch (Exception e) {
dto.setHostname("DECRYPTION_FAILED");
}
return dto;
}).toList();
}
public Devices findOrCreateDevice(String hostname, String hashedHostname, Client client) {
System.out.println("📥 findOrCreateDevice called with:");
System.out.println(" 🔹 hostname = " + hostname);
System.out.println(" 🔹 hashedHostname = " + hashedHostname);
System.out.println(" 🔹 clientId = " + client.getClientId());
Devices device = devicesRepository.findByHashedHostname(hashedHostname).orElse(null);
boolean isNew = false;
if (device == null) {
System.out.println("🆕 No existing device found. Creating new one...");
device = new Devices();
device.setHashedHostname(hashedHostname);
isNew = true;
} else {
System.out.println("✅ Existing device found. Updating...");
}
device.setClient(client);
device.setLastCheckedIn(LocalDateTime.now());
try {
device.setEncryptedHostname(encryptionService.encryptData(hostname));
} catch (Exception e) {
System.out.println("❌ Failed to encrypt hostname: " + e.getMessage());
throw new RuntimeException("Failed to encrypt hostname", e);
}
Devices savedDevice = devicesRepository.save(device);
if (isNew) {
System.out.println("📥 New device saved with ID: " + savedDevice.getDeviceId());
} else {
System.out.println("🔄 Existing device updated with ID: " + savedDevice.getDeviceId());
}
return savedDevice;
}
public Devices updateDeviceCheckIn(Devices device, Client client, String hostname) {
device.setClient(client);
device.setLastCheckedIn(LocalDateTime.now());
try {
device.setEncryptedHostname(encryptionService.encryptData(hostname));
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt hostname", e);
}
Devices updated = devicesRepository.save(device);
System.out.println("🔄 Updated device ID: " + updated.getDeviceId());
return updated;
}
public Devices saveDevice(Devices device) {
System.out.println("💾 Saving device to DB: " + device.getDeviceId());
return devicesRepository.save(device);
}
@Transactional
public void populateDeviceFromDTO(Devices device, SystemInfoDTO dto) {
System.out.println("🧩 Populating fields from DTO for device: " + device.getDeviceId());
System.out.println("🔍 DTO OS Name: " + dto.getOsName());
System.out.println("🔍 DTO GPU: " + dto.getGpuNames());
System.out.println("📡 IP DTOs: " + dto.getIpAddresses());
device.getDrives().clear();
for (DriveInfoDTO driveDTO : dto.getDrives()) {
Drive drive = new Drive();
drive.setDevice(device);
drive.setName(driveDTO.getName());
drive.setTotalSizeGB(driveDTO.getTotalSizeGB());
drive.setFreeSpaceGB(driveDTO.getFreeSpaceGB());
drive.setDriveType(driveDTO.getDriveType());
device.getDrives().add(drive);
}
ipAddressRepository.deleteByDevice(device);
if (dto.getIpAddresses() != null) {
for (IpAddressDTO ipDto : dto.getIpAddresses()) {
if (ipDto.getInterfaceName() != null && ipDto.getInterfaceName().contains("Loopback Pseudo")) {
continue; // ✅ Skip loopback pseudo interfaces
}
IpAddress ipAddress = new IpAddress();
ipAddress.setDevice(device);
ipAddress.setIpAddress(ipDto.getIpAddress());
ipAddress.setInterfaceName(ipDto.getInterfaceName());
ipAddress.setMacAddress(ipDto.getMacAddress());
ipAddressRepository.save(ipAddress);
}
}
device.setOsName(dto.getOsName());
device.setOsVersion(dto.getOsVersion());
device.setWindowsVersion(dto.getWindowsVersion());
device.setWindowsBuild(dto.getWindowsBuild());
device.setOsArchitecture(dto.getOsArchitecture());
device.setProcessorName(dto.getProcessorName());
device.setProcessorArchitecture(dto.getProcessorArchitecture());
device.setGpuName(String.join(", ", dto.getGpuNames()));
device.setTotalMemory(dto.getTotalMemory());
device.setLastBootTime(dto.getLastBootTime());
device.setLastCheckedIn(LocalDateTime.now());
}
public DetailedDeviceDTO getDeviceDetails(Long deviceId) {
Devices device = devicesRepository.findById(deviceId)
.orElseThrow(() -> new RuntimeException("Device not found"));
DetailedDeviceDTO dto = new DetailedDeviceDTO();
try {
dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
} catch (Exception e) {
dto.setHostname("DECRYPTION_FAILED");
}
dto.setDeviceId(device.getDeviceId());
dto.setOsName(device.getOsName());
dto.setOsVersion(device.getOsVersion());
dto.setWindowsVersion(device.getWindowsVersion());
dto.setWindowsBuild(device.getWindowsBuild());
dto.setOsArchitecture(device.getOsArchitecture());
dto.setProcessorName(device.getProcessorName());
dto.setProcessorArchitecture(device.getProcessorArchitecture());
if (device.getGpuName() != null) {
dto.setGpuNames(
Arrays.stream(device.getGpuName().split(","))
.map(String::trim)
.collect(Collectors.toList())
);
} else {
dto.setGpuNames(Collections.emptyList());
}
dto.setTotalMemory(device.getTotalMemory());
dto.setLastBootTime(device.getLastBootTime());
dto.setLastCheckedIn(device.getLastCheckedIn());
dto.setDrives(device.getDrives().stream().map(d -> {
DriveInfoDTO dDto = new DriveInfoDTO();
dDto.setName(d.getName());
dDto.setTotalSizeGB(d.getTotalSizeGB());
dDto.setFreeSpaceGB(d.getFreeSpaceGB());
dDto.setDriveType(d.getDriveType());
return dDto;
}).collect(Collectors.toList()));
dto.setIpAddresses(
ipAddressRepository.findByDeviceDeviceId(deviceId).stream()
.filter(ip -> ip.getInterfaceName() == null || !ip.getInterfaceName().contains("Loopback Pseudo"))
.map(ip -> {
IpAddressDTO ipDto = new IpAddressDTO();
ipDto.setIpAddress(ip.getIpAddress());
ipDto.setInterfaceName(ip.getInterfaceName());
ipDto.setMacAddress(ip.getMacAddress());
return ipDto;
})
.collect(Collectors.toList())
);
dto.setInstalledApplications(
installedSoftwareRepository.findByDevice(device).stream()
.map(app -> {
InstalledAppDTO appDto = new InstalledAppDTO();
appDto.setApp_name(app.getAppName());
appDto.setApp_version(app.getAppVersion());
appDto.setPublisher(app.getPublisher());
return appDto;
})
.collect(Collectors.toList())
);
return dto;
}
public List<DeviceDTO> getAllDevices() {
List<Devices> devices = devicesRepository.findAll();
return devices.stream().map(device -> {
DeviceDTO dto = new DeviceDTO();
dto.setDeviceId(device.getDeviceId());
dto.setLastCheckedIn(device.getLastCheckedIn());
dto.setClientId(device.getClient().getClientId());
dto.setClientIdentifier(device.getClient().getClientIdentifier());
try {
dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
} catch (Exception e) {
dto.setHostname("DECRYPTION_FAILED");
}
try {
dto.setClientName(encryptionService.decryptData(device.getClient().getClientNameEncrypted()));
} catch (Exception e) {
dto.setClientName("[Decryption failed]");
}
return dto;
}).toList();
}
public Map<Long, List<CveMatchResult>> getVulnerabilitiesGroupedByDevice(Long clientId) {
List<Devices> devices = devicesRepository.findByClient_ClientId(clientId);
return devices.stream()
.collect(Collectors.toMap(
Devices::getDeviceId,
device -> vulnScanner.getVulnerabilitiesForDevice(device.getDeviceId())
));
}
@Transactional
public void deleteDeviceAndChildren(Long deviceId) {
Devices device = devicesRepository.findById(deviceId)
.orElseThrow(() -> new RuntimeException("Device not found"));
devicesRepository.delete(device); // 🚀 This cascades everything
}
@Transactional
public void reassignClient(Long deviceId, Long newClientId) {
Devices device = devicesRepository.findById(deviceId)
.orElseThrow(() -> new RuntimeException("Device not found with ID: " + deviceId));
Client newClient = clientRepository.findById(newClientId)
.orElseThrow(() -> new RuntimeException("Client not found with ID: " + newClientId));
device.setClient(newClient);
devicesRepository.save(device);
System.out.println("🔄 Reassigned device " + deviceId + " to client " + newClientId);
}
}

View File

@@ -0,0 +1,47 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Value("${app.mail.from}")
private String from;
@Value("${app.mail.to}")
private String to;
private final JavaMailSender mailSender;
private final Environment env;
@Autowired
public EmailService(JavaMailSender mailSender, Environment env) {
this.mailSender = mailSender;
this.env = env;
}
public void sendHtmlEmail(String subject, String htmlBody) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlBody, true); // `true` enables HTML
mailSender.send(message);
} catch (MessagingException e) {
System.err.println("❌ Error sending HTML email: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,51 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.util.NormalizationUtils;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class NormalizationService {
private final InstalledSoftwareRepository installedSoftwareRepository;
@Transactional
public int normalizeInstalledSoftware() {
List<InstalledSoftware> all = installedSoftwareRepository.findAll();
int updatedCount = 0;
for (InstalledSoftware app : all) {
String normPub = NormalizationUtils.simplifyPublisher(app.getPublisher());
String normProd = NormalizationUtils.normalizeProduct(app.getAppName());
String normName = NormalizationUtils.normalize(app.getAppName());
boolean changed = false;
if (!normPub.equals(app.getNormalizedPublisher())) {
app.setNormalizedPublisher(normPub);
changed = true;
}
if (!normProd.equals(app.getNormalizedProduct())) {
app.setNormalizedProduct(normProd);
changed = true;
}
if (!normName.equals(app.getNormalizedAppName())) {
app.setNormalizedAppName(normName);
changed = true;
}
if (changed) updatedCount++;
}
installedSoftwareRepository.saveAll(all);
return updatedCount;
}
}

View File

@@ -0,0 +1,118 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.*;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CveRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.DevicesRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SoftwareService {
@Autowired
private InstalledSoftwareRepository installedSoftwareRepository;
@Autowired
private DevicesRepository devicesRepository;
@Autowired
private CveRepository cveRepository;
@Autowired
private EncryptionService encryptionService;
@Transactional(readOnly = true)
public List<SoftwareSummaryDTO> getSoftwareOverviewForClient(Long clientId) {
List<InstalledSoftware> softwareList = installedSoftwareRepository.findAllByClientId(clientId);
return mapToSoftwareSummary(softwareList);
}
@Transactional(readOnly = true)
public List<SoftwareSummaryDTO> getSoftwareOverviewForAllClients() {
List<InstalledSoftware> softwareList = installedSoftwareRepository.findAllWithDeviceAndClient();
return mapToSoftwareSummary(softwareList);
}
private List<SoftwareSummaryDTO> mapToSoftwareSummary(List<InstalledSoftware> softwareList) {
System.out.println("📊 Mapping software summary for " + softwareList.size() + " entries");
// Step 1: Get distinct app names for batch lookup
List<String> productNames = softwareList.stream()
.map(InstalledSoftware::getAppName)
.filter(name -> name != null && !name.isBlank())
.map(String::toLowerCase)
.distinct()
.toList();
// Step 2: Batch-fetch CVEs
List<ProductCveDTO> cveList = cveRepository.findAllProductMatches(productNames);
// 🔥 Map to DeviceVulnerabilityDTO
Map<String, List<DeviceVulnerabilityDTO>> cveMap = cveList.stream()
.collect(Collectors.groupingBy(
dto -> dto.getProduct().toLowerCase(),
Collectors.mapping(
dto -> new DeviceVulnerabilityDTO(
dto.getCveId(),
dto.getDescription(), // using description as "title"
dto.getSeverity(),
null, // no score available in ProductCveDTO
null, // no published date either
null
),
Collectors.toList()
)
));
System.out.println("🛡️ CVEs fetched: " + cveList.size());
// Step 3: Group software entries
Map<String, List<SoftwareInstanceDTO>> softwareMap = new HashMap<>();
for (InstalledSoftware sw : softwareList) {
String hostname;
try {
hostname = encryptionService.decryptData(sw.getDevice().getEncryptedHostname());
} catch (Exception e) {
hostname = "DECRYPTION_FAILED";
}
String appName = sw.getAppName();
if (appName == null || appName.isBlank()) continue;
softwareMap.putIfAbsent(appName, new ArrayList<>());
SoftwareInstanceDTO instance = new SoftwareInstanceDTO();
instance.setId(sw.getId());
instance.setDeviceId(sw.getDevice().getDeviceId());
instance.setHostname(hostname);
instance.setVersion(sw.getAppVersion());
instance.setPublisher(sw.getPublisher());
instance.setLastUpdated(sw.getLastUpdated());
String key = appName.toLowerCase();
instance.setVulnerabilities(cveMap.getOrDefault(key, List.of())); // ✅ CORRECT now
softwareMap.get(appName).add(instance);
}
System.out.println("✅ Done mapping software entries");
// Step 4: Format for frontend
return softwareMap.entrySet().stream().map(entry -> {
SoftwareSummaryDTO summary = new SoftwareSummaryDTO();
summary.setSoftwareName(entry.getKey());
summary.setInstances(entry.getValue());
summary.setTotalDevices(entry.getValue().size());
return summary;
}).toList();
}
}

View File

@@ -0,0 +1,172 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.ChangePasswordRequest;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.UserProfileDTO;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.UserAuth;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.UserAuthRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Primary
@Transactional
public class UserService implements CustomUserDetailsService {
private final UserAuthRepository userAuthRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
private EncryptionService encryptionService;
@Autowired
public UserService(UserAuthRepository userAuthRepository, PasswordEncoder passwordEncoder) {
this.userAuthRepository = userAuthRepository;
this.passwordEncoder = passwordEncoder;
}
@Autowired
private ClientRepository clientRepository;
public UserAuth registerUser(String username, String password, String role, Long clientId) {
if (password == null || password.isEmpty()) {
throw new IllegalArgumentException("Password cannot be null or empty");
}
String hashedPassword = passwordEncoder.encode(password);
UserAuth userAuth = new UserAuth();
userAuth.setUsername(username);
userAuth.setPasswordHash(hashedPassword);
userAuth.setRole(role);
userAuth.setCreatedAt(java.time.LocalDateTime.now());
Client client = clientRepository.findById(clientId)
.orElseThrow(() -> new IllegalArgumentException("Invalid client ID"));
userAuth.setClient(client);
return userAuthRepository.save(userAuth);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserAuth> userOpt = userAuthRepository.findByUsername(username);
UserAuth userAuth = userOpt.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return User.builder()
.username(userAuth.getUsername())
.password(userAuth.getPasswordHash())
.roles(userAuth.getRole())
.build();
}
public UserAuth findByUsername(String username) {
return userAuthRepository.findByUsernameWithClient(username)
.orElse(null);
}
public boolean changePassword(String username, ChangePasswordRequest request) {
Optional<UserAuth> userOpt = userAuthRepository.findByUsername(username);
if (userOpt.isPresent()) {
UserAuth user = userOpt.get();
try {
String decryptedCurrentPassword = encryptionService.decryptData(request.getCurrentPassword());
String decryptedNewPassword = encryptionService.decryptData(request.getNewPassword());
if (passwordEncoder.matches(decryptedCurrentPassword, user.getPasswordHash())) {
user.setPasswordHash(passwordEncoder.encode(decryptedNewPassword));
user.setPasswordChangedAt(LocalDateTime.now());
userAuthRepository.save(user);
return true;
}
} catch (Exception e) {
System.out.println("❌ Decryption failed: " + e.getMessage());
}
}
return false;
}
public UserProfileDTO getUserProfile(String username) {
UserAuth user = userAuthRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
try {
return new UserProfileDTO(
user.getUsername(),
user.getDisplayNameHash() != null ? encryptionService.decryptData(user.getDisplayNameHash()) : "",
user.getFirstNameHash() != null ? encryptionService.decryptData(user.getFirstNameHash()) : "",
user.getLastNameHash() != null ? encryptionService.decryptData(user.getLastNameHash()) : "",
user.getEmailHash() != null ? encryptionService.decryptData(user.getEmailHash()) : ""
);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt user profile data", e);
}
}
public void updateUserProfile(String username, UserProfileDTO profileDto) {
UserAuth user = userAuthRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
try {
user.setDisplayNameHash(encryptionService.encryptData(profileDto.getDisplayName()));
user.setFirstNameHash(encryptionService.encryptData(profileDto.getFirstName()));
user.setLastNameHash(encryptionService.encryptData(profileDto.getLastName()));
user.setEmailHash(encryptionService.encryptData(profileDto.getEmail()));
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt user profile fields", e);
}
userAuthRepository.save(user);
}
public List<UserDTO> getAllDecryptedUsers() {
return userAuthRepository.findAll().stream()
.map(user -> {
try {
return new UserDTO(
user.getId(),
user.getUsername(),
encryptionService.decryptData(user.getDisplayNameHash()),
encryptionService.decryptData(user.getFirstNameHash()),
encryptionService.decryptData(user.getLastNameHash()),
encryptionService.decryptData(user.getEmailHash()),
user.getRole(),
user.getClient().getClientIdentifier(),
encryptionService.decryptData(user.getClient().getClientNameEncrypted()),
user.isEnabled()
);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt user data for user: " + user.getUsername(), e);
}
})
.collect(Collectors.toList());
}
public UserAuth save(UserAuth user) {
return userAuthRepository.save(user);
}
public void setUserEnabledState(Long userId, boolean enabled) {
UserAuth user = userAuthRepository.findById(userId)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
user.setEnabled(enabled);
userAuthRepository.save(user);
}
}

View File

@@ -0,0 +1,175 @@
package com.psg.dlsysinfo.dl_sysinfo_server.service;
import com.psg.dlsysinfo.dl_sysinfo_server.dto.RawCveMatch;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedInstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResultImpl;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CveRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository;
import lombok.RequiredArgsConstructor;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class VulnerabilityScannerService {
private final InstalledSoftwareRepository installedSoftwareRepository;
private final CveRepository cveRepository;
private final boolean LENIENT_VERSION_MATCHING = true;
private final boolean DEBUG = true;
public List<CveMatchResult> getVulnerabilitiesForDevice(Long deviceId) {
List<Object[]> rawRows = cveRepository.findUnfilteredMatches(deviceId);
if (DEBUG) {
System.out.println("✅ Raw match count for device " + deviceId + ": " + rawRows.size());
}
return rawRows.stream()
.map(this::mapToRawCveMatch)
.peek(match -> {
System.out.printf(
"[Device %d] 🧪 CVE %-12s | Installed: %-10s | Start: %-10s | End: %-10s | Version: %-10s\n",
deviceId,
match.cveId(),
match.appVersion(),
match.versionStart(),
match.versionEnd(),
match.version()
);
})
.filter(match -> {
boolean result = isVulnerable(match);
System.out.println(result ? "✅ Marked vulnerable!" : "❌ Skipped (not vulnerable).");
return result;
})
.map(this::toCveMatchResult)
.toList();
}
private RawCveMatch mapToRawCveMatch(Object[] row) {
String cveId = (String) row[0]; // v.id
String description = (String) row[1]; // v.description
String severity = (String) row[2]; // ✅ v.severity
String versionStart = (String) row[3]; // c.version_start
String versionEnd = (String) row[4]; // c.version_end
String version = (String) row[5]; // c.version
String appVersion = (String) row[6]; // i.appVersion
String appName = (String) row[7]; // i.appName
Double score = row[9] != null ? ((Number) row[9]).doubleValue() : null; // v.cvss_score
java.time.LocalDateTime lastModified = row[10] != null
? ((java.sql.Timestamp) row[10]).toLocalDateTime()
: null;
boolean lenient = LENIENT_VERSION_MATCHING
&& versionStart == null && versionEnd == null
&& version != null && !version.equals("*") && !version.equals("-");
return new RawCveMatch(
appName, // ✅ appName (index 7)
appVersion, // ✅ appVersion (index 6)
versionStart, // ✅ versionStart (index 3)
versionEnd, // ✅ versionEnd (index 4)
version, // ✅ version (index 5)
cveId, // ✅ cveId (index 0)
description, // ✅ description (index 1)
severity, // ✅ severity (index 2)
score, // ✅ score (index 9)
lastModified, // ✅ lastModifiedDate (index 10)
lenient // ✅ lenient match flag
);
}
private boolean isVulnerable(RawCveMatch match) {
if (match.appVersion() == null || match.appVersion().isBlank()) return false;
ComparableVersion installed = new ComparableVersion(match.appVersion().trim());
boolean inRange = false;
if (match.versionStart() != null || match.versionEnd() != null) {
boolean startOK = match.versionStart() == null || installed.compareTo(new ComparableVersion(match.versionStart().trim())) >= 0;
boolean endOK = match.versionEnd() == null || installed.compareTo(new ComparableVersion(match.versionEnd().trim())) <= 0;
inRange = startOK && endOK;
}
boolean exactOrOlder = false;
if (match.version() != null && !match.version().equals("*") && !match.version().equals("-")) {
ComparableVersion vuln = new ComparableVersion(match.version().trim());
if (LENIENT_VERSION_MATCHING) {
exactOrOlder = installed.compareTo(vuln) <= 0; // vulnerable if installed version <= vuln version
} else {
exactOrOlder = installed.compareTo(vuln) == 0; // only match exact
}
}
return inRange || exactOrOlder;
}
private boolean isVersionInRange(ComparableVersion installed, String start, String end) {
boolean startOK = (start == null || installed.compareTo(new ComparableVersion(start)) >= 0);
boolean endOK = (end == null || installed.compareTo(new ComparableVersion(end)) <= 0);
return startOK && endOK;
}
private CveMatchResult toCveMatchResult(RawCveMatch raw) {
return new CveMatchResultImpl(
raw.cveId(),
raw.severity(),
raw.cvssScore(),
raw.description(),
raw.appName(),
raw.appVersion(),
raw.lastModifiedDate(),
raw.lenient() // ✅ correctly passed now
);
}
public Page<CveMatchResult> getPaginatedVulnerabilitiesForDevice(Long deviceId, Pageable pageable) {
// fallback for paginated SQL-based view
return cveRepository.findPaginatedVulnerabilitiesForDevice(deviceId, pageable);
}
private List<CveMatchResult> getVulnerabilitiesForSoftware(Long deviceId, String softwareName, String appVersion) {
List<Object[]> rawRows = cveRepository.findUnfilteredMatches(deviceId);
return rawRows.stream()
.map(this::mapToRawCveMatch)
.filter(match -> {
boolean nameMatch = match.appName().equalsIgnoreCase(softwareName);
boolean versionMatch = match.appVersion().equalsIgnoreCase(appVersion);
boolean vulnerable = isVulnerable(match);
return nameMatch && versionMatch && vulnerable;
})
.map(this::toCveMatchResult)
.toList();
}
public void enrichCachedSoftwareEntries(List<CachedInstalledSoftware> entries) {
for (CachedInstalledSoftware software : entries) {
List<CveMatchResult> vulns = getVulnerabilitiesForSoftware(software.getDeviceId(), software.getSoftwareName(), software.getAppVersion());
software.setTotalCves(vulns.size());
software.setCveList(
vulns.stream()
.map(CveMatchResult::getCveId)
.collect(Collectors.joining(","))
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More