2270: renamed datasource config flag

This commit is contained in:
DarioGii 2024-12-23 10:40:25 +00:00 committed by Dario Ghunney Ware
parent fb48e0d67c
commit 1d4fa4941f
11 changed files with 349 additions and 46 deletions

View File

@ -72,6 +72,29 @@ sourceSets {
}
}
test {
java {
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
exclude "stirling/software/SPDF/config/security/**"
exclude "stirling/software/SPDF/controller/api/UserControllerTest.java"
exclude "stirling/software/SPDF/controller/api/DatabaseControllerTest.java"
exclude "stirling/software/SPDF/controller/web/AccountWebControllerTest.java"
exclude "stirling/software/SPDF/controller/web/DatabaseWebControllerTest.java"
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationTokenTest.java"
exclude "stirling/software/SPDF/model/AttemptCounterTest.java"
exclude "stirling/software/SPDF/model/AuthorityTest.java"
exclude "stirling/software/SPDF/model/PersistentLoginTest.java"
exclude "stirling/software/SPDF/model/SessionEntityTest.java"
exclude "stirling/software/SPDF/model/UserTest.java"
exclude "stirling/software/SPDF/repository/**"
}
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
}
}
}
openApi {

View File

@ -34,7 +34,7 @@ services:
SYSTEM_DATASOURCE_HOSTNAME: "db"
SYSTEM_DATASOURCE_PORT: "5432"
SYSTEM_DATASOURCE_NAME: "stirling_pdf"
SYSTEM_DATASOURCE_USEDEFAULT: "false"
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "false"
SYSTEM_DATASOURCE_USERNAME: "admin"
SYSTEM_DATASOURCE_PASSWORD: "stirling"
restart: on-failure:5

View File

@ -42,7 +42,7 @@ services:
SYSTEM_DATASOURCE_HOSTNAME: "db"
SYSTEM_DATASOURCE_PORT: "5432"
SYSTEM_DATASOURCE_NAME: "stirling_pdf"
SYSTEM_DATASOURCE_USEDEFAULT: "false"
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "false"
SYSTEM_DATASOURCE_USERNAME: "admin"
SYSTEM_DATASOURCE_PASSWORD: "stirling"
restart: on-failure:5

View File

@ -36,7 +36,7 @@ services:
SYSTEM_DATASOURCE_HOSTNAME: "db"
SYSTEM_DATASOURCE_PORT: "5432"
SYSTEM_DATASOURCE_NAME: "stirling_pdf"
SYSTEM_DATASOURCE_USEDEFAULT: "false"
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "false"
SYSTEM_DATASOURCE_USERNAME: "admin"
SYSTEM_DATASOURCE_PASSWORD: "stirling"
restart: on-failure:5

View File

@ -31,7 +31,7 @@ services:
SYSTEM_DATASOURCE_HOSTNAME: "db"
SYSTEM_DATASOURCE_PORT: "5432"
SYSTEM_DATASOURCE_NAME: "stirling_pdf"
SYSTEM_DATASOURCE_USEDEFAULT: "false"
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "false"
SYSTEM_DATASOURCE_USERNAME: "admin"
SYSTEM_DATASOURCE_PASSWORD: "stirling"
restart: on-failure:5

View File

@ -18,6 +18,8 @@ import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.support.EncodedResource;
@ -29,7 +31,6 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.exception.BackupNotFoundException;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import stirling.software.SPDF.utils.FileInfo;
@Slf4j
@ -40,7 +41,14 @@ public class DatabaseService implements DatabaseInterface {
public static final String SQL_SUFFIX = ".sql";
private static final String BACKUP_DIR = "configs/db/backup/";
@Autowired private DatabaseConfig databaseConfig;
private final ApplicationProperties applicationProperties;
private final DataSource dataSource;
@Autowired
public DatabaseService(ApplicationProperties applicationProperties, DataSource dataSource) {
this.applicationProperties = applicationProperties;
this.dataSource = dataSource;
}
/**
* Checks if there is at least one backup
@ -133,7 +141,7 @@ public class DatabaseService implements DatabaseInterface {
/** Filter and delete old backups if there are more than 5 */
@Override
public void exportDatabase() throws SQLException, UnsupportedProviderException {
public void exportDatabase() throws SQLException {
List<FileInfo> filteredBackupList =
this.getBackupList().stream()
.filter(backup -> !backup.getFileName().startsWith(BACKUP_PREFIX + "user_"))
@ -148,12 +156,12 @@ public class DatabaseService implements DatabaseInterface {
Path insertOutputFilePath =
this.getBackupFilePath(BACKUP_PREFIX + dateNow.format(myFormatObj) + SQL_SUFFIX);
try (Connection conn = databaseConfig.connection()) {
try (Connection conn = dataSource.getConnection()) {
ScriptUtils.executeSqlScript(
conn, new EncodedResource(new PathResource(insertOutputFilePath)));
log.info("Database export completed: {}", insertOutputFilePath);
} catch (SQLException | UnsupportedProviderException e) {
} catch (SQLException e) {
log.error("Error during database export: {}", e.getMessage(), e);
throw e;
} catch (ScriptException e) {
@ -184,13 +192,12 @@ public class DatabaseService implements DatabaseInterface {
public String getH2Version() {
String version = "Unknown";
if (databaseConfig
.getApplicationProperties()
if (applicationProperties
.getSystem()
.getDatasource()
.getType()
.equals(ApplicationProperties.Driver.H2.name())) {
try (Connection conn = databaseConfig.connection()) {
try (Connection conn = dataSource.getConnection()) {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
if (rs.next()) {
@ -198,7 +205,7 @@ public class DatabaseService implements DatabaseInterface {
log.info("H2 Database Version: {}", version);
}
}
} catch (SQLException | UnsupportedProviderException e) {
} catch (SQLException e) {
log.error("Error retrieving H2 version: {}", e.getMessage(), e);
}
}
@ -240,16 +247,23 @@ public class DatabaseService implements DatabaseInterface {
}
private void executeDatabaseScript(Path scriptPath) {
try (Connection conn = databaseConfig.connection()) {
ScriptUtils.executeSqlScript(conn, new EncodedResource(new PathResource(scriptPath)));
if (applicationProperties
.getSystem()
.getDatasource()
.getType()
.equals(ApplicationProperties.Driver.H2.name())) {
try (Connection conn = dataSource.getConnection()) {
ScriptUtils.executeSqlScript(
conn, new EncodedResource(new PathResource(scriptPath)));
log.info("Database import completed: {}", scriptPath);
} catch (SQLException | UnsupportedProviderException e) {
} catch (SQLException e) {
log.error("Error during database import: {}", e.getMessage(), e);
} catch (ScriptException e) {
log.error("Error: File {} not found", scriptPath.toString(), e);
}
}
}
/**
* Checks for invalid characters or sequences

View File

@ -0,0 +1,117 @@
package stirling.software.SPDF.config.security.database;
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 lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Slf4j
@Getter
@Configuration
public class DatabaseConfig {
public static final String DATASOURCE_DEFAULT_URL =
"jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
public static final String DATASOURCE_URL_TEMPLATE = "jdbc:%s://%s:%4d/%s";
public static final String DEFAULT_DRIVER = "org.h2.Driver";
public static final String DEFAULT_USERNAME = "sa";
public static final String POSTGRES_DRIVER = "org.postgresql.Driver";
private final ApplicationProperties applicationProperties;
@Autowired
public DatabaseConfig(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
/**
* Creates the <code>DataSource</code> for the connection to the DB. If <code>useDefault</code>
* is set to <code>true</code>, it will use the default H2 DB. If it is set to <code>false
* </code>, it will use the user's custom configuration set in the settings.yml.
*
* @return a <code>DataSource</code> using the configuration settings in the settings.yml
* @throws UnsupportedProviderException if the type of database selected is not supported
*/
@Bean
public DataSource dataSource() throws UnsupportedProviderException {
ApplicationProperties.System system = applicationProperties.getSystem();
ApplicationProperties.Datasource datasource = system.getDatasource();
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
if (datasource.isEnableCustomDatabase()) {
log.debug("Using default H2 database");
dataSourceBuilder.driverClassName(DEFAULT_DRIVER);
dataSourceBuilder.url(DATASOURCE_DEFAULT_URL);
dataSourceBuilder.username(DEFAULT_USERNAME);
return dataSourceBuilder.build();
}
dataSourceBuilder.driverClassName(getDriverClassName(datasource.getType()));
dataSourceBuilder.url(
getDataSourceUrl(
datasource.getType(),
datasource.getHostName(),
datasource.getPort(),
datasource.getName()));
dataSourceBuilder.username(datasource.getUsername());
dataSourceBuilder.password(datasource.getPassword());
return dataSourceBuilder.build();
}
/**
* Generate the URL the <code>DataSource</code> will use to connect to the database
*
* @param dataSourceType the type of the database
* @param hostname the host name
* @param port the port number to use for the database
* @param dataSourceName the name the database to connect to
* @return the <code>DataSource</code> URL
*/
private String getDataSourceUrl(
String dataSourceType, String hostname, Integer port, String dataSourceName) {
return DATASOURCE_URL_TEMPLATE.formatted(dataSourceType, hostname, port, dataSourceName);
}
/**
* Selects the database driver based on the type of database chosen.
*
* @param driverName the type of the driver (e.g. 'h2', 'postgresql')
* @return the fully qualified driver for the database chosen
* @throws UnsupportedProviderException when an unsupported database is selected
*/
private String getDriverClassName(String driverName) throws UnsupportedProviderException {
try {
ApplicationProperties.Driver driver =
ApplicationProperties.Driver.valueOf(driverName.toUpperCase());
switch (driver) {
case H2 -> {
log.debug("H2 driver selected");
return DEFAULT_DRIVER;
}
case POSTGRESQL -> {
log.debug("Postgres driver selected");
return POSTGRES_DRIVER;
}
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");
}
}
}

View File

@ -8,7 +8,6 @@ import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.Driver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -113,23 +112,6 @@ public class ApplicationProperties {
return saml2.getEnabled() || oauth2.getEnabled();
}
public boolean isUserPass() {
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
}
public boolean isOauth2Activ() {
return (oauth2 != null
&& oauth2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
public boolean isSaml2Activ() {
return (saml2 != null
&& saml2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
public enum LoginMethods {
ALL("all"),
NORMAL("normal"),
@ -148,6 +130,23 @@ public class ApplicationProperties {
}
}
public boolean isUserPass() {
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
}
public boolean isOauth2Activ() {
return (oauth2 != null
&& oauth2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
public boolean isSaml2Activ() {
return (saml2 != null
&& saml2.getEnabled()
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
}
@Data
public static class InitialLogin {
private String username;
@ -288,23 +287,36 @@ public class ApplicationProperties {
@Data
public static class Datasource {
private String url;
private Driver driver;
private boolean enableCustomDatabase;
private String type;
private String hostName;
private Integer port;
private String name;
private String username;
private String password;
@ToString.Exclude private String password;
}
public enum Driver {
H2("h2"),
POSTGRESQL("postgresql"),
ORACLE("oracle"),
MY_SQL("mysql");
MYSQL("mysql");
private final String driverName;
Driver(String driverName) {
this.driverName = driverName;
}
@Override
public String toString() {
return """
Driver {
driverName='%s'
}
"""
.formatted(driverName);
}
}
@Data

View File

@ -86,10 +86,13 @@ system:
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
enableAnalytics: undefined # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
datasource:
url: jdbc:postgresql://localhost:5432/stirling-pdf-DB
driver: postgresql
username: postgres
password:
enableCustomDatabase: true # set this property to 'true' if you would like to use the default database configuration
type: postgresql # the type of the database to set (e.g. 'h2', 'postgresql')
hostName: localhost # the host name to use for the database url. Set to 'localhost' when running the app locally. Set to match the name of the container name of your database container when running the app on a server (Docker configuration)
port: 5432 # set the port number of the database. Ensure this matches the port the database is listening to
name: postgres # set the name of your database. Should match the name of the database you create
username: postgres # set the database username
password: postgres # set the database password
ui:
appName: '' # application's visible name

View File

@ -0,0 +1,75 @@
package stirling.software.SPDF.config.security.database;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import javax.sql.DataSource;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DatabaseConfigTest {
@Mock
private ApplicationProperties applicationProperties;
@InjectMocks
private DatabaseConfig databaseConfig;
@Test
void testDefaultConfigurationForDataSource() throws UnsupportedProviderException {
var system = mock(ApplicationProperties.System.class);
var datasource = mock(ApplicationProperties.Datasource.class);
when(applicationProperties.getSystem()).thenReturn(system);
when(system.getDatasource()).thenReturn(datasource);
when(datasource.isEnableCustomDatabase()).thenReturn(true);
var result = databaseConfig.dataSource();
assertInstanceOf(DataSource.class, result);
}
@Test
void testCustomConfigurationForDataSource() throws UnsupportedProviderException {
var system = mock(ApplicationProperties.System.class);
var datasource = mock(ApplicationProperties.Datasource.class);
when(applicationProperties.getSystem()).thenReturn(system);
when(system.getDatasource()).thenReturn(datasource);
when(datasource.isEnableCustomDatabase()).thenReturn(false);
when(datasource.getType()).thenReturn("postgresql");
when(datasource.getHostName()).thenReturn("localhost");
when(datasource.getPort()).thenReturn(5432);
when(datasource.getName()).thenReturn("postgres");
when(datasource.getUsername()).thenReturn("postgres");
when(datasource.getPassword()).thenReturn("postgres");
var result = databaseConfig.dataSource();
assertInstanceOf(DataSource.class, result);
}
@ParameterizedTest(name = "Exception thrown when the DB type [{arguments}] is not supported")
@ValueSource(strings = {"oracle", "mysql", "mongoDb"})
void exceptionThrownWhenDBTypeIsUnsupported(String datasourceType) {
var system = mock(ApplicationProperties.System.class);
var datasource = mock(ApplicationProperties.Datasource.class);
when(applicationProperties.getSystem()).thenReturn(system);
when(system.getDatasource()).thenReturn(datasource);
when(datasource.isEnableCustomDatabase()).thenReturn(false);
when(datasource.getType()).thenReturn(datasourceType);
assertThrows(UnsupportedProviderException.class, () -> databaseConfig.dataSource());
}
}

View File

@ -0,0 +1,59 @@
package stirling.software.SPDF.integrationtests;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import stirling.software.SPDF.SPDFApplication;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.Files.createFile;
import static java.nio.file.Files.delete;
import static java.nio.file.Files.exists;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class SPDFApplicationIntegrationTest {
@Test
public void testMainApplicationStartup() throws IOException, InterruptedException {
// Setup mock environment for the main method
Path configPath = Path.of("test/configs");
Path settingsPath = Paths.get("test/configs/settings.yml");
Path customSettingsPath = Paths.get("test/configs/custom_settings.yml");
Path staticPath = Path.of("test/customFiles/static/");
Path templatesPath = Path.of("test/customFiles/templates/");
// Ensure the files do not exist for the test
if (exists(settingsPath)) {
delete(settingsPath);
}
if (exists(customSettingsPath)) {
delete(customSettingsPath);
}
if (exists(staticPath)) {
delete(staticPath);
}
if (exists(templatesPath)) {
delete(templatesPath);
}
// Ensure the directories are created for testing
createDirectories(configPath);
createDirectories(staticPath);
createDirectories(templatesPath);
createFile(settingsPath);
createFile(customSettingsPath);
// Run the main method
SPDFApplication.main(new String[] {"-Dspring.profiles.active=default"});
// Verify that the directories were created
assertTrue(exists(settingsPath));
assertTrue(exists(customSettingsPath));
assertTrue(exists(staticPath));
assertTrue(exists(templatesPath));
}
}