Compare commits

..
4 changed files with 65 additions and 35 deletions
@@ -367,8 +367,8 @@ public class ApplicationProperties {
private TempFileManagement tempFileManagement = new TempFileManagement();
private DatabaseBackup databaseBackup = new DatabaseBackup();
private List<String> corsAllowedOrigins = new ArrayList<>();
private String frontendUrl; // Base URL for frontend (used for invite links, etc.). If not set,
private ServerCertificate serverCertificate = new ServerCertificate();
private String
frontendUrl; // Base URL for frontend (used for invite links, etc.). If not set,
// falls back to backend URL.
@@ -387,14 +387,6 @@ public class ApplicationProperties {
return this.isAnalyticsEnabled()
&& (this.getEnableScarf() == null || this.getEnableScarf());
}
@Data
public static class ServerCertificate {
private boolean enabled = true;
private String organizationName = "Stirling-PDF";
private int validity = 365; // days
private boolean regenerateOnStartup = false;
}
}
@Data
@@ -191,6 +191,12 @@ public class CertSignController {
switch (certType) {
case "PEM":
privateKeyFile =
validateFilePresent(
privateKeyFile, "PEM private key", "private key file is required");
certFile =
validateFilePresent(
certFile, "PEM certificate", "certificate file is required");
ks = KeyStore.getInstance("JKS");
ks.load(null);
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
@@ -200,10 +206,16 @@ public class CertSignController {
break;
case "PKCS12":
case "PFX":
p12File =
validateFilePresent(
p12File, "PKCS12 keystore", "PKCS12/PFX keystore file is required");
ks = KeyStore.getInstance("PKCS12");
ks.load(p12File.getInputStream(), password.toCharArray());
break;
case "JKS":
jksfile =
validateFilePresent(
jksfile, "JKS keystore", "JKS keystore file is required");
ks = KeyStore.getInstance("JKS");
ks.load(jksfile.getInputStream(), password.toCharArray());
break;
@@ -251,6 +263,17 @@ public class CertSignController {
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_signed.pdf"));
}
private MultipartFile validateFilePresent(
MultipartFile file, String argumentName, String errorDescription) {
if (file == null || file.isEmpty()) {
throw ExceptionUtils.createIllegalArgumentException(
"error.invalidArgument",
"Invalid argument: {0}",
argumentName + " - " + errorDescription);
}
return file;
}
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
throws IOException, OperatorCreationException, PKCSException {
try (PEMParser pemParser =
@@ -1,9 +1,10 @@
package stirling.software.SPDF.controller.api.security;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.lenient;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@@ -107,7 +108,8 @@ class CertSignControllerTest {
derCertBytes = baos.toByteArray();
}
when(pdfDocumentFactory.load(any(MultipartFile.class)))
lenient()
.when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(
invocation -> {
MultipartFile file = invocation.getArgument(0);
@@ -167,6 +169,31 @@ class CertSignControllerTest {
assertTrue(response.getBody().length > 0);
}
@Test
void testSignPdfWithMissingPkcs12FileThrowsError() {
MockMultipartFile pdfFile =
new MockMultipartFile(
"fileInput", "test.pdf", MediaType.APPLICATION_PDF_VALUE, pdfBytes);
SignPDFWithCertRequest request = new SignPDFWithCertRequest();
request.setFileInput(pdfFile);
request.setCertType("PFX");
request.setPassword("password");
request.setShowSignature(false);
request.setReason("test");
request.setLocation("test");
request.setName("tester");
request.setPageNumber(1);
request.setShowLogo(false);
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() -> certSignController.signPDFWithCert(request));
assertTrue(exception.getMessage().contains("PKCS12 keystore"));
}
@Test
void testSignPdfWithJks() throws Exception {
MockMultipartFile pdfFile =
+11 -23
View File
@@ -93,6 +93,15 @@ export function useAdminSettings<T = any>(
try {
setSaving(true);
// Compute delta: only include fields that changed from original
const delta = computeDelta(originalSettings, settings);
console.log(`[useAdminSettings:${sectionName}] Delta (changed fields):`, JSON.stringify(delta, null, 2));
if (Object.keys(delta).length === 0) {
console.log(`[useAdminSettings:${sectionName}] No changes detected, skipping save`);
return;
}
if (saveTransformer) {
// Use custom save logic for complex sections
const { sectionData, deltaSettings } = saveTransformer(settings);
@@ -102,14 +111,12 @@ export function useAdminSettings<T = any>(
// Save section data (with delta applied) - compare transformed vs transformed
const sectionDelta = computeDelta(originalSectionData, sectionData);
const hasSectionDelta = Object.keys(sectionDelta).length > 0;
if (hasSectionDelta) {
if (Object.keys(sectionDelta).length > 0) {
await apiClient.put(`/api/v1/admin/settings/section/${sectionName}`, sectionDelta);
}
// Save delta settings if provided (filter to only changed values)
const hasDeltaSettings = deltaSettings && Object.keys(deltaSettings).length > 0;
if (hasDeltaSettings) {
if (deltaSettings && Object.keys(deltaSettings).length > 0) {
// Build deltaSettings from original using same transformer to get correct structure
const { deltaSettings: originalDeltaSettings } = saveTransformer(originalSettings);
@@ -139,27 +146,8 @@ export function useAdminSettings<T = any>(
} else {
console.log(`[useAdminSettings:${sectionName}] No delta settings changed, skipping`);
}
} else {
console.log(`[useAdminSettings:${sectionName}] No delta settings provided or changed, skipping delta save`);
}
if (!hasSectionDelta && !hasDeltaSettings) {
console.log(`[useAdminSettings:${sectionName}] No changes detected after transformation, skipping save`);
return;
}
} else {
// Compute delta: only include fields that changed from original
const delta = computeDelta(originalSettings, settings);
console.log(
`[useAdminSettings:${sectionName}] Delta (changed fields):`,
JSON.stringify(delta, null, 2)
);
if (Object.keys(delta).length === 0) {
console.log(`[useAdminSettings:${sectionName}] No changes detected, skipping save`);
return;
}
// Simple single-endpoint save with delta
await apiClient.put(`/api/v1/admin/settings/section/${sectionName}`, delta);
}