diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 7b8933c67..0ffe2e26e 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -303,11 +303,10 @@ public class ApplicationProperties { @Data public static class Jwt { - private boolean enableKeystore = true; - private boolean enableKeyRotation = false; - private boolean enableKeyCleanup = true; + private boolean enabled = true; + private boolean keyCleanup = true; private int keyRetentionDays = 7; - private boolean secureCookie; + private Boolean secureCookie; } } @@ -377,16 +376,19 @@ public class ApplicationProperties { @JsonIgnore public String getBaseTmpDir() { - return baseTmpDir != null && !baseTmpDir.isEmpty() - ? baseTmpDir - : java.lang.System.getProperty("java.io.tmpdir") + "/stirling-pdf"; + if (baseTmpDir != null && !baseTmpDir.isEmpty()) { + return baseTmpDir; + } + String tmp = java.lang.System.getProperty("java.io.tmpdir"); + return new File(tmp, "stirling-pdf").getPath(); } @JsonIgnore public String getLibreofficeDir() { - return libreofficeDir != null && !libreofficeDir.isEmpty() - ? libreofficeDir - : getBaseTmpDir() + "/libreoffice"; + if (libreofficeDir != null && !libreofficeDir.isEmpty()) { + return libreofficeDir; + } + return new File(getBaseTmpDir(), "libreoffice").getPath(); } } diff --git a/app/common/src/main/java/stirling/software/common/util/PdfUtils.java b/app/common/src/main/java/stirling/software/common/util/PdfUtils.java index 65cb27432..842baffcb 100644 --- a/app/common/src/main/java/stirling/software/common/util/PdfUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/PdfUtils.java @@ -636,7 +636,7 @@ public class PdfUtils { case "equal" -> actualPageCount == pageCount; case "less" -> actualPageCount < pageCount; default -> - throw ExceptionUtils.createInvalidArgumentException("comparator", comparator); + throw ExceptionUtils.createInvalidArgumentException("comparator", comparator); }; } @@ -659,15 +659,9 @@ public class PdfUtils { return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; } - /** - * Key for storing the dimensions of a rendered image in a map. - */ - private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) { - } + /** Key for storing the dimensions of a rendered image in a map. */ + private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {} - /** - * Value for storing the dimensions of a rendered image in a map. - */ - private record PdfImageDimensionValue(int width, int height) { - } + /** Value for storing the dimensions of a rendered image in a map. */ + private record PdfImageDimensionValue(int width, int height) {} } diff --git a/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java b/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java index c6068f25f..3f06ce14f 100644 --- a/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java +++ b/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java @@ -14,7 +14,6 @@ import stirling.software.common.model.ApplicationProperties.Driver; import stirling.software.common.model.ApplicationProperties.Premium; import stirling.software.common.model.ApplicationProperties.Security; import stirling.software.common.model.exception.UnsupportedProviderException; -import stirling.software.common.util.RegexPatternUtils; class ApplicationPropertiesLogicTest { @@ -39,15 +38,12 @@ class ApplicationPropertiesLogicTest { new ApplicationProperties.TempFileManagement(); String expectedBase = - RegexPatternUtils.getInstance() - .getTrailingSlashesPattern() - .matcher(java.lang.System.getProperty("java.io.tmpdir")) - .replaceAll("") - + "/stirling-pdf"; - assertEquals(expectedBase, normalize.apply(tfm.getBaseTmpDir())); + Paths.get(java.lang.System.getProperty("java.io.tmpdir"), "stirling-pdf") + .toString(); + assertEquals(expectedBase, tfm.getBaseTmpDir()); - String expectedLibre = expectedBase + "/libreoffice"; - assertEquals(expectedLibre, normalize.apply(tfm.getLibreofficeDir())); + String expectedLibre = Paths.get(expectedBase, "libreoffice").toString(); + assertEquals(expectedLibre, tfm.getLibreofficeDir()); tfm.setBaseTmpDir("/custom/base"); assertEquals("/custom/base", normalize.apply(tfm.getBaseTmpDir())); diff --git a/app/core/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java b/app/core/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java index 35b3af62d..c1267ed56 100644 --- a/app/core/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java +++ b/app/core/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java @@ -227,7 +227,9 @@ public class LoadingWindow extends JDialog { if (!existingPids .contains( pid)) { - log.debug("Found new explorer.exe with PID: {}", pid); + log.debug( + "Found new explorer.exe with PID: {}", + pid); ProcessBuilder killProcess = new ProcessBuilder( @@ -245,7 +247,9 @@ public class LoadingWindow extends JDialog { 2, TimeUnit .SECONDS); - log.debug("Explorer process terminated: {}", pid); + log.debug( + "Explorer process terminated: {}", + pid); } } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java index 1eee5ff40..391eb8f0e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java @@ -87,13 +87,15 @@ public class FilterController { PDDocument document = pdfDocumentFactory.load(inputFile); int actualPageCount = document.getNumberOfPages(); // Perform the comparison - boolean valid = switch (comparator) { - case "Greater" -> actualPageCount > pageCount; - case "Equal" -> actualPageCount == pageCount; - case "Less" -> actualPageCount < pageCount; - default -> - throw ExceptionUtils.createInvalidArgumentException("comparator", comparator); - }; + boolean valid = + switch (comparator) { + case "Greater" -> actualPageCount > pageCount; + case "Equal" -> actualPageCount == pageCount; + case "Less" -> actualPageCount < pageCount; + default -> + throw ExceptionUtils.createInvalidArgumentException( + "comparator", comparator); + }; if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); return null; @@ -123,13 +125,15 @@ public class FilterController { float standardArea = standardSize.getWidth() * standardSize.getHeight(); // Perform the comparison - boolean valid = switch (comparator) { - case "Greater" -> actualArea > standardArea; - case "Equal" -> actualArea == standardArea; - case "Less" -> actualArea < standardArea; - default -> - throw ExceptionUtils.createInvalidArgumentException("comparator", comparator); - }; + boolean valid = + switch (comparator) { + case "Greater" -> actualArea > standardArea; + case "Equal" -> actualArea == standardArea; + case "Less" -> actualArea < standardArea; + default -> + throw ExceptionUtils.createInvalidArgumentException( + "comparator", comparator); + }; if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); return null; @@ -149,13 +153,15 @@ public class FilterController { long actualFileSize = inputFile.getSize(); // Perform the comparison - boolean valid = switch (comparator) { - case "Greater" -> actualFileSize > fileSize; - case "Equal" -> actualFileSize == fileSize; - case "Less" -> actualFileSize < fileSize; - default -> - throw ExceptionUtils.createInvalidArgumentException("comparator", comparator); - }; + boolean valid = + switch (comparator) { + case "Greater" -> actualFileSize > fileSize; + case "Equal" -> actualFileSize == fileSize; + case "Less" -> actualFileSize < fileSize; + default -> + throw ExceptionUtils.createInvalidArgumentException( + "comparator", comparator); + }; if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); return null; @@ -179,13 +185,15 @@ public class FilterController { int actualRotation = firstPage.getRotation(); // Perform the comparison - boolean valid = switch (comparator) { - case "Greater" -> actualRotation > rotation; - case "Equal" -> actualRotation == rotation; - case "Less" -> actualRotation < rotation; - default -> - throw ExceptionUtils.createInvalidArgumentException("comparator", comparator); - }; + boolean valid = + switch (comparator) { + case "Greater" -> actualRotation > rotation; + case "Equal" -> actualRotation == rotation; + case "Less" -> actualRotation < rotation; + default -> + throw ExceptionUtils.createInvalidArgumentException( + "comparator", comparator); + }; if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); return null; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java index cc21bd084..d857ea7f0 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java @@ -23,9 +23,9 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.misc.MetadataRequest; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.RegexPatternUtils; -import stirling.software.common.service.PdfMetadataService; import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index 00cf6bd9b..23546f93d 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -170,14 +170,15 @@ public class WatermarkController { throws IOException { String resourceDir = ""; PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); - resourceDir = switch (alphabet) { - case "arabic" -> "static/fonts/NotoSansArabic-Regular.ttf"; - case "japanese" -> "static/fonts/Meiryo.ttf"; - case "korean" -> "static/fonts/malgun.ttf"; - case "chinese" -> "static/fonts/SimSun.ttf"; - case "thai" -> "static/fonts/NotoSansThai-Regular.ttf"; - default -> "static/fonts/NotoSans-Regular.ttf"; - }; + resourceDir = + switch (alphabet) { + case "arabic" -> "static/fonts/NotoSansArabic-Regular.ttf"; + case "japanese" -> "static/fonts/Meiryo.ttf"; + case "korean" -> "static/fonts/malgun.ttf"; + case "chinese" -> "static/fonts/SimSun.ttf"; + case "thai" -> "static/fonts/NotoSansThai-Regular.ttf"; + default -> "static/fonts/NotoSans-Regular.ttf"; + }; ClassPathResource classPathResource = new ClassPathResource(resourceDir); String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(".")); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index d0c895599..2aab753ef 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -288,8 +288,8 @@ public class GeneralWebController { case "eot" -> "embedded-opentype"; case "svg" -> "svg"; default -> - // or throw an exception if an unexpected extension is encountered - ""; + // or throw an exception if an unexpected extension is encountered + ""; }; } diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index a2b290ae4..b68a2b154 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -60,11 +60,10 @@ security: privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair jwt: # This feature is currently under development and not yet fully supported. Do not use in production. - persistence: true # Set to 'true' to enable JWT key store - enableKeyRotation: true # Set to 'true' to enable key pair rotation - enableKeyCleanup: true # Set to 'true' to enable key pair cleanup + enabled: false # Set to 'true' to enable JWT key store + keyCleanup: false # Set to 'true' to enable key pair cleanup keyRetentionDays: 7 # Number of days to retain old keys. The default is 7 days. - secureCookie: false # Set to 'true' to use secure cookies for JWTs + secureCookie: true # Set to 'true' to use secure cookies for JWTs premium: key: 00000000-0000-0000-0000-000000000000 diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java index e145e2754..1629bb619 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; @@ -26,6 +27,9 @@ import stirling.software.proprietary.security.service.UserService; @RequiredArgsConstructor public class InitialSecuritySetup { + @Value("${v2:false}") + private boolean v2Enabled = false; + private final UserService userService; private final TeamService teamService; private final ApplicationProperties applicationProperties; @@ -43,6 +47,7 @@ public class InitialSecuritySetup { } } + configureJWTSettings(); assignUsersToDefaultTeamIfMissing(); initializeInternalApiUser(); } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { @@ -51,6 +56,18 @@ public class InitialSecuritySetup { } } + private void configureJWTSettings() { + ApplicationProperties.Security.Jwt jwtProperties = + applicationProperties.getSecurity().getJwt(); + + boolean jwtEnabled = jwtProperties.isEnabled(); + if (!v2Enabled || !jwtEnabled) { + log.debug("V2 enabled: {}, JWT enabled: {} - disabling all JWT features", v2Enabled, jwtEnabled); + + jwtProperties.setKeyCleanup(false); + } + } + private void assignUsersToDefaultTeamIfMissing() { Team defaultTeam = teamService.getOrCreateDefaultTeam(); Team internalTeam = teamService.getOrCreateInternalTeam(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPairCleanupService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPairCleanupService.java index af7c5f7e2..059e052dd 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPairCleanupService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPairCleanupService.java @@ -40,7 +40,7 @@ public class KeyPairCleanupService { @PostConstruct @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.DAYS) public void cleanup() { - if (!jwtProperties.isEnableKeyCleanup() || !keyPersistenceService.isKeystoreEnabled()) { + if (!jwtProperties.isEnabled() || !jwtProperties.isKeyCleanup()) { return; } @@ -71,7 +71,7 @@ public class KeyPairCleanupService { } private void removePrivateKey(String keyId) throws IOException { - if (!keyPersistenceService.isKeystoreEnabled()) { + if (!jwtProperties.isEnabled()) { return; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java index eb02b6368..3ea6beea3 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java @@ -84,7 +84,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface { @PostConstruct public void initializeKeystore() { - if (!isKeystoreEnabled()) { + if (!jwtProperties.isEnabled()) { return; } @@ -132,7 +132,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface { @Override public Optional getKeyPair(String keyId) { - if (!isKeystoreEnabled()) { + if (!jwtProperties.isEnabled()) { return Optional.empty(); } @@ -155,11 +155,6 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface { } } - @Override - public boolean isKeystoreEnabled() { - return jwtProperties.isEnableKeystore(); - } - @Override public JwtVerificationKey refreshActiveKeyPair() { return generateAndStoreKeypair(); @@ -268,4 +263,8 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } + + public boolean isKeystoreEnabled() { + return jwtProperties.isEnabled(); + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterface.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterface.java index f3050472e..aa660e253 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterface.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterface.java @@ -16,8 +16,6 @@ public interface KeyPersistenceServiceInterface { Optional getKeyPair(String keyId); - boolean isKeystoreEnabled(); - JwtVerificationKey refreshActiveKeyPair(); List getKeysEligibleForCleanup(LocalDateTime cutoffDate); diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterfaceTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterfaceTest.java index 33b971e5a..3852a2600 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterfaceTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/KeyPersistenceServiceInterfaceTest.java @@ -1,6 +1,5 @@ package stirling.software.proprietary.security.service; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -21,8 +20,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; @@ -58,23 +55,7 @@ class KeyPersistenceServiceInterfaceTest { lenient().when(applicationProperties.getSecurity()).thenReturn(security); lenient().when(security.getJwt()).thenReturn(jwtConfig); - lenient().when(jwtConfig.isEnableKeystore()).thenReturn(true); // Default value - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testKeystoreEnabled(boolean keystoreEnabled) { - when(jwtConfig.isEnableKeystore()).thenReturn(keystoreEnabled); - - try (MockedStatic mockedStatic = - mockStatic(InstallationPathConfig.class)) { - mockedStatic - .when(InstallationPathConfig::getPrivateKeyPath) - .thenReturn(tempDir.toString()); - keyPersistenceService = new KeyPersistenceService(applicationProperties, cacheManager); - - assertEquals(keystoreEnabled, keyPersistenceService.isKeystoreEnabled()); - } + lenient().when(jwtConfig.isEnabled()).thenReturn(true); } @Test @@ -177,7 +158,7 @@ class KeyPersistenceServiceInterfaceTest { @Test void testGetKeyPairWhenKeystoreDisabled() { - when(jwtConfig.isEnableKeystore()).thenReturn(false); + when(jwtConfig.isEnabled()).thenReturn(false); try (MockedStatic mockedStatic = mockStatic(InstallationPathConfig.class)) { diff --git a/testing/allEndpointsRemovedSettings.yml b/testing/allEndpointsRemovedSettings.yml index e47e7ea47..04cb000cd 100644 --- a/testing/allEndpointsRemovedSettings.yml +++ b/testing/allEndpointsRemovedSettings.yml @@ -59,6 +59,11 @@ security: idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair + jwt: # This feature is currently under development and not yet fully supported. Do not use in production. + enabled: true # Set to 'true' to enable JWT key store + keyCleanup: true # Set to 'true' to enable key pair cleanup + keyRetentionDays: 7 # Number of days to retain old keys. The default is 7 days. + secureCookie: false # Set to 'true' to use secure cookies for JWTs premium: key: 00000000-0000-0000-0000-000000000000