Compare commits

..
Author SHA1 Message Date
Sychev Andrey d529f9f751 dbeaver/pro#6364 refactor: update types for better usage (its strange to compare two values with different types) 2025-10-30 15:56:57 +01:00
Sychev Andrey 43b33088a9 Merge branch 'devel' into dbeaver/pro#6364-add-option-to-set-default-view-for-project 2025-10-28 16:47:27 +01:00
Ruslan 54fb2c81cd dbeaver/pro#6364 add default connection view 2025-10-28 15:37:26 +01:00
Sychev Andrey d4e65343c9 dbeaver/pro#6364 refactor: remove non-admin project mutations 2025-10-27 12:13:07 +01:00
Sychev Andrey 0e68e09ca0 Merge branch 'devel' into dbeaver/pro#6364-add-option-to-set-default-view-for-project 2025-10-27 10:10:29 +01:00
Ruslan df9b86e290 dbeaver/pro#6364 add option to set default view for project 2025-10-17 12:00:55 +02:00
Sychev Andrey 837c6fe52c dbeaver/pro#6364 feat: add GQL to handle project settings 2025-10-17 11:14:11 +02:00
Ruslan 32f56c2d82 dbeaver/pro#6364 add option to set default view for project 2025-10-17 04:07:50 +02:00
Ruslan 1c11c47dc4 dbeaver/pro#6364 add option to set default view for project 2025-10-16 18:13:52 +02:00
Ruslan c7233f4c09 dbeaver/pro#6364 add option to set default view for project 2025-10-16 16:44:43 +02:00
Ruslan 37f0520669 dbeaver/pro#6364 fix test 2025-10-16 11:45:29 +02:00
Sychev Andrey 21aee4c11d Merge branch 'devel' into dbeaver/pro#6364-add-option-to-set-default-view-for-project 2025-10-16 10:43:57 +02:00
Ruslan 28809be5f4 dbeaver/pro#6364 add option to set default view for project 2025-10-16 01:06:19 +02:00
429 changed files with 1673 additions and 7184 deletions
+2 -2
View File
@@ -15,11 +15,11 @@
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"syler.sass-indented",
"github.copilot-chat",
"VisualStudioExptTeam.vscodeintellicode",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode",
"devnaumov.licensify"
]
}
}
-10
View File
@@ -25,16 +25,6 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.2.5 2025-11-17
- A "Show SQL" button has been added to the Data Editor and the grouping panel, enabling users to inspect the exact SQL query behind displayed results—helping validate data transformations, debug query logic, and ensure analytical accuracy;
- The search option has been added to the default catalog/schema selector in the SQL Editor's main toolbar;
- The SQL Server driver has been updated to version 13.2.1.
### 25.2.4 2025-11-03
- A confirmation prompt has been added for potentially dangerous queries in the SQL Editor to help prevent unintended execution. You can customize this behavior in Preferences > SQL Editor;
- The value panel in the Data Editor has been redesigned to provide users with more space for viewing and editing data. Tab buttons for different data type visualizations are now aligned on a single level, creating a cleaner layout;
- Permission configuration for connections was simplified, providing a clearer appearance and improved performance.
### 25.2.3 2025-10-20
- Added visualization for column reordering in the Data Editor;
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.90.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.88.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.90-SNAPSHOT</version>
<version>1.0.88-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -17,25 +17,32 @@
package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.utils.CommonUtils;
import java.util.Map;
public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C extends DataSourceDescriptor> {
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
protected final T input;
protected final DBPDataSourceRegistry registry;
protected final WebSession webSession;
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput, WebSession webSession) {
this.registry = registry;
this.input = configInput;
this.webSession = webSession;
}
public C createDataSourceContainer() throws DBWebException {
@@ -47,9 +54,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
C newDataSource = createDataSourceContainerFromInput(driver);
if (ServletAppUtils.getServletApplication().getAppConfiguration() instanceof WebAppConfiguration webAppConfiguration) {
newDataSource.setNavigatorSettings(webAppConfiguration.getDefaultNavigatorSettings());
}
presetNavigatorSettings(newDataSource);
WebDataSourceUtils.saveAuthProperties(
newDataSource,
@@ -61,6 +66,29 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
return newDataSource;
}
private <C extends DataSourceDescriptor> void presetNavigatorSettings(C newDataSource) {
// if (ServletAppUtils.getServletApplication().getAppConfiguration() instanceof WebAppConfiguration webAppConfiguration) {
// newDataSource.setNavigatorSettings(webAppConfiguration.getDefaultNavigatorSettings());
// }
try {
DBPProject project = newDataSource.getProject();
String projectId = project.getId();
Map<String, Object> projectNavigatorSettings = webSession.getUserContext().getSecurityController().getObjectSettings(
projectId, SMObjectType.project,
ServletAppUtils.getServletApplication().getAppConfiguration().getDefaultUserTeam(),
DataSourceNavigatorSettings.getSettingsKeySet()
);
if (!CommonUtils.isEmpty(projectNavigatorSettings)) {
DataSourceNavigatorSettings navigatorSettings = new DataSourceNavigatorSettings();
navigatorSettings.loadSettings(projectNavigatorSettings);
newDataSource.setNavigatorSettings(navigatorSettings);
}
} catch (Exception e) {
log.warn("Error loading navigator settings", e);
}
}
public void updateDataSource(@NotNull C dataSource) throws DBWebException {
dataSource.setId(dataSource.getId());
if (!CommonUtils.isEmpty(input.getName())) {
@@ -19,6 +19,7 @@ package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -252,6 +253,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
Map<String, Object> loadedSettings = webSession.getSecurityController().getObjectSettings(
getId(),
SMObjectType.project,
ServletAppUtils.getServletApplication().getAppConfiguration().getDefaultUserTeam(),
null
);
projectSettings.clear();
@@ -268,7 +270,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
if (CommonUtils.isEmpty(configMap)) {
throw new DBWebException("Connection configuration parameters are missing");
}
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(getConnectionConfigInput(configMap));
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(configMap);
return addDataSourceToProject(newDataSource);
}
@@ -302,7 +304,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
DBPDataSourceRegistry registry = getDataSourceRegistry();
getInputConfigHandler(config).updateDataSource(dataSource);
getInputConfigHandler(configMap).updateDataSource(dataSource);
connectionInfo.setCredentialsSavedInSession(null);
try {
registry.updateDataSource(dataSource);
@@ -348,8 +350,8 @@ public class WebSessionProjectImpl extends WebProjectImpl {
}
@NotNull
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull WebConnectionConfig configInput) throws DBWebException {
return getInputConfigHandler(configInput).createDataSourceContainer();
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull Map<String, Object> configMap) throws DBWebException {
return getInputConfigHandler(configMap).createDataSourceContainer();
}
@NotNull
@@ -358,8 +360,8 @@ public class WebSessionProjectImpl extends WebProjectImpl {
}
@NotNull
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull WebConnectionConfig configInput) {
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), configInput);
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull Map<String, Object> configMap) {
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), getConnectionConfigInput(configMap), webSession);
}
@@ -74,8 +74,4 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
*/
default void postAuthentication() {}
@Override
default boolean supportsOpeningSessionAsChild() {
return true;
}
}
@@ -341,16 +341,6 @@ public class WebConnectionInfo {
if (CommonUtils.isEmpty(authModelId)) {
authModelId = AuthModelDatabaseNative.ID;
}
// Patch auth model
DBPAuthModelDescriptor authModel = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getAuthModel(authModelId);
if (authModel != null) {
DBPAuthModelDescriptor amReplace = authModel.getReplacedBy(dataSourceContainer.getDriver());
if (amReplace != null && amReplace != authModel) {
return amReplace.getId();
}
}
return authModelId;
}
@@ -23,23 +23,13 @@ import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
import picocli.CommandLine;
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
@Nullable
private final CommandLineAuthenticator authenticator;
private final CloudBeaverMixin mixin;
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
super();
this.authenticator = authenticator;
this.mixin = new CloudBeaverMixin();
}
public CloudBeaverCommandLine(
@Nullable CommandLineAuthenticator authenticator,
@NotNull CloudBeaverMixin mixin
) {
super();
this.authenticator = authenticator;
this.mixin = mixin;
}
@Override
@@ -61,8 +51,6 @@ public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationIn
@NotNull CommandLineContext context,
@NotNull CLIRunMeta runMeta
) {
var cmd = super.initCommandLine(applicationInstanceController, context, runMeta);
cmd.addMixin("cloudbeaver", mixin);
return cmd;
return super.initCommandLine(applicationInstanceController, context, runMeta);
}
}
@@ -32,7 +32,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
this.commandLine = new CloudBeaverCommandLine(null);
}
public CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
protected CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = commandLine;
}
@@ -1,32 +0,0 @@
/*
* 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.model.cli;
import picocli.CommandLine;
/**
* CloudBeaver specific mixin options
*/
public class CloudBeaverMixin {
@CommandLine.Option(
names = {"-web-config"},
arity = "1",
hidden = true,
scope = CommandLine.ScopeType.INHERIT
)
private String webConfig;
}
@@ -24,7 +24,7 @@ import org.jkiss.dbeaver.model.cli.CommandLineContext;
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
import picocli.CommandLine;
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands")
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands", mixinStandardHelpOptions = true)
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
protected CloudBeaverTopLevelCommand(
@Nullable ApplicationInstanceController controller,
@@ -1277,6 +1277,4 @@ public class LocalResourceController extends BaseLocalResourceController {
return project;
}
}
@@ -20,7 +20,6 @@ import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.impl.preferences.AbstractUserPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.util.Map;
@@ -86,13 +85,4 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
public void save() throws IOException {
throw new RuntimeException("Not implemented");
}
public boolean getUserPreferenceBoolean(@NotNull String name, boolean fallbackValue) {
String value = CommonUtils.toString(userPreferences.get(name));
if (value.isEmpty()) {
return fallbackValue;
} else {
return toBoolean(value);
}
}
}
@@ -17,18 +17,12 @@
package io.cloudbeaver.registry;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPConditionalProperty;
import org.jkiss.dbeaver.model.impl.LocalizedPropertyDescriptor;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.osgi.framework.Bundle;
public class WebAuthProviderProperty extends LocalizedPropertyDescriptor implements DBPConditionalProperty {
private static final String WEB_AUTH_PROPERTY_PREFIX = "prop.auth.model.";
private final String[] requiredFeatures;
@Nullable
private final String type;
@@ -36,7 +30,6 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
private final String authProviderId;
private final String hideExpr;
private final String readOnlyExpr;
private final Bundle bundle;
public WebAuthProviderProperty(String category, IConfigurationElement config, String authProviderId) {
super(category, config);
@@ -46,7 +39,6 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
this.type = config.getAttribute("type");
this.hideExpr = config.getAttribute("hideExpr");
this.readOnlyExpr = config.getAttribute("readOnlyExpr");
this.bundle = extractBundle(config);
}
@NotNull
@@ -71,34 +63,12 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
return readOnlyExpr;
}
@NotNull
@Override
public String getLocalizedName(@NotNull String locale) {
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId());
}
@Nullable
@Override
public String getLocalizedDescription(@NotNull String locale) {
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId() + "." + ATTR_DESCRIPTION);
}
private String getPropertyId() {
public String getPropertyId() {
if (authProviderId != null) {
return WEB_AUTH_PROPERTY_PREFIX + authProviderId + "." + this.getId();
return "prop.auth.model." + authProviderId + "." + this.getId();
} else {
return WEB_AUTH_PROPERTY_PREFIX + this.getId();
return "prop.auth.model." + this.getId();
}
}
@NotNull
private Bundle extractBundle(@NotNull IConfigurationElement config) {
final Bundle bundle;
String bundleName = config.getContributor().getName();
bundle = Platform.getBundle(bundleName);
if (bundle == null) {
throw new IllegalStateException("Bundle '" + bundleName + "' not found");
}
return bundle;
}
}
@@ -47,5 +47,5 @@ public class WebSQLConstants {
public static final String TASK_CONFIRMATION_ATTR_PREFIX = "session_task_confirmation_";
public static final String SKIP_TASK_CONFIRMATIONS_ATTR = "skip_session_task_confirmations";
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 60;
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 30;
}
@@ -21,7 +21,6 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
import org.jkiss.dbeaver.model.exec.trace.DBCTrace;
import org.jkiss.dbeaver.model.struct.*;
@@ -43,8 +42,6 @@ public class WebSQLResultsInfo {
// TODO: find a way to remove isSingleRow and use virtual keys for reading BLOB and string cell values.
private boolean isSingleRow;
private DBCTrace trace;
@Nullable
private DBDDataFilter dataFilter;
private String queryText;
public WebSQLResultsInfo(@NotNull DBSDataContainer dataContainer, @NotNull String id) {
@@ -154,13 +151,4 @@ public class WebSQLResultsInfo {
public void setSingleRow(boolean singleRow) {
isSingleRow = singleRow;
}
@Nullable
public DBDDataFilter getDataFilter() {
return dataFilter;
}
public void setDataFilter(@Nullable DBDDataFilter dataFilter) {
this.dataFilter = dataFilter;
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 25.3.0.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 25.2.4.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.135.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.133.qualifier
Bundle-Release-Date: 20251103
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.135-SNAPSHOT</version>
<version>1.0.133-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.3.0.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 25.2.4.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -17,6 +17,8 @@
package io.cloudbeaver.server;
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
import io.cloudbeaver.model.CBWebServerConfig;
import io.cloudbeaver.model.WebServerConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.rm.local.LocalResourceController;
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 25.3.0.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 25.2.4.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -158,6 +158,8 @@ type SQLExecuteInfo {
duration: Int!
# Actual conditions applied to query
filterText: String
# # original sql query without SQLDataFilter
# originalQuery: String
# Full query that was executed, contains all used filters
fullQuery: String
# Results
@@ -1,31 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a field or method as sensitive, so its value won't be logged.
*/
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface WebParameterSecure {
}
@@ -246,7 +246,14 @@ public class GraphQLEndpoint extends HttpServlet {
if (operationName != null) {
contextBuilder.operationName(operationName);
}
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
if (operationName != null) {
log.debug("API > " + operationName + loggerMessage);
} else if (DEBUG) {
log.debug("API > " + query + loggerMessage);
}
LocalDateTime startTime = LocalDateTime.now();
ExecutionInput executionInput = contextBuilder.build();
ExecutionResult executionResult = null;
@@ -16,27 +16,21 @@
*/
package io.cloudbeaver.server.graphql;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.WebParameterSecure;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.StringJoiner;
import java.util.Map;
import java.util.Set;
public class GraphQLLoggerUtil {
private static final Log log = Log.getLog(GraphQLLoggerUtil.class);
public static final String LOG_API_GRAPHQL_DEBUG_PARAMETER = "log.api.graphql.debug";
public static final Gson GSON = new GsonBuilder().create();
public static final String MASK_STRING = "****";
private static final Set<String> PROHIBITED_VARIABLES =
Set.of("password", "config", "parameters", "settings", "licenseText", "credentials", "username");
public static String getUserId(HttpServletRequest request) {
WebSession session = getWebSession(request);
@@ -69,67 +63,43 @@ public class GraphQLLoggerUtil {
.findWebSession(request);
}
public static String buildLoggerMessage(String sessionId, String userId, Method method, Object[] args) {
StringBuilder sb = new StringBuilder(64)
.append(" [user: ").append(userId)
.append(", sessionId: ").append(sessionId)
.append("]");
public static String buildLoggerMessage(String sessionId, String userId, Map<String, Object> variables) {
StringBuilder loggerMessage = new StringBuilder(" [user: ").append(userId)
.append(", sessionId: ").append(sessionId).append("]");
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)) {
sb.append('(');
String text = maskArgsToString(method, args);
if (CommonUtils.isNotEmpty(text)) {
sb.append(text);
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
&& variables != null
) {
loggerMessage.append(" [variables] ");
String parsedVariables = parseVarialbes(variables);
if (CommonUtils.isNotEmpty(parsedVariables)) {
loggerMessage.append(parseVarialbes(variables));
}
sb.append(')');
}
return sb.toString();
return loggerMessage.toString();
}
public static String maskArgsToString(Method method, Object[] args) {
Parameter[] params = method.getParameters();
if (params.length == 0 || args == null || args.length == 0) {
return "";
}
private static String parseVarialbes(Map<String, Object> map) {
StringBuilder result = new StringBuilder();
int limit = Math.min(args.length, params.length);
StringJoiner joiner = new StringJoiner(", ");
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
for (int i = 0; i < limit; i++) {
Object value = args[i];
if (value instanceof WebSession) {
//we already log sessionId
continue;
}
if (params[i].isAnnotationPresent(WebParameterSecure.class)) {
joiner.add(MASK_STRING);
continue;
}
if (value instanceof String sv && CommonUtils.isEmpty(sv)) {
boolean isProhibited = PROHIBITED_VARIABLES.stream()
.anyMatch(prohibitedKey -> key.toLowerCase().contains(prohibitedKey.toLowerCase()));
if (isProhibited) {
result.append(key).append(": ").append("******** ");
continue;
}
if (value != null && !isSimple(value.getClass())) {
String stringValue;
try {
stringValue = GSON.toJson(value);
} catch (Exception e) {
stringValue = value.toString();
}
joiner.add(stringValue);
if (value instanceof Map) {
result.append(parseVarialbes((Map<String, Object>) value));
} else {
joiner.add(String.valueOf(value));
result.append(key).append(": ").append(value).append(" ");
}
}
return joiner.toString();
return result.toString().trim();
}
private static boolean isSimple(Class<?> cls) {
return cls.isPrimitive()
|| Number.class.isAssignableFrom(cls)
|| CharSequence.class.isAssignableFrom(cls)
|| Boolean.class.equals(cls)
|| Enum.class.isAssignableFrom(cls);
}
}
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.service;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
@@ -28,11 +27,9 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionProvider;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.graphql.GraphQLLoggerUtil;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -164,7 +161,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
@NotNull
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @Nullable String projectId, @NotNull String connectionId) throws DBWebException {
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @NotNull String projectId, @NotNull String connectionId) throws DBWebException {
return WebDataSourceUtils.getWebConnectionInfo(session, projectId, connectionId);
}
@@ -343,27 +340,16 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
}
}
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
GraphQLContext graphQlContext = this.env.getGraphQlContext();
HttpServletRequest request = graphQlContext.get("request");
if (request == null) {
return;
}
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, method, args);
}
log.debug("API > " + method.getName() + loggerMessage);
setLogContext(method, args);
}
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
Log.setContext(null);
}
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
setLogContext(method, args);
}
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
Log.setContext(null);
}
protected void setLogContext(Method method, @Nullable Object[] args) {
@@ -16,7 +16,10 @@
*/
package io.cloudbeaver.service.core;
import io.cloudbeaver.*;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.WebProjectAction;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
@@ -116,7 +119,7 @@ public interface DBWServiceCore extends DBWService {
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId,
@WebParameterSecure @Nullable Map<String, Object> authProperties,
@Nullable Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
boolean saveCredentials,
boolean sharedCredentials,
@@ -127,7 +130,7 @@ public interface DBWServiceCore extends DBWService {
WebConnectionInfo createConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@WebParameterSecure @NotNull Map<String, Object> connectionConfig
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
@@ -496,8 +496,6 @@ public class WebServiceCore implements DBWServiceCore {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
WebConnectionConfig configInput = project.getConnectionConfigInput(connectionConfig);
configInput.setSaveCredentials(true); // It is used in createConnectionFromConfig
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
webSession, projectId, configInput.getConnectionId());
@@ -537,7 +535,7 @@ public class WebServiceCore implements DBWServiceCore {
true
);
} else {
testDataSource = project.getDataSourceContainerFromInput(configInput);
testDataSource = project.getDataSourceContainerFromInput(connectionConfig);
}
validateDriverLibrariesPresence(testDataSource);
webSession.provideAuthParameters(
@@ -17,7 +17,10 @@
package io.cloudbeaver.service.navigator.impl;
import io.cloudbeaver.*;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebCommandContext;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.rm.DBNAbstractResourceManagerNode;
@@ -108,14 +111,6 @@ public class WebServiceNavigator implements DBWServiceNavigator {
Set<String> nodeIds = new HashSet<>(); // filter duplicate node ids
for (DBNNode node : nodeChildren) {
// TODO: find a better way to filter out empty folder nodes for non-admin users
if (node instanceof DBNLocalFolder localFolderNode &&
localFolderNode.getDataSourceRegistry() instanceof WebGlobalProjectRegistryProxy globalProjectRegistryProxy
) {
if (!globalProjectRegistryProxy.getAllFolders().contains(localFolderNode.getFolder())) {
continue;
}
}
if (node instanceof DBNDatabaseFolder folderNode && CommonUtils.isEmpty(folderNode.getMeta().getChildren(null))) {
// Skip empty folders. Folder may become empty if their nested elements are provided by UI plugins.
continue;
@@ -69,7 +69,7 @@ public class WebSQLContextInfo implements WebSessionProvider {
private final transient WebSQLProcessor processor;
private final String id;
private final String projectId;
private final transient Map<String, WebSQLResultsInfo> resultInfoMap = new HashMap<>();
private final Map<String, WebSQLResultsInfo> resultInfoMap = new HashMap<>();
private final AtomicInteger resultId = new AtomicInteger();
@@ -71,7 +71,7 @@ public class WebSQLExecuteInfo {
@Property
public String getOriginalQuery() {
//TODO implement
return null; // should return the original query without data filters
return null;
}
@Property
@@ -20,7 +20,6 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionPreferenceStore;
import io.cloudbeaver.model.session.WebSessionProvider;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.jobs.SqlOutputLogReaderJob;
@@ -63,6 +62,7 @@ import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -231,7 +231,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (useEvents) {
boolean isConfirmed = confirmQueryIfNeeded(mainQuery.getScriptElements(), asyncTask, isGenerated);
if (!isConfirmed) {
throw new DBWebException("Query execution was cancelled by user");
throw new DBWebException("Query execution cancelled by user");
}
}
@@ -292,6 +292,8 @@ public class WebSQLProcessor implements WebSessionProvider {
sqlOutputLogReaderJob.join();
}
fillQueryResults(contextInfo, dataContainer, dbStat, hasResultSet, executeInfo, webDataFilter, dataFilter, dataFormat);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
}
}
@@ -333,9 +335,7 @@ public class WebSQLProcessor implements WebSessionProvider {
DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = executionContext.openSession(monitor, resolveQueryPurpose(dataFilter), "Read data from container")) {
try (
WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat, dataFilter)
) {
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
DBCStatistics statistics = dataContainer.readData(
new WebExecutionSource(dataContainer, executionContext, this),
session,
@@ -352,12 +352,14 @@ public class WebSQLProcessor implements WebSessionProvider {
results.setResultSet(resultSet);
executeInfo.setResults(new WebSQLQueryResults[]{results});
setResultFilterText(session.getDataSource(), executeInfo, dataFilter);
setResultFilterText(dataContainer, session.getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(statistics.getQueryText());
if (resultSet != null && resultSet.getRowsWithMetaData() != null && resultSet.getResultsInfo() != null) {
resultSet.getResultsInfo().setQueryText(statistics.getQueryText());
executeInfo.setStatusMessage(resultSet.getRowsWithMetaData().size() + " row(s) fetched");
}
} catch (DBException e) {
throw new InvocationTargetException(e);
}
}
});
@@ -397,7 +399,7 @@ public class WebSQLProcessor implements WebSessionProvider {
for (var rowIdentifier : rowIdentifierList) {
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, keyReceiver);
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver);
DBCExecutionContext executionContext = getExecutionContext(dataManipulator);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) {
@@ -498,7 +500,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull Set<WebSQLQueryResultSetRow> newResultSetRows,
@Nullable WebDataFormat dataFormat,
@NotNull DBRProgressMonitor monitor)
throws DBException {
throws DBCException {
try (DBCSession session = getExecutionContext().openSession(
monitor,
DBCExecutionPurpose.UTIL,
@@ -574,8 +576,9 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull String resultsId,
@Nullable List<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows
) throws DBException {
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat) throws DBException
{
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
@@ -591,7 +594,7 @@ public class WebSQLProcessor implements WebSessionProvider {
StringBuilder sqlBuilder = new StringBuilder();
for (var rowIdentifier : rowIdentifierList) {
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, null);
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null);
List<DBEPersistAction> actions = new ArrayList<>();
@@ -617,6 +620,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@Nullable List<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat,
@NotNull Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches,
@Nullable DBDDataReceiver keyReceiver)
throws DBException
@@ -876,6 +880,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull
public WebSQLExecutionPlan explainExecutionPlan(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> configuration) throws DBWebException {
@@ -900,6 +905,8 @@ public class WebSQLProcessor implements WebSessionProvider {
DBCQueryPlannerConfiguration planConfig = new DBCQueryPlannerConfiguration();
planConfig.getParameters().putAll(configuration);
dbcPlan[0] = planner.planQueryExecution(session, sql, planConfig);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
});
} catch (DBException e) {
@@ -939,14 +946,16 @@ public class WebSQLProcessor implements WebSessionProvider {
private void readCellDataValue(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull WebSQLResultsRow row,
@Nullable WebSQLResultsRow row,
@NotNull WebSQLCellValueReceiver dataReceiver
) throws DBException {
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Generate data update batches")) {
DBDDataFilter dataFilter = new DBDDataFilter();
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
if (!resultsInfo.isSingleRow()) {
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
}
WebExecutionSource executionSource = new WebExecutionSource(dataContainer, executionContext, this);
dataContainer.readData(
executionSource, session, dataReceiver, dataFilter,
@@ -956,16 +965,12 @@ public class WebSQLProcessor implements WebSessionProvider {
private void addKeyAttributes(
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull WebSQLResultsRow row,
@Nullable WebSQLResultsRow row,
@NotNull DBSDataContainer dataContainer,
@NotNull DBCSession session,
@NotNull DBDDataFilter dataFilter
) throws DBException {
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
if (rowIdentifier == null || rowIdentifier.isIncomplete()) {
return;
}
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
DBDAttributeBinding[] keyAttributes = resultsInfo.getDefaultRowIdentifier().getAttributes().toArray(new DBDAttributeBinding[0]);
Object[] rowValues = new Object[keyAttributes.length];
List<DBDAttributeConstraint> constraints = new ArrayList<>();
for (int i = 0; i < keyAttributes.length; i++) {
@@ -1069,16 +1074,10 @@ public class WebSQLProcessor implements WebSessionProvider {
if (resultSet == null) {
break;
}
try (
WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(
contextInfo,
dataContainer,
dataFormat,
dataFilter
)
) {
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
readResultSet(dbStat.getSession(), resultSet, webDataFilter, dataReceiver);
results.setResultSet(dataReceiver.getResultSet());
dataReceiver.getResultSet().getResultsInfo().setQueryText(resultSet.getSourceStatement().getQueryString());
}
}
resultList.add(results);
@@ -1098,15 +1097,11 @@ public class WebSQLProcessor implements WebSessionProvider {
}
executeInfo.setResults(resultList.toArray(new WebSQLQueryResults[0]));
setResultFilterText(dbStat.getSession().getDataSource(), executeInfo, dataFilter);
setResultFilterText(dataContainer, dbStat.getSession().getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(dbStat.getQueryString());
}
private void setResultFilterText(
@NotNull DBPDataSource dataSource,
@NotNull WebSQLExecuteInfo executeInfo,
@NotNull DBDDataFilter filter
) throws DBException {
private void setResultFilterText(@NotNull DBSDataContainer dataContainer, @NotNull DBPDataSource dataSource, @NotNull WebSQLExecuteInfo executeInfo, @NotNull DBDDataFilter filter) throws DBException {
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
StringBuilder where = new StringBuilder();
SQLUtils.appendConditionString(
@@ -1119,13 +1114,8 @@ public class WebSQLProcessor implements WebSessionProvider {
}
}
private void readResultSet(
@NotNull DBCSession session,
@NotNull DBCResultSet dbResult,
@NotNull WebSQLDataFilter filter,
@NotNull WebSQLQueryDataReceiver dataReceiver
) throws DBException {
DBDDataReceiver.startFetchWorkflow(dataReceiver, session, dbResult, filter.getOffset(), filter.getLimit());
private void readResultSet(@NotNull DBCSession session, @NotNull DBCResultSet dbResult, @NotNull WebSQLDataFilter filter, @NotNull WebSQLQueryDataReceiver dataReceiver) throws DBCException {
dataReceiver.fetchStart(session, dbResult, filter.getOffset(), filter.getLimit());
int rowCount = 0;
while (dbResult.nextRow()) {
if (rowCount > filter.getLimit()) {
@@ -1135,6 +1125,7 @@ public class WebSQLProcessor implements WebSessionProvider {
dataReceiver.fetchRow(session, dbResult);
rowCount++;
}
dataReceiver.fetchEnd(session, dbResult);
}
/**
@@ -1172,6 +1163,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (keyValue == null) {
continue;
}
boolean updated = false;
if (!CommonUtils.isEmpty(keyAttribute.getName())) {
DBDAttributeBinding binding = DBUtils.findObject(resultsAttributes, keyAttribute.getName());
if (binding != null) {
@@ -1242,7 +1234,8 @@ public class WebSQLProcessor implements WebSessionProvider {
private Object setCellRowValue(Object cellRow, WebSession webSession, DBCSession dbcSession, DBDAttributeBinding allAttributes, boolean withoutExecution)
throws DBException {
if (cellRow instanceof Map<?, ?> variables) {
if (cellRow instanceof Map<?, ?>) {
Map<String, Object> variables = (Map<String, Object>) cellRow;
if (variables.get(FILE_ID) != null) {
Path path = WebAppUtils.getWebPlatform()
.getTempFolder(webSession.getProgressMonitor(), TEMP_FILE_FOLDER)
@@ -1260,6 +1253,7 @@ public class WebSQLProcessor implements WebSessionProvider {
return convertInputCellValue(dbcSession, allAttributes, cellRow, withoutExecution);
}
// TODO: Refactor to unify with desktop when confirmation settings will be added
private boolean confirmQueryIfNeeded(
@NotNull List<SQLScriptElement> scriptElements,
@NotNull WebAsyncTaskInfo asyncTask,
@@ -1287,28 +1281,25 @@ public class WebSQLProcessor implements WebSessionProvider {
.map(SQLScriptElement::getText)
.collect(Collectors.joining("\n\n"));
} else {
WebSessionPreferenceStore store = webSession.getUserPreferenceStore();
boolean confirmDangerousQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DANGER_SQL_KEY, true);
boolean confirmDropQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DROP_SQL_KEY, true);
for (SQLScriptElement scriptElement : scriptElements) {
if (scriptElement instanceof SQLQuery sqlQuery) {
if (confirmDangerousQueries && sqlQuery.isDeleteUpdateDangerous()) {
if (sqlQuery.isDeleteUpdateDangerous()) {
hasDangerousUpdates = true;
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL_ID);
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL);
title = descriptor.getLocalizedTitle(webSession.getLocale());
var entityMetadata = sqlQuery.getEntityMetadata(false);
message = MessageFormat.format(
descriptor.getLocalizedMessage(webSession.getLocale()),
sqlQuery.getType().name(),
entityMetadata != null ? entityMetadata.getEntityName() : "multiple tables"
entityMetadata != null ? entityMetadata.getEntityName() : null
);
break;
}
if (confirmDropQueries && sqlQuery.isDropTableDangerous()) {
if (sqlQuery.isDropTableDangerous()) {
hasDropStatement = true;
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL_ID);
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL);
title = descriptor.getLocalizedTitle(webSession.getLocale());
message = descriptor.getLocalizedMessage(webSession.getLocale());
break;
@@ -104,25 +104,14 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
long maxRows,
long flags,
int fetchSize
) throws DBException {
) throws DBCException {
return queryDataContainer.readData(source, session, dataReceiver, dataFilter, firstRow, maxRows, flags, fetchSize);
}
@Override
public long countData(@NotNull DBCExecutionSource source, @NotNull DBCSession session, @Nullable DBDDataFilter dataFilter, long flags) throws DBCException {
try {
String filteredQuery = dataFilter == null ? query : getDataSource().getSQLDialect().addFiltersToQuery(
session.getProgressMonitor(),
getDataSource(),
query,
dataFilter
);
SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery(
dataSource,
syntaxManager,
new SQLQuery(dataSource, filteredQuery)
);
SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery(dataSource, syntaxManager, new SQLQuery(dataSource, query));
return DBUtils.countDataFromQuery(source, session, countQuery);
} catch (DBException e) {
throw new DBCException("Error executing row count", e);
@@ -19,7 +19,6 @@ package io.cloudbeaver.service.sql;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
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.DBPDataKind;
@@ -42,14 +41,9 @@ import java.util.stream.Collectors;
class WebSQLQueryDataReceiver implements DBDDataReceiver {
private static final Log log = Log.getLog(WebSQLQueryDataReceiver.class);
@NotNull
private final WebSQLContextInfo contextInfo;
@NotNull
private final DBSDataContainer dataContainer;
@Nullable
private final WebDataFormat dataFormat;
@Nullable
private final DBDDataFilter dataFilter;
private final WebSQLQueryResultSet webResultSet = new WebSQLQueryResultSet();
private DBDAttributeBinding[] bindings;
@@ -57,16 +51,10 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
private List<WebSQLQueryResultSetRow> rows = new ArrayList<>();
private final Number rowLimit;
WebSQLQueryDataReceiver(
@NotNull WebSQLContextInfo contextInfo,
@NotNull DBSDataContainer dataContainer,
@Nullable WebDataFormat dataFormat,
@Nullable DBDDataFilter dataFilter
) {
WebSQLQueryDataReceiver(WebSQLContextInfo contextInfo, DBSDataContainer dataContainer, WebDataFormat dataFormat) {
this.contextInfo = contextInfo;
this.dataContainer = dataContainer;
this.dataFormat = dataFormat;
this.dataFilter = dataFilter;
rowLimit = ServletAppUtils.getServletApplication()
.getAppConfiguration()
.getResourceQuota(WebSQLConstants.QUOTA_PROP_ROW_LIMIT);
@@ -174,8 +162,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
webResultSet.setReadOnlyInfo(contextInfo.getProcessor().getExecutionContext());
WebSQLResultsInfo resultsInfo = contextInfo.saveResult(dataContainer, trace, bindings, rows.size() == 1);
resultsInfo.setDataFilter(dataFilter);
resultsInfo.setQueryText(resultSet.getSourceStatement().getQueryString());
webResultSet.setResultsInfo(resultsInfo);
boolean isSingleEntity = DBExecUtils.detectSingleSourceTable(bindings) != null;
@@ -246,6 +232,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
@Override
public void close() {
// no-op
rows.clear();
}
}
@@ -110,7 +110,7 @@ public class WebServiceSQL implements DBWServiceSQL {
@Override
@NotNull
public WebSQLDialectInfo getDialectInfo(@NotNull WebConnectionInfo connectionInfo) {
public WebSQLDialectInfo getDialectInfo(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
SQLDialect dialect = getSqlDialectFromConnection(dataSourceContainer);
return new WebSQLDialectInfo(dataSourceContainer.getDataSource(), dialect);
@@ -400,16 +400,21 @@ public class WebServiceSQL implements DBWServiceSQL {
DBExecUtils.tryExecuteRecover(
monitor,
contextInfo.getProcessor().getConnection().getDataSource(),
monitor1 ->
result[0] = contextInfo.getProcessor().updateResultsDataBatch(
monitor1, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat)
monitor1 -> {
try {
result[0] = contextInfo.getProcessor().updateResultsDataBatch(
monitor1, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
);
return result[0];
}
@FunctionalInterface
private interface ThrowableFunction<T, R> {
R apply(T obj) throws DBException;
R apply(T obj) throws Exception;
}
@Override
@@ -432,6 +437,9 @@ public class WebServiceSQL implements DBWServiceSQL {
@NotNull Integer lobColumnIndex,
@NotNull WebSQLResultsRow row
) throws DBWebException {
if (row == null) {
throw new DBWebException("Results row is not found");
}
WebSQLProcessor processor = contextInfo.getProcessor();
ThrowableFunction<DBRProgressMonitor, String> function = monitor -> processor.readStringValue(
monitor, contextInfo, resultsId, lobColumnIndex, row);
@@ -449,7 +457,13 @@ public class WebServiceSQL implements DBWServiceSQL {
DBExecUtils.tryExecuteRecover(
processor.getWebSession().getProgressMonitor(),
processor.getConnection().getDataSource(),
monitor -> result.append(function.apply(monitor))
monitor -> {
try {
result.append(function.apply(monitor));
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
);
return result.toString();
} catch (DBException e) {
@@ -462,8 +476,7 @@ public class WebServiceSQL implements DBWServiceSQL {
try {
return contextInfo.getProcessor().generateResultsDataUpdateScript(
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
contextInfo, resultsId, updatedRows, deletedRows, addedRows
);
contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat);
} catch (DBException e) {
throw new DBWebException("Error genering update script", e);
}
@@ -503,11 +516,10 @@ public class WebServiceSQL implements DBWServiceSQL {
@NotNull String nodePath,
@Nullable String resultId,
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat
) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Nullable WebDataFormat dataFormat) throws DBWebException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Read data", 1);
monitor.subTask("Extra data from " + nodePath);
@@ -521,8 +533,7 @@ public class WebServiceSQL implements DBWServiceSQL {
dataContainer,
resultId,
filter != null ? filter : new WebSQLDataFilter(),
dataFormat
);
dataFormat);
this.result = executeResults.getStatusMessage();
this.extendedResults = executeResults;
} catch (Throwable e) {
@@ -553,25 +564,24 @@ public class WebServiceSQL implements DBWServiceSQL {
@Override
public WebSQLExecuteInfo asyncGetQueryResults(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return (WebSQLExecuteInfo) taskStatus.getExtendedResult();
if (taskStatus != null) {
return (WebSQLExecuteInfo) taskStatus.getExtendedResult();
}
return null;
}
////////////////////////////////////////////////////
// Explain plan
@Override
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> configuration
) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(@NotNull WebSQLContextInfo contextInfo, @NotNull String sql, @NotNull Map<String, Object> configuration) throws DBException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Explain execution plan", 1);
monitor.subTask("Explain query [" + sql + "] execution plan");
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, sql, configuration);
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, contextInfo, sql, configuration);
this.result = "Execution plan explain has been scheduled";
this.extendedResults = executeResults;
} catch (Throwable e) {
@@ -587,11 +597,14 @@ public class WebServiceSQL implements DBWServiceSQL {
@Override
public WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
if (taskStatus != null) {
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
}
return null;
}
@Override
public WebSQLScriptInfo parseSqlScript(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript) {
public WebSQLScriptInfo parseSqlScript(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript) throws DBWebException {
SQLDialect dialect = getSqlDialectFromConnection(connectionInfo.getDataSourceContainer());
List<SQLScriptElement> queries = SQLScriptParser.parseScript(
connectionInfo.getDataSource(),
@@ -605,7 +618,7 @@ public class WebServiceSQL implements DBWServiceSQL {
}
@Override
public WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) {
public WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) throws DBWebException {
SQLDialect dialect = getSqlDialectFromConnection(connectionInfo.getDataSourceContainer());
SQLScriptElement query = SQLScriptParser.parseQuery(
connectionInfo.getDataSource(),
@@ -648,20 +661,14 @@ public class WebServiceSQL implements DBWServiceSQL {
}
@Override
public WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
public WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) throws DBWebException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Get row data count", 1);
WebSQLResultsInfo results = contextInfo.getResults(resultsId);
long rowCount = DBUtils.readRowCount(
monitor,
contextInfo.getProcessor().getExecutionContext(),
results.getDataContainer(),
results.getDataFilter(),
this
);
long rowCount = DBUtils.readRowCount(monitor, contextInfo.getProcessor().getExecutionContext(), results.getDataContainer(), null, this);
this.result = "Row data count completed";
this.extendedResults = rowCount;
} catch (Throwable e) {
@@ -678,16 +685,19 @@ public class WebServiceSQL implements DBWServiceSQL {
@Nullable
public Long getRowDataCountResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return (Long) taskStatus.getExtendedResult();
if (taskStatus != null) {
return (Long) taskStatus.getExtendedResult();
}
return null;
}
@Override
public WebAsyncTaskInfo asyncSqlSetAutoCommit(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, boolean autoCommit) {
public WebAsyncTaskInfo asyncSqlSetAutoCommit(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, boolean autoCommit) throws DBWebException {
return contextInfo.setAutoCommit(autoCommit);
}
@Override
public WebAsyncTaskInfo asyncSqlRollbackTransaction(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo) {
public WebAsyncTaskInfo asyncSqlRollbackTransaction(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo) throws DBWebException {
return contextInfo.rollbackTransaction();
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.134.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.132.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.134-SNAPSHOT</version>
<version>1.0.132-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -16,7 +16,10 @@
*/
package io.cloudbeaver.service.admin;
import io.cloudbeaver.*;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWFeatureSet;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebAuthProviderConfiguration;
@@ -113,7 +116,7 @@ public interface DBWServiceAdmin extends DBWService {
@NotNull WebSession webSession,
@NotNull String userID,
@NotNull String providerId,
@WebParameterSecure @NotNull Map<String, Object> credentials
@NotNull Map<String, Object> credentials
) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@@ -153,7 +156,7 @@ public interface DBWServiceAdmin extends DBWService {
boolean disabled,
@Nullable String iconURL,
@Nullable String description,
@WebParameterSecure @Nullable Map<String, Object> parameters) throws DBWebException;
@Nullable Map<String, Object> parameters) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteAuthProviderConfiguration(@NotNull WebSession webSession, @NotNull String id) throws DBWebException;
@@ -16,7 +16,10 @@
*/
package io.cloudbeaver.service.admin.impl;
import io.cloudbeaver.*;
import io.cloudbeaver.DBWFeatureSet;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.config.CBAppConfig;
@@ -360,11 +363,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
@Override
public boolean setUserCredentials(@NotNull WebSession webSession,
@NotNull String userID,
@NotNull String providerId,
@WebParameterSecure @NotNull Map<String, Object> credentials
) throws DBWebException {
public boolean setUserCredentials(@NotNull WebSession webSession, @NotNull String userID, @NotNull String providerId, @NotNull Map<String, Object> credentials) throws DBWebException {
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(providerId);
if (authProvider == null) {
throw new DBWebException("Invalid auth provider '" + providerId + "'");
@@ -666,6 +665,9 @@ public class WebServiceAdmin implements DBWServiceAdmin {
if (configurationMode) {
// In config mode we always refresh because admin user doesn't exist yet
webSession.resetUserState();
} else {
// Just reload session state
webSession.refreshUserData();
}
WebAppUtils.getWebApplication().getDriverRegistry().refreshApplicableDrivers();
} catch (Throwable e) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.134.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.132.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.134-SNAPSHOT</version>
<version>1.0.132-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -18,7 +18,6 @@ package io.cloudbeaver.service.auth;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.WebParameterSecure;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
@@ -78,11 +77,7 @@ public interface DBWServiceAuth extends DBWService {
WebAuthProviderInfo[] getAuthProviders(@NotNull HttpServletRequest request) throws DBWebException;
@WebAction()
boolean changeLocalPassword(
@NotNull WebSession webSession,
@WebParameterSecure @NotNull String oldPassword,
@WebParameterSecure @NotNull String newPassword
) throws DBWebException;
boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException;
@WebAction(authRequired = false)
WebPropertyInfo[] listUserProfileProperties(@NotNull WebSession webSession);
@@ -71,13 +71,8 @@ public class WebAsyncAuthJob extends AbstractJob implements CustomCancelableJob
@Override
public void cancelJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo) {
cancelJob(webSession, taskInfo, "Canceled by the user");
}
public void cancelJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo, @NotNull String errorMessage) {
taskInfo.setRunning(false);
taskInfo.setJobError(new DBException(errorMessage));
taskInfo.setJobError(new DBException("Canceled by the user"));
WebEventUtils.sendAsyncTaskEvent(webSession, taskInfo);
}
}
@@ -1,51 +0,0 @@
/*
* 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.auth;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.session.WebSession;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.jkiss.code.NotNull;
public class WebAsyncAuthTimeoutJob extends Job {
@NotNull
private final WebSession webSession;
@NotNull
private final WebAsyncTaskInfo asyncInfo;
@NotNull
private final WebAsyncAuthJob authJob;
public WebAsyncAuthTimeoutJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo asyncInfo, @NotNull WebAsyncAuthJob authJob) {
super("Auth timeout job for " + asyncInfo.getName());
this.webSession = webSession;
this.asyncInfo = asyncInfo;
this.authJob = authJob;
}
@NotNull
@Override
protected IStatus run(@NotNull IProgressMonitor monitor) {
if (asyncInfo.isRunning()) {
authJob.cancelJob(webSession, asyncInfo, "Authentication timeout exceeded");
}
return Status.OK_STATUS;
}
}
@@ -62,7 +62,7 @@ import java.util.Map;
public class WebServiceAuthImpl implements DBWServiceAuth {
private static final Log log = Log.getLog(WebServiceAuthImpl.class);
private static final long DEFAULT_TIMEOUT_MILLISECONDS = 5 * 60 * 1000;
public static final String CONFIG_TEMP_ADMIN_USER_ID = "temp_config_admin";
@Override
public WebAuthStatus authLogin(
@@ -120,15 +120,11 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
if (CommonUtils.isEmpty(smAuthInfo.getRedirectUrl())) {
throw new DBWebException("Missing redirect URL");
}
WebAsyncAuthJob job = new WebAsyncAuthJob(
providerId + " authentication job",
smAuthInfo.getAuthAttemptId(),
linkWithActiveUser
);
WebAsyncTaskInfo authTask = webSession.createAsyncTask(providerId + " authentication");
authTask.setRunning(true);
authTask.setJob(job);
new WebAsyncAuthTimeoutJob(webSession, authTask, job).schedule(DEFAULT_TIMEOUT_MILLISECONDS);
authTask.setJob(
new WebAsyncAuthJob(providerId + " authentication job", smAuthInfo.getAuthAttemptId(), linkWithActiveUser)
);
return new WebAsyncAuthStatus(smAuthInfo.getRedirectUrl(), authTask);
} catch (SMTooManySessionsException e) {
throw new DBWebException("User authentication failed", e.getErrorType(), e);
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.135.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.133.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.135-SNAPSHOT</version>
<version>1.0.133-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -61,7 +61,7 @@ public interface DBWServiceDataTransfer extends DBWService {
@NotNull WebSQLResultsInfo webSQLResultsInfo,
@NotNull WebSession webSession) throws DBWebException;
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_EXPORT)
@WebAction(requireGlobalPermissions = RMConstants.GLOBAL_PERMISSION_DATA_EDITOR_EXPORT)
WebAsyncTaskInfo dataTransferExportDataFromResults(
WebSQLContextInfo sqlContextInfo,
String resultsId,
@@ -77,7 +77,7 @@ public class WebDataTransferImportServlet extends WebServiceServletBase {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied");
return;
}
if (!session.hasGlobalPermission(DBWConstants.GLOBAL_PERMISSION_DATA_EDITOR_IMPORT)) {
if (!session.hasPermission(DBWConstants.GLOBAL_PERMISSION_DATA_EDITOR_IMPORT)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Import is not allowed for this user");
return;
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
Bundle-Version: 1.0.52.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.52-SNAPSHOT</version>
<version>1.0.50-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - LDAP
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.ldap.auth;singleton:=true
Bundle-Version: 1.0.17.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.15.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
@@ -11,7 +11,7 @@
<configuration>
<propertyGroup label="LDAP" description="LDAP authentication">
<property id="ldap-host" label="%ldap-host" type="string" description="%ldap-host.description" required="true"/>
<property id="ldap-port" label="%ldap-port" type="string" defaultValue="389" required="true" description="%ldap-port.description"/>
<property id="ldap-port" label="%ldap-port" type="integer" defaultValue="389" required="true" description="%ldap-port.description"/>
<property id="ldap-identifier-attr" label="%ldap-identifier-attr"
defaultValue="cn"
required="true"
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.ldap.auth</artifactId>
<version>1.0.17-SNAPSHOT</version>
<version>1.0.15-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -39,7 +39,6 @@ import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.utils.CommonUtils;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
@@ -49,8 +48,6 @@ import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
@@ -309,22 +306,18 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
@NotNull
private String findUserNameFromDN(@NotNull String fullUserDN, @NotNull LdapSettings ldapSettings)
throws DBException {
try {
LdapName ldapDN = new LdapName(fullUserDN);
for (Rdn rdn : ldapDN.getRdns()) {
if (rdn.getType().equalsIgnoreCase(ldapSettings.getUserIdentifierAttr())) {
Object v = rdn.getValue();
if (v instanceof byte[]) {
return new String((byte[]) v, StandardCharsets.UTF_8);
}
return String.valueOf(v);
}
throws DBException {
String userId = null;
for (String dn : fullUserDN.split(",")) {
if (dn.startsWith(ldapSettings.getUserIdentifierAttr() + "=")) {
userId = dn.split("=")[1];
break;
}
throw new DBException("Failed to determine userId from user DN: " + fullUserDN);
} catch (Exception e) {
throw new DBException("Invalid user DN: " + fullUserDN, e);
}
if (userId == null) {
throw new DBException("Failed to determine userId from user DN: " + fullUserDN);
}
return userId;
}
@NotNull
@@ -418,10 +411,10 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
private String buildFullUserDN(String userName, LdapSettings ldapSettings) {
String fullUserDN = userName;
if (!CommonUtils.startsWithIgnoreCase(fullUserDN, ldapSettings.getUserIdentifierAttr())) {
if (!fullUserDN.startsWith(ldapSettings.getUserIdentifierAttr())) {
fullUserDN = String.join("=", ldapSettings.getUserIdentifierAttr(), userName);
}
if (CommonUtils.isNotEmpty(ldapSettings.getBaseDN()) && !CommonUtils.endsWithIgnoreCase(fullUserDN, ldapSettings.getBaseDN())) {
if (CommonUtils.isNotEmpty(ldapSettings.getBaseDN()) && !fullUserDN.endsWith(ldapSettings.getBaseDN())) {
fullUserDN = String.join(",", fullUserDN, ldapSettings.getBaseDN());
}
@@ -533,7 +526,8 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
@NotNull
private List<String> getGroupForMember(String fullDN, LdapSettings ldapSettings, Map<String, Object> authParameters) {
DirContext context = null;
Set<String> result = new LinkedHashSet<>();
NamingEnumeration<SearchResult> searchResults = null;
List<String> result = new ArrayList<>();
try {
Map<String, String> environment = creteAuthEnvironment(ldapSettings);
if (CommonUtils.isEmpty(ldapSettings.getBindUserDN())) {
@@ -553,12 +547,20 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
}
context = initConnection(environment);
List<String> groupsByMemberOfAttribute = findGroupsByMemberOfAttribute(fullDN, context);
log.debug("Found " + groupsByMemberOfAttribute.size() + " groups by memberOf attribute");
result.addAll(groupsByMemberOfAttribute);
List<String> groupsByMemberAttribute = findGroupsByMemberAttribute(fullDN, ldapSettings, context);
log.debug("Found " + groupsByMemberAttribute.size() + " groups by member attribute");
result.addAll(groupsByMemberAttribute);
String searchFilter = "(member=" + fullDN + ")";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchResults = context.search(ldapSettings.getBaseDN(), searchFilter, searchControls);
while (searchResults.hasMore()) {
try {
SearchResult next = searchResults.next();
result.add(next.getName());
} catch (Exception e) {
log.error("Failed fetch user group. Skipping...", e);
}
}
} catch (Exception e) {
log.error("Group not found", e);
} finally {
@@ -566,70 +568,13 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
if (context != null) {
context.close();
}
if (searchResults != null) {
searchResults.close();
}
} catch (Exception e) {
log.error("Close resource of ldap group search failed", e);
}
}
return new ArrayList<>(result);
}
private List<String> findGroupsByMemberOfAttribute(String fullDN, DirContext context) throws NamingException {
List<String> result = new ArrayList<>();
SearchControls memberOfSearch = new SearchControls();
memberOfSearch.setSearchScope(SearchControls.OBJECT_SCOPE);
memberOfSearch.setTimeLimit(30_000);
memberOfSearch.setReturningAttributes(new String[] {"*", "+"});
NamingEnumeration<SearchResult> userRecord = context.search(fullDN, "(objectClass=*)", memberOfSearch);
try {
if (userRecord.hasMore()) {
SearchResult userResult = userRecord.next();
Attributes userAttributes = userResult.getAttributes();
Attribute memberOfAttr = userAttributes.get("memberOf");
if (memberOfAttr != null) {
NamingEnumeration<?> groups = memberOfAttr.getAll();
while (groups.hasMore()) {
String groupDN = String.valueOf(groups.next());
result.add(groupDN);
}
}
}
} finally {
if (userRecord != null) {
userRecord.close();
}
}
return result;
}
private List<String> findGroupsByMemberAttribute(String fullDN, LdapSettings ldapSettings, DirContext context) throws NamingException {
List<String> result = new ArrayList<>();
String searchFilter = "(member={0})";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> searchResults = context.search(
ldapSettings.getBaseDN(),
searchFilter,
new Object[] {fullDN},
searchControls
);
try {
while (searchResults.hasMore()) {
try {
SearchResult next = searchResults.next();
//add full dn
result.add(next.getNameInNamespace());
//add relative dn to base dn
result.add(next.getName());
} catch (Exception e) {
log.error("Failed fetch user group. Skipping...", e);
}
}
} finally {
if (searchResults != null) {
searchResults.close();
}
}
return result;
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.138.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.136.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.138-SNAPSHOT</version>
<version>1.0.136-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Resource manager NIO implementation
Bundle-SymbolicName: io.cloudbeaver.service.rm.nio;singleton:=true
Bundle-Version: 1.0.52.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.model,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
<version>1.0.52-SNAPSHOT</version>
<version>1.0.50-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
Bundle-Version: 1.0.87.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.85.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.87-SNAPSHOT</version>
<version>1.0.85-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -69,6 +69,9 @@ extend type Query {
"Returns project settings that are specified for a current user"
rmUserProjectSettings(projectId: ID!): Object @since(version: "25.2.1")
"Returns project settings that are specified for a subject"
rmAdminUserProjectSettings(projectId: ID!, subjectId: String!): Object @since(version: "25.2.3")
}
extend type Mutation {
@@ -137,4 +140,9 @@ extend type Mutation {
rmAddUserProjectSettings(projectId: ID!, settings: Object!): Boolean! @since(version: "25.2.1")
"Deletes project settings that are specified for a current user"
rmDeleteUserProjectSettings(projectId: ID!, settingIds: [String!]!): Boolean! @since(version: "25.2.1")
"Adds project settings (e.g. enables simple view for a project) for a subject"
rmAdminAddUserProjectSettings(projectId: ID!, subjectId:String!, settings: Object!): Boolean! @since(version: "25.2.3")
"Deletes project settings that are specified for a subject"
rmAdminDeleteUserProjectSettings(projectId: ID!, subjectId:String!, settingIds: [String!]!): Boolean! @since(version: "25.2.3")
}
@@ -214,4 +214,29 @@ public interface DBWServiceRM extends DBWService {
@NotNull @WebObjectId String projectId,
@Nullable List<String> settings
) throws DBWebException;
@NotNull
@WebProjectAction(requireProjectPermissions = DBWConstants.PERMISSION_ADMIN)
Map<String, Object> getProjectSettings(
@NotNull WebSession webSession,
@NotNull @WebObjectId String projectId,
@NotNull String subjectId,
@Nullable String settingId
) throws DBWebException;
@WebProjectAction(requireProjectPermissions = DBWConstants.PERMISSION_ADMIN)
boolean addProjectSettings(
@NotNull WebSession webSession,
@NotNull @WebObjectId String projectId,
@NotNull String subjectId,
@NotNull Map<String, Object> settings
) throws DBWebException;
@WebProjectAction(requireProjectPermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteProjectSettings(
@NotNull WebSession webSession,
@NotNull @WebObjectId String projectId,
@NotNull String subjectId,
@Nullable List<String> settings
) throws DBWebException;
}
@@ -70,6 +70,14 @@ public class WebServiceBindingRM extends WebServiceBindingBase<DBWServiceRM> {
getArgument(env, "settingId")
)
)
.dataFetcher(
"rmAdminUserProjectSettings", env -> getService(env).getProjectSettings(
getWebSession(env),
getArgumentVal(env, "projectId"),
getArgumentVal(env, "subjectId"),
getArgument(env, "settingId")
)
)
;
model.getMutationType()
.dataFetcher("rmCreateResource",
@@ -152,6 +160,22 @@ public class WebServiceBindingRM extends WebServiceBindingBase<DBWServiceRM> {
getArgument(env, "settingIds")
)
)
.dataFetcher(
"rmAdminAddUserProjectSettings", env -> getService(env).addProjectSettings(
getWebSession(env),
getArgumentVal(env, "projectId"),
getArgumentVal(env, "subjectId"),
getArgumentVal(env, "settings")
)
)
.dataFetcher(
"rmAdminDeleteUserProjectSettings", env -> getService(env).deleteProjectSettings(
getWebSession(env),
getArgumentVal(env, "projectId"),
getArgumentVal(env, "subjectId"),
getArgument(env, "settingIds")
)
)
;
}
@@ -469,22 +469,79 @@ public class WebServiceRM implements DBWServiceRM {
}
}
@Override
public boolean addProjectSettings(@NotNull WebSession webSession, @NotNull String projectId, @NotNull Map<String, Object> settings)
throws DBWebException {
try {
validateProject(webSession, projectId);
//fixme transactional
SMController securityController = webSession.getSecurityController();
securityController.deleteObjectSettings(
projectId,
SMObjectType.project,
settings.keySet()
);
securityController.setObjectSettings(
projectId,
SMObjectType.project,
settings
);
return true;
} catch (DBException e) {
throw new DBWebException("Error adding object settings", e);
}
}
@NotNull
@Override
public Map<String, Object> getProjectSettings(@NotNull WebSession webSession, @NotNull String projectId, @Nullable String settingId)
throws DBWebException {
try {
validateProject(webSession, projectId);
//todo accumulate settings from user and team. For now just return team settings
return webSession.getSecurityController().getObjectSettings(
projectId,
SMObjectType.project,
ServletAppUtils.getServletApplication().getAppConfiguration().getDefaultUserTeam(),
settingId == null ? null : Set.of(settingId)
);
} catch (DBException e) {
throw new DBWebException("Error getting project settings", e);
}
}
@Override
public boolean deleteProjectSettings(@NotNull WebSession webSession, @NotNull String projectId, @Nullable List<String> settings)
throws DBWebException {
try {
validateProject(webSession, projectId);
webSession.getSecurityController().deleteObjectSettings(
projectId,
SMObjectType.project,
settings == null ? null : new HashSet<>(settings)
);
return true;
} catch (DBException e) {
throw new DBWebException("Error deleting object settings", e);
}
}
@NotNull
@Override
public Map<String, Object> getProjectSettings(
@NotNull WebSession webSession,
@NotNull String projectId,
@Nullable String subjectId,
@Nullable String settingId
) throws DBWebException {
try {
var project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
validateProject(webSession, projectId);
return webSession.getSecurityController().getObjectSettings(
projectId,
SMObjectType.project,
settingId
subjectId,
settingId == null ? null : Set.of(settingId)
);
} catch (DBException e) {
throw new DBWebException("Error getting project settings", e);
@@ -495,16 +552,23 @@ public class WebServiceRM implements DBWServiceRM {
public boolean addProjectSettings(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String subjectId,
@NotNull Map<String, Object> settings
) throws DBWebException {
try {
var project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
webSession.getSecurityController().setObjectSettings(
validateProject(webSession, projectId);
//fixme transactional
SMAdminController adminSecurityController = webSession.getAdminSecurityController();
adminSecurityController.deleteObjectSettings(
projectId,
SMObjectType.project,
subjectId,
settings.keySet()
);
adminSecurityController.setObjectSettings(
projectId,
SMObjectType.project,
subjectId,
settings
);
return true;
@@ -517,16 +581,15 @@ public class WebServiceRM implements DBWServiceRM {
public boolean deleteProjectSettings(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String subjectId,
@Nullable List<String> settings
) throws DBWebException {
try {
var project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
webSession.getSecurityController().deleteObjectSettings(
validateProject(webSession, projectId);
webSession.getAdminSecurityController().deleteObjectSettings(
projectId,
SMObjectType.project,
subjectId,
settings == null ? null : new HashSet<>(settings)
);
return true;
@@ -535,6 +598,13 @@ public class WebServiceRM implements DBWServiceRM {
}
}
private void validateProject(@NotNull WebSession webSession, @NotNull String projectId) throws DBWebException {
var project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
}
private RMController getResourceController(WebSession webSession) {
return webSession.getRmController();
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.90.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.88.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.90-SNAPSHOT</version>
<version>1.0.88-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -101,11 +101,6 @@ public class LocalAuthProvider implements SMAuthProvider<LocalAuthSession>, SMBr
}
@Override
public boolean supportsOpeningSessionAsChild() {
return false;
}
public static boolean changeUserPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBException {
String userName = webSession.getUser().getUserId();
@@ -111,69 +111,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
@NotNull Map<String, Object> settings
) throws DBException {
String userId = getUserIdOrThrow();
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
deleteObjectSettings(objectId, objectType, settings.keySet());
try (
PreparedStatement dbStat = dbCon.prepareStatement(
"INSERT INTO {table_prefix}CB_OBJECT_SETTINGS" +
"(OBJECT_ID,OBJECT_TYPE,SUBJECT_ID,SETTING_ID,SETTING_VALUE,UPDATED_BY,UPDATE_TIME) " +
"VALUES(?,?,?,?,?,?,CURRENT_TIMESTAMP)")
) {
for (Map.Entry<String, Object> entry : settings.entrySet()) {
dbStat.setString(1, objectId);
dbStat.setString(2, objectType.name());
dbStat.setString(3, userId);
dbStat.setString(4, entry.getKey());
dbStat.setString(5, CommonUtils.toString(entry.getValue()));
dbStat.setString(6, userId);
dbStat.addBatch();
}
dbStat.executeBatch();
}
txn.commit();
}
} catch (SQLException e) {
throw new DBCException("Error while adding object settings", e);
}
}
@NotNull
@Override
public Map<String, Object> getObjectSettings(
@NotNull String objectId,
@NotNull SMObjectType objectType,
@Nullable String settingId
) throws DBException {
String userId = getUserIdOrThrow();
try (Connection dbCon = database.openConnection()) {
try (
PreparedStatement dbStat = dbCon.prepareStatement("SELECT SETTING_ID,SETTING_VALUE " +
"FROM {table_prefix}CB_OBJECT_SETTINGS " +
"WHERE OBJECT_ID=? AND OBJECT_TYPE=? AND SUBJECT_ID=?" +
(settingId == null ? "" : " AND SETTING_ID=?"))
) {
int index = 1;
dbStat.setString(index++, objectId);
dbStat.setString(index++, objectType.name());
dbStat.setString(index++, userId);
if (settingId != null) {
dbStat.setString(index++, settingId);
}
try (ResultSet dbResult = dbStat.executeQuery()) {
Map<String, Object> result = new LinkedHashMap<>();
while (dbResult.next()) {
result.put(
dbResult.getString(1),
dbResult.getString(2)
);
}
return result;
}
}
} catch (SQLException e) {
throw new DBCException("Error while getting object settings", e);
}
setObjectSettings(objectId, objectType, userId, settings);
}
@Override
@@ -183,26 +121,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
@Nullable Set<String> settingIds
) throws DBException {
String userId = getUserIdOrThrow();
String sql = "DELETE FROM {table_prefix}CB_OBJECT_SETTINGS WHERE OBJECT_ID=? AND OBJECT_TYPE=? AND SUBJECT_ID=?";
if (settingIds != null && !settingIds.isEmpty()) {
sql += " AND SETTING_ID IN (" + SQLUtils.generateParamList(settingIds.size()) + ")";
}
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(sql)) {
int index = 1;
dbStat.setString(index++, objectId);
dbStat.setString(index++, objectType.name());
dbStat.setString(index++, userId);
if (settingIds != null) {
for (String settingId : settingIds) {
dbStat.setString(index++, settingId);
}
}
dbStat.executeUpdate();
}
} catch (SQLException e) {
throw new DBCException("Error while deleting object settings", e);
}
deleteObjectSettings(objectId, objectType, userId, settingIds);
}
protected boolean isSubjectExists(String subjectId) throws DBCException {
@@ -1363,6 +1282,112 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
}
}
@NotNull
@Override
public Map<String, Object> getObjectSettings(
@NotNull String objectId,
@NotNull SMObjectType objectType,
@Nullable String subjectId,
@Nullable Set<String> settingIds
) throws DBException {
try (Connection dbCon = database.openConnection()) {
try (
PreparedStatement dbStat = dbCon.prepareStatement("SELECT SETTING_ID,SETTING_VALUE " +
"FROM {table_prefix}CB_OBJECT_SETTINGS " +
"WHERE OBJECT_ID=? AND OBJECT_TYPE=? "
+ (subjectId == null ? "" : " AND SUBJECT_ID=?") +
(CommonUtils.isEmpty(settingIds) ? "" : " AND SETTING_ID IN (" + SQLUtils.generateParamList(settingIds.size()) + ")"))
) {
int index = 1;
dbStat.setString(index++, objectId);
dbStat.setString(index++, objectType.name());
if (subjectId != null) {
dbStat.setString(index++, subjectId);
}
if (!CommonUtils.isEmpty(settingIds)) {
for (String settingId : settingIds) {
dbStat.setString(index++, settingId);
}
}
try (ResultSet dbResult = dbStat.executeQuery()) {
Map<String, Object> result = new LinkedHashMap<>();
while (dbResult.next()) {
result.put(
dbResult.getString(1),
dbResult.getString(2)
);
}
return result;
}
}
} catch (SQLException e) {
throw new DBCException("Error while getting object settings", e);
}
}
@Override
public void setObjectSettings(
@NotNull String objectId,
@NotNull SMObjectType objectType,
@NotNull String subjectId,
@NotNull Map<String, Object> settings
) throws DBException {
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
deleteObjectSettings(objectId, objectType, settings.keySet());
try (
PreparedStatement dbStat = dbCon.prepareStatement(
"INSERT INTO {table_prefix}CB_OBJECT_SETTINGS" +
"(OBJECT_ID,OBJECT_TYPE,SUBJECT_ID,SETTING_ID,SETTING_VALUE,UPDATED_BY,UPDATE_TIME) " +
"VALUES(?,?,?,?,?,?,CURRENT_TIMESTAMP)")
) {
for (Map.Entry<String, Object> entry : settings.entrySet()) {
dbStat.setString(1, objectId);
dbStat.setString(2, objectType.name());
dbStat.setString(3, subjectId);
dbStat.setString(4, entry.getKey());
dbStat.setString(5, CommonUtils.toString(entry.getValue()));
dbStat.setString(6, subjectId);
dbStat.addBatch();
}
dbStat.executeBatch();
}
txn.commit();
}
} catch (SQLException e) {
throw new DBCException("Error while adding object settings", e);
}
}
@Override
public void deleteObjectSettings(
@NotNull String objectId,
@NotNull SMObjectType objectType,
@NotNull String subjectId,
@Nullable Set<String> settingIds
) throws DBException {
String sql = "DELETE FROM {table_prefix}CB_OBJECT_SETTINGS WHERE OBJECT_ID=? AND OBJECT_TYPE=? AND SUBJECT_ID=?";
if (settingIds != null && !settingIds.isEmpty()) {
sql += " AND SETTING_ID IN (" + SQLUtils.generateParamList(settingIds.size()) + ")";
}
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(sql)) {
int index = 1;
dbStat.setString(index++, objectId);
dbStat.setString(index++, objectType.name());
dbStat.setString(index++, subjectId);
if (settingIds != null) {
for (String settingId : settingIds) {
dbStat.setString(index++, settingId);
}
}
dbStat.executeUpdate();
}
} catch (SQLException e) {
throw new DBCException("Error while deleting object settings", e);
}
}
@NotNull
private SMTeam fetchTeam(ResultSet dbResult) throws SQLException {
return new SMTeam(
@@ -2472,13 +2497,6 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
if (autoAssign != null && CommonUtils.isNotEmpty(autoAssign.getAuthRoleAssignReason())) {
log.info(activeUserId + " authenticated with role " + autoAssign.getAuthRole() + ", reason: " + autoAssign.getAuthRoleAssignReason());
}
} else {
SMAuthProvider<?> authProviderInstance = authProvider.getInstance();
if (!authProviderInstance.supportsOpeningSessionAsChild()) {
var error = "Auth provider '" + authProviderId + "' doesn't support opening as a child session";
updateAuthStatus(authId, SMAuthStatus.ERROR, dbStoredUserData, error, null);
return SMAuthInfo.error(authId, error, isMainAuthSession, null, authInfo.getAppSessionId());
}
}
dbStoredUserData.put(
authConfiguration,
@@ -2577,8 +2595,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
.flatMap(externalTeamId -> findTeamByExternalTeamId(
allTeams,
externalTeamIdMetadataFieldName,
externalTeamId,
authProvider.isCaseInsensitive()
externalTeamId
).stream())
.map(SMTeam::getTeamId)
.toArray(String[]::new);
@@ -2615,29 +2632,17 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
}
@NotNull
private List<SMTeam> findTeamByExternalTeamId(
SMTeam[] allTeams,
String externalGroupParameterName,
String groupId,
boolean isCaseInsensitive
) {
private List<SMTeam> findTeamByExternalTeamId(SMTeam[] allTeams, String externalGroupParameterName, String groupId) {
List<SMTeam> result = new ArrayList<>();
for (SMTeam team : allTeams) {
String teamGroupId = team.getMetaParameters().get(externalGroupParameterName);
if (matchesGroupId(teamGroupId, groupId, isCaseInsensitive)) {
if (CommonUtils.equalObjects(teamGroupId, groupId)) {
result.add(team);
}
}
return result;
}
private boolean matchesGroupId(String teamGroupId, String groupId, boolean isCaseInsensitive) {
if (isCaseInsensitive) {
return teamGroupId != null && teamGroupId.equalsIgnoreCase(groupId);
}
return CommonUtils.equalObjects(teamGroupId, groupId);
}
private String readProviderConfigId(String authAttemptId, String authProviderId) throws DBException {
try (Connection dbCon = database.openConnection()) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver SLF4j Binding
Bundle-SymbolicName: io.cloudbeaver.slf4j;singleton:=true
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.48.qualifier
Bundle-Release-Date: 20251103
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Fragment-Host: slf4j.api
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.50-SNAPSHOT</version>
<version>1.0.48-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
-5
View File
@@ -38,11 +38,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
<version>1.8.1</version>
</dependency>
</dependencies>
</project>
-1
View File
@@ -23,7 +23,6 @@
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.25.5</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>13.2.1.jre11</version>
<version>12.8.0.jre11</version>
</dependency>
<!--
<dependency>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.158.qualifier"
version="1.0.156.qualifier"
provider-name="DBeaver Corp">
<plugin id="io.cloudbeaver.resources.drivers.base" download-size="0" install-size="0" version="0.0.0"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
<version>1.0.158-SNAPSHOT</version>
<version>1.0.156-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="25.3.0.qualifier"
version="25.2.4.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.4-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="25.3.0.qualifier"
version="25.2.4.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.4-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.88.qualifier"
version="1.0.86.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ws.feature</artifactId>
<version>1.0.88-SNAPSHOT</version>
<version>1.0.86-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<?pde version="3.5"?>
<product name="CloudBeaver ce Server" uid="io.cloudbeaver.product" id="io.cloudbeaver.product.ce.product"
application="io.cloudbeaver.product.ce.application" version="25.3.0.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="25.2.4.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.4-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
@@ -33,7 +33,6 @@
"typescript": "^5"
},
"dependencies": {
"@dbeaver/js-helpers": "workspace:*",
"@dbeaver/react-dnd": "workspace:^",
"@dbeaver/ui-kit": "workspace:^",
"react": "^19",
@@ -1,11 +1,3 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { memo, use, useMemo } from 'react';
import { Cell, type CellRendererProps } from 'react-data-grid';
import { DataGridCellContext, type IDataGridCellRenderer } from './DataGridCellContext.js';
@@ -85,17 +77,19 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
const renderDefaultCell = useMemo<IDataGridCellRenderer>(
() =>
({ onClick, onDoubleClick, onContextMenu, ...rest }) => (
<Cell
{...props}
title={tooltip}
onCellClick={(_, event) => onClick?.(event)}
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
onCellContextMenu={(_, event) => onContextMenu?.(event)}
{...rest}
isCellSelected={props.isCellSelected || rest.isFocused || false}
/>
),
({ onClick, onDoubleClick, onContextMenu, ...rest }) => {
return (
<Cell
{...props}
title={tooltip}
onCellClick={(_, event) => onClick?.(event)}
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
onCellContextMenu={(_, event) => onContextMenu?.(event)}
{...rest}
isCellSelected={props.isCellSelected || rest.isFocused || false}
/>
);
},
[...Object.values(props), tooltip],
);
@@ -1,14 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { Row, type RenderRowProps } from 'react-data-grid';
import { DataGridRowContext, type IDataGridRowRenderer } from './DataGridRowContext.js';
import { memo, use, useEffect, useMemo } from 'react';
import { DataGridRowContext } from './DataGridRowContext.js';
import { memo, use, useEffect } from 'react';
import { useGridReactiveValue } from './useGridReactiveValue.js';
export const BaseRow = memo(function BaseRow<TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) {
@@ -20,18 +12,5 @@ export const BaseRow = memo(function BaseRow<TRow, TSummaryRow>(props: RenderRow
rowContext?.onScrollToBottom?.();
}
}, [rowsCount]);
const renderDefaultRow = useMemo<IDataGridRowRenderer>(
() => override => (
<Row
{...props}
{...override}
/>
),
[...Object.values(props)],
);
const rowElement = useGridReactiveValue(rowContext?.rowElement, props.rowIdx, props, renderDefaultRow);
return rowElement ?? <Row {...props} />;
return <Row {...props} />;
}) as <TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) => React.ReactNode;
@@ -42,7 +42,6 @@ export interface DataGridCellKeyboardEvent extends React.KeyboardEvent<HTMLDivEl
export interface DataGridProps extends IDataGridCellContext, IDataGridRowContext, IDataGridHeaderCellContext, React.PropsWithChildren {
getRowHeight?: (rowIdx: number) => number;
getRowId?: (rowIdx: number) => React.Key;
getRowClass?: (rowIdx: number) => string | null;
columnCount: IGridReactiveValue<number, []>;
getColumnKey?: (colIdx: number) => string;
onScroll?: (event: React.UIEvent<HTMLDivElement>) => void;
@@ -79,11 +78,9 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
getCellEditable,
columnCount,
getColumnKey,
rowElement,
rowCount,
getRowId,
getRowHeight,
getRowClass,
onHeaderReorder,
onScroll,
onScrollToBottom,
@@ -184,7 +181,7 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
return (
<HeaderDnDContext value={dndHeaderContext}>
<DataGridRowContext value={{ rowElement, rowCount, onScrollToBottom }}>
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
<DataGridCellHeaderContext
value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState, onHeaderKeyDown }}
@@ -197,7 +194,6 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
headerRowHeight={getHeaderHeight?.()}
rowHeight={getRowHeight ? row => getRowHeight(row.idx) : undefined}
rowKeyGetter={getRowId ? row => getRowId(row.idx) : undefined}
rowClass={getRowClass ? row => getRowClass(row.idx) : undefined}
columnWidths={columnWidths}
renderers={{
renderRow: rowRenderer,
@@ -1,11 +1,3 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { createContext } from 'react';
import type { IGridReactiveValue } from './IGridReactiveValue.js';
@@ -1,22 +1,7 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { createContext } from 'react';
import type { IGridReactiveValue } from './IGridReactiveValue.js';
export interface IDataGridRowProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {}
export interface IDataGridRowRenderer {
(propsOverride: Partial<IDataGridRowProps>): React.ReactNode;
}
export interface IDataGridRowContext {
rowElement?: IGridReactiveValue<React.ReactNode, [rowIdx: number, props: IDataGridRowProps, renderDefaultRow: IDataGridRowRenderer]>;
rowCount: IGridReactiveValue<number, []>;
onScrollToBottom?: () => void;
}
@@ -1,18 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export * from './DataGrid.js';
export * from './DataGridRowContext.js';
export * from './DataGridCellContext.js';
export * from './DataGridCellInnerContext.js';
export * from './IGridReactiveValue.js';
export * from './useCreateGridReactiveValue.js';
export * from './useGridReactiveValue.js';
export * from './reorderArray.js';
export * from './formatters/NullFormatter.js';
export * from './formatters/BooleanFormatter.js';
@@ -0,0 +1,11 @@
export function reorderArray<T>(array: T[], from: number, to: number): T[] {
const newArray = [...array];
if (from < to) {
newArray.splice(to, 0, array[from]!);
newArray.splice(from, 1);
} else {
newArray.splice(from, 1);
newArray.splice(to, 0, array[from]!);
}
return newArray;
}
@@ -1,15 +1,7 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 type { ColumnOrColumnGroup } from 'react-data-grid';
import type { IInnerRow } from './IInnerRow.js';
import { createContext, useId, useMemo, useState } from 'react';
import { reorderArray } from '@dbeaver/js-helpers';
import { reorderArray } from './reorderArray.js';
interface IHeaderDnDOptions {
columns: Array<ColumnOrColumnGroup<IInnerRow, unknown>>;
@@ -14,9 +14,6 @@
"src"
],
"references": [
{
"path": "../../../common-typescript/@dbeaver/js-helpers"
},
{
"path": "../react-dnd"
},
@@ -1 +0,0 @@
# @dbeaver/react-translate

Some files were not shown because too many files have changed in this diff Show More