From 947814a405676739e0709118f26575ccfa45a103 Mon Sep 17 00:00:00 2001 From: DarioGii Date: Mon, 9 Dec 2024 12:16:10 +0000 Subject: [PATCH] refactoring wip --- .../software/SPDF/SPdfApplication.java | 2 +- ...pInterface.java => DatabaseInterface.java} | 6 +- .../config/security/InitialSecuritySetup.java | 48 +++++++------- .../SPDF/config/security/UserService.java | 22 +++---- .../security/database/DataSourceConfig.java | 29 --------- .../security/database/DatabaseConfig.java | 44 ++++++++----- ...ackupService.java => DatabaseService.java} | 57 +++-------------- .../config/security/database/JpaConfig.java | 20 ------ .../security/database/ScheduledTasks.java | 4 +- .../controller/api/DatabaseController.java | 14 ++--- .../controller/api/security/GetInfoOnPDF.java | 4 +- .../security/ValidateSignatureController.java | 44 ++++++++----- .../controller/web/DatabaseWebController.java | 8 +-- .../SPDF/model/ApplicationProperties.java | 3 +- .../security/SignatureValidationResult.java | 23 ++++--- .../exception/UnsupportedDriverException.java | 7 +++ .../service/CertificateValidationService.java | 3 +- .../resources/application-mysql.properties | 2 - .../resources/application-oracle.properties | 2 - .../resources/application-postgres.properties | 2 - src/main/resources/application.properties | 1 - src/main/resources/settings.yml.template | 2 + src/main/resources/setup_pg_admin_user.sql | 25 ++++++++ .../security/InitialSecuritySetupTest.java | 62 +++++++++++++++++++ .../database/DatabaseServiceTest.java | 38 ++++++++++++ 25 files changed, 268 insertions(+), 204 deletions(-) rename src/main/java/stirling/software/SPDF/config/interfaces/{DatabaseBackupInterface.java => DatabaseInterface.java} (72%) delete mode 100644 src/main/java/stirling/software/SPDF/config/security/database/DataSourceConfig.java rename src/main/java/stirling/software/SPDF/config/security/database/{DatabaseBackupService.java => DatabaseService.java} (81%) delete mode 100644 src/main/java/stirling/software/SPDF/config/security/database/JpaConfig.java create mode 100644 src/main/java/stirling/software/SPDF/model/exception/UnsupportedDriverException.java create mode 100644 src/main/resources/setup_pg_admin_user.sql create mode 100644 src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java create mode 100644 src/test/java/stirling/software/SPDF/config/security/database/DatabaseServiceTest.java diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index eddf7306c07..f4200db346d 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -24,8 +24,8 @@ import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.model.ApplicationProperties; -@SpringBootApplication @EnableScheduling +@SpringBootApplication public class SPdfApplication { private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class); diff --git a/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseBackupInterface.java b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java similarity index 72% rename from src/main/java/stirling/software/SPDF/config/interfaces/DatabaseBackupInterface.java rename to src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java index 36ec92cef1a..81cb0f41c51 100644 --- a/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseBackupInterface.java +++ b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java @@ -5,14 +5,10 @@ import stirling.software.SPDF.utils.FileInfo; -public interface DatabaseBackupInterface { +public interface DatabaseInterface { void setAdminUser(); void exportDatabase() throws IOException; - void importDatabase(); - - boolean hasBackup(); - List getBackupList(); } diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java index 699a9e8b21c..ea53e37e8a2 100644 --- a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -8,12 +8,12 @@ import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; +import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.Role; -@Component @Slf4j +@Component public class InitialSecuritySetup { public static final String POSTGRES = "postgres"; @@ -22,21 +22,26 @@ public class InitialSecuritySetup { @Autowired private ApplicationProperties applicationProperties; - @Autowired private DatabaseBackupInterface databaseBackupService; + @Autowired private DatabaseInterface databaseService; @PostConstruct - public void init() throws IllegalArgumentException, IOException { - if (applicationProperties.getSystem().getEnvironmentName().equals(POSTGRES)) { - log.debug("PostgreSQL configuration settings detected. Creating admin user"); - databaseBackupService.setAdminUser(); + public void init() { + if (applicationProperties.getSystem().getSpringProfilesActive().equals(POSTGRES)) { + log.debug("Postgres configuration settings detected. Creating admin user"); + databaseService.setAdminUser(); } - if (!userService.hasUsers()) { - initializeAdminUser(); - } + try { + if (!userService.hasUsers()) { + initializeAdminUser(); + } - userService.migrateOauth2ToSSO(); - initializeInternalApiUser(); + userService.migrateOauth2ToSSO(); + initializeInternalApiUser(); + } catch (IllegalArgumentException | IOException e) { + log.error("Failed to initialize security setup.", e); + System.exit(1); + } } private void initializeAdminUser() throws IOException { @@ -48,25 +53,22 @@ private void initializeAdminUser() throws IOException { && !initialUsername.isEmpty() && initialPassword != null && !initialPassword.isEmpty() - && !userService.findByUsernameIgnoreCase(initialUsername).isPresent()) { - try { - userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId()); - log.info("Admin user created: " + initialUsername); - } catch (IllegalArgumentException e) { - log.error("Failed to initialize security setup", e); - System.exit(1); - } + && userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) { + + userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId()); + log.info("Admin user created: {}", initialUsername); } else { createDefaultAdminUser(); } } - private void createDefaultAdminUser() throws IllegalArgumentException, IOException { + private void createDefaultAdminUser() throws IOException { String defaultUsername = "admin"; String defaultPassword = "stirling"; - if (!userService.findByUsernameIgnoreCase(defaultUsername).isPresent()) { + + if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) { userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true); - log.info("Default admin user created: " + defaultUsername); + log.info("Default admin user created: {}", defaultUsername); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index d7f35d387ac..26659ec132f 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -21,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; +import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; @@ -47,7 +47,7 @@ public class UserService implements UserServiceInterface { @Autowired private SessionPersistentRegistry sessionRegistry; - @Autowired DatabaseBackupInterface databaseBackupHelper; + @Autowired DatabaseInterface databaseService; @Autowired ApplicationProperties applicationProperties; @@ -167,7 +167,7 @@ public void saveUser(String username, AuthenticationType authenticationType, Str user.addAuthority(new Authority(role, user)); user.setAuthenticationType(authenticationType); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void saveUser(String username, String password) @@ -181,7 +181,7 @@ public void saveUser(String username, String password) user.setEnabled(true); user.setAuthenticationType(AuthenticationType.WEB); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void saveUser(String username, String password, String role, boolean firstLogin) @@ -197,7 +197,7 @@ public void saveUser(String username, String password, String role, boolean firs user.setAuthenticationType(AuthenticationType.WEB); user.setFirstLogin(firstLogin); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void saveUser(String username, String password, String role) @@ -249,7 +249,7 @@ public void updateUserSettings(String username, Map updates) user.setSettings(settingsMap); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } } @@ -276,32 +276,32 @@ public void changeUsername(User user, String newUsername) } user.setUsername(newUsername); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void changePassword(User user, String newPassword) throws IOException { user.setPassword(passwordEncoder.encode(newPassword)); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void changeFirstUse(User user, boolean firstUse) throws IOException { user.setFirstLogin(firstUse); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void changeRole(User user, String newRole) throws IOException { Authority userAuthority = this.findRole(user); userAuthority.setAuthority(newRole); authorityRepository.save(userAuthority); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public void changeUserEnabled(User user, Boolean enbeled) throws IOException { user.setEnabled(enbeled); userRepository.save(user); - databaseBackupHelper.exportDatabase(); + databaseService.exportDatabase(); } public boolean isPasswordCorrect(User user, String currentPassword) { diff --git a/src/main/java/stirling/software/SPDF/config/security/database/DataSourceConfig.java b/src/main/java/stirling/software/SPDF/config/security/database/DataSourceConfig.java deleted file mode 100644 index 6e68ff22927..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/database/DataSourceConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package stirling.software.SPDF.config.security.database; - -import javax.sql.DataSource; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Configuration; - -import lombok.Data; - -@Data -@Configuration -@ConfigurationProperties(prefix = "spring.datasource") -public class DataSourceConfig { - - private String driverClassName; - private String url; - private String username; - private String password; - - public DataSource dataSource() { - DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); - dataSourceBuilder.driverClassName(driverClassName); - dataSourceBuilder.url(url); - dataSourceBuilder.username(username); - dataSourceBuilder.password(password); - return dataSourceBuilder.build(); - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java index c062d815435..e2f73fea3cd 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java @@ -1,35 +1,51 @@ package stirling.software.SPDF.config.security.database; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; -import javax.sql.DataSource; - import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import lombok.Getter; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.exception.UnsupportedDriverException; @Getter @Configuration public class DatabaseConfig { - @Autowired private DataSourceConfig dataSourceConfig; - - @Autowired private JpaConfig jpaConfig; + @Autowired private ApplicationProperties applicationProperties; @Bean - public DataSource dataSource() { - return dataSourceConfig.dataSource(); + public Connection connection() throws SQLException { + ApplicationProperties.Datasource datasource = + applicationProperties.getSystem().getDatasource(); + + DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); + dataSourceBuilder.driverClassName(getDriverClassName(datasource.getDriverClassName())); + dataSourceBuilder.url(datasource.getUrl()); + dataSourceBuilder.username(datasource.getUsername()); + dataSourceBuilder.password(datasource.getPassword()); + + return dataSourceBuilder.build().getConnection(); } - @Bean - public Connection connection() throws SQLException { - return DriverManager.getConnection( - dataSourceConfig.getUrl(), - dataSourceConfig.getUsername(), - dataSourceConfig.getPassword()); + private String getDriverClassName(ApplicationProperties.Driver driverName) { + switch (driverName) { + case POSTGRESQL -> { + return "org.postgresql.Driver"; + } + case ORACLE -> { + return "oracle.jdbc.OracleDriver"; + } + case MY_SQL -> { + return "com.mysql.cj.jdbc.Driver"; + } + default -> + throw new UnsupportedDriverException( + "The database driver " + driverName + " is not supported."); + } } } diff --git a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupService.java b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java similarity index 81% rename from src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupService.java rename to src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java index 91026cf687f..1cfbaa3f083 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupService.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java @@ -17,7 +17,6 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.PathResource; @@ -27,66 +26,34 @@ import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; -import stirling.software.SPDF.model.exception.BackupNotFoundException; +import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.utils.FileInfo; @Slf4j @Service -public class DatabaseBackupService implements DatabaseBackupInterface { +public class DatabaseService implements DatabaseInterface { public static final String BACKUP_PREFIX = "backup_"; public static final String SQL_SUFFIX = ".sql"; private static final Path BACKUP_PATH = Paths.get("configs/db/backup/"); + private static final Path PG_ADMIN_SCRIPT_PATH = + Paths.get("src/main/resources/setup_pg_admin_user.sql"); @Autowired private DatabaseConfig databaseConfig; @Override public void setAdminUser() { - String adminScript = - """ - DO - $do$ - BEGIN - IF EXISTS ( - SELECT FROM pg_catalog.pg_roles - WHERE rolname = 'admin') THEN - - RAISE NOTICE 'Role "admin" already exists. Skipping.'; - ELSE - CREATE USER admin WITH ENCRYPTED PASSWORD 'stirling'; - END IF; - END - $do$; - - CREATE SCHEMA IF NOT EXISTS stirling_pdf AUTHORIZATION admin; - GRANT ALL PRIVILEGES ON DATABASE postgres TO admin; - ALTER DATABASE postgres SET search_path TO stirling_pdf; - ALTER USER admin SET search_path TO stirling_pdf; - """ - .trim(); - try (Connection connection = databaseConfig.connection(); Statement statement = connection.createStatement()) { - statement.execute(adminScript); - } catch (SQLException e) { + String script = Files.readString(PG_ADMIN_SCRIPT_PATH); + statement.execute(script); + } catch (SQLException | IOException e) { log.error("Error: Failed to create admin user for database", e); } log.info("Created admin user for database"); } - @Override - public boolean hasBackup() { - // Check if there is at least one backup - try (Stream entries = Files.list(BACKUP_PATH)) { - return entries.findFirst().isPresent(); - } catch (IOException e) { - log.error("Error reading backup directory: {}", e.getMessage(), e); - throw new RuntimeException(e); - } - } - @Override public List getBackupList() { List backupFiles = new ArrayList<>(); @@ -150,16 +117,6 @@ private void importDatabaseFromUI(Path tempTemplatePath) throws IOException { Files.deleteIfExists(tempTemplatePath); } - @Override - public void importDatabase() { - if (!hasBackup()) throw new BackupNotFoundException("No backups found"); - - List backupList = getBackupList(); - backupList.sort(Comparator.comparing(FileInfo::getModificationDate).reversed()); - executeDatabaseScript(Paths.get(backupList.get(0).getFilePath())); - } - - // fixMe: Check the type of DB before executing script @Override public void exportDatabase() { // Filter and delete old backups if there are more than 5 diff --git a/src/main/java/stirling/software/SPDF/config/security/database/JpaConfig.java b/src/main/java/stirling/software/SPDF/config/security/database/JpaConfig.java deleted file mode 100644 index f3edc531b2b..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/database/JpaConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package stirling.software.SPDF.config.security.database; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import lombok.Data; - -@Data -@Configuration -@ConfigurationProperties(prefix = "spring.jpa") -public class JpaConfig { - - @Value("${environment.name}") - private String environmentName; - - private String databasePlatform; - private String openInView; - private String generateDDL; -} diff --git a/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java b/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java index ca5d272009a..f8d56a91fcd 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java @@ -9,10 +9,10 @@ @Component public class ScheduledTasks { - @Autowired private DatabaseBackupService databaseBackupService; + @Autowired private DatabaseService databaseService; @Scheduled(cron = "0 0 0 * * ?") public void performBackup() throws IOException { - databaseBackupService.exportDatabase(); + databaseService.exportDatabase(); } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java b/src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java index 0f98952a75b..3a030ea25d4 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java @@ -28,7 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.security.database.DatabaseBackupService; +import stirling.software.SPDF.config.security.database.DatabaseService; @Slf4j @Controller @@ -37,7 +37,7 @@ @Tag(name = "Database", description = "Database APIs") public class DatabaseController { - @Autowired DatabaseBackupService databaseBackupService; + @Autowired DatabaseService databaseService; @Hidden @PostMapping(consumes = "multipart/form-data", value = "import-database") @@ -56,7 +56,7 @@ public String importDatabase( try (InputStream in = file.getInputStream()) { Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING); boolean importSuccess = - databaseBackupService.importDatabaseFromUI(tempTemplatePath.toString()); + databaseService.importDatabaseFromUI(tempTemplatePath.toString()); if (importSuccess) { redirectAttributes.addAttribute("infoMessage", "importIntoDatabaseSuccessed"); } else { @@ -79,14 +79,14 @@ public String importDatabaseFromBackupUI(@PathVariable String fileName) // Check if the file exists in the backup list boolean fileExists = - databaseBackupService.getBackupList().stream() + databaseService.getBackupList().stream() .anyMatch(backup -> backup.getFileName().equals(fileName)); if (!fileExists) { log.error("File {} not found in backup list", fileName); return "redirect:/database?error=fileNotFound"; } log.info("Received file: {}", fileName); - if (databaseBackupService.importDatabaseFromUI(fileName)) { + if (databaseService.importDatabaseFromUI(fileName)) { log.info("File {} imported to database", fileName); return "redirect:/database?infoMessage=importIntoDatabaseSuccessed"; } @@ -104,7 +104,7 @@ public String deleteFile(@PathVariable String fileName) { throw new IllegalArgumentException("File must not be null or empty"); } try { - if (databaseBackupService.deleteBackupFile(fileName)) { + if (databaseService.deleteBackupFile(fileName)) { log.info("Deleted file: {}", fileName); } else { log.error("Failed to delete file: {}", fileName); @@ -128,7 +128,7 @@ public ResponseEntity downloadFile(@PathVariable String fileName) { throw new IllegalArgumentException("File must not be null or empty"); } try { - Path filePath = databaseBackupService.getBackupFilePath(fileName); + Path filePath = databaseService.getBackupFilePath(fileName); InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 96745c4a514..d6950e95507 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -595,7 +595,9 @@ private void setNodePermissions(PDDocument pdfBoxDoc, ObjectNode permissionsNode permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument())); permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent())); - permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility())); + permissionsNode.put( + "Extracting for accessibility", + getPermissionState(ap.canExtractForAccessibility())); permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm())); permissionsNode.put("Modifying", getPermissionState(ap.canModify())); permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations())); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java b/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java index 94e99dd93f8..317c6424b34 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java @@ -92,20 +92,29 @@ public ResponseEntity> validateSignature( SignerInformationStore signerStore = signedData.getSignerInfos(); for (SignerInformation signer : signerStore.getSigners()) { - X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next(); - X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder); - - boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); + X509CertificateHolder certHolder = + (X509CertificateHolder) + certStore.getMatches(signer.getSID()).iterator().next(); + X509Certificate cert = + new JcaX509CertificateConverter().getCertificate(certHolder); + + boolean isValid = + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); result.setValid(isValid); // Additional validations - result.setChainValid(customCert != null - ? certValidationService.validateCertificateChainWithCustomCert(cert, customCert) - : certValidationService.validateCertificateChain(cert)); - - result.setTrustValid(customCert != null - ? certValidationService.validateTrustWithCustomCert(cert, customCert) - : certValidationService.validateTrustStore(cert)); + result.setChainValid( + customCert != null + ? certValidationService + .validateCertificateChainWithCustomCert( + cert, customCert) + : certValidationService.validateCertificateChain(cert)); + + result.setTrustValid( + customCert != null + ? certValidationService.validateTrustWithCustomCert( + cert, customCert) + : certValidationService.validateTrustStore(cert)); result.setNotRevoked(!certValidationService.isRevoked(cert)); result.setNotExpired(!cert.getNotAfter().before(new Date())); @@ -123,17 +132,18 @@ public ResponseEntity> validateSignature( result.setValidFrom(cert.getNotBefore().toString()); result.setValidUntil(cert.getNotAfter().toString()); result.setSignatureAlgorithm(cert.getSigAlgName()); - + // Get key size (if possible) try { - result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength()); + result.setKeySize( + ((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength()); } catch (Exception e) { // If not RSA or error, set to 0 result.setKeySize(0); } result.setVersion(String.valueOf(cert.getVersion())); - + // Set key usage List keyUsages = new ArrayList<>(); boolean[] keyUsageFlags = cert.getKeyUsage(); @@ -150,9 +160,11 @@ public ResponseEntity> validateSignature( } } result.setKeyUsages(keyUsages); - + // Check if self-signed - result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())); + result.setSelfSigned( + cert.getSubjectX500Principal() + .equals(cert.getIssuerX500Principal())); } } catch (Exception e) { result.setValid(false); diff --git a/src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java b/src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java index 6ee489c782e..628d95e5dbd 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java @@ -12,14 +12,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import stirling.software.SPDF.config.security.database.DatabaseBackupService; +import stirling.software.SPDF.config.security.database.DatabaseService; import stirling.software.SPDF.utils.FileInfo; @Controller @Tag(name = "Database Management", description = "Database management and security APIs") public class DatabaseWebController { - @Autowired private DatabaseBackupService databaseBackupService; + @Autowired private DatabaseService databaseService; @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/database") @@ -33,10 +33,10 @@ public String database(HttpServletRequest request, Model model, Authentication a model.addAttribute("infoMessage", confirmed); } - List backupList = databaseBackupService.getBackupList(); + List backupList = databaseService.getBackupList(); model.addAttribute("backupFiles", backupList); - model.addAttribute("databaseVersion", databaseBackupService.getH2Version()); + model.addAttribute("databaseVersion", databaseService.getH2Version()); return "database"; } diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index c5e750119e6..6a397e8d550 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -238,6 +238,7 @@ public Provider get(String registrationId) throws UnsupportedProviderException { @Data public static class System { + private String springProfilesActive; private String defaultLocale; private Boolean googlevisibility; private boolean showUpdate; @@ -253,12 +254,12 @@ public static class System { @Data public static class Datasource { private String url; + private Driver driverClassName; private String username; private String password; } public enum Driver { - H2("h2"), POSTGRESQL("postgresql"), ORACLE("oracle"), MY_SQL("mysql"); diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java b/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java index 1aafd8eca5d..b4c51f36543 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java @@ -16,16 +16,15 @@ public class SignatureValidationResult { private boolean trustValid; private boolean notExpired; private boolean notRevoked; - - private String issuerDN; // Certificate issuer's Distinguished Name - private String subjectDN; // Certificate subject's Distinguished Name - private String serialNumber; // Certificate serial number - private String validFrom; // Certificate validity start date - private String validUntil; // Certificate validity end date - private String signatureAlgorithm;// Algorithm used for signing - private int keySize; // Key size in bits - private String version; // Certificate version - private List keyUsages; // List of key usage purposes - private boolean isSelfSigned; // Whether the certificate is self-signed - + + private String issuerDN; // Certificate issuer's Distinguished Name + private String subjectDN; // Certificate subject's Distinguished Name + private String serialNumber; // Certificate serial number + private String validFrom; // Certificate validity start date + private String validUntil; // Certificate validity end date + private String signatureAlgorithm; // Algorithm used for signing + private int keySize; // Key size in bits + private String version; // Certificate version + private List keyUsages; // List of key usage purposes + private boolean isSelfSigned; // Whether the certificate is self-signed } diff --git a/src/main/java/stirling/software/SPDF/model/exception/UnsupportedDriverException.java b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedDriverException.java new file mode 100644 index 00000000000..b52101e9373 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedDriverException.java @@ -0,0 +1,7 @@ +package stirling.software.SPDF.model.exception; + +public class UnsupportedDriverException extends RuntimeException { + public UnsupportedDriverException(String message) { + super(message); + } +} diff --git a/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java b/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java index 41f54f4aa22..550db680124 100644 --- a/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java +++ b/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java @@ -1,6 +1,5 @@ package stirling.software.SPDF.service; -import io.github.pixee.security.BoundedLineReader; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -24,6 +23,8 @@ import org.springframework.stereotype.Service; +import io.github.pixee.security.BoundedLineReader; + import jakarta.annotation.PostConstruct; @Service diff --git a/src/main/resources/application-mysql.properties b/src/main/resources/application-mysql.properties index fa35282e444..02d4b117ddf 100644 --- a/src/main/resources/application-mysql.properties +++ b/src/main/resources/application-mysql.properties @@ -1,5 +1,3 @@ -environment.name=mysql - spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/stirling_pdf?createDatabaseIfNotExist=true spring.datasource.username=admin diff --git a/src/main/resources/application-oracle.properties b/src/main/resources/application-oracle.properties index be48037ffbd..7bf0d98051b 100644 --- a/src/main/resources/application-oracle.properties +++ b/src/main/resources/application-oracle.properties @@ -1,5 +1,3 @@ -environment.name=oracle - spring.datasource.driver-class-name=oracle.jdbc.OracleDriver spring.datasource.url=jdbc:oracle:thin:@localhost:1521:stirling_pdf spring.datasource.username=admin diff --git a/src/main/resources/application-postgres.properties b/src/main/resources/application-postgres.properties index 7c20aa4adec..08db7a8f3ba 100644 --- a/src/main/resources/application-postgres.properties +++ b/src/main/resources/application-postgres.properties @@ -1,5 +1,3 @@ -environment.name=postgres - spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 09db2f9ea8a..b48c8343d58 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -48,6 +48,5 @@ springdoc.api-docs.path=/v1/api-docs # Set the URL of the OpenAPI JSON for the Swagger UI springdoc.swagger-ui.url=/v1/api-docs - posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq posthog.host=https://eu.i.posthog.com diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 6eed8c3b6a1..7ea4190e864 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -77,6 +77,7 @@ legal: impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum). Empty string to disable or filename to load from local file in static folder system: + springProfilesActive: postgres # set the default environment (e.g. 'postgres', 'mysql', 'oracle') defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc) googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes) @@ -88,6 +89,7 @@ system: environmentName: postgres # set the default environment (e.g. 'postgres', 'mysql', 'oracle') datasource: url: jdbc:postgresql://localhost:5432/postgres + driverClassName: postgresql username: postgres password: postgres diff --git a/src/main/resources/setup_pg_admin_user.sql b/src/main/resources/setup_pg_admin_user.sql new file mode 100644 index 00000000000..cc645326fd6 --- /dev/null +++ b/src/main/resources/setup_pg_admin_user.sql @@ -0,0 +1,25 @@ +DO +$do$ +BEGIN + IF EXISTS ( + SELECT FROM pg_catalog.pg_roles + WHERE rolname = 'admin') THEN + + RAISE NOTICE 'Role "admin" already exists. Skipping.'; + ELSE + CREATE USER admin WITH ENCRYPTED PASSWORD 'stirling'; + END IF; +END +$do$; + +CREATE SCHEMA IF NOT EXISTS stirling_pdf AUTHORIZATION admin; +GRANT ALL PRIVILEGES ON DATABASE postgres TO admin; +GRANT ALL PRIVILEGES ON SCHEMA stirling_pdf TO admin; +ALTER DATABASE postgres SET search_path TO stirling_pdf; +ALTER TABLE authorities OWNER TO admin; +ALTER TABLE persistent_logins OWNER TO admin; +ALTER TABLE sessions OWNER TO admin; +ALTER TABLE user_settings OWNER TO admin; +ALTER TABLE users OWNER TO admin; +ALTER USER admin SET search_path TO stirling_pdf; +SET ROLE admin; \ No newline at end of file diff --git a/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java b/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java new file mode 100644 index 00000000000..f38ef7e3923 --- /dev/null +++ b/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java @@ -0,0 +1,62 @@ +package stirling.software.SPDF.config.security; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import stirling.software.SPDF.config.security.database.DatabaseService; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.User; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class InitialSecuritySetupTest { + + @Mock + private UserService userService; + + @Mock + private ApplicationProperties applicationProperties; + + @Mock + private DatabaseService databaseService; + + @InjectMocks + private InitialSecuritySetup initialSecuritySetup; + + @Test + void testInit() throws IOException { + String username = "admin"; + String password = "stirling"; + ApplicationProperties.System system = mock(ApplicationProperties.System.class); + ApplicationProperties.Security security = mock(ApplicationProperties.Security.class); + ApplicationProperties.Security.InitialLogin initialLogin = mock(ApplicationProperties.Security.InitialLogin.class); + Optional user = Optional.of(mock(User.class)); + + when(applicationProperties.getSystem()).thenReturn(system); + when(system.getSpringProfilesActive()).thenReturn("postgres"); + doNothing().when(databaseService).setAdminUser(); + when(userService.hasUsers()).thenReturn(false); + when(applicationProperties.getSecurity()).thenReturn(security); + when(security.getInitialLogin()).thenReturn(initialLogin); + when(initialLogin.getUsername()).thenReturn(username); + when(initialLogin.getPassword()).thenReturn(password); + when(userService.findByUsernameIgnoreCase(username)).thenReturn(user); + when(userService.usernameExistsIgnoreCase(anyString())).thenReturn(false); + + initialSecuritySetup.init(); + + verify(userService).saveUser(anyString(), anyString(), anyString()); + verify(userService).migrateOauth2ToSSO(); + verify(userService).addApiKeyToUser(anyString()); + } + +} \ No newline at end of file diff --git a/src/test/java/stirling/software/SPDF/config/security/database/DatabaseServiceTest.java b/src/test/java/stirling/software/SPDF/config/security/database/DatabaseServiceTest.java new file mode 100644 index 00000000000..c153937bb63 --- /dev/null +++ b/src/test/java/stirling/software/SPDF/config/security/database/DatabaseServiceTest.java @@ -0,0 +1,38 @@ +package stirling.software.SPDF.config.security.database; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class DatabaseServiceTest { + + @Mock + private DatabaseConfig databaseConfig; + + @InjectMocks + private DatabaseService databaseService; + + @Test + void setAdminUser() throws SQLException { + Connection connection = mock(Connection.class); + Statement statement = mock(Statement.class); + + when(databaseConfig.connection()).thenReturn(connection); + when(connection.createStatement()).thenReturn(statement); + + databaseService.setAdminUser(); + + verify(statement).execute(anyString()); + } + +} \ No newline at end of file