diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionConfig.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionConfig.java similarity index 100% rename from server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionConfig.java rename to server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionConfig.java diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebPropertyInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebPropertyInfo.java index 881d9f2922..b4e0c00f6c 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebPropertyInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebPropertyInfo.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,12 @@ public class WebPropertyInfo { private DBPPropertySource propertySource; private boolean showProtected; + private Object[] validValues; + + private String[] supportedConfigurationTypes = new String[0]; + + private Object defaultValue; + public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property, DBPPropertySource propertySource) { this.session = session; this.property = property; @@ -123,7 +129,7 @@ public PropertyLength getLength() { @Property public Object getDefaultValue() throws DBException { - var defaultValue = property.getDefaultValue(); + var defaultValue = property.getDefaultValue() == null ? this.defaultValue : property.getDefaultValue(); return defaultValue == null ? getValue() : defaultValue; } @@ -154,9 +160,9 @@ public Object[] getValidValues() { } return validValues; } - return null; + return validValues; } - return null; + return validValues; } @Property @@ -172,7 +178,7 @@ public String[] getSupportedConfigurationTypes() { .map(DBPDriverConfigurationType::toString) .toArray(String[]::new); } - return new String[0]; + return supportedConfigurationTypes; } @Property @@ -252,4 +258,20 @@ public List getScopes() { } return null; } + + //TODO: delete after refactoring on front-end + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + //TODO: delete after refactoring on front-end + public void setValidValues(Object[] validValues) { + this.validValues = validValues; + } + + //TODO: delete after refactoring on front-end + public void setSupportedConfigurationTypes(String[] supportedConfigurationTypes) { + this.supportedConfigurationTypes = supportedConfigurationTypes; + } + + } 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 3995bdfd1a..31edf180a4 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 @@ -31,6 +31,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration protected final Map plugins; protected String defaultUserTeam = DEFAULT_APP_ANONYMOUS_TEAM_NAME; protected boolean resourceManagerEnabled; + protected boolean secretManagerEnabled; protected boolean showReadOnlyConnectionInfo; protected String[] enabledFeatures; protected String[] disabledBetaFeatures; @@ -42,6 +43,7 @@ public BaseWebAppConfiguration() { this.enabledFeatures = null; this.disabledBetaFeatures = new String[0]; this.showReadOnlyConnectionInfo = false; + this.secretManagerEnabled = false; } public BaseWebAppConfiguration(BaseWebAppConfiguration src) { @@ -51,6 +53,7 @@ public BaseWebAppConfiguration(BaseWebAppConfiguration src) { this.enabledFeatures = src.enabledFeatures; this.disabledBetaFeatures = src.disabledBetaFeatures; this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo; + this.secretManagerEnabled = src.secretManagerEnabled; } @Override @@ -84,6 +87,11 @@ public boolean isResourceManagerEnabled() { return resourceManagerEnabled; } + @Override + public boolean isSecretManagerEnabled() { + return secretManagerEnabled; + } + @Override public boolean isFeatureEnabled(String id) { return ArrayUtils.contains(getEnabledFeatures(), id); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/ServletAppConfiguration.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/ServletAppConfiguration.java index 5f55eb5b47..04bae47333 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/ServletAppConfiguration.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/ServletAppConfiguration.java @@ -40,6 +40,8 @@ public interface ServletAppConfiguration { boolean isResourceManagerEnabled(); + boolean isSecretManagerEnabled(); + boolean isFeaturesEnabled(String[] requiredFeatures); boolean isFeatureEnabled(String id); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/config/CBAppConfig.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/config/CBAppConfig.java index 4dfdb1000a..cd0950f8db 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/config/CBAppConfig.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/config/CBAppConfig.java @@ -134,6 +134,10 @@ public void setResourceManagerEnabled(boolean resourceManagerEnabled) { this.resourceManagerEnabled = resourceManagerEnabled; } + public void setSecretManagerEnabled(boolean secretManagerEnabled) { + this.secretManagerEnabled = secretManagerEnabled; + } + public boolean isSupportsCustomConnections() { return supportsCustomConnections; } 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 197a6d9b7a..a8f956761f 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 @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,13 +114,22 @@ protected BaseWebProjectImpl getWebProject(String projectId, boolean refresh) th if (project == null || refresh) { SessionContextImpl sessionContext = new SessionContextImpl(null); RMProject rmProject = makeProjectFromId(projectId, false); - project = new InternalWebProjectImpl(sessionContext, rmProject, getProjectPath(projectId)); + project = createWebProjectImpl(projectId, sessionContext, rmProject); projectRegistries.put(projectId, project); } return project; } } + @NotNull + protected InternalWebProjectImpl createWebProjectImpl( + String projectId, + SessionContextImpl sessionContext, + RMProject rmProject + ) throws DBException { + return new InternalWebProjectImpl(sessionContext, rmProject, getProjectPath(projectId)); + } + @NotNull @Override public RMProject[] listAccessibleProjects() throws DBException { @@ -946,16 +955,19 @@ public String ping() { return "pong (RM)"; } - public static final class Builder { - private final SMCredentialsProvider credentialsProvider; - private final Supplier smController; - private final DBPWorkspace workspace; + public static class Builder { + protected final SMCredentialsProvider credentialsProvider; + protected final Supplier smController; + protected final DBPWorkspace workspace; - private Path rootPath; - private Path userProjectsPath; - private Path sharedProjectsPath; + protected Path rootPath; + protected Path userProjectsPath; + protected Path sharedProjectsPath; - private Builder(DBPWorkspace workspace, SMCredentialsProvider credentialsProvider, Supplier smControllerSupplier) { + protected Builder( + DBPWorkspace workspace, SMCredentialsProvider credentialsProvider, + Supplier smControllerSupplier + ) { this.workspace = workspace; this.credentialsProvider = credentialsProvider; this.smController = smControllerSupplier; diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/CBConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/CBConstants.java index 5fb391293d..2d1c943bd1 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/CBConstants.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/CBConstants.java @@ -46,6 +46,7 @@ public class CBConstants { public static final String PARAM_STATIC_CONTENT = "staticContent"; public static final String PARAM_RESOURCE_QUOTAS = "resourceQuotas"; public static final String PARAM_RESOURCE_MANAGER_ENABLED = "resourceManagerEnabled"; + public static final String PARAM_SECRET_MANAGER_ENABLED = "secretManagerEnabled"; public static final String PARAM_SHOW_READ_ONLY_CONN_INFO = "showReadOnlyConnectionInfo"; public static final String PARAM_CONN_GRANT_ANON_ACCESS = "grantConnectionsAccessToAnonymousTeam"; public static final String PARAM_AUTH_PROVIDERS = "authConfiguration"; diff --git a/server/bundles/io.cloudbeaver.server.ce/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.server.ce/META-INF/MANIFEST.MF index 9cf47a7d23..31dd07b88e 100644 --- a/server/bundles/io.cloudbeaver.server.ce/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.server.ce/META-INF/MANIFEST.MF @@ -15,6 +15,7 @@ Export-Package: io.cloudbeaver, io.cloudbeaver.model.config, io.cloudbeaver.server, io.cloudbeaver.service, + io.cloudbeaver.service.core, io.cloudbeaver.service.session Import-Package: org.slf4j Automatic-Module-Name: io.cloudbeaver.server.ce diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplication.java index 0f6df490d6..db0867073f 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplication.java @@ -29,6 +29,8 @@ import io.cloudbeaver.registry.WebDriverRegistry; import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.jetty.CBJettyServer; +import io.cloudbeaver.service.ConnectionController; +import io.cloudbeaver.service.ConnectionControllerCE; import io.cloudbeaver.service.DBWServiceInitializer; import io.cloudbeaver.service.DBWServiceServerConfigurator; import io.cloudbeaver.service.session.CBSessionManager; @@ -773,4 +775,9 @@ public Map getInitActions() { public WebServerConfig getWebServerConfig() { return new CBWebServerConfig(this); } + + @Override + public ConnectionController getConnectionController() { + return new ConnectionControllerCE(); + } } diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBServerConfigurationController.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBServerConfigurationController.java index 637b287941..025ae6cb61 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBServerConfigurationController.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBServerConfigurationController.java @@ -436,6 +436,11 @@ protected Map collectConfigurationProperties( appConfigProperties, CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, appConfig.isResourceManagerEnabled()); + copyConfigValue( + oldAppConfig, + appConfigProperties, + CBConstants.PARAM_SECRET_MANAGER_ENABLED, + appConfig.isSecretManagerEnabled()); copyConfigValue( oldAppConfig, appConfigProperties, diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 9b4603d8ec..2022abe368 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -130,6 +130,8 @@ type ServerConfig { supportsCustomConnections: Boolean! resourceManagerEnabled: Boolean! + secretManagerEnabled: Boolean! @since(version: "24.3.2") + publicCredentialsSaveEnabled: Boolean! adminCredentialsSaveEnabled: Boolean! diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebDatabaseDriverInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebDatabaseDriverInfo.java index 5a620ea9c2..fa399e10e1 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebDatabaseDriverInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebDatabaseDriverInfo.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; /** * Web driver configuration @@ -255,9 +257,19 @@ public WebPropertyInfo[] getMainProperties() { @Property public WebPropertyInfo[] getProviderProperties() { - return Arrays.stream(driver.getProviderPropertyDescriptors()) + WebPropertyInfo[] additionalWebProperty = Optional.of(WebAppUtils.getWebApplication()) + .filter(app -> app.getAppConfiguration().isSecretManagerEnabled()) + .map(app -> app.getConnectionController().getExternalInfo(webSession)) + .orElse(new WebPropertyInfo[0]); + + WebPropertyInfo[] providerProperties = Arrays.stream(driver.getProviderPropertyDescriptors()) .map(p -> new WebPropertyInfo(webSession, p, null)) .toArray(WebPropertyInfo[]::new); + + return Stream.concat( + Arrays.stream(additionalWebProperty), + Arrays.stream(providerProperties) + ).toArray(WebPropertyInfo[]::new); } @Property diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index 8309444230..b1ec00ef96 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -110,6 +110,11 @@ public boolean isResourceManagerEnabled() { return application.getAppConfiguration().isResourceManagerEnabled(); } + @Property + public boolean isSecretManagerEnabled() { + return application.getAppConfiguration().isSecretManagerEnabled(); + } + @Property public String[] getEnabledFeatures() { return application.getAppConfiguration().getEnabledFeatures(); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/WebApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/WebApplication.java index 25de26e9d0..f6d973c029 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/WebApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/WebApplication.java @@ -21,6 +21,7 @@ import io.cloudbeaver.model.app.WebAppConfiguration; import io.cloudbeaver.model.app.WebServerConfiguration; import io.cloudbeaver.registry.WebDriverRegistry; +import io.cloudbeaver.service.ConnectionController; import org.jkiss.code.NotNull; import java.net.InetAddress; @@ -52,4 +53,6 @@ public interface WebApplication extends ServletApplication { WebServerConfig getWebServerConfig(); + ConnectionController getConnectionController(); + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionController.java new file mode 100644 index 0000000000..3f61db48b9 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionController.java @@ -0,0 +1,102 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2025 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.service; + +import io.cloudbeaver.DBWebException; +import io.cloudbeaver.WebObjectId; +import io.cloudbeaver.model.WebConnectionConfig; +import io.cloudbeaver.model.WebConnectionInfo; +import io.cloudbeaver.model.WebNetworkHandlerConfigInput; +import io.cloudbeaver.model.WebPropertyInfo; +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.DBPDataSourceContainer; +import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; +import org.jkiss.dbeaver.registry.DataSourceDescriptor; + +import java.util.List; +import java.util.Map; + + +public interface ConnectionController { + + DBPDataSourceContainer createDataSourceContainer( + @NotNull WebSession webSession, + @Nullable @WebObjectId String projectId, + @NotNull WebConnectionConfig connectionConfig + ) throws DBWebException; + + WebConnectionInfo createConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + DBPDataSourceRegistry sessionRegistry, + DBPDataSourceContainer newDataSource + ) throws DBWebException; + + DBPDataSourceContainer getDatasourceConnection( + @NotNull WebSession webSession, + @Nullable @WebObjectId String projectId, + @NotNull WebConnectionConfig connectionConfig) throws DBWebException; + + WebConnectionInfo updateConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig config, + DBPDataSourceContainer dataSource, + DBPDataSourceRegistry sessionRegistry + ) throws DBWebException; + + boolean deleteConnection( + @NotNull WebSession webSession, + @Nullable @WebObjectId String projectId, + @NotNull String connectionId) throws DBWebException; + + DataSourceDescriptor prepareTestConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig connectionConfig) throws DBWebException; + + WebConnectionInfo testConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig connectionConfig, + DataSourceDescriptor dataSource + ) throws DBWebException; + + WebConnectionInfo getConnectionState( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull String connectionId + ) throws DBWebException; + + WebConnectionInfo initConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull String connectionId, + @NotNull Map authProperties, + @Nullable List networkCredentials, + boolean saveCredentials, + boolean sharedCredentials, + @Nullable String selectedSecretId + ) throws DBWebException; + + void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException; + + WebPropertyInfo[] getExternalInfo(WebSession webSession); +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java new file mode 100644 index 0000000000..82343321aa --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java @@ -0,0 +1,540 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2025 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.service; + +import io.cloudbeaver.*; +import io.cloudbeaver.model.WebConnectionConfig; +import io.cloudbeaver.model.WebConnectionInfo; +import io.cloudbeaver.model.WebNetworkHandlerConfigInput; +import io.cloudbeaver.model.WebPropertyInfo; +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.utils.ServletAppUtils; +import io.cloudbeaver.utils.WebDataSourceUtils; +import io.cloudbeaver.utils.WebEventUtils; +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.DBPDataSourceContainer; +import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; +import org.jkiss.dbeaver.model.exec.DBCConnectException; +import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; +import org.jkiss.dbeaver.model.net.DBWHandlerType; +import org.jkiss.dbeaver.model.rm.RMProjectType; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.model.secret.DBSSecretController; +import org.jkiss.dbeaver.model.secret.DBSSecretValue; +import org.jkiss.dbeaver.model.websocket.WSConstants; +import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceConnectEvent; +import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty; +import org.jkiss.dbeaver.registry.DataSourceDescriptor; +import org.jkiss.dbeaver.runtime.DBWorkbench; +import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob; +import org.jkiss.dbeaver.utils.RuntimeUtils; +import org.jkiss.utils.CommonUtils; + +import java.util.List; +import java.util.Map; + +public class ConnectionControllerCE implements ConnectionController { + + private static final Log log = Log.getLog(ConnectionController.class); + + + @Override + public DBPDataSourceContainer createDataSourceContainer( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig connectionConfig + ) throws DBWebException { + WebSessionProjectImpl project = getProjectById(webSession, projectId); + var rmProject = project.getRMProject(); + if (rmProject.getType() == RMProjectType.USER + && !webSession.hasPermission(DBWConstants.PERMISSION_ADMIN) + && !ServletAppUtils.getServletApplication().getAppConfiguration().isSupportsCustomConnections() + ) { + throw new DBWebException("New connection create is restricted by server configuration"); + } + webSession.addInfoMessage("Create new connection"); + DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); + + // we don't need to save credentials for templates + if (connectionConfig.isTemplate()) { + connectionConfig.setSaveCredentials(false); + } + DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, + sessionRegistry); + if (CommonUtils.isEmpty(newDataSource.getName())) { + newDataSource.setName(CommonUtils.notNull(connectionConfig.getName(), "NewConnection")); + } + return newDataSource; + } + + @Override + @NotNull + public WebConnectionInfo createConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + DBPDataSourceRegistry sessionRegistry, + DBPDataSourceContainer newDataSource + ) throws DBWebException { + WebSessionProjectImpl project = getProjectById(webSession, projectId); + try { + sessionRegistry.addDataSource(newDataSource); + + sessionRegistry.checkForErrors(); + } catch (DBException e) { + sessionRegistry.removeDataSource(newDataSource); + throw new DBWebException("Failed to create connection", e); + } + + WebConnectionInfo connectionInfo = project.addConnection(newDataSource); + webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo( + newDataSource)); + WebEventUtils.addDataSourceUpdatedEvent( + webSession.getProjectById(projectId), + webSession, + connectionInfo.getId(), + WSConstants.EventAction.CREATE, + WSDataSourceProperty.CONFIGURATION + ); + return connectionInfo; + } + + @Override + public DBPDataSourceContainer getDatasourceConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig config + ) throws DBWebException { + // Do not check for custom connection option. Already created connections can be edited. + // Also template connections can be edited +// if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) { +// throw new DBWebException("Connection edit is restricted by server configuration"); +// } + + WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId()); + DBPDataSourceContainer dataSource = connectionInfo.getDataSourceContainer(); + webSession.addInfoMessage("Update connection - " + WebServiceUtils.getConnectionContainerInfo(dataSource)); + getOldDataSource(dataSource); + if (!CommonUtils.isEmpty(config.getName())) { + dataSource.setName(config.getName()); + } + + if (config.getDescription() != null) { + dataSource.setDescription(config.getDescription()); + } + + WebSessionProjectImpl project = getProjectById(webSession, projectId); + DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); + dataSource.setFolder(config.getFolder() != null ? sessionRegistry.getFolder(config.getFolder()) : null); + if (config.isDefaultAutoCommit() != null) { + dataSource.setDefaultAutoCommit(config.isDefaultAutoCommit()); + } + WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(), + dataSource.getConnectionConfiguration(), + config); + + // we should check that the config has changed but not check for password changes + dataSource.setSharedCredentials(config.isSharedCredentials()); + dataSource.setSavePassword(config.isSaveCredentials()); + boolean sharedCredentials = isSharedCredentials(dataSource); + if (sharedCredentials) { + //we must notify about the shared password change + WebServiceUtils.saveAuthProperties( + dataSource, + dataSource.getConnectionConfiguration(), + config.getCredentials(), + config.isSaveCredentials(), + config.isSharedCredentials() + ); + } + connectionInfo.setCredentialsSavedInSession(null); + // same here + return dataSource; + } + + private static boolean isSharedCredentials(DBPDataSourceContainer dataSource) { + return dataSource.isSharedCredentials() || !dataSource.getProject() + .isUseSecretStorage() && dataSource.isSavePassword(); + } + + private static DataSourceDescriptor getOldDataSource(DBPDataSourceContainer dataSource) { + DataSourceDescriptor oldDataSource; + oldDataSource = dataSource.getRegistry().createDataSource(dataSource); + oldDataSource.setId(dataSource.getId()); + return oldDataSource; + } + + @Override + public WebConnectionInfo updateConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig config, + DBPDataSourceContainer dataSource, + DBPDataSourceRegistry sessionRegistry + ) throws DBWebException { + WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId()); + boolean sendEvent = !((DataSourceDescriptor) dataSource).equalSettings(getOldDataSource(dataSource)); + if (!isSharedCredentials(dataSource)) { + // secret controller is responsible for notification, password changes applied after checks + WebServiceUtils.saveAuthProperties( + dataSource, + dataSource.getConnectionConfiguration(), + config.getCredentials(), + config.isSaveCredentials(), + config.isSharedCredentials() + ); + } + + WSDataSourceProperty property = getDatasourceEventProperty(getOldDataSource(dataSource), dataSource); + + try { + sessionRegistry.updateDataSource(dataSource); + sessionRegistry.checkForErrors(); + } catch (DBException e) { + throw new DBWebException("Failed to update connection", e); + } + if (sendEvent) { + WebEventUtils.addDataSourceUpdatedEvent( + webSession.getProjectById(projectId), + webSession, + connectionInfo.getId(), + WSConstants.EventAction.UPDATE, + property + ); + } + return connectionInfo; + } + + @Override + public boolean deleteConnection( + @NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId + ) throws DBWebException { + WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId); + if (connectionInfo.getDataSourceContainer().getProject() != getProjectById(webSession, projectId)) { + throw new DBWebException("Global connection '" + connectionInfo.getName() + "' configuration cannot be deleted"); + } + webSession.addInfoMessage("Delete connection - " + + WebServiceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer())); + closeAndDeleteConnection(webSession, projectId, connectionId, true); + WebEventUtils.addDataSourceUpdatedEvent( + webSession.getProjectById(projectId), + webSession, + connectionId, + WSConstants.EventAction.DELETE, + WSDataSourceProperty.CONFIGURATION + ); + return true; + } + + @Override + public DataSourceDescriptor prepareTestConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig connectionConfig + ) throws DBWebException { + String connectionId = connectionConfig.getConnectionId(); + + connectionConfig.setSaveCredentials(true); // It is used in createConnectionFromConfig + + DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource( + webSession, projectId, connectionId); + + validateConnection(dataSource); + + WebProjectImpl project = getProjectById(webSession, projectId); + DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); + DataSourceDescriptor testDataSource; + if (dataSource != null) { + try { + // Check that creds are saved to trigger secrets resolve + dataSource.isCredentialsSaved(); + } catch (DBException e) { + throw new DBWebException("Can't determine whether datasource credentials are saved", e); + } + + testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry()); + WebServiceUtils.setConnectionConfiguration( + testDataSource.getDriver(), + testDataSource.getConnectionConfiguration(), + connectionConfig + ); + if (connectionConfig.getSelectedSecretId() != null) { + try { + dataSource.listSharedCredentials() + .stream() + .filter(secret -> connectionConfig.getSelectedSecretId().equals(secret.getSubjectId())) + .findFirst() + .ifPresent(testDataSource::setSelectedSharedCredentials); + + } catch (DBException e) { + throw new DBWebException("Failed to load secret value: " + connectionConfig.getSelectedSecretId()); + } + } + WebServiceUtils.saveAuthProperties( + testDataSource, + testDataSource.getConnectionConfiguration(), + connectionConfig.getCredentials(), + true, + false, + true + ); + } else { + testDataSource = (DataSourceDescriptor) WebServiceUtils.createConnectionFromConfig(connectionConfig, + sessionRegistry); + } + return testDataSource; + } + + @Override + @NotNull + public WebConnectionInfo testConnection( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig connectionConfig, + DataSourceDescriptor testDataSource + ) throws DBWebException { + validateDriverLibrariesPresence(testDataSource); + webSession.provideAuthParameters(webSession.getProgressMonitor(), + testDataSource, + testDataSource.getConnectionConfiguration()); + testDataSource.setSavePassword(true); // We need for test to avoid password callback + if (DataSourceDescriptor.class.isAssignableFrom(testDataSource.getClass())) { + testDataSource.setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)); + } + try { + ConnectionTestJob ct = new ConnectionTestJob(testDataSource, param -> { + }); + ct.run(webSession.getProgressMonitor()); + if (ct.getConnectError() != null) { + if (ct.getConnectError() instanceof DBCConnectException error) { + Throwable rootCause = CommonUtils.getRootCause(error); + if (rootCause instanceof ClassNotFoundException) { + throwDriverNotFoundException(testDataSource); + } + } + throw new DBWebException("Connection failed", ct.getConnectError()); + } + WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, testDataSource); + connectionInfo.setConnectError(ct.getConnectError()); + connectionInfo.setServerVersion(ct.getServerVersion()); + connectionInfo.setClientVersion(ct.getClientVersion()); + connectionInfo.setConnectTime(RuntimeUtils.formatExecutionTime(ct.getConnectTime())); + return connectionInfo; + } catch (DBException e) { + throw new DBWebException("Error connecting to database", e); + } + } + + private WebSessionProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException { + WebSessionProjectImpl project = webSession.getProjectById(projectId); + if (project == null) { + throw new DBWebException("Project '" + projectId + "' not found"); + } + return project; + } + + private WSDataSourceProperty getDatasourceEventProperty( + DataSourceDescriptor oldDataSource, + DBPDataSourceContainer dataSource + ) { + if (!oldDataSource.equalConfiguration((DataSourceDescriptor) dataSource)) { + return WSDataSourceProperty.CONFIGURATION; + } + + var nameChanged = !CommonUtils.equalObjects(oldDataSource.getName(), dataSource.getName()); + var descriptionChanged = !CommonUtils.equalObjects(oldDataSource.getDescription(), dataSource.getDescription()); + if (nameChanged && descriptionChanged) { + return WSDataSourceProperty.CONFIGURATION; + } + + return nameChanged ? WSDataSourceProperty.NAME : WSDataSourceProperty.CONFIGURATION; + } + + @NotNull + private WebConnectionInfo closeAndDeleteConnection( + @NotNull WebSession webSession, + @NotNull String projectId, + @NotNull String connectionId, + boolean forceDelete + ) throws DBWebException { + WebSessionProjectImpl project = getProjectById(webSession, projectId); + WebConnectionInfo connectionInfo = project.getWebConnectionInfo(connectionId); + + DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer(); + boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer); + if (forceDelete) { + DBPDataSourceRegistry registry = project.getDataSourceRegistry(); + registry.removeDataSource(dataSourceContainer); + try { + registry.checkForErrors(); + } catch (DBException e) { + try { + registry.addDataSource(dataSourceContainer); + } catch (DBException ex) { + log.error("Error re-adding after delete attempt", e); + } + throw new DBWebException("Failed to delete connection", e); + } + project.removeConnection(dataSourceContainer); + } else { + // Just reset saved credentials + connectionInfo.clearCache(); + } + + return connectionInfo; + } + + @Override + public WebConnectionInfo getConnectionState( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull String connectionId + ) throws DBWebException { + return WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId); + } + + @Override + public WebConnectionInfo initConnection(@NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId, @NotNull Map authProperties, @Nullable List networkCredentials, boolean saveCredentials, boolean sharedCredentials, @Nullable String selectedSecretId) throws DBWebException { + WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId); + connectionInfo.setSavedCredentials(authProperties, networkCredentials); + + var dataSourceContainer = connectionInfo.getDataSourceContainer();; + validateConnection(dataSourceContainer); + if (dataSourceContainer.isConnected()) { + throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected"); + } + if (dataSourceContainer.isSharedCredentials() && selectedSecretId != null) { + List allSecrets; + try { + allSecrets = dataSourceContainer.listSharedCredentials(); + } catch (DBException e) { + throw new DBWebException("Error loading connection secret", e); + } + DBSSecretValue selectedSecret = + allSecrets.stream() + .filter(secret -> selectedSecretId.equals(secret.getUniqueId())) + .findFirst().orElse(null); + if (selectedSecret == null) { + throw new DBWebException("Secret not found:" + selectedSecretId); + } + dataSourceContainer.setSelectedSharedCredentials(selectedSecret); + } + + boolean oldSavePassword = dataSourceContainer.isSavePassword(); + DBRProgressMonitor monitor = webSession.getProgressMonitor(); + validateDriverLibrariesPresence(dataSourceContainer); + try { + boolean connect = dataSourceContainer.connect(monitor, true, false); + if (connect) { + webSession.addSessionEvent( + new WSDataSourceConnectEvent( + projectId, + connectionId, + webSession.getSessionId(), + webSession.getUserId() + ) + ); + } + } catch (Exception e) { + throw new DBWebException("Error connecting to database", e); + } finally { + dataSourceContainer.setSavePassword(oldSavePassword); + connectionInfo.clearCache(); + } + // Mark all specified network configs as saved + boolean[] saveConfig = new boolean[1]; + + if (networkCredentials != null) { + networkCredentials.forEach(c -> { + if (CommonUtils.toBoolean(c.isSavePassword())) { + DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration() + .getHandler(c.getId()); + if (handlerCfg != null && + // check username param only for ssh config + !(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(), + DBWHandlerType.TUNNEL)) + ) { + WebDataSourceUtils.updateHandlerCredentials(handlerCfg, c); + handlerCfg.setSavePassword(true); + saveConfig[0] = true; + } + } + }); + } + if (saveCredentials) { + // Save all passed credentials in the datasource container + WebServiceUtils.saveAuthProperties( + dataSourceContainer, + dataSourceContainer.getConnectionConfiguration(), + authProperties, + true, + sharedCredentials + ); + + var project = dataSourceContainer.getProject(); + if (project.isUseSecretStorage()) { + try { + dataSourceContainer.persistSecrets( + DBSSecretController.getProjectSecretController(dataSourceContainer.getProject()) + ); + } catch (DBException e) { + throw new DBWebException("Failed to save credentials", e); + } + } + + WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo, + dataSourceContainer, + dataSourceContainer.getConnectionConfiguration()); + saveConfig[0] = true; + } + if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) { + // Do not flush config for global project (only admin can do it - CB-2415) + if (saveCredentials) { + connectionInfo.setCredentialsSavedInSession(true); + } + saveConfig[0] = false; + } + if (saveConfig[0]) { + dataSourceContainer.persistConfiguration(); + } + + return connectionInfo; + } + + @Override + public void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException { + } + + public WebPropertyInfo[] getExternalInfo(WebSession session) { + return null; + } + + + private void validateDriverLibrariesPresence(@NotNull DBPDataSourceContainer container) throws DBWebException { + if (!DBWorkbench.isDistributed() && container.getDriver().needsExternalDependencies()) { + throwDriverNotFoundException(container); + } + } + + @NotNull + private static String throwDriverNotFoundException(@NotNull DBPDataSourceContainer container) throws DBWebException { + throw new DBWebException("Driver files for %s are not found. Please ask the administrator to download it." + .formatted(container.getDriver().getName())); + } +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index f08c41b0e4..00a6cab21f 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,6 @@ import org.jkiss.dbeaver.model.net.DBWNetworkHandler; import org.jkiss.dbeaver.model.net.DBWTunnel; import org.jkiss.dbeaver.model.net.ssh.SSHSession; -import org.jkiss.dbeaver.model.rm.RMProjectType; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.secret.DBSSecretController; import org.jkiss.dbeaver.model.secret.DBSSecretValue; @@ -328,7 +327,7 @@ public WebConnectionInfo getConnectionState( @Nullable String projectId, @NotNull String connectionId ) throws DBWebException { - return WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId); + return WebAppUtils.getWebApplication().getConnectionController().getConnectionState(webSession, projectId, connectionId); } @@ -343,109 +342,8 @@ public WebConnectionInfo initConnection( boolean sharedCredentials, @Nullable String selectedSecretId ) throws DBWebException { - WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId); - connectionInfo.setSavedCredentials(authProperties, networkCredentials); - - var dataSourceContainer = (DataSourceDescriptor) connectionInfo.getDataSourceContainer(); - if (dataSourceContainer.isConnected()) { - throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected"); - } - if (dataSourceContainer.isSharedCredentials() && selectedSecretId != null) { - List allSecrets; - try { - allSecrets = dataSourceContainer.listSharedCredentials(); - } catch (DBException e) { - throw new DBWebException("Error loading connection secret", e); - } - DBSSecretValue selectedSecret = - allSecrets.stream() - .filter(secret -> selectedSecretId.equals(secret.getUniqueId())) - .findFirst().orElse(null); - if (selectedSecret == null) { - throw new DBWebException("Secret not found:" + selectedSecretId); - } - dataSourceContainer.setSelectedSharedCredentials(selectedSecret); - } - - boolean oldSavePassword = dataSourceContainer.isSavePassword(); - DBRProgressMonitor monitor = webSession.getProgressMonitor(); - validateDriverLibrariesPresence(dataSourceContainer); - try { - boolean connect = dataSourceContainer.connect(monitor, true, false); - if (connect) { - webSession.addSessionEvent( - new WSDataSourceConnectEvent( - projectId, - connectionId, - webSession.getSessionId(), - webSession.getUserId() - ) - ); - } - } catch (Exception e) { - throw new DBWebException("Error connecting to database", e); - } finally { - dataSourceContainer.setSavePassword(oldSavePassword); - connectionInfo.clearCache(); - } - // Mark all specified network configs as saved - boolean[] saveConfig = new boolean[1]; - - if (networkCredentials != null) { - networkCredentials.forEach(c -> { - if (CommonUtils.toBoolean(c.isSavePassword())) { - DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration() - .getHandler(c.getId()); - if (handlerCfg != null && - // check username param only for ssh config - !(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(), - DBWHandlerType.TUNNEL)) - ) { - WebDataSourceUtils.updateHandlerCredentials(handlerCfg, c); - handlerCfg.setSavePassword(true); - saveConfig[0] = true; - } - } - }); - } - if (saveCredentials) { - // Save all passed credentials in the datasource container - WebServiceUtils.saveAuthProperties( - dataSourceContainer, - dataSourceContainer.getConnectionConfiguration(), - authProperties, - true, - sharedCredentials - ); - - var project = dataSourceContainer.getProject(); - if (project.isUseSecretStorage()) { - try { - dataSourceContainer.persistSecrets( - DBSSecretController.getProjectSecretController(dataSourceContainer.getProject()) - ); - } catch (DBException e) { - throw new DBWebException("Failed to save credentials", e); - } - } - - WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo, - dataSourceContainer, - dataSourceContainer.getConnectionConfiguration()); - saveConfig[0] = true; - } - if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) { - // Do not flush config for global project (only admin can do it - CB-2415) - if (saveCredentials) { - connectionInfo.setCredentialsSavedInSession(true); - } - saveConfig[0] = false; - } - if (saveConfig[0]) { - dataSourceContainer.persistConfiguration(); - } - - return connectionInfo; + return WebAppUtils.getWebApplication().getConnectionController().initConnection(webSession, projectId, + connectionId, authProperties, networkCredentials, saveCredentials, sharedCredentials, selectedSecretId); } @Override @@ -454,47 +352,14 @@ public WebConnectionInfo createConnection( @Nullable String projectId, @NotNull WebConnectionConfig connectionConfig ) throws DBWebException { - WebSessionProjectImpl project = getProjectById(webSession, projectId); - var rmProject = project.getRMProject(); - if (rmProject.getType() == RMProjectType.USER - && !webSession.hasPermission(DBWConstants.PERMISSION_ADMIN) - && !ServletAppUtils.getServletApplication().getAppConfiguration().isSupportsCustomConnections() - ) { - throw new DBWebException("New connection create is restricted by server configuration"); - } - webSession.addInfoMessage("Create new connection"); - DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); - - // we don't need to save credentials for templates - if (connectionConfig.isTemplate()) { - connectionConfig.setSaveCredentials(false); - } - DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, - sessionRegistry); - if (CommonUtils.isEmpty(newDataSource.getName())) { - newDataSource.setName(CommonUtils.notNull(connectionConfig.getName(), "NewConnection")); - } - - try { - sessionRegistry.addDataSource(newDataSource); - - sessionRegistry.checkForErrors(); - } catch (DBException e) { - sessionRegistry.removeDataSource(newDataSource); - throw new DBWebException("Failed to create connection", e); - } - - WebConnectionInfo connectionInfo = project.addConnection(newDataSource); - webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo( - newDataSource)); - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), + DBPDataSourceContainer dataSourceContainer = + WebAppUtils.getWebApplication().getConnectionController().createDataSourceContainer(webSession, projectId, connectionConfig); + return WebAppUtils.getWebApplication().getConnectionController().createConnection( webSession, - connectionInfo.getId(), - WSConstants.EventAction.CREATE, - WSDataSourceProperty.CONFIGURATION + projectId, + dataSourceContainer.getRegistry(), + dataSourceContainer ); - return connectionInfo; } @Override @@ -503,82 +368,15 @@ public WebConnectionInfo updateConnection( @Nullable String projectId, @NotNull WebConnectionConfig config ) throws DBWebException { - // Do not check for custom connection option. Already created connections can be edited. - // Also template connections can be edited -// if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) { -// throw new DBWebException("Connection edit is restricted by server configuration"); -// } - - WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId()); - DBPDataSourceContainer dataSource = connectionInfo.getDataSourceContainer(); - webSession.addInfoMessage("Update connection - " + WebServiceUtils.getConnectionContainerInfo(dataSource)); - DataSourceDescriptor oldDataSource; - oldDataSource = dataSource.getRegistry().createDataSource(dataSource); - oldDataSource.setId(dataSource.getId()); - if (!CommonUtils.isEmpty(config.getName())) { - dataSource.setName(config.getName()); - } - - if (config.getDescription() != null) { - dataSource.setDescription(config.getDescription()); - } - - WebSessionProjectImpl project = getProjectById(webSession, projectId); - DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); - dataSource.setFolder(config.getFolder() != null ? sessionRegistry.getFolder(config.getFolder()) : null); - if (config.isDefaultAutoCommit() != null) { - dataSource.setDefaultAutoCommit(config.isDefaultAutoCommit()); - } - WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(), - dataSource.getConnectionConfiguration(), - config); - - // we should check that the config has changed but not check for password changes - dataSource.setSharedCredentials(config.isSharedCredentials()); - dataSource.setSavePassword(config.isSaveCredentials()); - boolean sharedCredentials = dataSource.isSharedCredentials() || !dataSource.getProject() - .isUseSecretStorage() && dataSource.isSavePassword(); - if (sharedCredentials) { - //we must notify about the shared password change - WebServiceUtils.saveAuthProperties( - dataSource, - dataSource.getConnectionConfiguration(), - config.getCredentials(), - config.isSaveCredentials(), - config.isSharedCredentials() - ); - } - boolean sendEvent = !((DataSourceDescriptor) dataSource).equalSettings(oldDataSource); - if (!sharedCredentials) { - // secret controller is responsible for notification, password changes applied after checks - WebServiceUtils.saveAuthProperties( - dataSource, - dataSource.getConnectionConfiguration(), - config.getCredentials(), - config.isSaveCredentials(), - config.isSharedCredentials() - ); - } - connectionInfo.setCredentialsSavedInSession(null); - - WSDataSourceProperty property = getDatasourceEventProperty(oldDataSource, dataSource); - - try { - sessionRegistry.updateDataSource(dataSource); - sessionRegistry.checkForErrors(); - } catch (DBException e) { - throw new DBWebException("Failed to update connection", e); - } - if (sendEvent) { - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - connectionInfo.getId(), - WSConstants.EventAction.UPDATE, - property - ); - } - return connectionInfo; + DBPDataSourceContainer dataSourceContainer = + WebAppUtils.getWebApplication().getConnectionController().getDatasourceConnection(webSession, projectId, config); + return WebAppUtils.getWebApplication().getConnectionController().updateConnection( + webSession, + projectId, + config, + dataSourceContainer, + dataSourceContainer.getRegistry() + ); } private WSDataSourceProperty getDatasourceEventProperty( @@ -602,21 +400,8 @@ private WSDataSourceProperty getDatasourceEventProperty( public boolean deleteConnection( @NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId ) throws DBWebException { - WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId); - if (connectionInfo.getDataSourceContainer().getProject() != getProjectById(webSession, projectId)) { - throw new DBWebException("Global connection '" + connectionInfo.getName() + "' configuration cannot be deleted"); - } - webSession.addInfoMessage("Delete connection - " + - WebServiceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer())); - closeAndDeleteConnection(webSession, projectId, connectionId, true); - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - connectionId, - WSConstants.EventAction.DELETE, - WSDataSourceProperty.CONFIGURATION - ); - return true; + return WebAppUtils.getWebApplication().getConnectionController().deleteConnection(webSession, projectId, connectionId); + } @Override @@ -713,86 +498,14 @@ public WebConnectionInfo copyConnectionFromNode( @Override public WebConnectionInfo testConnection( - @NotNull WebSession webSession, @Nullable String projectId, @NotNull WebConnectionConfig connectionConfig + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull WebConnectionConfig connectionConfig ) throws DBWebException { - String connectionId = connectionConfig.getConnectionId(); - - connectionConfig.setSaveCredentials(true); // It is used in createConnectionFromConfig - - DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource( - webSession, projectId, connectionId); - - WebProjectImpl project = getProjectById(webSession, projectId); - DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); - DataSourceDescriptor testDataSource; - if (dataSource != null) { - try { - // Check that creds are saved to trigger secrets resolve - dataSource.isCredentialsSaved(); - } catch (DBException e) { - throw new DBWebException("Can't determine whether datasource credentials are saved", e); - } - - testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry()); - WebServiceUtils.setConnectionConfiguration( - testDataSource.getDriver(), - testDataSource.getConnectionConfiguration(), - connectionConfig - ); - if (connectionConfig.getSelectedSecretId() != null) { - try { - dataSource.listSharedCredentials() - .stream() - .filter(secret -> connectionConfig.getSelectedSecretId().equals(secret.getSubjectId())) - .findFirst() - .ifPresent(testDataSource::setSelectedSharedCredentials); - - } catch (DBException e) { - throw new DBWebException("Failed to load secret value: " + connectionConfig.getSelectedSecretId()); - } - } - WebServiceUtils.saveAuthProperties( - testDataSource, - testDataSource.getConnectionConfiguration(), - connectionConfig.getCredentials(), - true, - false, - true - ); - } else { - testDataSource = (DataSourceDescriptor) WebServiceUtils.createConnectionFromConfig(connectionConfig, - sessionRegistry); - } - validateDriverLibrariesPresence(testDataSource); - webSession.provideAuthParameters(webSession.getProgressMonitor(), - testDataSource, - testDataSource.getConnectionConfiguration()); - testDataSource.setSavePassword(true); // We need for test to avoid password callback - if (DataSourceDescriptor.class.isAssignableFrom(testDataSource.getClass())) { - testDataSource.setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)); - } - try { - ConnectionTestJob ct = new ConnectionTestJob(testDataSource, param -> { - }); - ct.run(webSession.getProgressMonitor()); - if (ct.getConnectError() != null) { - if (ct.getConnectError() instanceof DBCConnectException error) { - Throwable rootCause = CommonUtils.getRootCause(error); - if (rootCause instanceof ClassNotFoundException) { - throwDriverNotFoundException(testDataSource); - } - } - throw new DBWebException("Connection failed", ct.getConnectError()); - } - WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, testDataSource); - connectionInfo.setConnectError(ct.getConnectError()); - connectionInfo.setServerVersion(ct.getServerVersion()); - connectionInfo.setClientVersion(ct.getClientVersion()); - connectionInfo.setConnectTime(RuntimeUtils.formatExecutionTime(ct.getConnectTime())); - return connectionInfo; - } catch (DBException e) { - throw new DBWebException("Error connecting to database", e); - } + DataSourceDescriptor dataSourceDescriptor = WebAppUtils.getWebApplication().getConnectionController() + .prepareTestConnection(webSession, projectId, connectionConfig); + return WebAppUtils.getWebApplication().getConnectionController() + .testConnection(webSession, projectId, connectionConfig, dataSourceDescriptor); } @Override 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 20f53605dd..2b7e1bc721 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -114,6 +114,7 @@ input ServerConfigInput { publicCredentialsSaveEnabled: Boolean adminCredentialsSaveEnabled: Boolean resourceManagerEnabled: Boolean + secretManagerEnabled: Boolean @since(version: "24.3.2") enabledFeatures: [ID!] enabledAuthProviders: [ID!] diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java index e459b2d2a0..b786d5ffd3 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java @@ -37,6 +37,7 @@ public class AdminServerConfig { private final boolean anonymousAccessEnabled; private final boolean resourceManagerEnabled; + private final boolean secretManagerEnabled; private final boolean customConnectionsEnabled; private final boolean publicCredentialsSaveEnabled; private final boolean adminCredentialsSaveEnabled; @@ -59,6 +60,7 @@ public AdminServerConfig(Map params) { this.publicCredentialsSaveEnabled = JSONUtils.getBoolean(params, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled()); this.adminCredentialsSaveEnabled = JSONUtils.getBoolean(params, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled()); this.resourceManagerEnabled = JSONUtils.getBoolean(params, "resourceManagerEnabled", appConfig.isResourceManagerEnabled()); + this.secretManagerEnabled = JSONUtils.getBoolean(params, "secretManagerEnabled", appConfig.isSecretManagerEnabled()); if (params.containsKey("enabledFeatures")) { this.enabledFeatures = JSONUtils.getStringList(params, "enabledFeatures"); @@ -162,4 +164,8 @@ public String[] getDisabledDrivers() { public boolean isResourceManagerEnabled() { return resourceManagerEnabled; } + + public boolean isSecretManagerEnabled() { + return secretManagerEnabled; + } } 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 f499ff68a2..e37d666b24 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 @@ -564,6 +564,7 @@ public boolean configureServer(WebSession webSession, Map params // custom logic for enabling embedded drivers updateDisabledDriversConfig(appConfig, config.getDisabledDrivers()); appConfig.setResourceManagerEnabled(config.isResourceManagerEnabled()); + appConfig.setSecretManagerEnabled(config.isSecretManagerEnabled()); if (CommonUtils.isEmpty(config.getEnabledAuthProviders())) { // All of them diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index ec8e86c09f..9f2045ed5d 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -100,6 +100,10 @@ export class ServerConfigResource extends CachedDataResource) = anonymousAccessEnabled: true, adminCredentialsSaveEnabled: true, publicCredentialsSaveEnabled: true, + secretManagerEnabled: true, resourceManagerEnabled: true, licenseRequired: false, licenseValid: false, diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql index 1f184e12af..3892631847 100644 --- a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql @@ -17,6 +17,7 @@ fragment ServerConfig on ServerConfig { publicCredentialsSaveEnabled resourceManagerEnabled + secretManagerEnabled configurationMode developmentMode diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts index 0000d88ce9..e60c1120eb 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts @@ -19,6 +19,7 @@ const ServerConfigurationFormPartStateConfigSchema = schema.object({ enabledFeatures: schema.array(schema.string()).optional(), publicCredentialsSaveEnabled: schema.boolean().optional(), resourceManagerEnabled: schema.boolean().optional(), + secretManagerEnabled: schema.boolean().optional(), serverName: schema.string().optional(), serverURL: schema.string().optional(), sessionExpireTime: schema.number().optional(), diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts index a38730c334..7c82591565 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts @@ -29,6 +29,7 @@ function DEFAULT_STATE_GETTER(): IServerConfigurationFormPartState { enabledFeatures: [], publicCredentialsSaveEnabled: false, resourceManagerEnabled: false, + secretManagerEnabled: false, serverName: '', serverURL: '', sessionExpireTime: MIN_SESSION_EXPIRE_TIME * 1000 * 60, @@ -147,6 +148,7 @@ export class ServerConfigurationFormPart extends FormPart { await this.serverLicenseStatusResource.load(); }, - order: 12, + order: 13, }); }