diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf index 957d7fe14d..70181672f5 100644 --- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf +++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf @@ -44,6 +44,7 @@ resourceQuotas: { dataExportFileSizeLimit: 10000000, + resourceManagerFileSizeLimit: 500000, sqlMaxRunningQueries: 100, sqlResultSetRowsLimit: 100000, sqlResultSetMemoryLimit: 2000000, diff --git a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf index ed8551dc39..5126b83f01 100644 --- a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf +++ b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf @@ -44,6 +44,7 @@ resourceQuotas: { dataExportFileSizeLimit: 10000000, + resourceManagerFileSizeLimit: 500000, sqlMaxRunningQueries: 100, sqlResultSetRowsLimit: 100000, sqlResultSetMemoryLimit: 2000000, diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderExternal.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderExternal.java index 3e30e7bd68..6bae9f9469 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderExternal.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderExternal.java @@ -16,9 +16,6 @@ */ package io.cloudbeaver.auth; -import org.jkiss.dbeaver.model.auth.SMAuthProvider; -import org.jkiss.dbeaver.model.auth.SMSession; -import org.jkiss.dbeaver.model.security.SMController; import io.cloudbeaver.DBWUserIdentity; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebUser; @@ -26,6 +23,8 @@ import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.DBPObject; +import org.jkiss.dbeaver.model.auth.SMAuthProvider; +import org.jkiss.dbeaver.model.auth.SMSession; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import java.util.Map; @@ -46,18 +45,6 @@ public interface SMAuthProviderExternal extends @NotNull Map authParameters // Passed auth parameters (e.g. user name or password) ) throws DBException; - /** - * Validates that external user may be associated with local user - * @param userCredentials credentials from authExternalUser - * @return new user ID. If activeUser is not null then it must be the same as activeUser ID. - */ - @NotNull - String validateLocalAuth( - @NotNull DBRProgressMonitor monitor, - @NotNull SMController securityController, - @NotNull Map providerConfig, - @NotNull Map userCredentials, - @Nullable WebUser activeUser) throws DBException; @NotNull DBWUserIdentity getUserIdentity( diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java index 1452c40f4a..97e22670ba 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java @@ -53,6 +53,12 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements return new EclipseWorkspaceImpl(platform, eclipseWorkspace); } + @Nullable + @Override + public Path getDefaultWorkingFolder() { + return null; + } + @Nullable protected Path loadServerConfiguration() { String configPath = DEFAULT_CONFIG_FILE_PATH; diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index d19a9aa682..a8b1375094 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -17,6 +17,8 @@ package io.cloudbeaver.model.rm.local; import io.cloudbeaver.DBWConstants; +import io.cloudbeaver.service.sql.WebSQLConstants; +import io.cloudbeaver.utils.WebAppUtils; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; @@ -28,6 +30,7 @@ import org.jkiss.dbeaver.model.rm.RMController; import org.jkiss.dbeaver.model.rm.RMProject; import org.jkiss.dbeaver.model.rm.RMResource; import org.jkiss.dbeaver.model.rm.RMResourceChange; +import org.jkiss.dbeaver.model.sql.DBQuotaException; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.CommonUtils; @@ -225,6 +228,16 @@ public class LocalResourceController implements RMController { @NotNull String resourcePath, @NotNull byte[] data) throws DBException { + Number fileSizeLimit = WebAppUtils.getWebApplication() + .getAppConfiguration() + .getResourceQuota(WebSQLConstants.QUOTA_PROP_RM_FILE_SIZE_LIMIT); + if (fileSizeLimit != null && data.length > fileSizeLimit.longValue()) { + throw new DBQuotaException( + "File size quota exceeded", + WebSQLConstants.QUOTA_PROP_RM_FILE_SIZE_LIMIT, + fileSizeLimit.longValue(), + data.length); + } Path targetPath = getTargetPath(projectId, resourcePath); if (!Files.exists(targetPath)) { throw new DBException("Resource '" + resourcePath + "' doesn't exists"); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java index 04a556b123..119c828cb0 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java @@ -80,7 +80,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, private static final Log log = Log.getLog(WebSession.class); - public static final SMSessionType CB_SESSION_TYPE = new SMSessionType("Cloudbeaver"); + public static final SMSessionType CB_SESSION_TYPE = new SMSessionType("CloudBeaver"); private static final String ATTR_LOCALE = "locale"; @@ -215,7 +215,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, return user; } - private synchronized String getUserId() { + public synchronized String getUserId() { return user == null ? null : user.getUserId(); } @@ -365,7 +365,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, return Arrays.stream(securityController .getSubjectConnectionAccess(new String[]{subjectId})) .map(SMDataSourceGrant::getDataSourceId).collect(Collectors.toSet()); - } catch (DBCException e) { + } catch (DBException e) { addSessionError(e); log.error("Error reading connection grants", e); return Collections.emptySet(); @@ -425,12 +425,12 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, this.accessibleConnectionIds = readAccessibleConnectionIds(); } - private synchronized void authAsAnonymousUser() throws DBCException { + private synchronized void authAsAnonymousUser() throws DBException { if (!application.getAppConfiguration().isAnonymousAccessEnabled()) { this.sessionPermissions = Collections.emptySet(); return; } - var authInfo = securityController.authenticateAnonymousUser(this.id, getSessionParameters(), CB_SESSION_TYPE); + SMAuthInfo authInfo = securityController.authenticateAnonymousUser(this.id, getSessionParameters(), CB_SESSION_TYPE); updateSMAuthInfo(authInfo); } @@ -893,7 +893,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, this.adminSecurityController = null; } - public synchronized void updateSMAuthInfo(SMAuthInfo smAuthInfo) throws DBCException { + public synchronized void updateSMAuthInfo(SMAuthInfo smAuthInfo) throws DBException { var isNonAnonymousUserAuthorized = isAuthorizedInSecurityManager() && getUser() != null; if (isNonAnonymousUserAuthorized && !Objects.equals(getUserId(), smAuthInfo.getUserId())) { throw new DBCException("Another user is already logged in"); @@ -904,6 +904,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, this.adminSecurityController = application.getAdminSecurityController(this); this.user = smAuthInfo.getUserId() == null ? null : new WebUser(securityController.getUserById(smAuthInfo.getUserId())); + refreshUserData(); } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java index ec5a2ea1b1..85d6a6a60f 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java @@ -26,6 +26,7 @@ public class WebSQLConstants { public static final String QUOTA_PROP_QUERY_LIMIT = "sqlMaxRunningQueries"; public static final String QUOTA_PROP_TEXT_PREVIEW_MAX_LENGTH = "sqlTextPreviewMaxLength"; public static final String QUOTA_PROP_BINARY_PREVIEW_MAX_LENGTH = "sqlBinaryPreviewMaxLength"; + public static final String QUOTA_PROP_RM_FILE_SIZE_LIMIT = "resourceManagerFileSizeLimit"; public static final int TEXT_PREVIEW_MAX_LENGTH = 4 * 1024; public static final int BINARY_PREVIEW_MAX_LENGTH = 255 * 1024; diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java index 5c0bf44502..ef7466d107 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java @@ -17,6 +17,7 @@ package io.cloudbeaver.service.admin; import io.cloudbeaver.model.session.WebSession; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.meta.Property; import org.jkiss.dbeaver.model.security.SMDataSourceGrant; @@ -59,7 +60,7 @@ public class AdminRoleInfo { } @Property - public SMDataSourceGrant[] getGrantedConnections() throws DBCException { + public SMDataSourceGrant[] getGrantedConnections() throws DBException { return session.getSecurityController().getSubjectConnectionAccess(new String[]{getRoleId()}); } 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 b734553af7..d38906a4bb 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 @@ -20,8 +20,8 @@ import io.cloudbeaver.DBWebException; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebUser; import io.cloudbeaver.model.user.WebUserOriginInfo; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; -import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.meta.Property; import org.jkiss.dbeaver.model.security.SMDataSourceGrant; import org.jkiss.dbeaver.model.security.user.SMRole; @@ -65,7 +65,7 @@ public class AdminUserInfo { } @Property - public String[] getGrantedRoles() throws DBCException { + public String[] getGrantedRoles() throws DBException { if (user.getRoles() == null) { SMRole[] userRoles = session.getSecurityController().getUserRoles(getUserId()); user.setRoles(userRoles); @@ -74,7 +74,7 @@ public class AdminUserInfo { } @Property - public SMDataSourceGrant[] getGrantedConnections() throws DBCException { + public SMDataSourceGrant[] getGrantedConnections() throws DBException { return session.getSecurityController().getSubjectConnectionAccess(new String[]{getUserId()}); } @@ -103,7 +103,7 @@ public class AdminUserInfo { } try { userLinkedProviders = session.getSecurityController().getUserLinkedProviders(user.getUserId()); - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error reading user linked providers", e); } return userLinkedProviders; diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java index d495753e20..0beca5913d 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java @@ -376,7 +376,7 @@ public class WebServiceAdmin implements DBWServiceAdmin { try { webSession.getSecurityController().setConnectionSubjectAccess(id, null, null); - } catch (DBCException e) { + } catch (DBException e) { log.error(e); } return true; @@ -560,7 +560,7 @@ public class WebServiceAdmin implements DBWServiceAdmin { public SMDataSourceGrant[] getConnectionSubjectAccess(WebSession webSession, String connectionId) throws DBWebException { try { return webSession.getSecurityController().getConnectionSubjectAccess(connectionId); - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error getting connection access info", e); } } @@ -577,7 +577,7 @@ public class WebServiceAdmin implements DBWServiceAdmin { } try { webSession.getSecurityController().setConnectionSubjectAccess(connectionId, subjects.toArray(new String[0]), grantor.getUserId()); - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error setting connection subject access", e); } return true; @@ -587,7 +587,7 @@ public class WebServiceAdmin implements DBWServiceAdmin { public SMDataSourceGrant[] getSubjectConnectionAccess(@NotNull WebSession webSession, @NotNull String subjectId) throws DBWebException { try { return webSession.getSecurityController().getSubjectConnectionAccess(new String[]{subjectId}); - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error getting connection access info", e); } } diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/RPSessionHandler.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/RPSessionHandler.java index c336597d60..4a8ddec114 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/RPSessionHandler.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/RPSessionHandler.java @@ -78,7 +78,7 @@ public class RPSessionHandler implements DBWSessionHandler { Map sessionParameters = webSession.getSessionParameters(); sessionParameters.put(SMConstants.SESSION_PARAM_TRUSTED_USER_ROLES, userRoles); Map userCredentials = authProviderExternal.authExternalUser( - webSession.getProgressMonitor(), sessionParameters, credentials); + webSession.getProgressMonitor(), sessionParameters, credentials); try { SMAuthInfo smAuthInfo = securityController.authenticate( webSession.getSessionId(), sessionParameters, diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java index 5f1319e8cb..4e3b9aa3b7 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java @@ -19,6 +19,7 @@ package io.cloudbeaver.service.auth; import io.cloudbeaver.DBWebException; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebUser; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.meta.Property; @@ -65,9 +66,8 @@ public class WebUserInfo { public List getLinkedAuthProviders() throws DBWebException { if (linkedProviders == null) { try { - linkedProviders = session.getSecurityController().getUserLinkedProviders(session.getUser().getUserId()); - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error reading user linked providers", e); } } diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index adf83e425c..2a174e1209 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -101,7 +101,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { linkWithActiveUser = true; } } else if (!providerEnabled) { - if (!isAdminAuthTry(webSession, authProvider, userCredentials)) { + if (!isAdminAuthTry(webSession, authProvider, authParameters)) { throw new DBWebException("Authentication provider '" + providerId + "' is disabled"); } } @@ -121,7 +121,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { securityController, providerConfig, userCredentials, - webSession.getUser()); + webSession.getUserId()); } else { userId = "temp_config_admin"; } @@ -239,7 +239,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { SMAuthInfo authInfo = securityController.authenticate(session.getSessionId(), session.getSessionParameters(), WebSession.CB_SESSION_TYPE, authProvider.getId(), userCredentials); isAdmin = authInfo.getPermissions().contains(DBWConstants.PERMISSION_ADMIN); - } catch (DBCException e) { + } catch (DBException e) { log.error(e); } @@ -285,7 +285,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { } else { return new WebUserInfo(webSession, webSession.getUser()); } - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error reading user details", e); } } @@ -324,7 +324,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { name, value); return true; - } catch (DBCException e) { + } catch (DBException e) { throw new DBWebException("Error setting user parameter", e); } } diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBNResourceManagerResource.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBNResourceManagerResource.java index 36d724dc1a..19b08862ef 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBNResourceManagerResource.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBNResourceManagerResource.java @@ -82,10 +82,10 @@ public class DBNResourceManagerResource extends DBNNode { private String getResourceFolder() { StringBuilder folder = new StringBuilder(); - for (DBNNode parent = getParentNode(); parent != null; parent = parent.getParentNode()) { + for (DBNNode parent = this; parent != null; parent = parent.getParentNode()) { if (parent instanceof DBNResourceManagerResource) { - if (folder.length() > 0) folder.append('/'); - folder.append(parent.getName()); + if (folder.length() > 0) folder.insert(0, '/'); + folder.insert(0, parent.getName()); } else { break; } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java index fc0ca50342..4896f14621 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java @@ -18,11 +18,13 @@ package io.cloudbeaver.auth.provider.local; import io.cloudbeaver.model.session.WebSession; import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.auth.AuthPropertyEncryption; import org.jkiss.dbeaver.model.auth.SMAuthProvider; import org.jkiss.dbeaver.model.auth.SMSession; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.model.security.SMController; import org.jkiss.dbeaver.registry.auth.AuthProviderDescriptor; import org.jkiss.dbeaver.registry.auth.AuthProviderRegistry; import org.jkiss.utils.CommonUtils; @@ -38,17 +40,19 @@ public class LocalAuthProvider implements SMAuthProvider { public static final String CRED_USER = LocalAuthProviderConstants.CRED_USER; public static final String CRED_PASSWORD = LocalAuthProviderConstants.CRED_PASSWORD; + @NotNull @Override - public LocalAuthSession openSession(@NotNull DBRProgressMonitor monitor, @NotNull SMSession mainSession, @NotNull Map providerConfig, @NotNull Map userCredentials) throws DBException { + public String validateLocalAuth(@NotNull DBRProgressMonitor monitor, + @NotNull SMController securityController, + @NotNull Map providerConfig, + @NotNull Map userCredentials, + @Nullable String activeUserId) throws DBException { + + String userName = CommonUtils.toString(userCredentials.get(CRED_USER), null); - if (!WebSession.class.isAssignableFrom(mainSession.getClass())) { - throw new DBException("Invalid parent session, expected web session"); - } - - var mainWebSession = (WebSession) mainSession; AuthProviderDescriptor authProvider = AuthProviderRegistry.getInstance().getAuthProvider(PROVIDER_ID); - Map storedCredentials = mainWebSession.getSecurityController().getUserCredentials(userName, authProvider.getId()); + Map storedCredentials = securityController.getUserCredentials(userName, authProvider.getId()); if (storedCredentials == null) { throw new DBException("Invalid user name or password"); } @@ -65,6 +69,16 @@ public class LocalAuthProvider implements SMAuthProvider { if (!storedPasswordHash.equals(clientPasswordHash)) { throw new DBException("Invalid user name or password"); } + + return activeUserId == null ? userName : activeUserId; + } + + @Override + public LocalAuthSession openSession(@NotNull DBRProgressMonitor monitor, @NotNull SMSession mainSession, @NotNull Map providerConfig, @NotNull Map userCredentials) throws DBException { + String userName = CommonUtils.toString(userCredentials.get(CRED_USER)); + if (CommonUtils.isEmpty(userName)) { + throw new DBException("Invalid user name"); + } return new LocalAuthSession(mainSession, userName); } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java index 78d89a7afb..b652caa400 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java @@ -49,13 +49,17 @@ public class RPAuthProvider implements SMAuthProviderExternal { @NotNull @Override - public String validateLocalAuth(@NotNull DBRProgressMonitor monitor, @NotNull SMController securityController, @NotNull Map providerConfig, @NotNull Map userCredentials, @Nullable WebUser activeUser) throws DBException { + public String validateLocalAuth(@NotNull DBRProgressMonitor monitor, + @NotNull SMController securityController, + @NotNull Map providerConfig, + @NotNull Map userCredentials, + @Nullable String activeUserId) throws DBException { String userName = (String) userCredentials.get("user"); - if (activeUser == null) { + if (activeUserId == null) { return userName; } else { - return activeUser.getUserId(); + return activeUserId; } } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/CBSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/CBSecurityController.java index c490b90b16..100dbddb2a 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/CBSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/CBSecurityController.java @@ -17,9 +17,7 @@ package io.cloudbeaver.service.security.internal; import io.cloudbeaver.DBWConstants; -import io.cloudbeaver.DBWebException; import io.cloudbeaver.auth.SMAuthProviderExternal; -import io.cloudbeaver.auth.provider.rp.RPAuthProvider; import io.cloudbeaver.model.app.WebApplication; import io.cloudbeaver.service.security.internal.db.CBDatabase; import io.cloudbeaver.utils.WebAppUtils; @@ -31,6 +29,7 @@ import org.jkiss.dbeaver.model.auth.*; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCTransaction; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; import org.jkiss.dbeaver.model.security.*; import org.jkiss.dbeaver.model.security.exception.SMException; @@ -155,7 +154,7 @@ public class CBSecurityController implements SMAdminController { @NotNull @Override - public SMRole[] getUserRoles(String userId) throws DBCException { + public SMRole[] getUserRoles(String userId) throws DBException { try (Connection dbCon = database.openConnection()) { try (PreparedStatement dbStat = dbCon.prepareStatement( "SELECT R.* FROM CB_USER_ROLE UR,CB_ROLE R " + @@ -175,7 +174,7 @@ public class CBSecurityController implements SMAdminController { } @Override - public SMUser getUserById(String userId) throws DBCException { + 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=?")) { @@ -298,7 +297,7 @@ public class CBSecurityController implements SMAdminController { } @Override - public void setUserParameter(String userId, String name, Object value) throws DBCException { + public void setUserParameter(String userId, String name, Object value) throws DBException { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { if (value == null) { @@ -358,7 +357,7 @@ public class CBSecurityController implements SMAdminController { } @Override - public void setUserCredentials(String userId, String authProviderId, Map credentials) throws DBCException { + public void setUserCredentials(String userId, String authProviderId, Map credentials) throws DBException { List transformedCredentials; AuthProviderDescriptor authProvider = getAuthProvider(authProviderId); try { @@ -495,7 +494,7 @@ public class CBSecurityController implements SMAdminController { } @Override - public String[] getUserLinkedProviders(String userId) throws DBCException { + public String[] getUserLinkedProviders(String userId) throws DBException { try (Connection dbCon = database.openConnection()) { try (PreparedStatement dbStat = dbCon.prepareStatement( "SELECT DISTINCT PROVIDER_ID FROM CB_USER_CREDENTIALS\n" + @@ -696,7 +695,7 @@ public class CBSecurityController implements SMAdminController { @NotNull @Override - public Set getSubjectPermissions(String subjectId) throws DBCException { + public Set getSubjectPermissions(String subjectId) throws DBException { try (Connection dbCon = database.openConnection()) { Set permissions = new HashSet<>(); try (PreparedStatement dbStat = dbCon.prepareStatement("SELECT PERMISSION_ID FROM CB_AUTH_PERMISSIONS WHERE SUBJECT_ID=?")) { @@ -715,7 +714,7 @@ public class CBSecurityController implements SMAdminController { @NotNull @Override - public Set getUserPermissions(String userId) throws DBCException { + public Set getUserPermissions(String userId) throws DBException { try (Connection dbCon = database.openConnection()) { Set permissions = new HashSet<>(); try (PreparedStatement dbStat = dbCon.prepareStatement( @@ -747,7 +746,7 @@ public class CBSecurityController implements SMAdminController { // Sessions @Override - public boolean isSessionPersisted(String id) throws DBCException { + public boolean isSessionPersisted(String id) throws DBException { try (Connection dbCon = database.openConnection()) { try (PreparedStatement dbStat = dbCon.prepareStatement( "SELECT 1 FROM CB_SESSION WHERE SESSION_ID=?")) { @@ -764,8 +763,8 @@ public class CBSecurityController implements SMAdminController { } } - private void createSessionIfNotExist(@NotNull String appSessionId, @Nullable String userId, @NotNull Map parameters, @NotNull SMSessionType sessionType, Connection dbCon) throws SQLException, DBCException { - if(isSessionPersisted(appSessionId)) { + private void createSessionIfNotExist(@NotNull String appSessionId, @Nullable String userId, @NotNull Map parameters, @NotNull SMSessionType sessionType, Connection dbCon) throws SQLException, DBException { + if (isSessionPersisted(appSessionId)) { return; } try (PreparedStatement dbStat = dbCon.prepareStatement( @@ -798,7 +797,7 @@ public class CBSecurityController implements SMAdminController { } @Override - public SMAuthInfo authenticateAnonymousUser(@NotNull String appSessionId, @NotNull Map sessionParameters, @NotNull SMSessionType sessionType) throws DBCException { + public SMAuthInfo authenticateAnonymousUser(@NotNull String appSessionId, @NotNull Map sessionParameters, @NotNull SMSessionType sessionType) throws DBException { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { createSessionIfNotExist(appSessionId, null, sessionParameters, sessionType, dbCon); @@ -809,15 +808,18 @@ public class CBSecurityController implements SMAdminController { return new SMAuthInfo(token, null, permissions); } } catch (SQLException e) { - throw new DBCException("Error during auth", e); + throw new DBException(e.getMessage(), e); } } @Override - public SMAuthInfo authenticate(@NotNull String appSessionId, @NotNull Map sessionParameters, @NotNull SMSessionType sessionType, @NotNull String authProviderId, @NotNull Map userCredentials) throws DBCException { + public SMAuthInfo authenticate(@NotNull String appSessionId, @NotNull Map sessionParameters, @NotNull SMSessionType sessionType, @NotNull String authProviderId, @NotNull Map userCredentials) throws DBException { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { - var userId = findOrCreateExternalUserByCredentials(authProviderId, sessionParameters, userCredentials); + var userId = findOrCreateExternalUserByCredentials(authProviderId, + sessionParameters, + userCredentials, + new VoidProgressMonitor()); if (userId == null) { throw new SMException("Invalid user credentials"); } @@ -827,28 +829,34 @@ public class CBSecurityController implements SMAdminController { txn.commit(); return new SMAuthInfo(token, userId, permissions); } - } catch (SQLException | DBException e) { - throw new DBCException("Error during auth", e); + } catch (SQLException e) { + throw new DBException(e.getMessage(), e); } } private String findOrCreateExternalUserByCredentials(@NotNull String authProviderId, @NotNull Map sessionParameters, - @NotNull Map userCredentials) throws DBException { - var userId = findUserByCredentials(authProviderId, userCredentials); + @NotNull Map userCredentials, + @NotNull DBRProgressMonitor progressMonitor) throws DBException { AuthProviderDescriptor authProvider = getAuthProvider(authProviderId); + SMAuthProvider smAuthProviderInstance = authProvider.getInstance(); + String userId = findUserByCredentials(authProviderId, userCredentials); + String expectedUserId; + try { + expectedUserId = smAuthProviderInstance.validateLocalAuth(progressMonitor, this, Map.of(), userCredentials, null); + } catch (DBException e) { + return null; + } + if (userId != null && !expectedUserId.equals(userId)) { + return null; + } + if (userId == null) { if (!(authProvider.getInstance() instanceof SMAuthProviderExternal)) { return null; } - var externalAuthProvider = (SMAuthProviderExternal) authProvider.getInstance(); - userId = externalAuthProvider.validateLocalAuth( - new VoidProgressMonitor(), - this, - Collections.emptyMap(), - userCredentials, - null); + userId = expectedUserId; if (!isSubjectExists(userId)) { var newUser = new SMUser(userId); createUser(newUser.getUserId(), newUser.getMetaParameters()); @@ -861,7 +869,7 @@ public class CBSecurityController implements SMAdminController { } if (authProvider.isTrusted()) { if (WebAppUtils.getWebApplication().isMultiNode()) { - throw new DBWebException("Authorization through trusted provider is not available in multi node"); + throw new SMException("Authorization through trusted provider is not available in multi node"); } Object reverseProxyUserRoles = sessionParameters.get(SMConstants.SESSION_PARAM_TRUSTED_USER_ROLES); if (reverseProxyUserRoles instanceof List) { @@ -930,7 +938,7 @@ public class CBSecurityController implements SMAdminController { @NotNull @Override - public SMDataSourceGrant[] getSubjectConnectionAccess(@NotNull String[] subjectIds) throws DBCException { + public SMDataSourceGrant[] getSubjectConnectionAccess(@NotNull String[] subjectIds) throws DBException { if (subjectIds.length == 0) { return new SMDataSourceGrant[0]; } @@ -1005,7 +1013,7 @@ public class CBSecurityController implements SMAdminController { @NotNull @Override - public SMDataSourceGrant[] getConnectionSubjectAccess(String connectionId) throws DBCException { + public SMDataSourceGrant[] getConnectionSubjectAccess(String connectionId) throws DBException { try (Connection dbCon = database.openConnection()) { { try (PreparedStatement dbStat = dbCon.prepareStatement( @@ -1031,7 +1039,7 @@ public class CBSecurityController implements SMAdminController { } @Override - public void setConnectionSubjectAccess(@NotNull String connectionId, @Nullable String[] subjects, @Nullable String grantorId) throws DBCException { + public void setConnectionSubjectAccess(@NotNull String connectionId, @Nullable String[] subjects, @Nullable String grantorId) throws DBException { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { // Delete all permissions diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/db/CBDatabase.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/db/CBDatabase.java index 406283874a..cca77e20ae 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/db/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/db/CBDatabase.java @@ -242,8 +242,9 @@ public class CBDatabase { } @NotNull - private SMUser createAdminUser(@NotNull String adminName, @Nullable String adminPassword) throws DBCException { + private SMUser createAdminUser(@NotNull String adminName, @Nullable String adminPassword) throws DBException { SMUser adminUser = adminSecurityController.getUserById(adminName); + if (adminUser == null) { adminUser = new SMUser(adminName); adminSecurityController.createUser(adminUser.getUserId(), adminUser.getMetaParameters()); diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Column.ts b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Column.ts index 6198e915f9..d076e787b4 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Column.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Column.ts @@ -10,8 +10,10 @@ import type { Column as DataGridColumn } from 'react-data-grid'; import type { DBObject } from '@cloudbeaver/core-app'; -export type DataColumn = DataGridColumn; +export interface IDataColumn extends DataGridColumn { + description?: string; +} -export interface ICustomColumn extends DataColumn { +export interface ICustomColumn extends IDataColumn { order?: number; } \ No newline at end of file diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/HeaderRenderer.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/HeaderRenderer.tsx new file mode 100644 index 0000000000..b48d1f3589 --- /dev/null +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/HeaderRenderer.tsx @@ -0,0 +1,26 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; +import type { HeaderRendererProps } from 'react-data-grid'; + +import type { DBObject } from '@cloudbeaver/core-app'; + +import { TableContext } from './TableContext'; + +export const HeaderRenderer = observer>(function HeaderRenderer(props) { + const tableContext = useContext(TableContext); + const dataColumn = tableContext.tableData?.columns.find(column => column.key === props.column.key); + + return ( +
+ {props.column.name} +
+ ); +}); \ No newline at end of file diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Table.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Table.tsx index e8860023ea..7a35de864d 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Table.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/Table.tsx @@ -23,8 +23,10 @@ import { isDefined, TextTools } from '@cloudbeaver/core-utils'; import { getValue } from '../../helpers'; import { ObjectPropertyTableFooter } from '../ObjectPropertyTableFooter'; import { CellFormatter } from './CellFormatter'; +import type { IDataColumn } from './Column'; import { ColumnIcon } from './Columns/ColumnIcon/ColumnIcon'; import { ColumnSelect } from './Columns/ColumnSelect/ColumnSelect'; +import { HeaderRenderer } from './HeaderRenderer'; import { RowRenderer } from './RowRenderer'; import baseStyles from './styles/base.scss'; import { tableStyles } from './styles/styles'; @@ -58,21 +60,15 @@ interface Props { } function getMeasuredCells(columns: ObjectPropertyInfo[], rows: DBObject[]) { - const columnNames = columns.map(column => column.displayName).filter(isDefined); - - let rowStrings: string[] = []; + const columnNames = columns.map(column => column.displayName?.toUpperCase()).filter(isDefined); + const rowStrings: string[] = Array(columns.length).fill(''); for (const row of rows.slice(0, 100)) { if (row.object?.properties) { - if (rowStrings.length === 0) { - rowStrings = row.object.properties.map(p => getValue(p.value)); - continue; - } - for (let i = 0; i < row.object.properties.length; i++) { const value = getValue(row.object.properties[i].value); - if (value.length > (rowStrings[i] ?? '').length) { + if (value.length > rowStrings[i].length) { rowStrings[i] = value; } } @@ -82,12 +78,12 @@ function getMeasuredCells(columns: ObjectPropertyInfo[], rows: DBObject[]) { return TextTools.getWidth({ font: '400 12px Roboto', text: columnNames.map((cell, i) => { - if (cell.length > (rowStrings[i] ?? '').length) { + if (cell.length >= rowStrings[i].length) { return cell; } return rowStrings[i]; }), - }).map(v => v + 32 + 8); + }).map(v => v + 16 + 8); } const CUSTOM_COLUMNS = [ColumnSelect, ColumnIcon]; @@ -107,20 +103,22 @@ export const Table = observer(function Table({ const baseObject = objects .slice() - .sort((a, b) => (a.object?.properties?.length || 0) - (b.object?.properties?.length || 0)); + .sort((a, b) => (b.object?.properties?.length || 0) - (a.object?.properties?.length || 0)); const nodeIds = objects.map(object => object.id); - const properties = baseObject[0].object?.properties || []; + const properties = baseObject[0].object?.properties ?? []; const measuredCells = getMeasuredCells(properties, objects); - const dataColumns = properties.map((property, index) => ({ + const dataColumns: IDataColumn[] = properties.map((property, index) => ({ key: property.id!, name: property.displayName ?? '', + description: property.description, columnDataIndex: null, width: Math.min(300, measuredCells[index]), minWidth: 40, resizable: true, formatter: CellFormatter, + headerRenderer: HeaderRenderer, })); const tableData = useTableData(dataColumns, CUSTOM_COLUMNS); diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/styles/base.scss b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/styles/base.scss index ecc06ce739..d5b36ff540 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/styles/base.scss +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/styles/base.scss @@ -30,6 +30,7 @@ border-color: transparent; display: flex; flex-direction: row; + user-select: text; } .rdg-cell-selected { diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/useTableData.ts b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/useTableData.ts index afba5575d4..a794e7efac 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/useTableData.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Table/useTableData.ts @@ -12,17 +12,17 @@ import type { CalculatedColumn as GridCalculatedColumn } from 'react-data-grid'; import type { DBObject } from '@cloudbeaver/core-app'; import { useObservableRef } from '@cloudbeaver/core-blocks'; -import type { DataColumn, ICustomColumn } from './Column'; +import type { IDataColumn, ICustomColumn } from './Column'; type CalculatedColumn = GridCalculatedColumn; export interface ITableData { - columns: DataColumn[]; + columns: IDataColumn[]; isCustomColumn: (column: CalculatedColumn) => boolean; getColumnIdx: (column: CalculatedColumn) => number; } -export function useTableData(dataColumns: DataColumn[], customColumns: ICustomColumn[]): ITableData { +export function useTableData(dataColumns: IDataColumn[], customColumns: ICustomColumn[]): ITableData { return useObservableRef(() => ({ get columns() { return this.customColumns.slice().sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).concat(this.dataColumns); diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts index b82de1385d..13e2253089 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts @@ -8,11 +8,21 @@ import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; -export function getValue(value: string | { displayName: string }): string { +type Value = string | null | undefined | { displayName: string } | string[]; + +export function getValue(value: Value): string { if (value === null || value === undefined) { return ''; } - return typeof value === 'string' ? value : value.displayName; + + switch (typeof value) { + case 'string': + return value; + case 'object': + return Array.isArray(value) ? value.join(', ') : value.displayName; + default: + return ''; + } } export function matchType(type?: string) {