From 02a82261960b15ee3d4854165aae268c9c247345 Mon Sep 17 00:00:00 2001 From: Ruslan Musaev <43766501+HocKu7@users.noreply.github.com> Date: Thu, 10 Apr 2025 11:06:09 +0200 Subject: [PATCH] Cb 5875 creating schemas only for pro (#3324) --- .../db/cb_schema_create.sql | 67 +------ .../db/cb_schema_update_10.sql | 1 - .../db/cb_schema_update_22.sql | 2 +- .../db/cb_schema_update_24.sql | 2 + .../db/cb_schema_update_5.sql | 2 +- .../EmbeddedSecurityControllerFactory.java | 7 +- .../service/security/db/CBDatabase.java | 181 +++++++----------- .../security/db/CBSchemaVersionManager.java | 140 ++++++++++++++ 8 files changed, 227 insertions(+), 175 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_24.sql create mode 100644 server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBSchemaVersionManager.java diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql index 55ea7b36dd..86ebcb670e 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql @@ -1,7 +1,8 @@ CREATE TABLE {table_prefix}CB_SCHEMA_INFO ( - VERSION INTEGER NOT NULL, - UPDATE_TIME TIMESTAMP NOT NULL + MODULE_ID VARCHAR(10) NOT NULL, + VERSION INTEGER NOT NULL, + UPDATE_TIME TIMESTAMP NOT NULL ); CREATE TABLE {table_prefix}CB_INSTANCE @@ -236,65 +237,3 @@ CREATE TABLE {table_prefix}CB_AUTH_ATTEMPT_INFO PRIMARY KEY (AUTH_ID, AUTH_PROVIDER_ID), FOREIGN KEY (AUTH_ID) REFERENCES {table_prefix}CB_AUTH_ATTEMPT (AUTH_ID) ON DELETE CASCADE ); - --- Secrets - -CREATE TABLE {table_prefix}CB_USER_SECRETS -( - USER_ID VARCHAR(128) NOT NULL, - SECRET_ID VARCHAR(512) NOT NULL, - SECRET_VALUE TEXT NOT NULL, - - SECRET_LABEL VARCHAR(128), - SECRET_DESCRIPTION VARCHAR(1024), - ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL, - UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - - PRIMARY KEY (USER_ID, SECRET_ID), - FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER (USER_ID) ON DELETE CASCADE -); - -CREATE TABLE {table_prefix}CB_SUBJECT_SECRETS -( - SUBJECT_ID VARCHAR(128) NOT NULL, - SECRET_ID VARCHAR(255) NOT NULL, - - PROJECT_ID VARCHAR(128), - OBJECT_TYPE VARCHAR(32), - OBJECT_ID VARCHAR(128), - - SECRET_VALUE TEXT NOT NULL, - - ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL, - CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - - PRIMARY KEY (SUBJECT_ID, SECRET_ID), - FOREIGN KEY (SUBJECT_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE -); - -CREATE INDEX IDX_SUBJECT_SECRETS_PROJECT ON {table_prefix}CB_SUBJECT_SECRETS (PROJECT_ID,SUBJECT_ID); -CREATE INDEX IDX_SUBJECT_SECRETS_OBJECT ON {table_prefix}CB_SUBJECT_SECRETS (PROJECT_ID,OBJECT_TYPE,OBJECT_ID); - --- Tasks -CREATE TABLE {table_prefix}CB_TASKS -( - TASK_ID VARCHAR(128) NOT NULL, - CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - TIMEOUT INTEGER NOT NULL, - - PRIMARY KEY (TASK_ID) -); - -CREATE TABLE {table_prefix}CB_ACCESS_TOKEN -( - TOKEN_ID VARCHAR(128) NOT NULL, - USER_ID VARCHAR(128) NOT NULL, - TOKEN_NAME VARCHAR(128) NOT NULL, - CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - EXPIRATION_TIME TIMESTAMP NULL, - - PRIMARY KEY (USER_ID, TOKEN_ID), - UNIQUE (USER_ID, TOKEN_NAME), - FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER(USER_ID) ON DELETE CASCADE -); diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql index 8d1d44cf82..4f3fb877a0 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql @@ -14,4 +14,3 @@ CREATE TABLE {table_prefix}CB_USER_SECRETS ); CREATE INDEX CB_USER_SECRETS_ID ON {table_prefix}CB_USER_SECRETS (USER_ID,SECRET_ID); - diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_22.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_22.sql index 201502ce6c..8ac5fdab27 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_22.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_22.sql @@ -9,4 +9,4 @@ CREATE TABLE {table_prefix}CB_ACCESS_TOKEN PRIMARY KEY (USER_ID, TOKEN_ID), UNIQUE (USER_ID, TOKEN_NAME), FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER(USER_ID) ON DELETE CASCADE -); +); \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_24.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_24.sql new file mode 100644 index 0000000000..486d55d3e1 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_24.sql @@ -0,0 +1,2 @@ +ALTER TABLE {table_prefix}CB_SCHEMA_INFO +ADD MODULE_ID VARCHAR(10) DEFAULT 'CB_CE' NOT NULL; \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql index b7ccc68097..ea46bc1458 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql @@ -10,4 +10,4 @@ CREATE TABLE {table_prefix}CB_AUTH_TOKEN PRIMARY KEY (TOKEN_ID), FOREIGN KEY (SESSION_ID) REFERENCES {table_prefix}CB_SESSION (SESSION_ID) ON DELETE CASCADE, FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER (USER_ID) ON DELETE CASCADE -); +); \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java index 8528ee1a9a..4aa8d40ecc 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java @@ -17,6 +17,7 @@ package io.cloudbeaver.service.security; import io.cloudbeaver.auth.NoAuthCredentialsProvider; +import io.cloudbeaver.model.app.ServletApplication; import io.cloudbeaver.model.app.ServletAuthApplication; import io.cloudbeaver.model.config.SMControllerConfiguration; import io.cloudbeaver.model.config.WebDatabaseConfig; @@ -73,7 +74,7 @@ public class EmbeddedSecurityControllerFactory @NotNull WebDatabaseConfig databaseConfig, @NotNull SMControllerConfiguration smConfig ) throws DBException { - var database = new CBDatabase(application, databaseConfig); + var database = makeDatabase(application, databaseConfig); var securityController = createEmbeddedSecurityController( application, database, new NoAuthCredentialsProvider(), smConfig ); @@ -97,4 +98,8 @@ public class EmbeddedSecurityControllerFactory ) { return new CBEmbeddedSecurityController(application, database, credentialsProvider, smConfig); } + + protected CBDatabase makeDatabase(ServletApplication application, WebDatabaseConfig databaseConfig) { + return new CBDatabase(application, databaseConfig); + } } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java index f9dad0c72f..9684f80ad4 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java @@ -44,7 +44,6 @@ import org.jkiss.dbeaver.model.security.user.SMTeam; import org.jkiss.dbeaver.model.security.user.SMUser; import org.jkiss.dbeaver.model.sql.db.InternalDB; import org.jkiss.dbeaver.model.sql.schema.SQLSchemaConfig; -import org.jkiss.dbeaver.model.sql.schema.SQLSchemaVersionManager; import org.jkiss.dbeaver.registry.DataSourceProviderRegistry; import org.jkiss.dbeaver.registry.storage.H2Migrator; import org.jkiss.dbeaver.runtime.DBWorkbench; @@ -54,7 +53,6 @@ import org.jkiss.utils.CommonUtils; import org.jkiss.utils.IOUtils; import org.jkiss.utils.SecurityUtils; -import javax.sql.DataSource; import java.io.*; import java.net.InetAddress; import java.net.UnknownHostException; @@ -65,6 +63,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; +import javax.sql.DataSource; /** * Database management @@ -72,15 +71,18 @@ import java.util.stream.Collectors; public class CBDatabase extends InternalDB { private static final Log log = Log.getLog(CBDatabase.class); - private static final int LEGACY_SCHEMA_VERSION = 1; - private static final int CURRENT_SCHEMA_VERSION = 23; + private static final int CURRENT_SCHEMA_VERSION = 24; + private static final String SCHEMA_ID = "CB_CE"; private static final SQLSchemaConfig SCHEMA_CREATE_CONFIG = new SQLSchemaConfig( - "CB", + SCHEMA_ID, "db/cb_schema_create.sql", "db/cb_schema_update_", CURRENT_SCHEMA_VERSION, - 0 + 0, + new CBSchemaVersionManager(CURRENT_SCHEMA_VERSION, SCHEMA_ID), + CBDatabase.class.getClassLoader(), + null ); private static final String DEFAULT_DB_USER_NAME = "cb-data"; @@ -97,8 +99,23 @@ public class CBDatabase extends InternalDB { private SMAdminController adminSecurityController; public CBDatabase(@NotNull ServletApplication application, @NotNull WebDatabaseConfig databaseConfiguration) { - super("Security Manager", databaseConfiguration, SCHEMA_CREATE_CONFIG); + this(application, databaseConfiguration, Collections.emptyList()); + } + + public CBDatabase( + @NotNull ServletApplication application, + @NotNull WebDatabaseConfig databaseConfiguration, + @NotNull List sqlSchemaConfigList + ) { + super("Security Manager", databaseConfiguration, appendSchemaConfig(sqlSchemaConfigList)); this.application = application; + SCHEMA_CREATE_CONFIG.setInitialSchemaFiller(this::fillInitialSchemaData); + } + + private static List appendSchemaConfig(List sqlSchemaConfigList) { + List sqlSchemaConfigs = new ArrayList<>(sqlSchemaConfigList); + sqlSchemaConfigs.add(0, SCHEMA_CREATE_CONFIG); + return sqlSchemaConfigs; } public void setAdminSecurityController(SMAdminController adminSecurityController) { @@ -201,7 +218,7 @@ public class CBDatabase extends InternalDB { throw new DBException("CB database connection is not defined"); } createSchemaIfNotExists(connection); - updateSchema(monitor, connection, CBDatabase.class.getClassLoader(), new CBSchemaVersionManager()); + updateSchema(monitor, connection); validateInstancePersistentState(connection); } @@ -322,113 +339,63 @@ public class CBDatabase extends InternalDB { closeConnection(); } - private class CBSchemaVersionManager implements SQLSchemaVersionManager { - @Override - public int getCurrentSchemaVersion(DBRProgressMonitor monitor, Connection connection, String schemaName) - throws DBException, SQLException { - // Check and update schema - try { - int version = CommonUtils.toInt(JDBCUtils.executeQuery(connection, - normalizeTableNames("SELECT VERSION FROM {table_prefix}CB_SCHEMA_INFO"))); - return version == 0 ? 1 : version; - } catch (SQLException e) { - try { - Object legacyVersion = CommonUtils.toInt(JDBCUtils.executeQuery(connection, - normalizeTableNames("SELECT SCHEMA_VERSION FROM {table_prefix}CB_SERVER"))); - // Table CB_SERVER exist - this is a legacy schema - return LEGACY_SCHEMA_VERSION; - } catch (SQLException ex) { - // Empty schema. Create it from scratch - return -1; + public void fillInitialSchemaData(DBRProgressMonitor monitor, Connection connection) throws DBException, SQLException { + // Set exclusive connection. Otherwise security controller will open a new one and won't see new schema objects. + exclusiveConnection = new DelegatingConnection(connection) { + @Override + public void close() throws SQLException { + // do nothing + } + }; + + try { + // Fill initial data + + + if (initialData == null) { + return; + } + + String adminName = initialData.getAdminName(); + String adminPassword = initialData.getAdminPassword(); + List initialTeams = initialData.getTeams(); + String defaultTeam = application.getAppConfiguration().getDefaultUserTeam(); + if (CommonUtils.isNotEmpty(defaultTeam)) { + Set initialTeamNames = initialTeams == null + ? Set.of() + : initialTeams.stream().map(SMTeam::getTeamId).collect(Collectors.toSet()); + if (!initialTeamNames.contains(defaultTeam)) { + throw new DBException("Initial teams configuration doesn't contain default team " + defaultTeam); } } - } - - @Override - public int getLatestSchemaVersion() { - return SCHEMA_CREATE_CONFIG.schemaVersionActual(); - } - - @Override - public void updateCurrentSchemaVersion( - DBRProgressMonitor monitor, - @NotNull Connection connection, - @NotNull String schemaName, - int version - ) throws DBException, SQLException { - var updateCount = JDBCUtils.executeUpdate( - connection, - normalizeTableNames("UPDATE {table_prefix}CB_SCHEMA_INFO SET VERSION=?,UPDATE_TIME=CURRENT_TIMESTAMP"), - version - ); - if (updateCount <= 0) { - JDBCUtils.executeSQL( - connection, - normalizeTableNames( - "INSERT INTO {table_prefix}CB_SCHEMA_INFO (VERSION,UPDATE_TIME) VALUES(?,CURRENT_TIMESTAMP)"), - version - ); - } - } - - @Override - //TODO move out - public void fillInitialSchemaData(DBRProgressMonitor monitor, Connection connection) - throws DBException, SQLException { - // Set exclusive connection. Otherwise security controller will open a new one and won't see new schema objects. - exclusiveConnection = new DelegatingConnection(connection) { - @Override - public void close() throws SQLException { - // do nothing - } - }; - - try { - // Fill initial data - if (initialData == null) { - return; - } - - String adminName = initialData.getAdminName(); - String adminPassword = initialData.getAdminPassword(); - List initialTeams = initialData.getTeams(); - String defaultTeam = application.getAppConfiguration().getDefaultUserTeam(); - if (CommonUtils.isNotEmpty(defaultTeam)) { - Set initialTeamNames = initialTeams == null - ? Set.of() - : initialTeams.stream().map(SMTeam::getTeamId).collect(Collectors.toSet()); - if (!initialTeamNames.contains(defaultTeam)) { - throw new DBException("Initial teams configuration doesn't contain default team " + defaultTeam); + if (!CommonUtils.isEmpty(initialTeams)) { + // Create teams + for (SMTeam team : initialTeams) { + adminSecurityController.createTeam( + team.getTeamId(), + team.getName(), + team.getDescription(), + adminName + ); + if (!application.isMultiNode()) { + adminSecurityController.setSubjectPermissions( + team.getTeamId(), + new ArrayList<>(team.getPermissions()), + "initial-data-configuration" + ); } } - if (!CommonUtils.isEmpty(initialTeams)) { - // Create teams - for (SMTeam team : initialTeams) { - adminSecurityController.createTeam(team.getTeamId(), - team.getName(), - team.getDescription(), - adminName); - if (!application.isMultiNode()) { - adminSecurityController.setSubjectPermissions( - team.getTeamId(), - new ArrayList<>(team.getPermissions()), - "initial-data-configuration" - ); - } - } - } - - if (!CommonUtils.isEmpty(adminName)) { - // Create admin user - createAdminUser(adminName, adminPassword); - } - } finally { - exclusiveConnection = null; } + + if (!CommonUtils.isEmpty(adminName)) { + // Create admin user + createAdminUser(adminName, adminPassword); + } + } finally { + exclusiveConnection = null; } } - ////////////////////////////////////////// // Persistence diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBSchemaVersionManager.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBSchemaVersionManager.java new file mode 100644 index 0000000000..a872a258eb --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBSchemaVersionManager.java @@ -0,0 +1,140 @@ +package io.cloudbeaver.service.security.db; + +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.model.sql.schema.SQLSchemaVersionManager; +import org.jkiss.utils.CommonUtils; + +import java.sql.Connection; +import java.sql.SQLException; + +public class CBSchemaVersionManager implements SQLSchemaVersionManager { + + private static final Log log = Log.getLog(CBSchemaVersionManager.class); + private static final int LEGACY_SCHEMA_VERSION = 1; + private final int currentSchemaVersion; + private final String schemaId; + + public CBSchemaVersionManager(int currentSchemaVersion, String schemaId) { + this.currentSchemaVersion = currentSchemaVersion; + this.schemaId = schemaId; + } + + @Override + public int getCurrentSchemaVersion( + DBRProgressMonitor monitor, + Connection connection, + String schemaName + ) throws DBException, SQLException { + + Integer version = tryGetVersion( + connection, + CommonUtils.normalizeTableNames("SELECT VERSION FROM {table_prefix}CB_SCHEMA_INFO WHERE MODULE_ID = ?", schemaName), + getSchemaId() + ); + if (version != null) { + return version; + } + version = tryGetVersion( + connection, + CommonUtils.normalizeTableNames("SELECT VERSION FROM {table_prefix}CB_SCHEMA_INFO", schemaName) + ); + if (version != null) { + return version; + } + version = tryGetVersion( + connection, + CommonUtils.normalizeTableNames("SELECT SCHEMA_VERSION FROM {table_prefix}CB_SERVER", schemaName) + ); + if (version != null) { + return LEGACY_SCHEMA_VERSION; + } + // Empty schema. Create it from scratch + return -1; + } + + @Override + public int getLatestSchemaVersion() { + return currentSchemaVersion; + } + + @Override + public void updateCurrentSchemaVersion( + DBRProgressMonitor monitor, + @NotNull Connection connection, + @NotNull String schemaName, + int version + ) throws DBException, SQLException { + var selectCount = CommonUtils.toInt(JDBCUtils.executeQuery( + connection, + CommonUtils.normalizeTableNames( + "SELECT count(1) FROM {table_prefix}CB_SCHEMA_INFO", + schemaName + ) + )); + if (selectCount <= 0) { + log.debug("Didn't find any records in " + + CommonUtils.normalizeTableNames("{table_prefix}CB_SCHEMA_INFO", schemaName)); + JDBCUtils.executeSQL( + connection, + CommonUtils.normalizeTableNames( + "INSERT INTO {table_prefix}CB_SCHEMA_INFO (MODULE_ID, VERSION,UPDATE_TIME) VALUES(?, ?, CURRENT_TIMESTAMP)", + schemaName + ), + getSchemaId(), + version + ); + return; + } + if (selectCount == 1) { + log.debug("Found only one record in " + + CommonUtils.normalizeTableNames("{table_prefix}CB_SCHEMA_INFO", schemaName)); + JDBCUtils.executeUpdate( + connection, + CommonUtils.normalizeTableNames( + "UPDATE {table_prefix}CB_SCHEMA_INFO SET VERSION=?,UPDATE_TIME=CURRENT_TIMESTAMP", + schemaName + ), + version + ); + return; + } + JDBCUtils.executeUpdate( + connection, + CommonUtils.normalizeTableNames( + "UPDATE {table_prefix}CB_SCHEMA_INFO SET VERSION=?,UPDATE_TIME=CURRENT_TIMESTAMP WHERE MODULE_ID = ?", + schemaName + ), + version, + getSchemaId() + ); + + } + + protected Integer tryGetVersion(Connection connection, String sql, Object... params) { + return tryGetVersion(connection, sql, 1, params); + } + + protected Integer tryGetVersion(Connection connection, String sql, Integer defaultVersion, Object... params) { + try { + Object result = JDBCUtils.executeQuery(connection, sql, params); + return result == null ? defaultVersion : CommonUtils.toInt(result); + } catch (Exception e) { + try { + connection.rollback(); + } catch (SQLException ex) { + log.error("Can't rollback after unsuccessful try to get version of current schema", ex); + } + return null; + } + } + + + protected String getSchemaId() { + return schemaId; + } +} +