Initial commit of ld-sysinfo-server backend
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.psg.dlsysinfo.dl_sysinfo_server.controller;
|
||||
|
||||
public enum ActionType {
|
||||
ADD,
|
||||
DELETE
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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 we’re 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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") + "]";
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
) {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 // ✅
|
||||
) {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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...
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.psg.dlsysinfo.dl_sysinfo_server.projection;
|
||||
|
||||
public interface CveSummaryProjection {
|
||||
|
||||
String getId();
|
||||
String getTitle();
|
||||
String getSeverityV3();
|
||||
Double getCvssScoreV3();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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("<", "<").replace(">", ">"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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); // you’ll 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user