feat(back-end): add ClamAV service remember to add env and compose.deploy on server
This commit is contained in:
@@ -30,6 +30,7 @@ dependencies {
|
|||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
implementation 'xyz.capybara:clamav-client:2.1.2'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,17 @@ public class CustomQuoteRequestController {
|
|||||||
|
|
||||||
private final CustomQuoteRequestRepository requestRepo;
|
private final CustomQuoteRequestRepository requestRepo;
|
||||||
private final CustomQuoteRequestAttachmentRepository attachmentRepo;
|
private final CustomQuoteRequestAttachmentRepository attachmentRepo;
|
||||||
|
private final com.printcalculator.service.ClamAVService clamAVService;
|
||||||
|
|
||||||
// TODO: Inject Storage Service
|
// TODO: Inject Storage Service
|
||||||
private static final String STORAGE_ROOT = "storage_requests";
|
private static final String STORAGE_ROOT = "storage_requests";
|
||||||
|
|
||||||
public CustomQuoteRequestController(CustomQuoteRequestRepository requestRepo,
|
public CustomQuoteRequestController(CustomQuoteRequestRepository requestRepo,
|
||||||
CustomQuoteRequestAttachmentRepository attachmentRepo) {
|
CustomQuoteRequestAttachmentRepository attachmentRepo,
|
||||||
|
com.printcalculator.service.ClamAVService clamAVService) {
|
||||||
this.requestRepo = requestRepo;
|
this.requestRepo = requestRepo;
|
||||||
this.attachmentRepo = attachmentRepo;
|
this.attachmentRepo = attachmentRepo;
|
||||||
|
this.clamAVService = clamAVService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Create Custom Quote Request
|
// 1. Create Custom Quote Request
|
||||||
@@ -68,6 +71,9 @@ public class CustomQuoteRequestController {
|
|||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
if (file.isEmpty()) continue;
|
if (file.isEmpty()) continue;
|
||||||
|
|
||||||
|
// Scan for virus
|
||||||
|
clamAVService.scan(file.getInputStream());
|
||||||
|
|
||||||
CustomQuoteRequestAttachment attachment = new CustomQuoteRequestAttachment();
|
CustomQuoteRequestAttachment attachment = new CustomQuoteRequestAttachment();
|
||||||
attachment.setRequest(request);
|
attachment.setRequest(request);
|
||||||
attachment.setOriginalFilename(file.getOriginalFilename());
|
attachment.setOriginalFilename(file.getOriginalFilename());
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class OrderController {
|
|||||||
private final QuoteSessionRepository quoteSessionRepo;
|
private final QuoteSessionRepository quoteSessionRepo;
|
||||||
private final QuoteLineItemRepository quoteLineItemRepo;
|
private final QuoteLineItemRepository quoteLineItemRepo;
|
||||||
private final CustomerRepository customerRepo;
|
private final CustomerRepository customerRepo;
|
||||||
|
private final com.printcalculator.service.ClamAVService clamAVService;
|
||||||
|
|
||||||
// TODO: Inject Storage Service or use a base path property
|
// TODO: Inject Storage Service or use a base path property
|
||||||
private static final String STORAGE_ROOT = "storage_orders";
|
private static final String STORAGE_ROOT = "storage_orders";
|
||||||
@@ -36,12 +37,14 @@ public class OrderController {
|
|||||||
OrderItemRepository orderItemRepo,
|
OrderItemRepository orderItemRepo,
|
||||||
QuoteSessionRepository quoteSessionRepo,
|
QuoteSessionRepository quoteSessionRepo,
|
||||||
QuoteLineItemRepository quoteLineItemRepo,
|
QuoteLineItemRepository quoteLineItemRepo,
|
||||||
CustomerRepository customerRepo) {
|
CustomerRepository customerRepo,
|
||||||
|
com.printcalculator.service.ClamAVService clamAVService) {
|
||||||
this.orderRepo = orderRepo;
|
this.orderRepo = orderRepo;
|
||||||
this.orderItemRepo = orderItemRepo;
|
this.orderItemRepo = orderItemRepo;
|
||||||
this.quoteSessionRepo = quoteSessionRepo;
|
this.quoteSessionRepo = quoteSessionRepo;
|
||||||
this.quoteLineItemRepo = quoteLineItemRepo;
|
this.quoteLineItemRepo = quoteLineItemRepo;
|
||||||
this.customerRepo = customerRepo;
|
this.customerRepo = customerRepo;
|
||||||
|
this.clamAVService = clamAVService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -229,6 +232,9 @@ public class OrderController {
|
|||||||
if (!item.getOrder().getId().equals(orderId)) {
|
if (!item.getOrder().getId().equals(orderId)) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan for virus
|
||||||
|
clamAVService.scan(file.getInputStream());
|
||||||
|
|
||||||
// Ensure path logic
|
// Ensure path logic
|
||||||
String relativePath = item.getStoredRelativePath();
|
String relativePath = item.getStoredRelativePath();
|
||||||
|
|||||||
@@ -22,15 +22,17 @@ public class QuoteController {
|
|||||||
private final SlicerService slicerService;
|
private final SlicerService slicerService;
|
||||||
private final QuoteCalculator quoteCalculator;
|
private final QuoteCalculator quoteCalculator;
|
||||||
private final PrinterMachineRepository machineRepo;
|
private final PrinterMachineRepository machineRepo;
|
||||||
|
private final com.printcalculator.service.ClamAVService clamAVService;
|
||||||
|
|
||||||
// Defaults (using aliases defined in ProfileManager)
|
// Defaults (using aliases defined in ProfileManager)
|
||||||
private static final String DEFAULT_FILAMENT = "pla_basic";
|
private static final String DEFAULT_FILAMENT = "pla_basic";
|
||||||
private static final String DEFAULT_PROCESS = "standard";
|
private static final String DEFAULT_PROCESS = "standard";
|
||||||
|
|
||||||
public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator, PrinterMachineRepository machineRepo) {
|
public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator, PrinterMachineRepository machineRepo, com.printcalculator.service.ClamAVService clamAVService) {
|
||||||
this.slicerService = slicerService;
|
this.slicerService = slicerService;
|
||||||
this.quoteCalculator = quoteCalculator;
|
this.quoteCalculator = quoteCalculator;
|
||||||
this.machineRepo = machineRepo;
|
this.machineRepo = machineRepo;
|
||||||
|
this.clamAVService = clamAVService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/api/quote")
|
@PostMapping("/api/quote")
|
||||||
@@ -99,6 +101,9 @@ public class QuoteController {
|
|||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan for virus
|
||||||
|
clamAVService.scan(file.getInputStream());
|
||||||
|
|
||||||
// Fetch Default Active Machine
|
// Fetch Default Active Machine
|
||||||
PrinterMachine machine = machineRepo.findFirstByIsActiveTrue()
|
PrinterMachine machine = machineRepo.findFirstByIsActiveTrue()
|
||||||
.orElseThrow(() -> new IOException("No active printer found in database"));
|
.orElseThrow(() -> new IOException("No active printer found in database"));
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public class QuoteSessionController {
|
|||||||
private final QuoteCalculator quoteCalculator;
|
private final QuoteCalculator quoteCalculator;
|
||||||
private final PrinterMachineRepository machineRepo;
|
private final PrinterMachineRepository machineRepo;
|
||||||
private final com.printcalculator.repository.PricingPolicyRepository pricingRepo;
|
private final com.printcalculator.repository.PricingPolicyRepository pricingRepo;
|
||||||
|
private final com.printcalculator.service.ClamAVService clamAVService;
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
private static final String DEFAULT_FILAMENT = "pla_basic";
|
private static final String DEFAULT_FILAMENT = "pla_basic";
|
||||||
@@ -50,13 +51,15 @@ public class QuoteSessionController {
|
|||||||
SlicerService slicerService,
|
SlicerService slicerService,
|
||||||
QuoteCalculator quoteCalculator,
|
QuoteCalculator quoteCalculator,
|
||||||
PrinterMachineRepository machineRepo,
|
PrinterMachineRepository machineRepo,
|
||||||
com.printcalculator.repository.PricingPolicyRepository pricingRepo) {
|
com.printcalculator.repository.PricingPolicyRepository pricingRepo,
|
||||||
|
com.printcalculator.service.ClamAVService clamAVService) {
|
||||||
this.sessionRepo = sessionRepo;
|
this.sessionRepo = sessionRepo;
|
||||||
this.lineItemRepo = lineItemRepo;
|
this.lineItemRepo = lineItemRepo;
|
||||||
this.slicerService = slicerService;
|
this.slicerService = slicerService;
|
||||||
this.quoteCalculator = quoteCalculator;
|
this.quoteCalculator = quoteCalculator;
|
||||||
this.machineRepo = machineRepo;
|
this.machineRepo = machineRepo;
|
||||||
this.pricingRepo = pricingRepo;
|
this.pricingRepo = pricingRepo;
|
||||||
|
this.clamAVService = clamAVService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Start a new empty session
|
// 1. Start a new empty session
|
||||||
@@ -99,6 +102,9 @@ public class QuoteSessionController {
|
|||||||
private QuoteLineItem addItemToSession(QuoteSession session, MultipartFile file, com.printcalculator.dto.PrintSettingsDto settings) throws IOException {
|
private QuoteLineItem addItemToSession(QuoteSession session, MultipartFile file, com.printcalculator.dto.PrintSettingsDto settings) throws IOException {
|
||||||
if (file.isEmpty()) throw new IOException("File is empty");
|
if (file.isEmpty()) throw new IOException("File is empty");
|
||||||
|
|
||||||
|
// Scan for virus
|
||||||
|
clamAVService.scan(file.getInputStream());
|
||||||
|
|
||||||
// 1. Define Persistent Storage Path
|
// 1. Define Persistent Storage Path
|
||||||
// Structure: storage_quotes/{sessionId}/{uuid}.{ext}
|
// Structure: storage_quotes/{sessionId}/{uuid}.{ext}
|
||||||
String storageDir = "storage_quotes/" + session.getId();
|
String storageDir = "storage_quotes/" + session.getId();
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.printcalculator.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(VirusDetectedException.class)
|
||||||
|
public ResponseEntity<Object> handleVirusDetectedException(
|
||||||
|
VirusDetectedException ex, WebRequest request) {
|
||||||
|
|
||||||
|
Map<String, Object> body = new LinkedHashMap<>();
|
||||||
|
body.put("timestamp", LocalDateTime.now());
|
||||||
|
body.put("message", ex.getMessage());
|
||||||
|
body.put("error", "Virus Detected");
|
||||||
|
|
||||||
|
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.printcalculator.exception;
|
||||||
|
|
||||||
|
public class VirusDetectedException extends RuntimeException {
|
||||||
|
public VirusDetectedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.printcalculator.service;
|
||||||
|
|
||||||
|
import com.printcalculator.exception.VirusDetectedException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import xyz.capybara.clamav.ClamavClient;
|
||||||
|
import xyz.capybara.clamav.commands.scan.result.ScanResult;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ClamAVService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClamAVService.class);
|
||||||
|
|
||||||
|
private final ClamavClient clamavClient;
|
||||||
|
private final boolean enabled;
|
||||||
|
|
||||||
|
public ClamAVService(
|
||||||
|
@Value("${clamav.host:clamav}") String host,
|
||||||
|
@Value("${clamav.port:3310}") int port,
|
||||||
|
@Value("${clamav.enabled:true}") boolean enabled
|
||||||
|
) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
ClamavClient client = null;
|
||||||
|
try {
|
||||||
|
if (enabled) {
|
||||||
|
logger.info("Initializing ClamAV client at {}:{}", host, port);
|
||||||
|
client = new ClamavClient(host, port);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to initialize ClamAV client: " + e.getMessage());
|
||||||
|
}
|
||||||
|
this.clamavClient = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean scan(InputStream inputStream) {
|
||||||
|
if (!enabled || clamavClient == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ScanResult result = clamavClient.scan(inputStream);
|
||||||
|
if (result instanceof ScanResult.OK) {
|
||||||
|
return true;
|
||||||
|
} else if (result instanceof ScanResult.VirusFound) {
|
||||||
|
Map<String, Collection<String>> viruses = ((ScanResult.VirusFound) result).getFoundViruses();
|
||||||
|
logger.warn("VIRUS DETECTED: {}", viruses);
|
||||||
|
throw new VirusDetectedException("Virus detected in the uploaded file: " + viruses);
|
||||||
|
} else {
|
||||||
|
logger.warn("Unknown scan result: {}. Allowing file (FAIL-OPEN)", result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (VirusDetectedException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error scanning file with ClamAV. Allowing file (FAIL-OPEN)", e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,4 +7,7 @@ TAG=dev
|
|||||||
BACKEND_PORT=18002
|
BACKEND_PORT=18002
|
||||||
FRONTEND_PORT=18082
|
FRONTEND_PORT=18082
|
||||||
|
|
||||||
|
CLAMAV_HOST=192.168.1.147
|
||||||
|
CLAMAV_PORT=3310
|
||||||
|
CLAMAV_ENABLED=true
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ TAG=int
|
|||||||
BACKEND_PORT=18001
|
BACKEND_PORT=18001
|
||||||
FRONTEND_PORT=18081
|
FRONTEND_PORT=18081
|
||||||
|
|
||||||
|
CLAMAV_HOST=192.168.1.147
|
||||||
|
CLAMAV_PORT=3310
|
||||||
|
CLAMAV_ENABLED=true
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ TAG=prod
|
|||||||
BACKEND_PORT=8000
|
BACKEND_PORT=8000
|
||||||
FRONTEND_PORT=80
|
FRONTEND_PORT=80
|
||||||
|
|
||||||
|
CLAMAV_HOST=192.168.1.147
|
||||||
|
CLAMAV_PORT=3310
|
||||||
|
CLAMAV_ENABLED=true
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ services:
|
|||||||
- DB_URL=${DB_URL}
|
- DB_URL=${DB_URL}
|
||||||
- DB_USERNAME=${DB_USERNAME}
|
- DB_USERNAME=${DB_USERNAME}
|
||||||
- DB_PASSWORD=${DB_PASSWORD}
|
- DB_PASSWORD=${DB_PASSWORD}
|
||||||
|
- CLAMAV_HOST=${CLAMAV_HOST}
|
||||||
|
- CLAMAV_PORT=${CLAMAV_PORT}
|
||||||
|
- CLAMAV_ENABLED=${CLAMAV_ENABLED}
|
||||||
- TEMP_DIR=/app/temp
|
- TEMP_DIR=/app/temp
|
||||||
- PROFILES_DIR=/app/profiles
|
- PROFILES_DIR=/app/profiles
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ services:
|
|||||||
- MARKUP_PERCENT=20
|
- MARKUP_PERCENT=20
|
||||||
- TEMP_DIR=/app/temp
|
- TEMP_DIR=/app/temp
|
||||||
- PROFILES_DIR=/app/profiles
|
- PROFILES_DIR=/app/profiles
|
||||||
|
- CLAMAV_HOST=clamav
|
||||||
|
- CLAMAV_PORT=3310
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
- clamav
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
@@ -49,5 +52,13 @@ services:
|
|||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
clamav:
|
||||||
|
platform: linux/amd64
|
||||||
|
image: clamav/clamav:latest
|
||||||
|
container_name: print-calculator-clamav
|
||||||
|
ports:
|
||||||
|
- "3310:3310"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
Reference in New Issue
Block a user