Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ public class WebConnectionInfo {
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";

private final WebSession session;
Expand Down Expand Up @@ -275,6 +280,18 @@ 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.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) {
features.add(FEATURE_RESTRICT_METADATA_EDIT);
}
if (dataSourceContainer.isProvided()) {
features.add(FEATURE_PROVIDED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ public String renameNode(
return node.getNodeUri();
}
if (node instanceof DBNDatabaseNode dbNode) {
checkMetadataEditPermission(dbNode);
return renameDatabaseObject(
session,
dbNode,
Expand Down Expand Up @@ -545,8 +546,9 @@ public int deleteNodes(
throw new DBWebException("Navigator node '" + path + "' not found");
}
checkProjectEditAccess(node, session);
if (node instanceof DBNDatabaseNode) {
DBSObject object = ((DBNDatabaseNode) node).getObject();
if (node instanceof DBNDatabaseNode dbnDatabaseNode) {
checkMetadataEditPermission(dbnDatabaseNode);
DBSObject object = dbnDatabaseNode.getObject();
DBEObjectMaker objectDeleter = DBWorkbench.getPlatform().getEditorsRegistry().getObjectManager(
object.getClass(), DBEObjectMaker.class);
if (objectDeleter == null || !objectDeleter.canDeleteObject(object)) {
Expand Down Expand Up @@ -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())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -509,6 +506,7 @@
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat
) throws DBException {
checkDataEditPermission(contextInfo);
WebSQLExecuteInfo[] result = new WebSQLExecuteInfo[1];

DBExecUtils.tryExecuteRecover(
Expand All @@ -521,9 +519,16 @@
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<T, R> {
R apply(T obj) throws DBException;

Check warning on line 531 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'T' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java:531:17: warning: Reference type 'T' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)

Check warning on line 531 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'R' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java:531:9: warning: Reference type 'R' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)
}

@Override
Expand Down Expand Up @@ -572,7 +577,8 @@
}

@Override
public String updateResultsDataBatchScript(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, @Nullable List<WebSQLResultsRow> updatedRows, @Nullable List<WebSQLResultsRow> deletedRows, @Nullable List<WebSQLResultsRow> addedRows, WebDataFormat dataFormat) throws DBWebException {

Check warning on line 580 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'WebDataFormat' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java:580:251: warning: Reference type 'WebDataFormat' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)

Check warning on line 580 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'String' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java:580:12: warning: Reference type 'String' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)

Check warning on line 580 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Line is longer than 140 characters (found 299). Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java:580:0: warning: Line is longer than 140 characters (found 299). (com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck)
checkDataEditPermission(contextInfo);
try {
return contextInfo.getProcessor().generateResultsDataUpdateScript(
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
Expand All @@ -599,6 +605,10 @@
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() + "'");
Expand Down
7 changes: 6 additions & 1 deletion webapp/packages/core-connections/src/EConnectionFeature.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -13,4 +13,9 @@ export enum EConnectionFeature {
readOnly = 'readOnly',
provided = 'provided',
manageable = 'manageable',

restrictDataEdit = 'restrictDataEdit',
restrictScriptExecute = 'restrictScriptExecute',
restrictDataImport = 'restrictDataImport',
restrictMetadataEdit = 'restrictMetadataEdit',
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
}
Expand All @@ -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<void> {
const currentTab = this.navigationTabsService.currentTab;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const CodeFormatter = observer<Props>(function CodeFormatter({ code, conv
<ActionIconButton
name="/icons/sql_exec.svg"
title={translate('plugin_ai_chat_query_execute')}
hidden={!aiChatMessageActionsService.isAllowed}
hidden={!aiChatMessageActionsService.isAllowed(connectionKey)}
disabled={disabled}
img
onClick={execute}
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/plugin-data-import/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
20 changes: 18 additions & 2 deletions webapp/packages/plugin-data-import/src/DataImportBootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -16,23 +17,26 @@
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();
}

override register() {

Check warning on line 39 in webapp/packages/plugin-data-import/src/DataImportBootstrap.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
this.actionService.addHandler({
id: 'data-import-base-handler',
contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX],
Expand Down Expand Up @@ -96,9 +100,21 @@
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<ResultSetDataSource>;
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 (
Expand Down
3 changes: 3 additions & 0 deletions webapp/packages/plugin-data-import/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
{
"path": "../core-cli"
},
{
"path": "../core-connections"
},
{
"path": "../core-di"
},
Expand Down
6 changes: 3 additions & 3 deletions webapp/packages/plugin-data-viewer/src/DataViewerService.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -21,11 +21,11 @@
export class DataViewerService {
readonly errorActionsContainer: PlaceholderContainer<IErrorActionsContainerData>;

get canCopyData() {

Check warning on line 24 in webapp/packages/plugin-data-viewer/src/DataViewerService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
return this.sessionPermissionsResource.has(EAdminPermission.admin) || !this.dataViewerSettingsService.disableCopyData;
}

get canExportData() {

Check warning on line 28 in webapp/packages/plugin-data-viewer/src/DataViewerService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
return this.sessionPermissionsResource.has(EAdminPermission.admin) || !this.dataViewerSettingsService.disableExportData;
}

Expand All @@ -36,8 +36,8 @@
this.errorActionsContainer = new PlaceholderContainer();
}

isDataEditable(connection: Connection) {

Check warning on line 39 in webapp/packages/plugin-data-viewer/src/DataViewerService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
if (connection.readOnly) {
if (connection.readOnly || connection.features.includes(EConnectionFeature.restrictDataEdit)) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
nodeDeleteContext,
NodeManagerUtils,
} from '@cloudbeaver/core-navigation-tree';
import { isConnectionNode } from '@cloudbeaver/core-connections';
import { ConnectionInfoResource, DATA_CONTEXT_CONNECTION, EConnectionFeature, isConnectionNode } from '@cloudbeaver/core-connections';
import { ResourceKeyUtils } from '@cloudbeaver/core-resource';
import {
ACTION_DELETE,
Expand Down Expand Up @@ -59,6 +59,7 @@ export interface INodeMenuData {
LocalizationService,
NavNodeInfoResource,
NavTreeSettingsService,
ConnectionInfoResource,
])
export class NavNodeContextMenuService extends Bootstrap {
constructor(
Expand All @@ -71,6 +72,7 @@ export class NavNodeContextMenuService extends Bootstrap {
private readonly localizationService: LocalizationService,
private readonly navNodeInfoResource: NavNodeInfoResource,
private readonly navTreeSettingsService: NavTreeSettingsService,
private readonly connectionInfoResource: ConnectionInfoResource,
) {
super();
}
Expand Down Expand Up @@ -116,6 +118,13 @@ export class NavNodeContextMenuService extends Bootstrap {
contexts: [DATA_CONTEXT_NAV_NODE],
isActionApplicable: (context, action) => {
const node = context.get(DATA_CONTEXT_NAV_NODE)!;
const connectionKey = context.get(DATA_CONTEXT_CONNECTION)!;

const connection = this.connectionInfoResource.get(connectionKey);

if (connection?.features.includes(EConnectionFeature.restrictMetadataEdit)) {
return false;
}

if (NodeManagerUtils.isDatabaseObject(node.uri) || isConnectionFolder(node)) {
if (action === ACTION_RENAME) {
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export class MenuBootstrap extends Bootstrap {
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA],
isBindingApplicable: (contexts, action) => {
const sqlEditorData = contexts.get(DATA_CONTEXT_SQL_EDITOR_DATA);
return action === ACTION_SQL_EDITOR_EXECUTE_SCRIPT && sqlEditorData?.isExecutionAllowed === true;
return action === ACTION_SQL_EDITOR_EXECUTE_SCRIPT && sqlEditorData?.isExecutionAllowed() === true;
},
handler: this.sqlEditorActionHandler.bind(this),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export interface ISQLEditorData {
readonly isIncomingChanges: boolean;
readonly value: string;
readonly incomingValue?: string;
readonly isExecutionAllowed: boolean;
readonly onExecute: ISyncExecutor<boolean>;
readonly onSegmentExecute: ISyncExecutor<ISegmentExecutionData>;
readonly onFormat: ISyncExecutor<[ISQLScriptSegment, string]>;
/** displays if last getHintProposals call ended with limit */
readonly hintsLimitIsMet: boolean;

isExecutionAllowed(): boolean;
updateParserScriptsDebounced(): Promise<void>;
setScript(query: string, source?: string, cursor?: ISqlEditorCursor): void;
setCursor(begin: number, end?: number): void;
Expand Down
Loading
Loading