From cf7131a36f8c427c33e47619de0a36806fade3ac Mon Sep 17 00:00:00 2001 From: Katherine Date: Wed, 26 Feb 2025 17:19:25 +0800 Subject: [PATCH] feat: add permission control for buttons with blacklist support (#6254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(versions): 😊 publish v1.6.0-alpha.24 * chore(versions): 😊 publish v1.6.0-alpha.25 * feat: support extending frontend filter operators (#6085) * feat: operator extension * fix: bug * refactor: code improve * fix: jsonLogic --------- Co-authored-by: chenos * refactor: remove registerOperators (#6224) * refactor(plugin-workflow): trigger workflow action settings (#6143) * refactor(plugin-workflow): move bind workflow settings to plugin * refactor(plugin-block-workbench): move component to core * refactor(plugin-block-workbench): adjust component api * fix(plugin-workflow-action-trigger): fix test cases * fix(plugin-workflow): fix component scope * fix(plugin-workflow-action-trigger): fix test cases * chore(versions): 😊 publish v1.6.0-alpha.26 * feat: support the extension of preset fields in collections (#6183) * feat: support the extension of preset fields in collections * fix: bug * fix: bug * fix: bug * refactor: create collection * fix: config * fix: test case * refactor: code improve * refactor: code improve * fix: bug * fix: bug --------- Co-authored-by: chenos * feat: support for the extension of optional fields for Kanban, Calendar, and Formula Field plugins (#6076) * feat: kanban field extention * fix: bug * fix: bug * fix: bug * fix: bug * feat: calender title fields * feat: background color fields * fix: bug * fix: bug * feat: formula field expression support field * feat: preset fields * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * refactor: code improve * fix: bug * fix: bug * fix: bug * fix: bug * refactor: code improve * revert: preset fields * refactor: code improve * refactor: code improve * fix: bug * fix: bug * fix: bug * refactor: code improve * fix: bug * refactor: code improve * refactor: code improve * fix: bug * fix: locale * refactor: code improve * fix: bug * refactor: code improve * refactor: code improve * refactor: code improve * refactor: locale * fix: test * fix: bug * fix: test * fix: test --------- Co-authored-by: chenos * chore(versions): 😊 publish v1.6.0-alpha.27 * fix(data-source-main): update order * fix: improve code * fix: getFontColor (#6241) * chore(versions): 😊 publish v1.6.0-alpha.28 * feat: add permission control for buttons with blacklist support * fix: print action e2e test (#6256) * fix: print action e2e test * fix: test * fix: bug * refactor: custom request * fix: merge bug * fix: merge bug * fix: merge bug * fix: merge bug * fix: bug * fix: bug * fix: bug * test: e2e test * fix: bug * test: e2e test * fix: bug * fix: bug * fix: bug * fix: bug * fix: test --------- Co-authored-by: nocobase[bot] <179432756+nocobase[bot]@users.noreply.github.com> Co-authored-by: chenos Co-authored-by: Junyi --- lerna.json | 4 +- packages/core/client/src/acl/ACLProvider.tsx | 14 +- .../delete/DestroyActionInitializer.tsx | 1 + .../actions/link/LinkActionInitializer.tsx | 1 + .../link/customizeLinkActionSettings.tsx | 7 +- .../PopupActionInitializer.tsx | 1 + .../UpdateActionInitializer.tsx | 1 + .../customizePopupActionSettings.tsx | 3 +- packages/core/client/src/pm/index.tsx | 6 - .../SchemaSettingAccessControl.tsx | 86 ++++++ .../src/schema-settings/SchemaSettings.tsx | 2 +- .../__e2e__/actionAccessControl.test.ts | 68 +++++ .../src/schema-settings/__e2e__/template.ts | 251 ++++++++++++++++++ .../core/client/src/schema-settings/index.ts | 1 + .../src/server/actions/role-check.ts | 18 ++ .../client/components/CustomRequestAction.tsx | 32 +-- .../CustomRequestActionDesigner.tsx | 53 +--- .../src/client/schemaSettings.ts | 9 +- .../src/client/schemas/CustomRequestACL.ts | 39 --- .../src/client/schemas/index.ts | 1 - .../src/client/WorkbenchAction.tsx | 24 +- ...stomRequestActionSchemaInitializerItem.tsx | 7 + ...rkbenchLinkActionSchemaInitializerItem.tsx | 7 + ...kbenchPopupActionSchemaInitializerItem.tsx | 7 + ...rkbenchScanActionSchemaInitializerItem.tsx | 7 + .../__tests__/view/view-collection.test.ts | 6 +- .../src/client/index.ts | 1 - .../collections/uiButtonSchemasRoles.ts | 16 ++ .../src/server/collections/uiSchemas.ts | 11 + .../src/server/server.ts | 2 +- 30 files changed, 543 insertions(+), 143 deletions(-) create mode 100644 packages/core/client/src/schema-settings/SchemaSettingAccessControl.tsx create mode 100644 packages/core/client/src/schema-settings/__e2e__/actionAccessControl.test.ts delete mode 100644 packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/CustomRequestACL.ts create mode 100644 packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiButtonSchemasRoles.ts diff --git a/lerna.json b/lerna.json index 271627cab0..43c6f6c90f 100644 --- a/lerna.json +++ b/lerna.json @@ -2,9 +2,7 @@ "version": "1.6.0-beta.12", "npmClient": "yarn", "useWorkspaces": true, - "npmClientArgs": [ - "--ignore-engines" - ], + "npmClientArgs": ["--ignore-engines"], "command": { "version": { "forcePublish": true, diff --git a/packages/core/client/src/acl/ACLProvider.tsx b/packages/core/client/src/acl/ACLProvider.tsx index 14404f27d8..81519b5339 100644 --- a/packages/core/client/src/acl/ACLProvider.tsx +++ b/packages/core/client/src/acl/ACLProvider.tsx @@ -304,30 +304,32 @@ export const ACLActionProvider = (props) => { const collection = useCollection(); const recordPkValue = useRecordPkValue(); const resource = useResourceName(); - const { parseAction } = useACLRoleContext(); + const { parseAction, uiButtonSchemasBlacklist } = useACLRoleContext(); const schema = useFieldSchema(); + const currentUid = schema['x-uid']; let actionPath = schema['x-acl-action']; const editablePath = ['create', 'update', 'destroy', 'importXlsx']; - if (!actionPath && resource && schema['x-action']) { + if (!actionPath && resource && schema['x-action'] && editablePath.includes(schema['x-action'])) { actionPath = `${resource}:${schema['x-action']}`; } - if (!actionPath?.includes(':')) { + if (actionPath && !actionPath?.includes(':')) { actionPath = `${resource}:${actionPath}`; } const params = useMemo( - () => parseAction(actionPath, { schema, recordPkValue }), + () => actionPath && parseAction(actionPath, { schema, recordPkValue }), [parseAction, actionPath, schema, recordPkValue], ); - + if (uiButtonSchemasBlacklist.includes(currentUid)) { + return {props.children}; + } if (!actionPath) { return <>{props.children}; } if (!resource) { return <>{props.children}; } - if (!params) { return {props.children}; } diff --git a/packages/core/client/src/modules/actions/delete/DestroyActionInitializer.tsx b/packages/core/client/src/modules/actions/delete/DestroyActionInitializer.tsx index 4380ded4a4..fa1bf1f9fc 100644 --- a/packages/core/client/src/modules/actions/delete/DestroyActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/delete/DestroyActionInitializer.tsx @@ -14,6 +14,7 @@ export const DestroyActionInitializer = (props) => { const schema = { title: '{{ t("Delete") }}', 'x-action': 'destroy', + 'x-acl-action': 'destroy', 'x-component': 'Action', 'x-use-component-props': 'useDestroyActionProps', 'x-toolbar': 'ActionSchemaToolbar', diff --git a/packages/core/client/src/modules/actions/link/LinkActionInitializer.tsx b/packages/core/client/src/modules/actions/link/LinkActionInitializer.tsx index 7ed05c9345..9959633b0d 100644 --- a/packages/core/client/src/modules/actions/link/LinkActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/link/LinkActionInitializer.tsx @@ -20,6 +20,7 @@ export const LinkActionInitializer = (props) => { 'x-settings': 'actionSettings:link', 'x-component': props?.['x-component'] || 'Action.Link', 'x-use-component-props': 'useLinkActionProps', + 'x-decorator': 'ACLActionProvider', }; const itemConfig = useSchemaInitializerItem(); diff --git a/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx b/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx index e1b0e1bf5c..c5a0cb87c8 100644 --- a/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx +++ b/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx @@ -16,7 +16,11 @@ import { useSchemaToolbar } from '../../../application'; import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings'; import { useCollection_deprecated } from '../../../collection-manager'; import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; -import { SchemaSettingsLinkageRules, SchemaSettingsModalItem } from '../../../schema-settings'; +import { + SchemaSettingsLinkageRules, + SchemaSettingsModalItem, + SchemaSettingAccessControl, +} from '../../../schema-settings'; import { useURLAndHTMLSchema } from './useURLAndHTMLSchema'; export const SchemaSettingsActionLinkItem: FC = () => { @@ -103,6 +107,7 @@ export const customizeLinkActionSettings = new SchemaSettings({ }; }, }, + SchemaSettingAccessControl, { name: 'remove', sort: 100, diff --git a/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx b/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx index 881e503410..dc2ac49b86 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx @@ -28,6 +28,7 @@ export const PopupActionInitializer = (props) => { openMode: defaultOpenMode, refreshDataBlockRequest: true, }, + 'x-decorator': 'ACLActionProvider', properties: { drawer: { type: 'void', diff --git a/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx b/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx index 7de0c0e3a8..99143ea724 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx @@ -20,6 +20,7 @@ export const UpdateActionInitializer = (props) => { type: 'void', title: '{{ t("Edit") }}', 'x-action': 'update', + 'x-acl-action': 'update', 'x-toolbar': 'ActionSchemaToolbar', 'x-settings': 'actionSettings:edit', 'x-component': 'Action', diff --git a/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx b/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx index fe50338b5f..4e86e008d8 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx @@ -14,7 +14,7 @@ import { useCollection_deprecated } from '../../../collection-manager'; import { useCollection } from '../../../data-source'; import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; -import { SchemaSettingsLinkageRules } from '../../../schema-settings'; +import { SchemaSettingsLinkageRules, SchemaSettingAccessControl } from '../../../schema-settings'; import { useOpenModeContext } from '../../popup/OpenModeProvider'; import { useCurrentPopupRecord } from '../../variable/variablesProvider/VariablePopupRecordProvider'; @@ -57,6 +57,7 @@ export const customizePopupActionSettings = new SchemaSettings({ }; }, }, + SchemaSettingAccessControl, { name: 'remove', sort: 100, diff --git a/packages/core/client/src/pm/index.tsx b/packages/core/client/src/pm/index.tsx index 06cb7133c0..0ee4017f49 100644 --- a/packages/core/client/src/pm/index.tsx +++ b/packages/core/client/src/pm/index.tsx @@ -29,12 +29,6 @@ export class PMPlugin extends Plugin { } addSettings() { - // this.app.pluginSettingsManager.add('acl', { - // title: '{{t("Access control")}}', - // icon: 'LockOutlined', - // Component: ACLPane, - // aclSnippet: 'pm.acl.roles', - // }); this.app.pluginSettingsManager.add('ui-schema-storage', { title: '{{t("Block templates")}}', icon: 'LayoutOutlined', diff --git a/packages/core/client/src/schema-settings/SchemaSettingAccessControl.tsx b/packages/core/client/src/schema-settings/SchemaSettingAccessControl.tsx new file mode 100644 index 0000000000..e646d1ba74 --- /dev/null +++ b/packages/core/client/src/schema-settings/SchemaSettingAccessControl.tsx @@ -0,0 +1,86 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { App } from 'antd'; +import { SchemaSettingsActionModalItem } from './SchemaSettings'; +import { useAPIClient } from '../api-client/hooks/useAPIClient'; +import { useRequest } from '../api-client'; +import { useACLContext } from '../acl'; + +export function AccessControl() { + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const apiClient = useAPIClient(); + const resource = apiClient.resource('uiSchemas.roles', fieldSchema['x-uid']); + const { message } = App.useApp(); + const { refresh, data }: any = useRequest( + { + url: `/uiSchemas/${fieldSchema['x-uid']}/roles:list`, + }, + { + manual: true, + }, + ); + const { refresh: refreshRoleCheck } = useACLContext(); + const AccessControl = ( + !data && refresh()} + onSubmit={async ({ roles }) => { + await resource.set({ values: roles.map((v) => v.name) }); + await refreshRoleCheck(); + return message.success(t('Saved successfully')); + }} + /> + ); + return AccessControl; +} + +export const SchemaSettingAccessControl = { + name: 'accessControl', + Component: AccessControl, + useVisible() { + const fieldSchema = useFieldSchema(); + return fieldSchema['x-decorator'] === 'ACLActionProvider'; + }, +}; diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 68c9399eba..e7542c2d97 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -725,8 +725,8 @@ export const SchemaSettingsActionModalItem: FC { + test('popup、link、custom request support access control', async ({ page, mockPage, mockRecord }) => { + const nocoPage = await mockPage(accessControlActionWithTable).waitForInit(); + await nocoPage.goto(); + await page.getByLabel('block-item-CardItem-users-').hover(); + //popup + await page.getByLabel('action-Action-Popup-customize').hover(); + await page.getByLabel('designer-schema-settings-Action-actionSettings:popup-users').hover(); + await expect(page.getByRole('menuitem', { name: 'Access control' })).toBeVisible(); + await page.getByLabel('designer-schema-settings-Action-actionSettings:popup-users').hover(); + await page.mouse.move(300, 0); + + //link + await page.getByLabel('action-Action-Link-customize:').hover(); + + await page.getByLabel('designer-schema-settings-Action-actionSettings:link-users').hover(); + await expect(page.getByRole('menuitem', { name: 'Access control' })).toBeVisible(); + await page.mouse.move(300, 0); + + // custom request + await page.getByLabel('action-CustomRequestAction-').hover(); + await page.getByLabel('designer-schema-settings-CustomRequestAction-actionSettings:customRequest-users').hover(); + await expect(page.getByRole('menuitem', { name: 'Access control' })).toBeVisible(); + await page.mouse.move(300, 0); + }); + test('access control with role ', async ({ page, mockPage, mockRecord }) => { + const nocoPage = await mockPage(accessControlActionWithTable).waitForInit(); + await nocoPage.goto(); + await page.getByLabel('block-item-CardItem-users-').hover(); + //popup only member can see + await page.getByLabel('action-Action-Popup-customize').hover(); + await page.getByLabel('designer-schema-settings-Action-actionSettings:popup-users').hover(); + await page.getByRole('menuitem', { name: 'Access control' }).click(); + await page.getByLabel('block-item-RemoteSelect-users').click(); + await page.getByText('Member').click(); + await page.getByRole('option', { name: 'Member' }).locator('div').click(); + await page.getByLabel('block-item-RemoteSelect-users').click(); + await page.getByRole('button', { name: 'Submit' }).click(); + + //root 角色有权限 + await expect(page.getByLabel('action-Action-Popup-customize')).toBeVisible(); + + //切换 为admin + await page.getByTestId('user-center-button').click(); + await page.getByText('Switch roleRoot').click(); + await page.getByText('Admin', { exact: true }).click(); + await expect(page.getByLabel('action-Action-Popup-customize')).not.toBeVisible(); + + // 切换 为 member + + await page.getByTestId('user-center-button').click(); + await page.getByText('Switch roleAdmin').click(); + await page.getByText('Member').click(); + await expect(page.getByLabel('action-Action-Popup-customize')).toBeVisible(); + }); +}); diff --git a/packages/core/client/src/schema-settings/__e2e__/template.ts b/packages/core/client/src/schema-settings/__e2e__/template.ts index 81ddd8af78..af6bfdde87 100644 --- a/packages/core/client/src/schema-settings/__e2e__/template.ts +++ b/packages/core/client/src/schema-settings/__e2e__/template.ts @@ -1964,3 +1964,254 @@ export const whenClearingARelationshipFieldTheValueOfTheAssociatedFieldShouldBeC 'x-index': 1, }, }; + +export const accessControlActionWithTable = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.6.0-beta.9', + properties: { + fvgd0c2akgf: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.6.0-beta.9', + properties: { + '0c4zy47hhyq': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.6.0-beta.9', + properties: { + b1y881c771g: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.6.0-beta.9', + properties: { + hccefuo80kx: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action': 'users:view', + 'x-decorator': 'DetailsBlockProvider', + 'x-use-decorator-props': 'useDetailsWithPaginationDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'users', + readPretty: true, + action: 'list', + params: { + pageSize: 1, + }, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:detailsWithPagination', + 'x-component': 'CardItem', + 'x-app-version': '1.6.0-beta.9', + properties: { + fctprt7i7ut: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Details', + 'x-read-pretty': true, + 'x-use-component-props': 'useDetailsWithPaginationProps', + 'x-app-version': '1.6.0-beta.9', + properties: { + b2xrbq4a060: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'details:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + 'x-app-version': '1.6.0-beta.9', + properties: { + e2ccvx3c7o5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:popup', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + refreshDataBlockRequest: true, + }, + 'x-decorator': 'ACLActionProvider', + 'x-action-context': { + dataSource: 'main', + collection: 'users', + }, + 'x-app-version': '1.6.0-beta.9', + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + 'x-app-version': '1.6.0-beta.9', + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + 'x-app-version': '1.6.0-beta.9', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': '1.6.0-beta.9', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + 'x-app-version': '1.6.0-beta.9', + 'x-uid': '3qfg5qfvsth', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'n0574ydwdua', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'm0039599o9q', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5t7ol82dt74', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'gn76wlmk619', + 'x-async': false, + 'x-index': 1, + }, + '3jat4vour4y': { + _isJSONSchemaObject: true, + version: '2.0', + title: '{{ t("Custom request") }}', + 'x-component': 'CustomRequestAction', + 'x-action': 'customize:form:request', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:customRequest', + 'x-decorator': 'CustomRequestAction.Decorator', + 'x-action-settings': { + onSuccess: { + manualClose: false, + redirecting: false, + successMessage: '{{t("Request success")}}', + }, + }, + type: 'void', + 'x-app-version': '1.6.0-beta.9', + 'x-uid': 'gu0shkseqoa', + 'x-async': false, + 'x-index': 2, + }, + '0tevnuro5d4': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Link") }}', + 'x-action': 'customize:link', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:link', + 'x-component': 'Action', + 'x-use-component-props': 'useLinkActionProps', + 'x-decorator': 'ACLActionProvider', + 'x-app-version': '1.6.0-beta.9', + 'x-uid': 'swtrz2mpnm4', + 'x-async': false, + 'x-index': 3, + }, + }, + 'x-uid': '32u6fnlj0ti', + 'x-async': false, + 'x-index': 1, + }, + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'details:configureFields', + 'x-app-version': '1.6.0-beta.9', + 'x-uid': '21ksski5wgs', + 'x-async': false, + 'x-index': 2, + }, + pagination: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Pagination', + 'x-use-component-props': 'useDetailsPaginationProps', + 'x-app-version': '1.6.0-beta.9', + 'x-uid': '2cz3ilk7hmv', + 'x-async': false, + 'x-index': 3, + }, + }, + 'x-uid': 'f6xosucy75q', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '33tsqeap83o', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ppglkb7uvt8', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'af78vam04ux', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'askp9xe9uag', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'huon5vcb8u8', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/core/client/src/schema-settings/index.ts b/packages/core/client/src/schema-settings/index.ts index 0202ecff16..83b37a62f0 100644 --- a/packages/core/client/src/schema-settings/index.ts +++ b/packages/core/client/src/schema-settings/index.ts @@ -26,6 +26,7 @@ export * from './SchemaSettingsRenderEngine'; export * from './hooks/useGetAriaLabelOfDesigner'; export * from './hooks/useIsAllowToSetDefaultValue'; export * from './SchemaSettingsLayoutItem'; +export * from './SchemaSettingAccessControl'; export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFilter'; export * from './isPatternDisabled'; export { SchemaSettingsPlugin } from './SchemaSettingsPlugin'; diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/actions/role-check.ts b/packages/plugins/@nocobase/plugin-acl/src/server/actions/role-check.ts index 02bccf3c5d..817bad7853 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/actions/role-check.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/actions/role-check.ts @@ -43,6 +43,23 @@ export async function checkAction(ctx, next) { } const availableActions = ctx.app.acl.getAvailableActions(); + let uiButtonSchemasBlacklist = []; + if (currentRole !== 'root') { + const eqCurrentRoleList = await ctx.db + .getRepository('uiButtonSchemasRoles') + .find({ + filter: { 'roleName.$eq': currentRole }, + }) + .then((list) => list.map((v) => v.uid)); + + const NECurrentRoleList = await ctx.db + .getRepository('uiButtonSchemasRoles') + .find({ + filter: { 'roleName.$ne': currentRole }, + }) + .then((list) => list.map((v) => v.uid)); + uiButtonSchemasBlacklist = NECurrentRoleList.filter((uid) => !eqCurrentRoleList.includes(uid)); + } ctx.body = { ...role.toJSON(), @@ -53,6 +70,7 @@ export async function checkAction(ctx, next) { allowConfigure: roleInstance.get('allowConfigure'), allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')), allowAnonymous: !!anonymous, + uiButtonSchemasBlacklist, }; await next(); diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestAction.tsx b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestAction.tsx index 7ab03dfcc1..9f5e62db8e 100644 --- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestAction.tsx +++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestAction.tsx @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Action, useAPIClient, useRequest, withDynamicSchemaProps } from '@nocobase/client'; +import { Action, useAPIClient, useRequest, withDynamicSchemaProps, ACLActionProvider } from '@nocobase/client'; import React from 'react'; import { useFieldSchema } from '@formily/react'; import { listByCurrentRoleUrl } from '../constants'; @@ -16,23 +16,23 @@ import { CustomRequestActionDesigner } from './CustomRequestActionDesigner'; export const CustomRequestActionACLDecorator = (props) => { const apiClient = useAPIClient(); - const isRoot = apiClient.auth.role === 'root'; - const fieldSchema = useFieldSchema(); - const { data } = useRequest<{ data: string[] }>( - { - url: listByCurrentRoleUrl, - }, - { - manual: isRoot, - cacheKey: listByCurrentRoleUrl, - }, - ); + // const isRoot = apiClient.auth.role === 'root'; + // const fieldSchema = useFieldSchema(); + // const { data } = useRequest<{ data: string[] }>( + // { + // url: listByCurrentRoleUrl, + // }, + // { + // manual: isRoot, + // cacheKey: listByCurrentRoleUrl, + // }, + // ); - if (!isRoot && !data?.data?.includes(fieldSchema?.['x-uid'])) { - return null; - } + // // if (!isRoot && !data?.data?.includes(fieldSchema?.['x-uid'])) { + // // return null; + // // } - return props.children; + return {props.children}; }; const components = { diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestActionDesigner.tsx b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestActionDesigner.tsx index 9b0ba18b0e..d6d4a6fb7f 100644 --- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestActionDesigner.tsx +++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/components/CustomRequestActionDesigner.tsx @@ -17,15 +17,13 @@ import { useCollection_deprecated, useDataSourceKey, useDesignable, - useRequest, + SchemaSettingAccessControl, } from '@nocobase/client'; -import { App } from 'antd'; import React, { useMemo } from 'react'; -import { listByCurrentRoleUrl } from '../constants'; import { useCustomRequestVariableOptions, useGetCustomRequest } from '../hooks'; import { useCustomRequestsResource } from '../hooks/useCustomRequestsResource'; import { useTranslation } from '../locale'; -import { CustomRequestACLSchema, CustomRequestConfigurationFieldsSchema } from '../schemas'; +import { CustomRequestConfigurationFieldsSchema } from '../schemas'; export function CustomRequestSettingsItem() { const { t } = useTranslation(); @@ -81,48 +79,6 @@ export function CustomRequestSettingsItem() { ); } -export function CustomRequestACL() { - const { t } = useTranslation(); - const fieldSchema = useFieldSchema(); - const customRequestsResource = useCustomRequestsResource(); - const { message } = App.useApp(); - const { data, refresh } = useGetCustomRequest(); - const { refresh: refreshRoleCustomKeys } = useRequest<{ data: string[] }>( - { - url: listByCurrentRoleUrl, - }, - { - manual: true, - cacheKey: listByCurrentRoleUrl, - }, - ); - - return ( - <> - !data && refresh()} - onSubmit={async ({ roles }) => { - await customRequestsResource.updateOrCreate({ - values: { - key: fieldSchema['x-uid'], - roles, - }, - filterKeys: ['key'], - }); - refresh(); - refreshRoleCustomKeys(); - return message.success(t('Saved successfully')); - }} - /> - - ); -} - /** * @deprecated */ @@ -137,10 +93,7 @@ export const customRequestActionSettings = new SchemaSettings({ name: 'request settings', Component: CustomRequestSettingsItem, }, - { - name: 'accessControl', - Component: CustomRequestACL, - }, + SchemaSettingAccessControl, ], }, ], diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts index 340c4d3128..ef4c43deb7 100644 --- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts +++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts @@ -19,8 +19,9 @@ import { useCollection, useCollectionRecord, useSchemaToolbar, + SchemaSettingAccessControl, } from '@nocobase/client'; -import { CustomRequestACL, CustomRequestSettingsItem } from './components/CustomRequestActionDesigner'; +import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner'; export const customizeCustomRequestActionSettings = new SchemaSettings({ name: 'actionSettings:customRequest', @@ -64,8 +65,10 @@ export const customizeCustomRequestActionSettings = new SchemaSettings({ Component: CustomRequestSettingsItem, }, { - name: 'accessControl', - Component: CustomRequestACL, + ...SchemaSettingAccessControl, + useVisible() { + return true; + }, }, { name: 'refreshDataBlockRequest', diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/CustomRequestACL.ts b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/CustomRequestACL.ts deleted file mode 100644 index 438a04fda4..0000000000 --- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/CustomRequestACL.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { DEFAULT_DATA_SOURCE_KEY } from '@nocobase/client'; -import { generateNTemplate } from '../locale'; - -export const CustomRequestACLSchema = { - type: 'object', - properties: { - roles: { - type: 'array', - title: generateNTemplate('Roles'), - 'x-decorator': 'FormItem', - 'x-decorator-props': { - tooltip: generateNTemplate('If not set, all roles can see this action'), - }, - 'x-component': 'RemoteSelect', - 'x-component-props': { - multiple: true, - objectValue: true, - dataSource: DEFAULT_DATA_SOURCE_KEY, - service: { - resource: 'roles', - }, - manual: false, - fieldNames: { - label: 'title', - value: 'name', - }, - }, - }, - }, -}; diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/index.ts b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/index.ts index 704e6b99fe..f8eaa24886 100644 --- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/index.ts +++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemas/index.ts @@ -8,4 +8,3 @@ */ export * from './CustomRequestConfigurationFields'; -export * from './CustomRequestACL'; diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx index 11367f561c..f549e6fead 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx @@ -8,7 +8,7 @@ */ import { useFieldSchema } from '@formily/react'; -import { Action, Icon, useCompile, useComponent, withDynamicSchemaProps } from '@nocobase/client'; +import { Action, Icon, useCompile, useComponent, withDynamicSchemaProps, ACLActionProvider } from '@nocobase/client'; import { Avatar } from 'antd'; import { createStyles } from 'antd-style'; import React, { useContext } from 'react'; @@ -46,9 +46,9 @@ function Button() { const compile = useCompile(); const title = compile(fieldSchema.title); return layout === WorkbenchLayout.Grid ? ( -
+
} /> -
{fieldSchema.title}
+
{title}
) : ( {title} @@ -61,13 +61,15 @@ export const WorkbenchAction = withDynamicSchemaProps((props) => { const fieldSchema = useFieldSchema(); const Component = useComponent(props?.targetComponent) || Action; return ( - } - confirmTitle={fieldSchema.title} - /> + + } + confirmTitle={fieldSchema.title} + /> + ); }); diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx index 9b1da436a0..833cce7a8e 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx @@ -13,6 +13,7 @@ import { SchemaSettingsActionLinkItem, useSchemaInitializer, ModalActionSchemaInitializerItem, + SchemaSettingAccessControl, } from '@nocobase/client'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -30,6 +31,12 @@ export const workbenchActionSettingsCustomRequest = new SchemaSettings({ name: 'editLink', Component: SchemaSettingsActionLinkItem, }, + { + ...SchemaSettingAccessControl, + useVisible() { + return true; + }, + }, { sort: 800, name: 'd1', diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx index b7aa1e2a78..7b17a862f6 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx @@ -14,6 +14,7 @@ import { useSchemaInitializer, useSchemaInitializerItem, ModalActionSchemaInitializerItem, + SchemaSettingAccessControl, } from '@nocobase/client'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -32,6 +33,12 @@ export const workbenchActionSettingsLink = new SchemaSettings({ name: 'editLink', Component: SchemaSettingsActionLinkItem, }, + { + ...SchemaSettingAccessControl, + useVisible() { + return true; + }, + }, { sort: 800, name: 'd1', diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx index f49beda7d3..3d1e7b09c5 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx @@ -14,6 +14,7 @@ import { useSchemaInitializer, useOpenModeContext, ModalActionSchemaInitializerItem, + SchemaSettingAccessControl, } from '@nocobase/client'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -44,6 +45,12 @@ export const workbenchActionSettingsPopup = new SchemaSettings({ }; }, }, + { + ...SchemaSettingAccessControl, + useVisible() { + return true; + }, + }, { sort: 800, name: 'd1', diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx index de32050e93..47f2c87587 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx @@ -14,6 +14,7 @@ import { useSchemaInitializer, useSchemaInitializerItem, ModalActionSchemaInitializerItem, + SchemaSettingAccessControl, } from '@nocobase/client'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -28,6 +29,12 @@ export const workbenchActionSettingsScanQrCode = new SchemaSettings({ return { hasIconColor: true }; }, }, + { + ...SchemaSettingAccessControl, + useVisible() { + return true; + }, + }, { name: 'd1', type: 'divider', diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/view/view-collection.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/view/view-collection.test.ts index f7728fc08d..ac45d9077b 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/view/view-collection.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/view/view-collection.test.ts @@ -264,7 +264,7 @@ describe('view collection', function () { const Role = await collectionRepository.create({ values: { - name: 'roles', + name: 'my_roles', fields: [{ name: 'name', type: 'string' }], }, context: {}, @@ -276,7 +276,7 @@ describe('view collection', function () { values: [{ name: 'u1' }, { name: 'u2' }], }); - await db.getRepository('roles').create({ + await db.getRepository('my_roles').create({ values: [{ name: 'r1' }, { name: 'r2' }], }); @@ -327,7 +327,7 @@ describe('view collection', function () { collectionName: 'users', name: 'roles', type: 'belongsToMany', - target: 'roles', + target: 'my_roles', through: 'test_view', foreignKey: 'user_id', otherKey: 'role_id', diff --git a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/client/index.ts b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/client/index.ts index 52dbe9d66b..3977dad7b5 100644 --- a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/client/index.ts +++ b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/client/index.ts @@ -8,7 +8,6 @@ */ import { Plugin } from '@nocobase/client'; - class PluginUISchemaStorageClient extends Plugin { async load() {} } diff --git a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiButtonSchemasRoles.ts b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiButtonSchemasRoles.ts new file mode 100644 index 0000000000..27e6f9b8b5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiButtonSchemasRoles.ts @@ -0,0 +1,16 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { defineCollection } from '@nocobase/database'; + +export default defineCollection({ + name: 'uiButtonSchemasRoles', + dumpRules: 'required', + migrationRules: ['overwrite', 'schema-only'], +}); diff --git a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiSchemas.ts b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiSchemas.ts index cd39ac8c45..8bffeae2a5 100644 --- a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiSchemas.ts +++ b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/collections/uiSchemas.ts @@ -39,5 +39,16 @@ export default { name: 'schema', defaultValue: {}, }, + { + type: 'belongsToMany', + name: 'roles', + onDelete: 'CASCADE', + through: 'uiButtonSchemasRoles', + target: 'roles', + foreignKey: 'uid', + otherKey: 'roleName', + sourceKey: 'x-uid', + targetKey: 'name', + }, ], } as CollectionOptions; diff --git a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/server.ts b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/server.ts index e1b7157b85..1ad9a34e0a 100644 --- a/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-ui-schema-storage/src/server/server.ts @@ -42,7 +42,7 @@ export class PluginUISchemaStorageServer extends Plugin { this.app.acl.registerSnippet({ name: 'ui.uiSchemas', - actions: ['uiSchemas:*'], + actions: ['uiSchemas:*', 'uiSchemas.roles:list', 'uiSchemas.roles:set'], }); db.on('uiSchemas.beforeCreate', function setUid(model) {