From 91f5b1075a9699fedcbea4609cf489b850726887 Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Thu, 25 Jan 2024 18:27:07 +0400 Subject: [PATCH] CB-3834 profile management api --- .../model/app/BaseWebAppConfiguration.java | 6 + .../model/app/WebAppConfiguration.java | 4 + .../io/cloudbeaver/model/user/WebUser.java | 5 + .../schema/service.admin.graphqls | 1 + .../service/admin/AdminUserInfo.java | 6 + .../CBEmbeddedSecurityController.java | 121 +++++++++++++++++- .../service/security/db/CBDatabase.java | 2 +- .../security/db/CBDatabaseInitialData.java | 6 + 8 files changed, 143 insertions(+), 8 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebAppConfiguration.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebAppConfiguration.java index 719feb4ed3e..25b1a23733d 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebAppConfiguration.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebAppConfiguration.java @@ -30,6 +30,7 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration { protected final Map plugins; protected String defaultUserTeam; + protected String defaultUserCredentialsProfile; protected boolean resourceManagerEnabled; protected boolean showReadOnlyConnectionInfo; protected String[] enabledFeatures; @@ -105,4 +106,9 @@ public void setEnabledFeatures(String[] enabledFeatures) { public boolean isShowReadOnlyConnectionInfo() { return showReadOnlyConnectionInfo; } + + + public String getDefaultUserCredentialsProfile() { + return defaultUserCredentialsProfile; + } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebAppConfiguration.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebAppConfiguration.java index 92c23889218..adc0afb372c 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebAppConfiguration.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebAppConfiguration.java @@ -17,6 +17,7 @@ package io.cloudbeaver.model.app; import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; import java.util.Map; @@ -32,6 +33,9 @@ public interface WebAppConfiguration { String getDefaultUserTeam(); + @Nullable + String getDefaultUserCredentialsProfile(); + T getPluginOption(@NotNull String pluginId, @NotNull String option); Map getPluginConfig(@NotNull String pluginId, boolean create); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java index bfc843edb45..e547e5109c6 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/user/WebUser.java @@ -92,4 +92,9 @@ public String toString() { public String getAuthRole() { return user.getAuthRole(); } + + public String getCredentialsProfileId() { + return user.getCredentialsProfileId(); + } + } diff --git a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls index fa77fb0dd7d..155a99446c0 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -43,6 +43,7 @@ type AdminUserInfo { linkedAuthProviders: [String!]! enabled: Boolean! authRole: String + credentialsProfileId: String @since(version: "23.3.4") } type AdminTeamInfo { diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java index 6f057c253aa..6b789f14b42 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminUserInfo.java @@ -59,6 +59,12 @@ public String getAuthRole() { return user.getAuthRole(); } + @Property + public String getCredentialsProfileId() { + return user.getCredentialsProfileId(); + } + + @Property public Map getMetaParameters() { return user.getMetaParameters(); 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 d9c223c9007..adeda79b1a4 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 @@ -76,6 +76,7 @@ public class CBEmbeddedSecurityController private static final String SUBJECT_USER = "U"; private static final String SUBJECT_TEAM = "R"; + private static final String SUBJECT_PROFILE = "P"; private static final Type MAP_STRING_OBJECT_TYPE = new TypeToken>() { }.getType(); private static final Gson gson = new GsonBuilder().create(); @@ -300,7 +301,9 @@ 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 USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE,CREDENTIALS_PROFILE_ID FROM {table_prefix}CB_USER " + + "WHERE USER_ID=?") )) { dbStat.setString(1, userId); try (ResultSet dbResult = dbStat.executeQuery()) { @@ -308,7 +311,8 @@ public SMUser getUserById(String userId) throws DBException { 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); + String credentialsProfileId = dbResult.getString(4); + user = new SMUser(userName, CHAR_BOOL_TRUE.equals(active), authRole, credentialsProfileId); } else { return null; } @@ -373,16 +377,18 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter) Map result = new LinkedHashMap<>(); // Read users try (PreparedStatement dbStat = dbCon.prepareStatement( - database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER" + database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE,CREDENTIALS_PROFILE_ID FROM " + + "{table_prefix}CB_USER" + buildUsersFilter(filter) + "\nORDER BY USER_ID " + getOffsetLimitPart(filter)))) { - int parameterIndex = setUsersFilterValues(dbStat, filter, 1); try (ResultSet dbResult = dbStat.executeQuery()) { while (dbResult.next()) { String userId = dbResult.getString(1); String active = dbResult.getString(2); String authRole = dbResult.getString(3); - result.put(userId, new SMUser(userId, CHAR_BOOL_TRUE.equals(active), authRole)); + String credentialsProfileId = dbResult.getString(4); + result.put(userId, + new SMUser(userId, CHAR_BOOL_TRUE.equals(active), authRole, credentialsProfileId)); } } } @@ -971,7 +977,10 @@ private SMTeam fetchTeam(ResultSet dbResult) throws SQLException { } @Override - public void createTeam(String teamId, String name, String description, String grantor) throws DBCException { + public void createTeam( + @NotNull String teamId, @Nullable String name, @Nullable String description, + @NotNull String grantor + ) throws DBCException { if (CommonUtils.isEmpty(teamId)) { throw new DBCException("Empty team name is not allowed"); } @@ -1000,7 +1009,7 @@ public void createTeam(String teamId, String name, String description, String gr txn.commit(); } } catch (SQLException e) { - throw new DBCException("Error saving tem in database", e); + throw new DBCException("Error saving team in database", e); } } @@ -1078,6 +1087,104 @@ public void deleteTeam(String teamId, boolean force) throws DBCException { } } + @Override + @NotNull + public SMCredentialsProfile[] readAllCredentialsProfiles() throws DBCException { + List profiles = new ArrayList<>(); + try (var dbCon = database.openConnection(); + var dbStat = dbCon.prepareStatement(database.normalizeTableNames( + "SELECT PROFILE_ID, PROFILE_NAME, PROFILE_DESCRIPTION, PARENT_PROFILE_ID FROM " + + "{table_prefix}CB_CREDENTIALS_PROFILE ORDER BY PROFILE_ID" + )); + var dbResult = dbStat.executeQuery(); + ) { + while (dbResult.next()) { + String profileId = dbResult.getString(1); + String name = dbResult.getString(2); + String description = dbResult.getString(3); + String parentProfile = dbResult.getString(4); + profiles.add(new SMCredentialsProfile(profileId, name, description, parentProfile)); + } + return profiles.toArray(SMCredentialsProfile[]::new); + } catch (SQLException e) { + throw new DBCException("Error reading credentials profiles from database", e); + } + } + + @Override + public void createCredentialsProfile(@NotNull SMCredentialsProfile credentialsProfile) throws DBCException { + if (isSubjectExists(credentialsProfile.getCredentialsProfileId())) { + throw new DBCException("User, team or credentials profile '" + credentialsProfile.getCredentialsProfileId() + "' already " + + "exists"); + } + + try (var dbCon = database.openConnection(); + var txn = new JDBCTransaction(dbCon)) { + createAuthSubject(dbCon, credentialsProfile.getCredentialsProfileId(), SUBJECT_TEAM); + try (PreparedStatement dbStat = dbCon.prepareStatement( + database.normalizeTableNames("INSERT INTO {table_prefix}CB_CREDENTIALS_PROFILE" + + "(PROFILE_ID,PROFILE_NAME,PROFILE_DESCRIPTION,PARENT_PROFILE_ID) VALUES(?,?,?,?)"))) { + dbStat.setString(1, credentialsProfile.getCredentialsProfileId()); + dbStat.setString(2, CommonUtils.notEmpty(credentialsProfile.getName())); + dbStat.setString(3, CommonUtils.notEmpty(credentialsProfile.getProfileDescription())); + if (CommonUtils.isNotEmpty(credentialsProfile.getParentProfileId())) { + dbStat.setString(4, credentialsProfile.getParentProfileId()); + } else { + dbStat.setNull(4, Types.VARCHAR); + } + dbStat.execute(); + } + txn.commit(); + } catch (SQLException e) { + throw new DBCException("Error saving credentials profile in database", e); + } + } + + @Override + public void deleteCredentialsProfile(@NotNull String credentialsProfileId) throws DBCException { + String defaultUserCredentialsProfile = application.getAppConfiguration().getDefaultUserCredentialsProfile(); + if (CommonUtils.isNotEmpty(defaultUserCredentialsProfile) + && defaultUserCredentialsProfile.equals(credentialsProfileId)) { + throw new DBCException("Default credentials profile cannot be deleted"); + } + + try (var dbCon = database.openConnection(); + var txn = new JDBCTransaction(dbCon)) { + deleteAuthSubject(dbCon, credentialsProfileId); + JDBCUtils.executeStatement( + dbCon, + database.normalizeTableNames("DELETE FROM {table_prefix}CB_CREDENTIALS_PROFILE WHERE PROFILE_ID=?"), + credentialsProfileId + ); + txn.commit(); + } catch (SQLException e) { + throw new DBCException("Error deleting credentials profile from database", e); + } + } + + @Override + public void setUserCredentialsProfile(@NotNull String userId, @Nullable String credentialsProfileId) + throws DBCException { + try (var dbCon = database.openConnection(); + PreparedStatement dbStat = dbCon.prepareStatement( + database.normalizeTableNames( + "UPDATE {table_prefix}CB_USER SET CREDENTIALS_PROFILE_ID=? WHERE USER_ID=?")) + ) { + if (CommonUtils.isEmpty(credentialsProfileId)) { + String defaultCredentialsProfile = application.getAppConfiguration().getDefaultUserCredentialsProfile(); + if (defaultCredentialsProfile == null) { + dbStat.setNull(1, Types.VARCHAR); + } else { + dbStat.setString(1, defaultCredentialsProfile); + } + } else { + dbStat.setString(1, credentialsProfileId); + } + } catch (SQLException e) { + throw new DBCException("Error saving credentials profile for user", e); + } + } + /////////////////////////////////////////// // Subject functions 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 5cddbba56aa..48377042885 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 @@ -301,7 +301,7 @@ private SMUser createAdminUser( SMUser adminUser = adminSecurityController.getUserById(adminName); if (adminUser == null) { - adminUser = new SMUser(adminName, true, "ADMINISTRATOR"); + adminUser = new SMUser(adminName, true, "ADMINISTRATOR", null); adminSecurityController.createUser(adminUser.getUserId(), adminUser.getMetaParameters(), true, adminUser.getAuthRole()); } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseInitialData.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseInitialData.java index 17c42babb23..6dd6bf94df2 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseInitialData.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabaseInitialData.java @@ -16,6 +16,7 @@ */ package io.cloudbeaver.service.security.db; +import org.jkiss.dbeaver.model.security.user.SMCredentialsProfile; import org.jkiss.dbeaver.model.security.user.SMTeam; import java.util.List; @@ -24,6 +25,7 @@ class CBDatabaseInitialData { private String adminName; private String adminPassword; private List teams; + private List credentialsProfiles; public String getAdminName() { return adminName; @@ -37,6 +39,10 @@ public List getTeams() { return teams; } + public List getCredentialsProfiles() { + return credentialsProfiles; + } + public void setTeams(List teams) { this.teams = teams; }