diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java index 3ec793dceba..3a02af3beec 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2025 DBeaver Corp and others + * Copyright (C) 2010-2026 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. @@ -26,6 +26,7 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.app.DBPWorkspace; +import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.fs.lock.LockManager; import org.jkiss.dbeaver.model.fs.lock.LockOptions; import org.jkiss.dbeaver.model.fs.lock.LockTarget; @@ -45,6 +46,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Map; import java.util.function.Predicate; public abstract class BaseLocalResourceController implements RMController { @@ -160,7 +162,9 @@ protected DataSourceParseResults updateProjectDataSourcesConfig( @Nullable List dataSourceIds ) throws DBException { try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("updateProjectDataSources"))) { - DBPProject project = getWebProject(projectId, false); + RMLocalProject project = getWebProject(projectId, false); + Map storedDataSourceConfigurations = + captureCurrentDataSourceConfigurations(project, dataSourceIds); return doFileWriteOperation( projectId, project.getMetadataFolder(false), () -> { @@ -176,6 +180,16 @@ protected DataSourceParseResults updateProjectDataSourcesConfig( dataSourceIds == null ); registry.checkForErrors(); + try { + processLoadedDataSourceConfigurationUpdate( + project, + projectId, + dataSourceIds, + storedDataSourceConfigurations + ); + } catch (DBException e) { + registry.checkForErrors(); + } log.debug("Save data sources configuration in project '" + projectId + "'"); ((DataSourcePersistentRegistry) registry).saveDataSources(); registry.checkForErrors(); @@ -185,6 +199,22 @@ protected DataSourceParseResults updateProjectDataSourcesConfig( } } + @NotNull + protected Map captureCurrentDataSourceConfigurations( + @NotNull RMLocalProject project, + @Nullable List dataSourceIds + ) { + return Map.of(); + } + + protected void processLoadedDataSourceConfigurationUpdate( + @NotNull RMLocalProject project, + @NotNull String projectId, + @Nullable List dataSourceIds, + @NotNull Map storedDataSourceConfigurations + ) throws DBException { + } + @Override public void deleteProjectDataSources( @NotNull String projectId, diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 5a0033c300d..e581f7a6115 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -33,12 +33,14 @@ import org.jkiss.dbeaver.model.app.DBPWorkspace; import org.jkiss.dbeaver.model.auth.SMCredentials; import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; +import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.fs.lock.LockManager; import org.jkiss.dbeaver.model.fs.lock.LockOptions; import org.jkiss.dbeaver.model.fs.lock.LockTarget; import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl; import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl; import org.jkiss.dbeaver.model.navigator.DBNLocalFolder; +import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; import org.jkiss.dbeaver.model.rm.*; import org.jkiss.dbeaver.model.security.SMAdminController; import org.jkiss.dbeaver.model.security.SMObjectType; @@ -52,6 +54,7 @@ import org.jkiss.dbeaver.registry.DataSourceParseResults; import org.jkiss.dbeaver.registry.ResourceTypeDescriptor; import org.jkiss.dbeaver.registry.ResourceTypeRegistry; +import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.IOUtils; @@ -411,6 +414,107 @@ public boolean updateProjectDataSources( return parseResults != null; } + @Override + @NotNull + protected Map captureCurrentDataSourceConfigurations( + @NotNull RMLocalProject project, + @Nullable List dataSourceIds + ) { + return project.getDataSourceRegistry().getDataSources().stream() + .filter(ds -> dataSourceIds == null || dataSourceIds.contains(ds.getId())) + .collect(Collectors.toMap( + DBPDataSourceContainer::getId, + ds -> new DBPConnectionConfiguration(ds.getConnectionConfiguration()) + )); + } + + @Override + protected void processLoadedDataSourceConfigurationUpdate( + @NotNull RMLocalProject project, + @NotNull String projectId, + @Nullable List dataSourceIds, + @NotNull Map storedDataSourceConfigurations + ) throws DBException { + Set userProjectPermissions = getProjectPermissions(projectId, project.getProjectType()); + Set grantedPermissions = userProjectPermissions.stream() + .flatMap(permission -> permission.getAllPermissions().stream()) + .collect(Collectors.toSet()); + + for (DBPDataSourceContainer dataSource : project.getDataSourceRegistry().getDataSources()) { + if (dataSourceIds != null && !dataSourceIds.contains(dataSource.getId())) { + continue; + } + + DBPConnectionConfiguration storedConfiguration = storedDataSourceConfigurations.get(dataSource.getId()); + reconcileNetworkHandlers( + storedConfiguration, + dataSource.getConnectionConfiguration(), + grantedPermissions + ); + } + } + + private void reconcileNetworkHandlers( + @Nullable DBPConnectionConfiguration storedConfiguration, + @NotNull DBPConnectionConfiguration updatedConfiguration, + @NotNull Set grantedPermissions + ) throws DBException { + + // Getting all handlers ids from both stored and updated configurations to check permissions for all of them + // and to add missing handlers if user has no permissions to add new ones + Set handlerIds = new LinkedHashSet<>(); + if (storedConfiguration != null) { + storedConfiguration.getHandlers().stream() + .map(DBWHandlerConfiguration::getId) + .forEach(handlerIds::add); + } + updatedConfiguration.getHandlers().stream() + .map(DBWHandlerConfiguration::getId) + .forEach(handlerIds::add); + + for (String handlerId : handlerIds) { + DBWHandlerConfiguration storedHandler = storedConfiguration == null ? null : storedConfiguration.getHandler(handlerId); + DBWHandlerConfiguration updatedHandler = updatedConfiguration.getHandler(handlerId); + DBWHandlerConfiguration descriptorSource = updatedHandler != null ? updatedHandler : storedHandler; + if (descriptorSource == null + || !(descriptorSource.getHandlerDescriptor() instanceof NetworkHandlerDescriptor descriptor)) { + continue; + } + + if (descriptor.getRequiredPermissions().isEmpty() + || grantedPermissions.containsAll(descriptor.getRequiredPermissions())) { + continue; + } + + if (storedHandler == null) { + throw new DBException("No permissions to configure network handler '" + handlerId + "'"); + } + + // if user doesn't have permissions to add new handler, we should keep old one + if (updatedHandler == null) { + updatedConfiguration.updateHandler(new DBWHandlerConfiguration(storedHandler)); + continue; + } + + if (!areSameHandlerConfiguration(storedHandler, updatedHandler)) { + throw new DBException("No permissions to modify network handler '" + handlerId + "'"); + } + } + } + + private boolean areSameHandlerConfiguration( + @NotNull DBWHandlerConfiguration storedHandler, + @NotNull DBWHandlerConfiguration updatedHandler + ) { + DBWHandlerConfiguration storedHandlerCopy = new DBWHandlerConfiguration(storedHandler); + storedHandlerCopy.setDataSource(null); + + DBWHandlerConfiguration updatedHandlerCopy = new DBWHandlerConfiguration(updatedHandler); + updatedHandlerCopy.setDataSource(null); + + return storedHandlerCopy.equals(updatedHandlerCopy); + } + @Override public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException { super.deleteProjectDataSources(projectId, dataSourceIds);