diff --git a/app/common/build.gradle b/app/common/build.gradle index 4168003cb..4c9e1b855 100644 --- a/app/common/build.gradle +++ b/app/common/build.gradle @@ -8,7 +8,6 @@ spotless { googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false) importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") - toggleOffOn() trimTrailingWhitespace() leadingTabsToSpaces() endWithNewline() diff --git a/app/proprietary/build.gradle b/app/proprietary/build.gradle index ce5c926a0..bd175abde 100644 --- a/app/proprietary/build.gradle +++ b/app/proprietary/build.gradle @@ -1,5 +1,6 @@ repositories { maven { url = "https://build.shibboleth.net/maven/releases" } + maven { url = "https://repository.jboss.org/" } } ext { @@ -63,12 +64,9 @@ dependencies { runtimeOnly "io.jsonwebtoken:jjwt-jackson:$jwtVersion" runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database runtimeOnly 'org.postgresql:postgresql:42.7.10' - constraints { - implementation "org.opensaml:opensaml-core:$openSamlVersion" - implementation "org.opensaml:opensaml-saml-api:$openSamlVersion" - implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion" + implementation('com.coveo:saml-client:5.0.0') { + exclude group: 'org.opensaml', module: 'opensaml-core' } - implementation 'com.coveo:saml-client:5.0.0' } tasks.register('prepareKotlinBuildScriptModel') {} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java index ecf9b1b19..47d6d0185 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java @@ -191,6 +191,20 @@ public class DatabaseController { if (fileName == null || fileName.isEmpty()) { throw new IllegalArgumentException("File must not be null or empty"); } + + // Validate that file is a legitimate backup file + // Only allow files matching the backup naming pattern + if (!fileName.startsWith("backup_") || !fileName.endsWith(".sql")) { + log.warn("Attempted download of non-backup file: {}", fileName); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body( + java.util.Map.of( + "error", + "invalidFileName", + "message", + "Only backup files are allowed")); + } + try { Path filePath = databaseService.getBackupFilePath(fileName); InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath)); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java index 88165b022..8595d0286 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.UUID; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.sql.DataSource; @@ -47,6 +48,48 @@ public class DatabaseService implements DatabaseServiceInterface { public static final String SQL_SUFFIX = ".sql"; private final Path BACKUP_DIR; + // Whitelist of allowed SQL patterns for H2 database backups (generated by SCRIPT command) + // Only standard DDL/DML operations are permitted - anything else is rejected by default + private static final java.util.List ALLOWED_PATTERNS = + java.util.List.of( + Pattern.compile("(?i)\\bCREATE\\b"), + Pattern.compile("(?i)\\bSET\\s+SCHEMA\\b"), + Pattern.compile("(?i)\\bALTER\\s+SEQUENCE\\b"), + Pattern.compile("(?i)\\bDROP\\s+TABLE\\b"), + Pattern.compile("(?i)\\bALTER\\s+TABLE\\b"), + Pattern.compile("(?i)\\bADD\\s+COLUMN\\b"), + Pattern.compile("(?i)\\bINSERT\\s+INTO\\b"), + Pattern.compile("(?i)\\bVALUES\\b"), + Pattern.compile("(?i)\\bADD\\s+CONSTRAINT\\b"), + Pattern.compile("(?i)\\bPRIMARY\\s+KEY\\b"), + Pattern.compile("(?i)\\bFOREIGN\\s+KEY\\b"), + Pattern.compile("(?i)\\bCONSTRAINT\\b"), + Pattern.compile("(?i)\\bCHECK\\b"), + Pattern.compile( + "(?i)\\bGENERATED\\s+(BY\\s+DEFAULT|ALWAYS)\\s+AS\\s+IDENTITY\\b"), + Pattern.compile("(?i)\\bDEFAULT\\b"), + Pattern.compile( + "(?i)\\bSET\\s+(REFERENTIAL_INTEGRITY|MODE|DATABASE|IGNORECASE|AUTOINCREMENT|DB_CLOSE_DELAY)\\b"), + Pattern.compile("(?i)\\bIF\\s+EXISTS\\b"), + Pattern.compile("(?i)\\bIF\\s+NOT\\s+EXISTS\\b"), + Pattern.compile("(?i)\\bNULLS\\s+(FIRST|LAST)\\b"), + Pattern.compile("(?i)\\bREFERENCES\\b"), + Pattern.compile("(?i)\\bNOCHECK\\b"), + Pattern.compile("(?i)\\bRESTART\\s+WITH\\b"), + Pattern.compile("(?i)\\bCASCADE\\b"), + Pattern.compile("(?i)\\bON\\b"), + Pattern.compile("(?i)\\bSTART\\s+WITH\\b"), + Pattern.compile("(?i)\\bAS\\b"), + Pattern.compile("(?i)\\bUNIQUE\\b"), + Pattern.compile("(?i)\\bDISTINCT\\b"), + Pattern.compile("(?i)\\bWITH\\s+TIME\\s+ZONE\\b"), + Pattern.compile("(?i)\\bTIMESTAMP\\b"), + Pattern.compile("(?i)\\bVARYING\\b"), + Pattern.compile("(?i)\\bNOT\\s+NULL\\b"), + Pattern.compile("(?i)\\bTRUE\\b"), + Pattern.compile("(?i)\\bFALSE\\b"), + Pattern.compile("(?i)\\bNULL\\b")); + private final ApplicationProperties.Datasource datasourceProps; private final DataSource dataSource; private final DatabaseNotificationServiceInterface backupNotificationService; @@ -450,6 +493,9 @@ public class DatabaseService implements DatabaseServiceInterface { throw new IllegalArgumentException("Backup verification failed for: " + scriptPath); } + // Validate SQL content before execution to prevent injection attacks + validateSqlContent(scriptPath); + String query = "RUNSCRIPT from ?;"; try (Connection conn = dataSource.getConnection(); @@ -466,6 +512,68 @@ public class DatabaseService implements DatabaseServiceInterface { log.info("Database import completed: {}", scriptPath); } + /** + * Validates SQL content to prevent execution of dangerous functions. Uses an allowlist approach + * - only allows standard DML/DDL operations generated by H2's SCRIPT command. + * + * @param scriptPath the path to the SQL file + * @throws IllegalArgumentException if dangerous SQL or disallowed patterns are detected + */ + private void validateSqlContent(Path scriptPath) { + try { + String content = Files.readString(scriptPath); + String normalizedContent = normalizeSqlContent(content); + + // Validate that content only contains allowed operations (whitelist approach) + // Split by semicolons to check individual statements + String[] statements = normalizedContent.split(";"); + for (String statement : statements) { + statement = statement.trim(); + if (statement.isEmpty()) { + continue; + } + + // Check if statement matches any allowed pattern + boolean isAllowed = false; + for (Pattern allowedPattern : ALLOWED_PATTERNS) { + if (allowedPattern.matcher(statement).find()) { + isAllowed = true; + break; + } + } + + if (!isAllowed) { + log.error( + "Unrecognized SQL statement in backup file: {}", + statement.substring(0, Math.min(50, statement.length()))); + throw new IllegalArgumentException( + "SQL script contains unrecognized or disallowed SQL statements. File may be" + + " corrupted or tampered with."); + } + } + + } catch (IOException e) { + log.error("Error reading SQL script for validation: {}", e.getMessage()); + throw new IllegalArgumentException("Failed to validate SQL script: " + e.getMessage()); + } + } + + /** + * Normalizes SQL content by removing comments to prevent bypass attacks. + * + * @param sql the SQL content to normalize + * @return normalized SQL without comments + */ + private String normalizeSqlContent(String sql) { + // Remove block comments (/* ... */) + sql = sql.replaceAll("/\\*[\\s\\S]*?\\*/", " "); + // Remove line comments (--....) + sql = sql.replaceAll("--[^\r\n]*", " "); + // Collapse multiple whitespaces + sql = sql.replaceAll("\\s+", " "); + return sql.trim(); + } + /** * Checks for invalid characters or sequences * diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/DatabaseServiceTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/DatabaseServiceTest.java index 082c13165..84e891f67 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/DatabaseServiceTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/DatabaseServiceTest.java @@ -128,4 +128,139 @@ class DatabaseServiceTest { org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString()); } + + @Test + void validateSqlContentAcceptsRealH2BackupFile() throws IOException { + String sqlContent = + "-- H2 2.4.240; \n" + + "SET DB_CLOSE_DELAY -1; \n" + + "; \n" + + "CREATE USER IF NOT EXISTS \"SA\" SALT 'd4a7c2f9e1b5a8c3' HASH 'b8f3e6c1a9d2f7e4c5a8d1f6b3e7a2c9d0f5b8e1a4c7f2d9e6b3a1c8f5d2' ADMIN; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"AUDIT_EVENTS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"AUTHORITIES\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"INVITE_TOKENS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"PERSISTENT_LOGINS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"SESSIONS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"TEAMS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"USER_LICENSE_SETTINGS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"USER_SETTINGS\" CASCADE; \n" + + "DROP TABLE IF EXISTS \"PUBLIC\".\"USERS\" CASCADE; \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"AUDIT_EVENTS\"(\n" + + " \"ID\" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL,\n" + + " \"DATA\" CHARACTER VARYING,\n" + + " \"PRINCIPAL\" CHARACTER VARYING(255),\n" + + " \"TIMESTAMP\" TIMESTAMP(6) WITH TIME ZONE,\n" + + " \"TYPE\" CHARACTER VARYING(255)\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"AUDIT_EVENTS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_C\" PRIMARY KEY(\"ID\"); \n" + + "-- 0 +/- SELECT COUNT(*) FROM PUBLIC.AUDIT_EVENTS; \n" + + "CREATE INDEX \"PUBLIC\".\"IDX_AUDIT_TIMESTAMP\" ON \"PUBLIC\".\"AUDIT_EVENTS\"(\"TIMESTAMP\" NULLS FIRST); \n" + + "CREATE INDEX \"PUBLIC\".\"IDX_AUDIT_PRINCIPAL\" ON \"PUBLIC\".\"AUDIT_EVENTS\"(\"PRINCIPAL\" NULLS FIRST); \n" + + "CREATE INDEX \"PUBLIC\".\"IDX_AUDIT_TYPE\" ON \"PUBLIC\".\"AUDIT_EVENTS\"(\"TYPE\" NULLS FIRST); \n" + + "CREATE INDEX \"PUBLIC\".\"IDX_AUDIT_PRINCIPAL_TYPE\" ON \"PUBLIC\".\"AUDIT_EVENTS\"(\"PRINCIPAL\" NULLS FIRST, \"TYPE\" NULLS FIRST); \n" + + "CREATE INDEX \"PUBLIC\".\"IDX_AUDIT_TYPE_TIMESTAMP\" ON \"PUBLIC\".\"AUDIT_EVENTS\"(\"TYPE\" NULLS FIRST, \"TIMESTAMP\" NULLS FIRST); \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"AUTHORITIES\"(\n" + + " \"ID\" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1 RESTART WITH 3) NOT NULL,\n" + + " \"AUTHORITY\" CHARACTER VARYING(255),\n" + + " \"USER_ID\" BIGINT\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"AUTHORITIES\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_A\" PRIMARY KEY(\"ID\"); \n" + + "-- 2 +/- SELECT COUNT(*) FROM PUBLIC.AUTHORITIES; \n" + + "INSERT INTO \"PUBLIC\".\"AUTHORITIES\"(\"ID\", \"AUTHORITY\", \"USER_ID\") VALUES(1, 'ROLE_ADMIN', 1); \n" + + "INSERT INTO \"PUBLIC\".\"AUTHORITIES\"(\"ID\", \"AUTHORITY\", \"USER_ID\") VALUES(2, 'STIRLING-PDF-BACKEND-API-USER', 2);\n" + + "CREATE CACHED TABLE \"PUBLIC\".\"INVITE_TOKENS\"(\n" + + " \"ID\" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL,\n" + + " \"CREATED_AT\" TIMESTAMP(6),\n" + + " \"CREATED_BY\" CHARACTER VARYING(255) NOT NULL,\n" + + " \"EMAIL\" CHARACTER VARYING(255),\n" + + " \"EXPIRES_AT\" TIMESTAMP(6) NOT NULL,\n" + + " \"ROLE\" CHARACTER VARYING(50) NOT NULL,\n" + + " \"TEAM_ID\" BIGINT,\n" + + " \"TOKEN\" CHARACTER VARYING(100) NOT NULL,\n" + + " \"USED\" BOOLEAN NOT NULL,\n" + + " \"USED_AT\" TIMESTAMP(6)\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"INVITE_TOKENS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_E\" PRIMARY KEY(\"ID\"); \n" + + "-- 0 +/- SELECT COUNT(*) FROM PUBLIC.INVITE_TOKENS; \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"PERSISTENT_LOGINS\"(\n" + + " \"SERIES\" CHARACTER VARYING(255) NOT NULL,\n" + + " \"LAST_USED\" TIMESTAMP(6) WITH TIME ZONE NOT NULL,\n" + + " \"TOKEN\" CHARACTER VARYING(64) NOT NULL,\n" + + " \"USERNAME\" CHARACTER VARYING(64) NOT NULL\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"PERSISTENT_LOGINS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_A3\" PRIMARY KEY(\"SERIES\"); \n" + + "-- 0 +/- SELECT COUNT(*) FROM PUBLIC.PERSISTENT_LOGINS; \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"SESSIONS\"(\n" + + " \"SESSION_ID\" CHARACTER VARYING(255) NOT NULL,\n" + + " \"EXPIRED\" BOOLEAN NOT NULL,\n" + + " \"LAST_REQUEST\" TIMESTAMP(6) WITH TIME ZONE,\n" + + " \"PRINCIPAL_NAME\" CHARACTER VARYING(255)\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"SESSIONS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_8\" PRIMARY KEY(\"SESSION_ID\"); \n" + + "-- 0 +/- SELECT COUNT(*) FROM PUBLIC.SESSIONS; \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"TEAMS\"(\n" + + " \"TEAM_ID\" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1 RESTART WITH 3) NOT NULL,\n" + + " \"NAME\" CHARACTER VARYING(255) NOT NULL\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"TEAMS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_4\" PRIMARY KEY(\"TEAM_ID\"); \n" + + "-- 2 +/- SELECT COUNT(*) FROM PUBLIC.TEAMS; \n" + + "INSERT INTO \"PUBLIC\".\"TEAMS\"(\"TEAM_ID\", \"NAME\") VALUES(1, 'Default'); \n" + + "INSERT INTO \"PUBLIC\".\"TEAMS\"(\"TEAM_ID\", \"NAME\") VALUES(2, 'Internal'); \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"USER_LICENSE_SETTINGS\"(\n" + + " \"ID\" BIGINT NOT NULL,\n" + + " \"GRANDFATHERED_USER_COUNT\" INTEGER NOT NULL,\n" + + " \"GRANDFATHERED_USER_SIGNATURE\" CHARACTER VARYING(256) NOT NULL,\n" + + " \"GRANDFATHERING_LOCKED\" BOOLEAN NOT NULL,\n" + + " \"INTEGRITY_SALT\" CHARACTER VARYING(64) NOT NULL,\n" + + " \"LICENSE_MAX_USERS\" INTEGER NOT NULL\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"USER_LICENSE_SETTINGS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_9\" PRIMARY KEY(\"ID\"); \n" + + "-- 0 +/- SELECT COUNT(*) FROM PUBLIC.USER_LICENSE_SETTINGS; \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"USER_SETTINGS\"(\n" + + " \"USER_ID\" BIGINT NOT NULL,\n" + + " \"SETTING_VALUE\" CHARACTER VARYING,\n" + + " \"SETTING_KEY\" CHARACTER VARYING(255) NOT NULL\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"USER_SETTINGS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_9A\" PRIMARY KEY(\"USER_ID\", \"SETTING_KEY\"); \n" + + "-- 4 +/- SELECT COUNT(*) FROM PUBLIC.USER_SETTINGS; \n" + + "INSERT INTO \"PUBLIC\".\"USER_SETTINGS\"(\"USER_ID\", \"SETTING_VALUE\", \"SETTING_KEY\") VALUES(1, 'false', 'mfaEnabled'); \n" + + "INSERT INTO \"PUBLIC\".\"USER_SETTINGS\"(\"USER_ID\", \"SETTING_VALUE\", \"SETTING_KEY\") VALUES(1, 'false', 'mfaRequired'); \n" + + "INSERT INTO \"PUBLIC\".\"USER_SETTINGS\"(\"USER_ID\", \"SETTING_VALUE\", \"SETTING_KEY\") VALUES(2, 'false', 'mfaEnabled'); \n" + + "INSERT INTO \"PUBLIC\".\"USER_SETTINGS\"(\"USER_ID\", \"SETTING_VALUE\", \"SETTING_KEY\") VALUES(2, 'false', 'mfaRequired'); \n" + + "CREATE CACHED TABLE \"PUBLIC\".\"USERS\"(\n" + + " \"USER_ID\" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1 RESTART WITH 3) NOT NULL,\n" + + " \"API_KEY\" CHARACTER VARYING(255),\n" + + " \"AUTHENTICATIONTYPE\" CHARACTER VARYING(255),\n" + + " \"CREATED_AT\" TIMESTAMP(6),\n" + + " \"ENABLED\" BOOLEAN,\n" + + " \"FORCE_PASSWORD_CHANGE\" BOOLEAN,\n" + + " \"HAS_COMPLETED_INITIAL_SETUP\" BOOLEAN,\n" + + " \"IS_FIRST_LOGIN\" BOOLEAN,\n" + + " \"OAUTH_GRANDFATHERED\" BOOLEAN,\n" + + " \"PASSWORD\" CHARACTER VARYING(255),\n" + + " \"ROLE_NAME\" CHARACTER VARYING(255),\n" + + " \"SSO_PROVIDER\" CHARACTER VARYING(255),\n" + + " \"SSO_PROVIDER_ID\" CHARACTER VARYING(255),\n" + + " \"UPDATED_AT\" TIMESTAMP(6),\n" + + " \"USERNAME\" CHARACTER VARYING(255),\n" + + " \"TEAM_ID\" BIGINT\n" + + "); \n" + + "ALTER TABLE \"PUBLIC\".\"USERS\" ADD CONSTRAINT \"PUBLIC\".\"CONSTRAINT_4D\" PRIMARY KEY(\"USER_ID\"); \n" + + "-- 2 +/- SELECT COUNT(*) FROM PUBLIC.USERS; \n" + + "INSERT INTO \"PUBLIC\".\"USERS\"(\"USER_ID\", \"API_KEY\", \"AUTHENTICATIONTYPE\", \"CREATED_AT\", \"ENABLED\", \"FORCE_PASSWORD_CHANGE\", \"HAS_COMPLETED_INITIAL_SETUP\", \"IS_FIRST_LOGIN\", \"OAUTH_GRANDFATHERED\", \"PASSWORD\", \"ROLE_NAME\", \"SSO_PROVIDER\", \"SSO_PROVIDER_ID\", \"UPDATED_AT\", \"USERNAME\", \"TEAM_ID\") VALUES(1, NULL, 'web', TIMESTAMP '2026-02-26 00:25:03.688329', TRUE, FALSE, FALSE, FALSE, FALSE, '$2a$10$k9mF3xL6pD8nB2v5rT1wSuZqJ4kP7mH8gN5aY3bX9cW2eR6fD9sY1', NULL, NULL, NULL, TIMESTAMP '2026-02-26 00:25:03.688365', 'ludy', 1); \n" + + "INSERT INTO \"PUBLIC\".\"USERS\"(\"USER_ID\", \"API_KEY\", \"AUTHENTICATIONTYPE\", \"CREATED_AT\", \"ENABLED\", \"FORCE_PASSWORD_CHANGE\", \"HAS_COMPLETED_INITIAL_SETUP\", \"IS_FIRST_LOGIN\", \"OAUTH_GRANDFATHERED\", \"PASSWORD\", \"ROLE_NAME\", \"SSO_PROVIDER\", \"SSO_PROVIDER_ID\", \"UPDATED_AT\", \"USERNAME\", \"TEAM_ID\") VALUES(2, 'e3c7f1a9-6b2d-4f5e-8c3a-9d1b7e4f2c6a', 'web', TIMESTAMP '2026-02-26 00:25:03.845308', TRUE, FALSE, FALSE, FALSE, FALSE, '$2a$10$m7pB4qL9tF2jW5hX8vR3nUYsK1dE6cG9oP0jM3iC8lA7nZ4fT9xR2', NULL, NULL, NULL, TIMESTAMP '2026-02-26 00:25:03.914916', 'STIRLING-PDF-BACKEND-API-USER', 2); \n" + + "ALTER TABLE \"PUBLIC\".\"USERS\" ADD CONSTRAINT \"PUBLIC\".\"UKR43AF9AP4EDM43MMTQ01ODDJ6\" UNIQUE NULLS DISTINCT (\"USERNAME\"); \n" + + "ALTER TABLE \"PUBLIC\".\"TEAMS\" ADD CONSTRAINT \"PUBLIC\".\"UKA510NO6SJWQCX153YD5SM4JRR\" UNIQUE NULLS DISTINCT (\"NAME\"); \n" + + "ALTER TABLE \"PUBLIC\".\"INVITE_TOKENS\" ADD CONSTRAINT \"PUBLIC\".\"UKEWCI50C0NCFIIHR2TC41NFBNF\" UNIQUE NULLS DISTINCT (\"TOKEN\"); \n" + + "ALTER TABLE \"PUBLIC\".\"AUTHORITIES\" ADD CONSTRAINT \"PUBLIC\".\"FKK91UPMBUEYIM93V469WJ7B2QH\" FOREIGN KEY(\"USER_ID\") REFERENCES \"PUBLIC\".\"USERS\"(\"USER_ID\") NOCHECK;\n" + + "ALTER TABLE \"PUBLIC\".\"USER_SETTINGS\" ADD CONSTRAINT \"PUBLIC\".\"FK8V82NJ88RMAI0NYCK19F873DW\" FOREIGN KEY(\"USER_ID\") REFERENCES \"PUBLIC\".\"USERS\"(\"USER_ID\") NOCHECK; \n" + + "ALTER TABLE \"PUBLIC\".\"USERS\" ADD CONSTRAINT \"PUBLIC\".\"FKFJWS1RDRUAB2BQG7QIPOQF65R\" FOREIGN KEY(\"TEAM_ID\") REFERENCES \"PUBLIC\".\"TEAMS\"(\"TEAM_ID\") NOCHECK; \n" + + ""; + + Path script = Files.createTempFile("backup", ".sql"); + Files.writeString(script, sqlContent); + + boolean result = databaseService.importDatabaseFromUI(script); + assertThat(result).isTrue(); + } } diff --git a/build.gradle b/build.gradle index e3bd51554..3f4501443 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ ext { lombokVersion = "1.18.42" bouncycastleVersion = "1.83" springSecuritySamlVersion = "7.0.2" - openSamlVersion = "4.3.2" + openSamlVersion = "5.2.1" commonmarkVersion = "0.27.1" googleJavaFormatVersion = "1.34.1" logback = "1.5.32" @@ -176,6 +176,7 @@ subprojects { } } maven { url = "https://build.shibboleth.net/maven/releases" } + maven { url = "https://repository.jboss.org/" } mavenCentral() }