From 2c788504148a77ca8f1de708cff9207eb00b15b7 Mon Sep 17 00:00:00 2001 From: Ruslan Date: Mon, 3 Aug 2026 14:35:13 +0200 Subject: [PATCH 1/5] dbeaver/pro#9637 support restriction for connection --- .../cloudbeaver/model/WebConnectionInfo.java | 12 ++++++++++++ .../service/sql/impl/WebServiceSQL.java | 18 ++++++++++++++---- .../transfer/impl/WebServiceDataTransfer.java | 5 +++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java index 4fe539750e..d25e77d463 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -71,6 +71,9 @@ public class WebConnectionInfo { private static final String FEATURE_READ_ONLY = "readOnly"; private static final String FEATURE_PROVIDED = "provided"; private static final String FEATURE_MANAGEABLE = "manageable"; + private static final String FEATURE_RESTRICT_DATA_EDIT = "restrictDataEdit"; + private static final String FEATURE_RESTRICT_SCRIPT_EXECUTE = "restrictScriptExecute"; + private static final String FEATURE_RESTRICT_DATA_IMPORT = "restrictDataImport"; private static final String TOOL_SESSION_MANAGER = "sessionManager"; @@ -275,6 +278,15 @@ public String[] getFeatures() { if (dataSourceContainer.isConnectionReadOnly()) { features.add(FEATURE_READ_ONLY); } + if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_DATA)) { + features.add(FEATURE_RESTRICT_DATA_EDIT); + } + if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EXECUTE_SCRIPTS)) { + features.add(FEATURE_RESTRICT_SCRIPT_EXECUTE); + } + if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_IMPORT_DATA)) { + features.add(FEATURE_RESTRICT_DATA_IMPORT); + } if (dataSourceContainer.isProvided()) { features.add(FEATURE_PROVIDED); } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java index 1c0372c6a5..4f6d6ad50d 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java @@ -31,10 +31,7 @@ import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; -import org.jkiss.dbeaver.model.DBPDataKind; -import org.jkiss.dbeaver.model.DBPDataSource; -import org.jkiss.dbeaver.model.DBPDataSourceContainer; -import org.jkiss.dbeaver.model.DBUtils; +import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.data.DBDAttributeBinding; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.exec.DBCLogicalOperator; @@ -509,6 +506,7 @@ private WebSQLExecuteInfo updateResultsDataBatch( @Nullable List addedRows, @Nullable WebDataFormat dataFormat ) throws DBException { + checkDataEditPermission(contextInfo); WebSQLExecuteInfo[] result = new WebSQLExecuteInfo[1]; DBExecUtils.tryExecuteRecover( @@ -521,6 +519,13 @@ private WebSQLExecuteInfo updateResultsDataBatch( return result[0]; } + private void checkDataEditPermission(@NotNull WebSQLContextInfo contextInfo) throws DBWebException { + if (!contextInfo.getProcessor().getConnection().getDataSourceContainer() + .hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_DATA)) { + throw new DBWebException("Data edit is restricted for this connection"); + } + } + @FunctionalInterface private interface ThrowableFunction { R apply(T obj) throws DBException; @@ -573,6 +578,7 @@ private String readValue( @Override public String updateResultsDataBatchScript(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, @Nullable List updatedRows, @Nullable List deletedRows, @Nullable List addedRows, WebDataFormat dataFormat) throws DBWebException { + checkDataEditPermission(contextInfo); try { return contextInfo.getProcessor().generateResultsDataUpdateScript( contextInfo.getProcessor().getWebSession().getProgressMonitor(), @@ -599,6 +605,10 @@ public WebAsyncTaskInfo asyncExecuteQuery( if (DBWorkbench.isDistributed() && !webSession.hasPermission(DBWConstants.PERMISSION_SQL_EXECUTE_QUERY)) { throw new DBWebException("Permission denied"); } + if (!contextInfo.getProcessor().getConnection().getDataSourceContainer() + .hasModifyPermission(DBPDataSourcePermission.PERMISSION_EXECUTE_SCRIPTS)) { + throw new DBWebException("Script execution is restricted for this connection"); + } return WebSQLUtils.createAsyncTaskExecuteSqlQuery( webSession, contextInfo, diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java index 86a93c4896..77acb2cbc6 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java +++ b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java @@ -32,6 +32,7 @@ import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.DBPDataSourcePermission; import org.jkiss.dbeaver.model.data.json.JSONUtils; import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; @@ -245,6 +246,10 @@ public WebAsyncTaskInfo asyncImportDataContainer( if (!validateImportPermission(webSession)) { throw new DBWebException("Permission denied. Data import is not allowed for this user"); } + if (!sqlContext.getProcessor().getConnection().getDataSourceContainer() + .hasModifyPermission(DBPDataSourcePermission.PERMISSION_IMPORT_DATA)) { + throw new DBWebException("Data import is restricted for this connection"); + } DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(parameters.getProcessorId()); if (processor == null) { throw new DBWebException("Wrong data processor '" + parameters.getProcessorId() + "'"); From 3b1a6cb2e17eae37dfb90291ef8c789e1e3ae2aa Mon Sep 17 00:00:00 2001 From: Ruslan Date: Mon, 3 Aug 2026 15:44:08 +0200 Subject: [PATCH 2/5] dbeaver/pro#9637 support restriction for connection --- .../src/io/cloudbeaver/model/WebConnectionInfo.java | 5 +++++ .../service/navigator/impl/WebServiceNavigator.java | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java index d25e77d463..40cf606df0 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -71,9 +71,11 @@ public class WebConnectionInfo { private static final String FEATURE_READ_ONLY = "readOnly"; private static final String FEATURE_PROVIDED = "provided"; private static final String FEATURE_MANAGEABLE = "manageable"; + private static final String FEATURE_RESTRICT_DATA_EDIT = "restrictDataEdit"; private static final String FEATURE_RESTRICT_SCRIPT_EXECUTE = "restrictScriptExecute"; private static final String FEATURE_RESTRICT_DATA_IMPORT = "restrictDataImport"; + private static final String FEATURE_RESTRICT_METADATA_EDIT = "restrictMetadataEdit"; private static final String TOOL_SESSION_MANAGER = "sessionManager"; @@ -287,6 +289,9 @@ public String[] getFeatures() { if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_IMPORT_DATA)) { features.add(FEATURE_RESTRICT_DATA_IMPORT); } + if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) { + features.add(FEATURE_RESTRICT_METADATA_EDIT); + } if (dataSourceContainer.isProvided()) { features.add(FEATURE_PROVIDED); } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java index a35c46d4e3..10b2c55d80 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java @@ -461,6 +461,7 @@ public String renameNode( return node.getNodeUri(); } if (node instanceof DBNDatabaseNode dbNode) { + checkMetadataEditPermission(dbNode); return renameDatabaseObject( session, dbNode, @@ -546,6 +547,7 @@ public int deleteNodes( } checkProjectEditAccess(node, session); if (node instanceof DBNDatabaseNode) { + checkMetadataEditPermission((DBNDatabaseNode) node); DBSObject object = ((DBNDatabaseNode) node).getObject(); DBEObjectMaker objectDeleter = DBWorkbench.getPlatform().getEditorsRegistry().getObjectManager( object.getClass(), DBEObjectMaker.class); @@ -602,6 +604,12 @@ public int deleteNodes( } } + private void checkMetadataEditPermission(@NotNull DBNDatabaseNode node) throws DBException { + if (!node.getDataSourceContainer().hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) { + throw new DBWebException("Structure edit is restricted for this connection"); + } + } + private void checkProjectEditAccess(@NotNull DBNNode node, @NotNull WebSession session) throws DBException { var project = node.getOwnerProject(); if (!(project instanceof BaseWebProjectImpl bwp) || !hasNodeEditPermission(session, node, bwp.getRMProject())) { From 8c16f0fbf867e541fad1e079974a3e1d6f020181 Mon Sep 17 00:00:00 2001 From: naumov Date: Tue, 4 Aug 2026 20:47:03 +0200 Subject: [PATCH 3/5] dbeaver/pro#9637 support edit and import restriction --- .../src/EConnectionFeature.ts | 7 ++++++- .../packages/plugin-data-import/package.json | 1 + .../src/DataImportBootstrap.ts | 20 +++++++++++++++++-- .../packages/plugin-data-import/tsconfig.json | 3 +++ .../src/DataViewerService.ts | 6 +++--- webapp/yarn.lock | 1 + 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/webapp/packages/core-connections/src/EConnectionFeature.ts b/webapp/packages/core-connections/src/EConnectionFeature.ts index 757e49eebe..106afc2852 100644 --- a/webapp/packages/core-connections/src/EConnectionFeature.ts +++ b/webapp/packages/core-connections/src/EConnectionFeature.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -13,4 +13,9 @@ export enum EConnectionFeature { readOnly = 'readOnly', provided = 'provided', manageable = 'manageable', + + restrictDataEdit = 'restrictDataEdit', + restrictScriptExecute = 'restrictScriptExecute', + restrictDataImport = 'restrictDataImport', + restrictMetadataEdit = 'restrictMetadataEdit', } diff --git a/webapp/packages/plugin-data-import/package.json b/webapp/packages/plugin-data-import/package.json index 88119f2dfe..6c8d101869 100644 --- a/webapp/packages/plugin-data-import/package.json +++ b/webapp/packages/plugin-data-import/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@cloudbeaver/core-blocks": "workspace:*", + "@cloudbeaver/core-connections": "workspace:*", "@cloudbeaver/core-di": "workspace:*", "@cloudbeaver/core-dialogs": "workspace:*", "@cloudbeaver/core-events": "workspace:*", diff --git a/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts b/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts index e881c96352..052e6678aa 100644 --- a/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts +++ b/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts @@ -8,6 +8,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { ACTION_IMPORT, ActionService, menuExtractItems, MenuService } from '@cloudbeaver/core-view'; +import { ConnectionInfoResource, createConnectionParam, EConnectionFeature } from '@cloudbeaver/core-connections'; import { DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, @@ -16,18 +17,21 @@ import { DatabaseDataFeature, DataViewerPresentationType, isResultSetDataModel, + ResultSetDataSource, + type IDatabaseDataModel, } from '@cloudbeaver/plugin-data-viewer'; import { DataImportDialogLazy } from './DataImportDialog/DataImportDialogLazy.js'; import { DataImportService } from './DataImportService.js'; -@injectable(() => [MenuService, ActionService, CommonDialogService, DataImportService]) +@injectable(() => [MenuService, ActionService, CommonDialogService, DataImportService, ConnectionInfoResource]) export class DataImportBootstrap extends Bootstrap { constructor( private readonly menuService: MenuService, private readonly actionService: ActionService, private readonly commonDialogService: CommonDialogService, private readonly dataImportService: DataImportService, + private readonly connectionInfoResource: ConnectionInfoResource, ) { super(); } @@ -96,9 +100,21 @@ export class DataImportBootstrap extends Bootstrap { menus: [DATA_VIEWER_DATA_MODEL_ACTIONS_MENU], contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], isApplicable: context => { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const presentation = context.get(DATA_CONTEXT_DV_PRESENTATION); const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + + const executionContext = model.source.executionContext?.context; + + if (executionContext) { + const connectionKey = createConnectionParam(executionContext.projectId, executionContext.connectionId); + const connection = this.connectionInfoResource.get(connectionKey); + + if (connection?.features.includes(EConnectionFeature.restrictDataImport)) { + return false; + } + } + const allowedFeatures = [DatabaseDataFeature.DataEditor]; return ( diff --git a/webapp/packages/plugin-data-import/tsconfig.json b/webapp/packages/plugin-data-import/tsconfig.json index 50882dab41..8e74883116 100644 --- a/webapp/packages/plugin-data-import/tsconfig.json +++ b/webapp/packages/plugin-data-import/tsconfig.json @@ -16,6 +16,9 @@ { "path": "../core-cli" }, + { + "path": "../core-connections" + }, { "path": "../core-di" }, diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerService.ts index 3562315164..8eebd6bfd9 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerService.ts @@ -1,11 +1,11 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 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 { Connection } from '@cloudbeaver/core-connections'; +import { EConnectionFeature, type Connection } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; @@ -37,7 +37,7 @@ export class DataViewerService { } isDataEditable(connection: Connection) { - if (connection.readOnly) { + if (connection.readOnly || connection.features.includes(EConnectionFeature.restrictDataEdit)) { return false; } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index e15e10472b..4a82d977be 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3152,6 +3152,7 @@ __metadata: dependencies: "@cloudbeaver/core-blocks": "workspace:*" "@cloudbeaver/core-cli": "workspace:*" + "@cloudbeaver/core-connections": "workspace:*" "@cloudbeaver/core-di": "workspace:*" "@cloudbeaver/core-dialogs": "workspace:*" "@cloudbeaver/core-events": "workspace:*" From 7d3be424f34f13e87553b16d8c7f7ee056c67960 Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 7 Aug 2026 00:18:07 +0200 Subject: [PATCH 4/5] dbeaver/pro#9637 support restrictions --- .../AIChatMessageActionsService.ts | 17 ++++++---- .../AIChat/AIChatMessage/CodeFormatter.tsx | 2 +- .../NodesManager/NavNodeContextMenuService.ts | 11 ++++++- .../plugin-sql-editor/src/MenuBootstrap.ts | 2 +- .../src/SqlEditor/ISQLEditorData.ts | 2 +- .../src/SqlEditor/useSqlEditor.ts | 21 ++++++++++--- .../src/SqlEditorPermissionService.ts | 30 ++++++++++++++++++ .../src/SqlResultTabs/SqlQueryService.ts | 31 ++++++++++--------- .../packages/plugin-sql-editor/src/index.ts | 1 + .../packages/plugin-sql-editor/src/module.ts | 4 ++- 10 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 webapp/packages/plugin-sql-editor/src/SqlEditorPermissionService.ts diff --git a/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/AIChatMessageActionsService.ts b/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/AIChatMessageActionsService.ts index 1dcd5189a9..5cbfa20ccc 100644 --- a/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/AIChatMessageActionsService.ts +++ b/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/AIChatMessageActionsService.ts @@ -16,9 +16,9 @@ import { ESqlDataSourceFeatures, LocalStorageSqlDataSource, SqlDataSourceService, + SqlEditorPermissionService, SqlEditorService, type ILocalStorageSqlDataSourceState, - SqlEditorSettingsService, type ISqlEditorTabState, } from '@cloudbeaver/plugin-sql-editor'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -41,12 +41,9 @@ const AI_CHAT_ID_METADATA_KEY = 'aiChatId'; SqlDataSourceService, LocalizationService, CommonDialogService, - SqlEditorSettingsService, + SqlEditorPermissionService, ]) export class AIChatMessageActionsService { - get isAllowed(): boolean { - return this.sqlEditorSettingsService.scriptExecutionEnabled; - } get isDisabled(): boolean { return !this.aiChatContextService.currentContext; } @@ -61,13 +58,21 @@ export class AIChatMessageActionsService { private readonly sqlDataSourceService: SqlDataSourceService, private readonly localizationService: LocalizationService, private readonly commonDialogService: CommonDialogService, - private readonly sqlEditorSettingsService: SqlEditorSettingsService, + private readonly sqlEditorPermissionService: SqlEditorPermissionService, ) { makeObservable(this, { isDisabled: computed, }); } + isAllowed(connectionKey: IConnectionInfoParams | null): boolean { + if (!connectionKey) { + return false; + } + + return this.sqlEditorPermissionService.isScriptExecutionEnabled(connectionKey); + } + async executeQuery(conversationId: string | undefined | null, connectionKey: IConnectionInfoParams | null, query: string): Promise { const currentTab = this.navigationTabsService.currentTab; diff --git a/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/CodeFormatter.tsx b/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/CodeFormatter.tsx index 433325a875..46fc209fa1 100644 --- a/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/CodeFormatter.tsx +++ b/webapp/packages/plugin-ai-chat/src/AIChat/AIChatMessage/CodeFormatter.tsx @@ -62,7 +62,7 @@ export const CodeFormatter = observer(function CodeFormatter({ code, conv