diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/VoidSecretController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/VoidSecretController.java index 64e3a3974a..e267f9e098 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/VoidSecretController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/VoidSecretController.java @@ -37,12 +37,12 @@ public VoidSecretController() { @Nullable @Override - public String getSecretValue(@NotNull String secretId) { + public String getPrivateSecretValue(@NotNull String secretId) { return null; } @Override - public void setSecretValue(@NotNull String secretId, @Nullable String secretValue) throws DBException { + public void setPrivateSecretValue(@NotNull String secretId, @Nullable String secretValue) throws DBException { throw new DBException("Secret controller is read-only"); } 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 03c7a6b5bb..08c792213f 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 @@ -60,8 +60,9 @@ CREATE TABLE {table_prefix}CB_WORKSPACE CREATE TABLE {table_prefix}CB_AUTH_SUBJECT ( - SUBJECT_ID VARCHAR(128) NOT NULL, - SUBJECT_TYPE VARCHAR(8) NOT NULL, + SUBJECT_ID VARCHAR(128) NOT NULL, + SUBJECT_TYPE VARCHAR(8) NOT NULL, + IS_SECRET_STORAGE CHAR(1) DEFAULT 'Y' NOT NULL, PRIMARY KEY (SUBJECT_ID) ); @@ -105,16 +106,30 @@ CREATE TABLE {table_prefix}CB_OBJECT_PERMISSIONS FOREIGN KEY (SUBJECT_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE ); +CREATE TABLE {table_prefix}CB_CREDENTIALS_PROFILE +( + PROFILE_ID VARCHAR(128) NOT NULL, + PROFILE_NAME VARCHAR(100) NOT NULL, + PROFILE_DESCRIPTION VARCHAR(255) NOT NULL, + PARENT_PROFILE_ID VARCHAR(128) NULL, + CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + + PRIMARY KEY (PROFILE_ID), + FOREIGN KEY (PROFILE_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE, + FOREIGN KEY (PARENT_PROFILE_ID) REFERENCES {table_prefix}CB_CREDENTIALS_PROFILE(PROFILE_ID) ON DELETE NO ACTION +); + CREATE TABLE {table_prefix}CB_USER ( USER_ID VARCHAR(128) NOT NULL, - IS_ACTIVE CHAR(1) NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, DEFAULT_AUTH_ROLE VARCHAR(32) NULL, + CREDENTIALS_PROFILE_ID VARCHAR(128) NULL, PRIMARY KEY (USER_ID), - FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE + FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE, + FOREIGN KEY (CREDENTIALS_PROFILE_ID) REFERENCES {table_prefix}CB_CREDENTIALS_PROFILE(PROFILE_ID) ON DELETE NO ACTION ); -- Additional user properties (profile) @@ -131,11 +146,10 @@ CREATE TABLE {table_prefix}CB_USER_PARAMETERS CREATE TABLE {table_prefix}CB_TEAM ( - TEAM_ID VARCHAR(128) NOT NULL, - TEAM_NAME VARCHAR(100) NOT NULL, - TEAM_DESCRIPTION VARCHAR(255) NOT NULL, - - CREATE_TIME TIMESTAMP NOT NULL, + TEAM_ID VARCHAR(128) NOT NULL, + TEAM_NAME VARCHAR(100) NOT NULL, + TEAM_DESCRIPTION VARCHAR(255) NOT NULL, + CREATE_TIME TIMESTAMP NOT NULL, PRIMARY KEY (TEAM_ID), FOREIGN KEY (TEAM_ID) REFERENCES {table_prefix}CB_AUTH_SUBJECT (SUBJECT_ID) ON DELETE CASCADE @@ -315,3 +329,25 @@ CREATE TABLE {table_prefix}CB_USER_SECRETS 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); diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_16.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_16.sql new file mode 100644 index 0000000000..42e350c4c2 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_16.sql @@ -0,0 +1,29 @@ +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); + +INSERT INTO {table_prefix}CB_SUBJECT_SECRETS (SUBJECT_ID, SECRET_ID, SECRET_VALUE, ENCODING_TYPE, CREATE_TIME, UPDATE_TIME) +SELECT USER_ID, SECRET_ID, SECRET_VALUE, ENCODING_TYPE, UPDATE_TIME, UPDATE_TIME FROM {table_prefix}CB_USER_SECRETS; + +DROP TABLE {table_prefix}CB_USER_SECRETS; + +ALTER TABLE {table_prefix}CB_AUTH_SUBJECT + ADD COLUMN IS_SECRET_STORAGE CHAR(1) DEFAULT 'Y' NOT NULL; \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index d9c223c900..bd1491b9b0 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -74,8 +74,6 @@ public class CBEmbeddedSecurityController protected static final String CHAR_BOOL_TRUE = "Y"; protected static final String CHAR_BOOL_FALSE = "N"; - private static final String SUBJECT_USER = "U"; - private static final String SUBJECT_TEAM = "R"; private static final Type MAP_STRING_OBJECT_TYPE = new TypeToken>() { }.getType(); private static final Gson gson = new GsonBuilder().create(); @@ -147,7 +145,7 @@ public void createUser( boolean enabled, @Nullable String defaultAuthRole ) throws DBException, SQLException { - createAuthSubject(dbCon, userId, SUBJECT_USER); + createAuthSubject(dbCon, userId, SMSubjectType.user, true); try (PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames("INSERT INTO {table_prefix}CB_USER" + "(USER_ID,IS_ACTIVE,CREATE_TIME,DEFAULT_AUTH_ROLE) VALUES(?,?,?,?)")) @@ -254,9 +252,10 @@ public void setUserTeams(@NotNull Connection dbCon, String userId, String[] team public SMTeam[] getUserTeams(String userId) throws DBException { Map teams = new LinkedHashMap<>(); try (Connection dbCon = database.openConnection()) { - try (PreparedStatement dbStat = dbCon.prepareStatement( - database.normalizeTableNames("SELECT R.* FROM {table_prefix}CB_USER_TEAM UR, {table_prefix}CB_TEAM R " + - "WHERE UR.USER_ID=? AND UR.TEAM_ID=R.TEAM_ID")) + try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames( + "SELECT R.*,S.IS_SECRET_STORAGE FROM {table_prefix}CB_USER_TEAM UR, {table_prefix}CB_TEAM R, " + + "{table_prefix}CB_AUTH_SUBJECT S " + + "WHERE UR.USER_ID=? AND UR.TEAM_ID=R.TEAM_ID AND S.SUBJECT_ID=R.TEAM_ID")) ) { dbStat.setString(1, userId); try (ResultSet dbResult = dbStat.executeQuery()) { @@ -300,15 +299,15 @@ public SMUser getUserById(String userId) throws DBException { try (Connection dbCon = database.openConnection()) { SMUser user; try (PreparedStatement dbStat = dbCon.prepareStatement( - database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER WHERE USER_ID=?") + database.normalizeTableNames( + "SELECT U.USER_ID,U.IS_ACTIVE,U.DEFAULT_AUTH_ROLE,S.IS_SECRET_STORAGE FROM " + + "{table_prefix}CB_USER U, {table_prefix}CB_AUTH_SUBJECT S " + + "WHERE U.USER_ID=? AND U.USER_ID=S.SUBJECT_ID") )) { dbStat.setString(1, userId); try (ResultSet dbResult = dbStat.executeQuery()) { if (dbResult.next()) { - String userName = dbResult.getString(1); - String active = dbResult.getString(2); - String authRole = dbResult.getString(3); - user = new SMUser(userName, CHAR_BOOL_TRUE.equals(active), authRole); + user = fetchUser(dbResult); } else { return null; } @@ -909,7 +908,9 @@ public SMTeam[] readAllTeams() throws DBCException { Map teams = new LinkedHashMap<>(); try (Statement dbStat = dbCon.createStatement()) { try (ResultSet dbResult = dbStat.executeQuery( - database.normalizeTableNames("SELECT * FROM {table_prefix}CB_TEAM ORDER BY TEAM_ID"))) { + database.normalizeTableNames("SELECT T.*,S.IS_SECRET_STORAGE FROM {table_prefix}CB_TEAM T," + + "{table_prefix}CB_AUTH_SUBJECT S " + + "WHERE T.TEAM_ID=S.SUBJECT_ID ORDER BY TEAM_ID"))) { while (dbResult.next()) { SMTeam team = fetchTeam(dbResult); teams.put(team.getTeamId(), team); @@ -966,7 +967,18 @@ private SMTeam fetchTeam(ResultSet dbResult) throws SQLException { return new SMTeam( dbResult.getString("TEAM_ID"), dbResult.getString("TEAM_NAME"), - dbResult.getString("TEAM_DESCRIPTION") + dbResult.getString("TEAM_DESCRIPTION"), + stringToBoolean(dbResult.getString("IS_SECRET_STORAGE")) + ); + } + + @NotNull + private SMUser fetchUser(ResultSet dbResult) throws SQLException { + return new SMUser( + dbResult.getString("USER_ID"), + stringToBoolean(dbResult.getString("IS_ACTIVE")), + dbResult.getString("DEFAULT_AUTH_ROLE"), + stringToBoolean(dbResult.getString("IS_SECRET_STORAGE")) ); } @@ -980,7 +992,7 @@ public void createTeam(String teamId, String name, String description, String gr } try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { - createAuthSubject(dbCon, teamId, SUBJECT_TEAM); + createAuthSubject(dbCon, teamId, SMSubjectType.team, true); try (PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames("INSERT INTO {table_prefix}CB_TEAM" + "(TEAM_ID,TEAM_NAME,TEAM_DESCRIPTION,CREATE_TIME) VALUES(?,?,?,?)"))) { @@ -2794,15 +2806,31 @@ public void initializeMetaInformation() throws DBCException { } } - private void createAuthSubject(Connection dbCon, String subjectId, String subjectType) throws SQLException { + private void createAuthSubject( + Connection dbCon, + String subjectId, + SMSubjectType subjectType, + boolean secretStorage + ) throws SQLException { try (PreparedStatement dbStat = dbCon.prepareStatement( - database.normalizeTableNames("INSERT INTO {table_prefix}CB_AUTH_SUBJECT(SUBJECT_ID,SUBJECT_TYPE) VALUES(?,?)"))) { + database.normalizeTableNames( + "INSERT INTO {table_prefix}CB_AUTH_SUBJECT(SUBJECT_ID,SUBJECT_TYPE,IS_SECRET_STORAGE) " + + "VALUES (?,?,?)"))) { dbStat.setString(1, subjectId); - dbStat.setString(2, subjectType); + dbStat.setString(2, subjectType.getCode()); + dbStat.setString(3, booleanToString(secretStorage)); dbStat.execute(); } } + public static String booleanToString(boolean value) { + return value ? CHAR_BOOL_TRUE : CHAR_BOOL_FALSE; + } + + public static boolean stringToBoolean(@Nullable String value) { + return CHAR_BOOL_TRUE.equals(value); + } + private void deleteAuthSubject(Connection dbCon, String subjectId) throws SQLException { try (PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames("DELETE FROM {table_prefix}CB_AUTH_SUBJECT WHERE SUBJECT_ID=?"))) { 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 8dfe25b61a..5cddbba56a 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 @@ -73,7 +73,7 @@ public class CBDatabase { public static final String SCHEMA_UPDATE_SQL_PATH = "db/cb_schema_update_"; private static final int LEGACY_SCHEMA_VERSION = 1; - private static final int CURRENT_SCHEMA_VERSION = 15; + private static final int CURRENT_SCHEMA_VERSION = 17; private static final String DEFAULT_DB_USER_NAME = "cb-data"; private static final String DEFAULT_DB_PWD_FILE = ".database-credentials.dat";