From f1da5369f2b5fc7c87e6633f8bd2cf4909de5f09 Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Fri, 14 Oct 2022 18:09:43 +0200 Subject: [PATCH] CB-2713 license role management --- .../io/cloudbeaver/model/user/WebUser.java | 4 + .../schema/service.admin.graphqls | 1 + .../service/admin/AdminUserInfo.java | 5 + .../CBEmbeddedSecurityController.java | 122 ++++++++++++++---- .../service/security/db/CBDatabase.java | 2 +- ...RefreshTokenInfo.java => SMTokenInfo.java} | 29 ++++- 6 files changed, 137 insertions(+), 26 deletions(-) rename server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/{RefreshTokenInfo.java => SMTokenInfo.java} (67%) 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 5516cddb32..2d66d6d386 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 @@ -88,4 +88,8 @@ public class WebUser { public String toString() { return user.getUserId(); } + + public String getAuthRole() { + return user.getAuthRole(); + } } 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 99de75f47c..6162ff0f8b 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -42,6 +42,7 @@ type AdminUserInfo { linkedAuthProviders: [String!]! enabled: Boolean! + authRole: String } 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 c542cbed51..ab8dae7a28 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 @@ -54,6 +54,11 @@ public class AdminUserInfo { return user.getUserId(); } + @Property + public String getAuthRole() { + return user.getAuthRole(); + } + @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 3a201cc405..5c71cced81 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 @@ -27,7 +27,7 @@ import io.cloudbeaver.model.app.WebAuthConfiguration; import io.cloudbeaver.model.session.WebAuthInfo; import io.cloudbeaver.service.security.db.CBDatabase; import io.cloudbeaver.service.security.internal.AuthAttemptSessionInfo; -import io.cloudbeaver.service.security.internal.RefreshTokenInfo; +import io.cloudbeaver.service.security.internal.SMTokenInfo; import io.cloudbeaver.utils.WebAppUtils; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; @@ -126,7 +126,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { createAuthSubject(dbCon, userId, SUBJECT_USER); try (PreparedStatement dbStat = dbCon.prepareStatement( - "INSERT INTO CB_USER(USER_ID,IS_ACTIVE,CREATE_TIME,DEFAULT_AUTH_ROLE) VALUES(?,?,?)") + "INSERT INTO CB_USER(USER_ID,IS_ACTIVE,CREATE_TIME,DEFAULT_AUTH_ROLE) VALUES(?,?,?,?)") ) { dbStat.setString(1, userId); dbStat.setString(2, enabled ? CHAR_BOOL_TRUE : CHAR_BOOL_FALSE); @@ -216,13 +216,16 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen public SMUser getUserById(String userId) throws DBException { try (Connection dbCon = database.openConnection()) { SMUser user; - try (PreparedStatement dbStat = dbCon.prepareStatement("SELECT * FROM CB_USER WHERE USER_ID=?")) { + try (PreparedStatement dbStat = dbCon.prepareStatement( + "SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM CB_USER WHERE USER_ID=?" + )) { dbStat.setString(1, userId); try (ResultSet dbResult = dbStat.executeQuery()) { if (dbResult.next()) { String userName = dbResult.getString(1); String active = dbResult.getString(2); - user = new SMUser(userName, CHAR_BOOL_TRUE.equals(active)); + String authRole = dbResult.getString(3); + user = new SMUser(userName, CHAR_BOOL_TRUE.equals(active), authRole); } else { return null; } @@ -263,8 +266,9 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen try (Connection dbCon = database.openConnection()) { Map result = new LinkedHashMap<>(); // Read users - try (PreparedStatement dbStat = dbCon.prepareStatement("SELECT * FROM CB_USER" + - (CommonUtils.isEmpty(userNameMask) ? "\nORDER BY USER_ID" : " WHERE USER_ID=?"))) { + try (PreparedStatement dbStat = dbCon.prepareStatement( + "SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM CB_USER" + + (CommonUtils.isEmpty(userNameMask) ? "\nORDER BY USER_ID" : " WHERE USER_ID=?"))) { if (!CommonUtils.isEmpty(userNameMask)) { dbStat.setString(1, userNameMask); } @@ -272,7 +276,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen while (dbResult.next()) { String userId = dbResult.getString(1); String active = dbResult.getString(2); - result.put(userId, new SMUser(userId, CHAR_BOOL_TRUE.equals(active))); + String authRole = dbResult.getString(3); + result.put(userId, new SMUser(userId, CHAR_BOOL_TRUE.equals(active), authRole)); } } } @@ -415,6 +420,19 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } } + @Override + public void setUserAuthRole(@NotNull String userId, @Nullable String authRole) throws DBException { + try (Connection dbCon = database.openConnection()) { + try (PreparedStatement dbStat = dbCon.prepareStatement("UPDATE CB_USER SET DEFAULT_AUTH_ROLE=? WHERE USER_ID=?")) { + dbStat.setString(1, authRole); + dbStat.setString(2, userId); + dbStat.executeUpdate(); + } + } catch (SQLException e) { + throw new DBCException("Error while updating user authentication role", e); + } + } + /////////////////////////////////////////// // Credentials @@ -1172,17 +1190,17 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen public SMTokens refreshSession(@NotNull String refreshToken) throws DBException { var currentUserCreds = getCurrentUserCreds(); var currentUserAccessToken = currentUserCreds.getSmToken(); - String currentUserAuthRole = null; // FIXME: read role from auth token + String currentUserAuthRole = readTokenAuthRole(currentUserAccessToken); - var expectedRefreshTokenInfo = findRefreshToken(currentUserAccessToken); + var smTokenInfo = readAccessTokenInfo(currentUserAccessToken); - if (!expectedRefreshTokenInfo.getRefreshToken().equals(refreshToken)) { + if (!smTokenInfo.getRefreshToken().equals(refreshToken)) { throw new SMException("Invalid refresh token"); } try (var dbCon = database.openConnection()) { invalidateUserTokens(currentUserAccessToken); - return generateNewSessionToken(expectedRefreshTokenInfo.getSessionId(), expectedRefreshTokenInfo.getUserId(), currentUserAuthRole, dbCon); + return generateNewSessionToken(smTokenInfo.getSessionId(), smTokenInfo.getUserId(), currentUserAuthRole, dbCon); } catch (SQLException e) { throw new DBException("Error refreshing sm session", e); } @@ -1220,10 +1238,10 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } } - private RefreshTokenInfo findRefreshToken(String smAccessToken) throws DBException { + private SMTokenInfo readAccessTokenInfo(String smAccessToken) throws DBException { try (Connection dbCon = database.openConnection(); PreparedStatement dbStat = dbCon.prepareStatement( - "SELECT REFRESH_TOKEN_ID,SESSION_ID,USER_ID,REFRESH_TOKEN_EXPIRATION_TIME FROM CB_AUTH_TOKEN WHERE TOKEN_ID=?" + "SELECT REFRESH_TOKEN_ID,SESSION_ID,USER_ID,REFRESH_TOKEN_EXPIRATION_TIME,AUTH_ROLE FROM CB_AUTH_TOKEN WHERE TOKEN_ID=?" ) ) { dbStat.setString(1, smAccessToken); @@ -1235,10 +1253,11 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen var sessionId = dbResult.getString(2); var userId = dbResult.getString(3); var expiredDate = dbResult.getTimestamp(4); + var authRole = dbResult.getString(5); if (Timestamp.from(Instant.now()).after(expiredDate)) { throw new SMRefreshTokenExpiredException("Refresh token expired"); } - return new RefreshTokenInfo(refreshToken, sessionId, userId); + return new SMTokenInfo(smAccessToken, refreshToken, sessionId, userId, authRole); } } catch (SQLException e) { throw new DBCException("Error reading token info in database", e); @@ -1287,7 +1306,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen userAuthData, finishAuthMonitor, activeUserId, - activeUserId == null + activeUserId == null, + authInfo.getAuthRole() ); if (userIdFromCreds == null) { @@ -1304,6 +1324,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen ); } + String tokenAuthRole = updateUserAuthRoleIfNeeded(activeUserId, authInfo.getAuthRole()); + if (smTokens == null && permissions == null) { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { @@ -1319,9 +1341,10 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } else { smSessionId = authAttemptSessionInfo.getSmSessionId(); } - smTokens = generateNewSessionToken(smSessionId, activeUserId, authInfo.getAuthRole(), dbCon); + + smTokens = generateNewSessionToken(smSessionId, activeUserId, tokenAuthRole, dbCon); permissions = new SMAuthPermissions( - activeUserId, smSessionId, getUserPermissions(activeUserId, authInfo.getAuthRole())); + activeUserId, smSessionId, getUserPermissions(activeUserId, tokenAuthRole)); txn.commit(); } } catch (SQLException e) { @@ -1342,6 +1365,40 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen ); } + @Nullable + private String readUserAuthRole(String userId) throws DBException { + try (Connection dbCon = database.openConnection()) { + try (PreparedStatement dbStat = dbCon.prepareStatement( + "SELECT DEFAULT_AUTH_ROLE FROM CB_USER WHERE USER_ID=?" + )) { + dbStat.setString(1, userId); + try (ResultSet dbResult = dbStat.executeQuery()) { + if (!dbResult.next()) { + throw new SMException("User not found"); + } + return dbResult.getString(1); + + } + } + } catch (SQLException e) { + throw new DBException("Failed to update user auth role", e); + } + } + + private String updateUserAuthRoleIfNeeded(@Nullable String userId, @Nullable String authRole) throws DBException { + if (userId == null) { + return null; + } + var currentAuthRole = readUserAuthRole(userId); + if (authRole == null) { + return currentAuthRole; + } + if (!Objects.equals(currentAuthRole, authRole)) { + setUserAuthRole(userId, authRole); + } + return authRole; + } + private AuthAttemptSessionInfo readAuthAttemptSessionInfo(@NotNull String authId) throws DBException { try (Connection dbCon = database.openConnection()) { try (PreparedStatement dbStat = dbCon.prepareStatement( @@ -1373,7 +1430,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen @NotNull Map userCredentials, @NotNull DBRProgressMonitor progressMonitor, @Nullable String activeUserId, - boolean createNewUserIfNotExist + boolean createNewUserIfNotExist, + String authRole ) throws DBException { AuthProviderDescriptor authProvider = getAuthProvider(authProviderId); SMAuthProvider smAuthProviderInstance = authProvider.getInstance(); @@ -1397,11 +1455,10 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen userId = userIdFromCredentials; if (!isSubjectExists(userId)) { - var newUser = new SMUser(userId, true); - createUser(newUser.getUserId(), - newUser.getMetaParameters(), + createUser(userId, + Map.of(), true, - resolveUserAuthRole(authProvider, userCredentials) + authRole ); String defaultTeamName = WebAppUtils.getWebApplication().getAppConfiguration().getDefaultUserTeam(); if (!CommonUtils.isEmpty(defaultTeamName)) { @@ -1426,7 +1483,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen @NotNull AuthProviderDescriptor authProvider, @NotNull Map userAuthData ) { - //TODO implement logic + //TODO read from external auth somehow return null; } @@ -1801,6 +1858,25 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen /////////////////////////////////////////// // Utils + protected String readTokenAuthRole(String smAccessToken) throws DBException { + try (Connection dbCon = database.openConnection(); + PreparedStatement dbStat = dbCon.prepareStatement( + "SELECT AUTH_ROLE FROM CB_AUTH_TOKEN " + + "WHERE TOKEN_ID=?" + ) + ) { + dbStat.setString(1, smAccessToken); + try (var dbResult = dbStat.executeQuery()) { + if (!dbResult.next()) { + throw new SMException("Invalid token"); + } + return dbResult.getString(1); + } + } catch (SQLException e) { + throw new DBCException("Error reading lm role in database", e); + } + } + public void initializeMetaInformation() throws DBCException { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { 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 dd222e13a3..b80f21c8ef 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 @@ -255,7 +255,7 @@ public class CBDatabase { SMUser adminUser = adminSecurityController.getUserById(adminName); if (adminUser == null) { - adminUser = new SMUser(adminName, true); + adminUser = new SMUser(adminName, true, adminAuthRole); adminSecurityController.createUser(adminUser.getUserId(), adminUser.getMetaParameters(), true, adminAuthRole); } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/RefreshTokenInfo.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/SMTokenInfo.java similarity index 67% rename from server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/RefreshTokenInfo.java rename to server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/SMTokenInfo.java index 89d39430cd..f8ff7e285e 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/RefreshTokenInfo.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/SMTokenInfo.java @@ -18,8 +18,12 @@ package io.cloudbeaver.service.security.internal; import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; + +public class SMTokenInfo { + @NotNull + private final String accessToken; -public class RefreshTokenInfo { @NotNull private final String refreshToken; @@ -29,10 +33,21 @@ public class RefreshTokenInfo { @NotNull private final String userId; - public RefreshTokenInfo(@NotNull String refreshToken, @NotNull String sessionId, @NotNull String userId) { + @Nullable + private final String authRole; + + public SMTokenInfo( + @NotNull String accessToken, + @NotNull String refreshToken, + @NotNull String sessionId, + @NotNull String userId, + @Nullable String authRole + ) { + this.accessToken = accessToken; this.refreshToken = refreshToken; this.sessionId = sessionId; this.userId = userId; + this.authRole = authRole; } @NotNull @@ -49,4 +64,14 @@ public class RefreshTokenInfo { public String getUserId() { return userId; } + + @NotNull + public String getAccessToken() { + return accessToken; + } + + @Nullable + public String getAuthRole() { + return authRole; + } }