mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	feat(database,Jwt): relocate backups and Jwt-keys to config/backup and add Enterprise cleanup endpoints (#4225)
				
					
				
			# Description of Changes
- **What was changed**
  - Centralized installation paths:
- Introduced `BACKUP_PATH`, `BACKUP_DB_PATH`, and
`BACKUP_PRIVATE_KEY_PATH` in `InstallationPathConfig`;
`getPrivateKeyPath()` now resolves to `backup/keys` and new
`getBackupPath()` returns `backup/db`.
- Removed old `PRIVATE_KEY_PATH` and switched all usages to the new
locations.
  - Database service enhancements:
- `DatabaseService` now uses `InstallationPathConfig.getBackupPath()`
and includes a one-time migration to move existing backups from
`config/db/backup` to `config/backup/db` (**@Deprecated(since = "2.0.0",
forRemoval = true)**).
- Added `deleteAllBackups()` and `deleteLastBackup()` methods and
exposed them via a new Enterprise controller.
  - New Enterprise-only API:
    - Added `DatabaseControllerEnterprise` with:
      - `DELETE /api/v1/database/deleteAll` — delete all backup files.
- `DELETE /api/v1/database/deleteLast` — delete the most recent backup.
- Endpoints gated by `@EnterpriseEndpoint` and
`@Conditional(H2SQLCondition.class)`.
  - Key persistence adjustments:
- `KeyPersistenceService` now migrates keys from `config/db/keys` to
`config/backup/keys` on startup (**@Deprecated(since = "2.0.0",
forRemoval = true)**).
  - Miscellaneous refactors/fixes:
- Switched driver resolution in `DatabaseConfig` to a switch expression.
    - Corrected HTTP status usage to `HttpStatus.SEE_OTHER`.
- Removed constructor `runningEE` flag from `AccountWebController` and
replaced EE checks with `@EnterpriseEndpoint`.
- Minor test and annotation improvements (e.g., `@Deprecated(since =
"0.45.0")`, method references, equals order).
  
- **Why the change was made**
- To standardize and future-proof storage locations for both backups and
keys under a clear `config/backup` hierarchy.
- To give Enterprise admins first-class, safe cleanup endpoints for
managing backup retention without manual file operations.
- To reduce conditional logic in controllers and rely on declarative EE
gating.
- To improve maintainability and correctness (status codes, switch
expression, null-safety patterns).
---
## Checklist
### General
- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
### UI Changes (if applicable)
- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
			
			
This commit is contained in:
		
							parent
							
								
									40cf337b23
								
							
						
					
					
						commit
						3af93f0adb
					
				@ -14,18 +14,22 @@ public class InstallationPathConfig {
 | 
			
		||||
    private static final String CONFIG_PATH;
 | 
			
		||||
    private static final String CUSTOM_FILES_PATH;
 | 
			
		||||
    private static final String CLIENT_WEBUI_PATH;
 | 
			
		||||
    private static final String SCRIPTS_PATH;
 | 
			
		||||
    private static final String PIPELINE_PATH;
 | 
			
		||||
 | 
			
		||||
    // Config paths
 | 
			
		||||
    private static final String SETTINGS_PATH;
 | 
			
		||||
    private static final String CUSTOM_SETTINGS_PATH;
 | 
			
		||||
    private static final String SCRIPTS_PATH;
 | 
			
		||||
    private static final String BACKUP_PATH;
 | 
			
		||||
 | 
			
		||||
    // Backup paths
 | 
			
		||||
    private static final String BACKUP_DB_PATH;
 | 
			
		||||
    private static final String BACKUP_PRIVATE_KEY_PATH;
 | 
			
		||||
 | 
			
		||||
    // Custom file paths
 | 
			
		||||
    private static final String STATIC_PATH;
 | 
			
		||||
    private static final String TEMPLATES_PATH;
 | 
			
		||||
    private static final String SIGNATURES_PATH;
 | 
			
		||||
    private static final String PRIVATE_KEY_PATH;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        BASE_PATH = initializeBasePath();
 | 
			
		||||
@ -41,12 +45,16 @@ public class InstallationPathConfig {
 | 
			
		||||
        SETTINGS_PATH = CONFIG_PATH + "settings.yml";
 | 
			
		||||
        CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
 | 
			
		||||
        SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator;
 | 
			
		||||
        BACKUP_PATH = CONFIG_PATH + "backup" + File.separator;
 | 
			
		||||
 | 
			
		||||
        // Initialize backup paths
 | 
			
		||||
        BACKUP_DB_PATH = BACKUP_PATH + "db" + File.separator;
 | 
			
		||||
        BACKUP_PRIVATE_KEY_PATH = BACKUP_PATH + "keys" + File.separator;
 | 
			
		||||
 | 
			
		||||
        // Initialize custom file paths
 | 
			
		||||
        STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
 | 
			
		||||
        TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
 | 
			
		||||
        SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
 | 
			
		||||
        PRIVATE_KEY_PATH = CONFIG_PATH + "db" + File.separator + "keys" + File.separator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String initializeBasePath() {
 | 
			
		||||
@ -124,6 +132,10 @@ public class InstallationPathConfig {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String getPrivateKeyPath() {
 | 
			
		||||
        return PRIVATE_KEY_PATH;
 | 
			
		||||
        return BACKUP_PRIVATE_KEY_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String getBackupPath() {
 | 
			
		||||
        return BACKUP_DB_PATH;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -179,7 +179,7 @@ class ApplicationPropertiesLogicTest {
 | 
			
		||||
        assertEquals(30, t.getOcrMyPdfTimeoutMinutes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    @Deprecated(since = "0.45.0")
 | 
			
		||||
    @Test
 | 
			
		||||
    void enterprise_metadata_defaults() {
 | 
			
		||||
        ApplicationProperties.EnterpriseEdition ee = new ApplicationProperties.EnterpriseEdition();
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@ import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Qualifier;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.security.core.Authentication;
 | 
			
		||||
import org.springframework.security.core.userdetails.UserDetails;
 | 
			
		||||
@ -59,19 +58,16 @@ public class AccountWebController {
 | 
			
		||||
    private final SessionPersistentRegistry sessionPersistentRegistry;
 | 
			
		||||
    // Assuming you have a repository for user operations
 | 
			
		||||
    private final UserRepository userRepository;
 | 
			
		||||
    private final boolean runningEE;
 | 
			
		||||
    private final TeamRepository teamRepository;
 | 
			
		||||
 | 
			
		||||
    public AccountWebController(
 | 
			
		||||
            ApplicationProperties applicationProperties,
 | 
			
		||||
            SessionPersistentRegistry sessionPersistentRegistry,
 | 
			
		||||
            UserRepository userRepository,
 | 
			
		||||
            TeamRepository teamRepository,
 | 
			
		||||
            @Qualifier("runningEE") boolean runningEE) {
 | 
			
		||||
            TeamRepository teamRepository) {
 | 
			
		||||
        this.applicationProperties = applicationProperties;
 | 
			
		||||
        this.sessionPersistentRegistry = sessionPersistentRegistry;
 | 
			
		||||
        this.userRepository = userRepository;
 | 
			
		||||
        this.runningEE = runningEE;
 | 
			
		||||
        this.teamRepository = teamRepository;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -207,11 +203,9 @@ public class AccountWebController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PreAuthorize("hasRole('ROLE_ADMIN')")
 | 
			
		||||
    @EnterpriseEndpoint
 | 
			
		||||
    @GetMapping("/usage")
 | 
			
		||||
    public String showUsage() {
 | 
			
		||||
        if (!runningEE) {
 | 
			
		||||
            return "error";
 | 
			
		||||
        }
 | 
			
		||||
        return "usage";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -243,7 +237,7 @@ public class AccountWebController {
 | 
			
		||||
 | 
			
		||||
                // Also check if user is part of the Internal team
 | 
			
		||||
                if (user.getTeam() != null
 | 
			
		||||
                        && user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
 | 
			
		||||
                        && TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) {
 | 
			
		||||
                    shouldRemove = true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -362,11 +356,9 @@ public class AccountWebController {
 | 
			
		||||
                teamRepository.findAll().stream()
 | 
			
		||||
                        .filter(
 | 
			
		||||
                                team ->
 | 
			
		||||
                                        !team.getName()
 | 
			
		||||
                                                .equals(
 | 
			
		||||
                                                        stirling.software.proprietary.security
 | 
			
		||||
                                                                .service.TeamService
 | 
			
		||||
                                                                .INTERNAL_TEAM_NAME))
 | 
			
		||||
                                        !stirling.software.proprietary.security.service.TeamService
 | 
			
		||||
                                                .INTERNAL_TEAM_NAME
 | 
			
		||||
                                                .equals(team.getName()))
 | 
			
		||||
                        .toList();
 | 
			
		||||
        model.addAttribute("teams", allTeams);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -134,21 +134,21 @@ public class DatabaseConfig {
 | 
			
		||||
            ApplicationProperties.Driver driver =
 | 
			
		||||
                    ApplicationProperties.Driver.valueOf(driverName.toUpperCase());
 | 
			
		||||
 | 
			
		||||
            switch (driver) {
 | 
			
		||||
            return switch (driver) {
 | 
			
		||||
                case H2 -> {
 | 
			
		||||
                    log.debug("H2 driver selected");
 | 
			
		||||
                    return DatabaseDriver.H2.getDriverClassName();
 | 
			
		||||
                    yield DatabaseDriver.H2.getDriverClassName();
 | 
			
		||||
                }
 | 
			
		||||
                case POSTGRESQL -> {
 | 
			
		||||
                    log.debug("Postgres driver selected");
 | 
			
		||||
                    return DatabaseDriver.POSTGRESQL.getDriverClassName();
 | 
			
		||||
                    yield DatabaseDriver.POSTGRESQL.getDriverClassName();
 | 
			
		||||
                }
 | 
			
		||||
                default -> {
 | 
			
		||||
                    log.warn("{} driver selected", driverName);
 | 
			
		||||
                    throw new UnsupportedProviderException(
 | 
			
		||||
                            driverName + " is not currently supported");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            };
 | 
			
		||||
        } catch (IllegalArgumentException e) {
 | 
			
		||||
            log.warn("Unknown driver: {}", driverName);
 | 
			
		||||
            throw new UnsupportedProviderException(driverName + " is not currently supported");
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,10 @@ import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.StandardCopyOption;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jetty.http.HttpStatus;
 | 
			
		||||
import org.springframework.context.annotation.Conditional;
 | 
			
		||||
import org.springframework.core.io.InputStreamResource;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
@ -145,7 +145,7 @@ public class DatabaseController {
 | 
			
		||||
                    .body(resource);
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            log.error("Error downloading file: {}", e.getMessage());
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.SEE_OTHER_303)
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.SEE_OTHER)
 | 
			
		||||
                    .location(URI.create("/database?error=downloadFailed"))
 | 
			
		||||
                    .build();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,101 @@
 | 
			
		||||
package stirling.software.proprietary.security.controller.api.enterprise;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.lang3.tuple.Pair;
 | 
			
		||||
import org.springframework.context.annotation.Conditional;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
 | 
			
		||||
import stirling.software.common.model.FileInfo;
 | 
			
		||||
import stirling.software.proprietary.security.config.EnterpriseEndpoint;
 | 
			
		||||
import stirling.software.proprietary.security.database.H2SQLCondition;
 | 
			
		||||
import stirling.software.proprietary.security.service.DatabaseService;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("/api/v1/database")
 | 
			
		||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
 | 
			
		||||
@EnterpriseEndpoint
 | 
			
		||||
@Conditional(H2SQLCondition.class)
 | 
			
		||||
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
public class DatabaseControllerEnterprise {
 | 
			
		||||
 | 
			
		||||
    private final DatabaseService databaseService;
 | 
			
		||||
 | 
			
		||||
    @Operation(
 | 
			
		||||
            summary = "Delete the last database backup file",
 | 
			
		||||
            description =
 | 
			
		||||
                    "Only Enterprise - Deletes the last database backup file from the server.")
 | 
			
		||||
    @DeleteMapping("/deleteLast")
 | 
			
		||||
    public ResponseEntity<?> deleteLastFile() {
 | 
			
		||||
        log.info("Deleting last database backup file...");
 | 
			
		||||
        List<Pair<FileInfo, Boolean>> results = databaseService.deleteLastBackup();
 | 
			
		||||
        return getDeleteAllResults(results);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(
 | 
			
		||||
            summary = "Delete all database backup files",
 | 
			
		||||
            description = "Only Enterprise - Deletes all database backup files from the server.")
 | 
			
		||||
    @DeleteMapping("/deleteAll")
 | 
			
		||||
    public ResponseEntity<?> deleteAllFiles() {
 | 
			
		||||
        log.info("Deleting all database backup files...");
 | 
			
		||||
        List<Pair<FileInfo, Boolean>> results = databaseService.deleteAllBackups();
 | 
			
		||||
        return getDeleteAllResults(results);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ResponseEntity<?> getDeleteAllResults(List<Pair<FileInfo, Boolean>> results) {
 | 
			
		||||
        if (results.isEmpty()) {
 | 
			
		||||
            log.info("No backup files found to delete.");
 | 
			
		||||
            return ResponseEntity.ok(new DeleteAllResult(List.of(), List.of(), "noContent"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<String> deleted =
 | 
			
		||||
                results.stream()
 | 
			
		||||
                        .filter(p -> Boolean.TRUE.equals(p.getRight()))
 | 
			
		||||
                        .map(p -> p.getLeft().getFileName())
 | 
			
		||||
                        .toList();
 | 
			
		||||
 | 
			
		||||
        List<String> failed =
 | 
			
		||||
                results.stream()
 | 
			
		||||
                        .filter(p -> !Boolean.TRUE.equals(p.getRight()))
 | 
			
		||||
                        .map(p -> p.getLeft().getFileName())
 | 
			
		||||
                        .toList();
 | 
			
		||||
 | 
			
		||||
        log.info("Deleted backup files: {}", deleted);
 | 
			
		||||
        if (!failed.isEmpty()) {
 | 
			
		||||
            log.warn("Some backup files could not be deleted: {}", failed);
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.MULTI_STATUS) // 207
 | 
			
		||||
                    .body(new DeleteAllResult(deleted, failed, "partialFailure"));
 | 
			
		||||
        }
 | 
			
		||||
        DeleteAllResult result = new DeleteAllResult(deleted, failed, "ok");
 | 
			
		||||
        log.debug(
 | 
			
		||||
                "DeleteAllResult: deleted={}, failed={}, status={}",
 | 
			
		||||
                result.deleted,
 | 
			
		||||
                result.failed,
 | 
			
		||||
                result.status);
 | 
			
		||||
        return ResponseEntity.ok(result); // 200
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final class DeleteAllResult {
 | 
			
		||||
        public final List<String> deleted;
 | 
			
		||||
        public final List<String> failed;
 | 
			
		||||
        public final String status;
 | 
			
		||||
 | 
			
		||||
        public DeleteAllResult(List<String> deleted, List<String> failed, String status) {
 | 
			
		||||
            this.deleted = deleted;
 | 
			
		||||
            this.failed = failed;
 | 
			
		||||
            this.status = status;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -5,6 +5,7 @@ import java.nio.file.DirectoryStream;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.nio.file.StandardCopyOption;
 | 
			
		||||
import java.nio.file.attribute.BasicFileAttributes;
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.PreparedStatement;
 | 
			
		||||
@ -21,6 +22,7 @@ import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.lang3.tuple.Pair;
 | 
			
		||||
import org.springframework.jdbc.datasource.init.CannotReadScriptException;
 | 
			
		||||
import org.springframework.jdbc.datasource.init.ScriptException;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
@ -45,10 +47,39 @@ public class DatabaseService implements DatabaseServiceInterface {
 | 
			
		||||
 | 
			
		||||
    public DatabaseService(
 | 
			
		||||
            ApplicationProperties.Datasource datasourceProps, DataSource dataSource) {
 | 
			
		||||
        this.BACKUP_DIR =
 | 
			
		||||
                Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize();
 | 
			
		||||
        this.BACKUP_DIR = Paths.get(InstallationPathConfig.getBackupPath()).normalize();
 | 
			
		||||
        this.datasourceProps = datasourceProps;
 | 
			
		||||
        this.dataSource = dataSource;
 | 
			
		||||
        moveBackupFiles();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Move all backup files from db/backup to backup/db */
 | 
			
		||||
    @Deprecated(since = "2.0.0", forRemoval = true)
 | 
			
		||||
    private void moveBackupFiles() {
 | 
			
		||||
        Path sourceDir =
 | 
			
		||||
                Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize();
 | 
			
		||||
 | 
			
		||||
        if (!Files.exists(sourceDir)) {
 | 
			
		||||
            log.info("Source directory does not exist: {}", sourceDir);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            Files.createDirectories(BACKUP_DIR);
 | 
			
		||||
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
 | 
			
		||||
                for (Path entry : stream) {
 | 
			
		||||
                    if (entry.getFileName().toString().startsWith(BACKUP_PREFIX)
 | 
			
		||||
                            && entry.getFileName().toString().endsWith(SQL_SUFFIX)) {
 | 
			
		||||
                        Files.move(
 | 
			
		||||
                                entry,
 | 
			
		||||
                                BACKUP_DIR.resolve(entry.getFileName()),
 | 
			
		||||
                                StandardCopyOption.REPLACE_EXISTING);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            log.error("Error moving backup files: {}", e.getMessage(), e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -198,6 +229,46 @@ public class DatabaseService implements DatabaseServiceInterface {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Pair<FileInfo, Boolean>> deleteAllBackups() {
 | 
			
		||||
        List<FileInfo> backupList = this.getBackupList();
 | 
			
		||||
        List<Pair<FileInfo, Boolean>> deletedFiles = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
        for (FileInfo backup : backupList) {
 | 
			
		||||
            try {
 | 
			
		||||
                Files.deleteIfExists(Paths.get(backup.getFilePath()));
 | 
			
		||||
                deletedFiles.add(Pair.of(backup, true));
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                log.error("Error deleting backup file: {}", backup.getFileName(), e);
 | 
			
		||||
                deletedFiles.add(Pair.of(backup, false));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return deletedFiles;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Pair<FileInfo, Boolean>> deleteLastBackup() {
 | 
			
		||||
 | 
			
		||||
        List<FileInfo> backupList = this.getBackupList();
 | 
			
		||||
        List<Pair<FileInfo, Boolean>> deletedFiles = new ArrayList<>();
 | 
			
		||||
        if (!backupList.isEmpty()) {
 | 
			
		||||
            FileInfo lastBackup = backupList.get(backupList.size() - 1);
 | 
			
		||||
            try {
 | 
			
		||||
                Files.deleteIfExists(Paths.get(lastBackup.getFilePath()));
 | 
			
		||||
                deletedFiles.add(Pair.of(lastBackup, true));
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                log.error("Error deleting last backup file: {}", lastBackup.getFileName(), e);
 | 
			
		||||
                deletedFiles.add(Pair.of(lastBackup, false));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return deletedFiles;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes the oldest backup file from the specified list.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filteredBackupList the list of backup files
 | 
			
		||||
     */
 | 
			
		||||
    private static void deleteOldestBackup(List<FileInfo> filteredBackupList) {
 | 
			
		||||
        try {
 | 
			
		||||
            filteredBackupList.sort(
 | 
			
		||||
@ -237,6 +308,11 @@ public class DatabaseService implements DatabaseServiceInterface {
 | 
			
		||||
        return version;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Checks if the current datasource is H2.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the datasource is H2, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isH2Database() {
 | 
			
		||||
        boolean isTypeH2 =
 | 
			
		||||
                datasourceProps.getType().equalsIgnoreCase(ApplicationProperties.Driver.H2.name());
 | 
			
		||||
@ -301,6 +377,11 @@ public class DatabaseService implements DatabaseServiceInterface {
 | 
			
		||||
        return filePath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Executes a database script.
 | 
			
		||||
     *
 | 
			
		||||
     * @param scriptPath the path to the script file
 | 
			
		||||
     */
 | 
			
		||||
    private void executeDatabaseScript(Path scriptPath) {
 | 
			
		||||
        if (isH2Database()) {
 | 
			
		||||
            String query = "RUNSCRIPT from ?;";
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ package stirling.software.proprietary.security.service;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.lang3.tuple.Pair;
 | 
			
		||||
 | 
			
		||||
import stirling.software.common.model.FileInfo;
 | 
			
		||||
import stirling.software.common.model.exception.UnsupportedProviderException;
 | 
			
		||||
 | 
			
		||||
@ -14,4 +16,8 @@ public interface DatabaseServiceInterface {
 | 
			
		||||
    boolean hasBackup();
 | 
			
		||||
 | 
			
		||||
    List<FileInfo> getBackupList();
 | 
			
		||||
 | 
			
		||||
    List<Pair<FileInfo, Boolean>> deleteAllBackups();
 | 
			
		||||
 | 
			
		||||
    List<Pair<FileInfo, Boolean>> deleteLastBackup();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
package stirling.software.proprietary.security.service;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.DirectoryStream;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.nio.file.StandardCopyOption;
 | 
			
		||||
import java.security.KeyFactory;
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.security.KeyPairGenerator;
 | 
			
		||||
@ -52,6 +54,34 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
 | 
			
		||||
        this.verifyingKeyCache = cacheManager.getCache("verifyingKeys");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Move all key files from db/keys to backup/keys */
 | 
			
		||||
    @Deprecated(since = "2.0.0", forRemoval = true)
 | 
			
		||||
    private void moveKeysToBackup() {
 | 
			
		||||
        Path sourceDir =
 | 
			
		||||
                Paths.get(InstallationPathConfig.getConfigPath(), "db", "keys").normalize();
 | 
			
		||||
 | 
			
		||||
        if (!Files.exists(sourceDir)) {
 | 
			
		||||
            log.info("Source directory does not exist: {}", sourceDir);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Path targetDir = Paths.get(InstallationPathConfig.getPrivateKeyPath()).normalize();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            Files.createDirectories(targetDir);
 | 
			
		||||
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
 | 
			
		||||
                for (Path entry : stream) {
 | 
			
		||||
                    Files.move(
 | 
			
		||||
                            entry,
 | 
			
		||||
                            targetDir.resolve(entry.getFileName()),
 | 
			
		||||
                            StandardCopyOption.REPLACE_EXISTING);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            log.error("Error moving key files to backup: {}", e.getMessage(), e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void initializeKeystore() {
 | 
			
		||||
        if (!isKeystoreEnabled()) {
 | 
			
		||||
@ -59,6 +89,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            moveKeysToBackup();
 | 
			
		||||
            ensurePrivateKeyDirectoryExists();
 | 
			
		||||
            loadKeyPair();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package stirling.software.proprietary.service;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Qualifier;
 | 
			
		||||
import org.springframework.boot.actuate.audit.AuditEvent;
 | 
			
		||||
import org.springframework.boot.actuate.audit.AuditEventRepository;
 | 
			
		||||
import org.springframework.security.core.Authentication;
 | 
			
		||||
@ -29,8 +30,7 @@ public class AuditService {
 | 
			
		||||
    public AuditService(
 | 
			
		||||
            AuditEventRepository repository,
 | 
			
		||||
            AuditConfigurationProperties auditConfig,
 | 
			
		||||
            @org.springframework.beans.factory.annotation.Qualifier("runningEE")
 | 
			
		||||
                    boolean runningEE) {
 | 
			
		||||
            @Qualifier("runningEE") boolean runningEE) {
 | 
			
		||||
        this.repository = repository;
 | 
			
		||||
        this.auditConfig = auditConfig;
 | 
			
		||||
        this.runningEE = runningEE;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user