mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-11-30 03:08:31 +08:00
feat: acl provider (#279)
* feat: acl provider * feat: menu server hook * feat: improve code * fix: fields filter
This commit is contained in:
parent
e109e2a37e
commit
f791d43716
@ -1,6 +1,6 @@
|
||||
import { ACLResource } from './acl-resource';
|
||||
import { AvailableStrategyOptions } from './acl-available-strategy';
|
||||
import { ACL, DefineOptions } from './acl';
|
||||
import { AvailableStrategyOptions } from './acl-available-strategy';
|
||||
import { ACLResource } from './acl-resource';
|
||||
|
||||
export interface RoleActionParams {
|
||||
fields?: string[];
|
||||
|
@ -241,7 +241,7 @@ export class ACL extends EventEmitter {
|
||||
}
|
||||
|
||||
if (!permission.can || typeof permission.can !== 'object') {
|
||||
ctx.throw(403, 'no permission');
|
||||
ctx.throw(403, 'No permissions');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
ACLProvider,
|
||||
ACLShortcut,
|
||||
AdminLayout,
|
||||
AntdConfigProvider,
|
||||
@ -99,6 +100,7 @@ const providers = [
|
||||
],
|
||||
BlockSchemaComponentProvider,
|
||||
AntdSchemaComponentProvider,
|
||||
ACLProvider,
|
||||
ChinaRegionProvider,
|
||||
WorkflowRouteProvider,
|
||||
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
|
||||
|
@ -1,13 +1,103 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { Spin } from 'antd';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { useRequest } from '../api-client';
|
||||
import { useCollection } from '../collection-manager';
|
||||
import { SchemaComponentOptions, useDesignable } from '../schema-component';
|
||||
|
||||
export const ACLContext = createContext(null);
|
||||
|
||||
export function ACLProvider() {
|
||||
export const ACLProvider = (props) => {
|
||||
return (
|
||||
<ACLContext.Provider value={{}}>
|
||||
<SchemaComponentOptions
|
||||
components={{ ACLCollectionFieldProvider, ACLActionProvider, ACLMenuItemProvider, ACLCollectionProvider }}
|
||||
>
|
||||
{props.children}
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
};
|
||||
|
||||
</ACLContext.Provider>
|
||||
)
|
||||
}
|
||||
export const ACLRolesCheckProvider = (props) => {
|
||||
const { setDesignable } = useDesignable();
|
||||
const result = useRequest(
|
||||
{
|
||||
url: 'roles:check',
|
||||
},
|
||||
{
|
||||
onSuccess(data) {
|
||||
if (!data?.data?.allowConfigure) {
|
||||
setDesignable(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
if (result.loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
if (result.error) {
|
||||
return <Redirect to={'/signin'} />;
|
||||
}
|
||||
return <ACLContext.Provider value={result}>{props.children}</ACLContext.Provider>;
|
||||
};
|
||||
|
||||
export const useACLRoleContext = () => {
|
||||
const ctx = useContext(ACLContext);
|
||||
const data = ctx.data?.data;
|
||||
return {
|
||||
...data,
|
||||
getActionParams(path) {
|
||||
return data?.actions?.[path];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const ACLAllowConfigure = (props) => {
|
||||
const { allowAll, allowConfigure } = useACLRoleContext();
|
||||
if (allowAll || allowConfigure) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const ACLCollectionProvider = (props) => {
|
||||
const { name } = useCollection();
|
||||
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
|
||||
export const ACLActionProvider = (props) => {
|
||||
const { name } = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { allowAll, getActionParams } = useACLRoleContext();
|
||||
if (!name || allowAll) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
const actionName = fieldSchema['x-action'];
|
||||
const params = getActionParams([`${name}:${actionName}`]);
|
||||
if (!params) {
|
||||
return null;
|
||||
}
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
|
||||
export const ACLCollectionFieldProvider = (props) => {
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
|
||||
export const ACLMenuItemProvider = (props) => {
|
||||
const { allowAll, allowMenuItemIds = [] } = useACLRoleContext();
|
||||
const fieldSchema = useFieldSchema();
|
||||
if (allowAll) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
if (!fieldSchema['x-uid']) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
if (allowMenuItemIds.includes(fieldSchema['x-uid'])) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ACLProvider;
|
||||
|
@ -2,6 +2,7 @@ import { FormItem, FormLayout } from '@formily/antd';
|
||||
import { ArrayField } from '@formily/core';
|
||||
import { connect, useField, useForm } from '@formily/react';
|
||||
import { Checkbox, Table, Tag } from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { createContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCollectionManager, useCompile, useRecord } from '../..';
|
||||
@ -13,6 +14,7 @@ const toActionMap = (arr: any[]) => {
|
||||
arr?.forEach?.((action) => {
|
||||
if (action.name) {
|
||||
obj[action.name] = action;
|
||||
obj[action.name]['scope'] = isEmpty(action.scope) ? null : action.scope;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
@ -21,7 +23,16 @@ const toActionMap = (arr: any[]) => {
|
||||
export const RoleResourceCollectionContext = createContext<any>({});
|
||||
|
||||
export const RolesResourcesActions = connect((props) => {
|
||||
const { onChange } = props;
|
||||
// const { onChange } = props;
|
||||
const onChange = (values) => {
|
||||
const items = values.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
scope: isEmpty(item.scope) ? null : item.scope,
|
||||
};
|
||||
});
|
||||
props.onChange(items);
|
||||
};
|
||||
const form = useForm();
|
||||
const roleCollection = useRecord();
|
||||
const availableActions = useAvailableActions();
|
||||
@ -39,7 +50,7 @@ export const RolesResourcesActions = connect((props) => {
|
||||
return action?.fields?.includes(fieldName);
|
||||
};
|
||||
const availableActionsWithFields = availableActions.filter((action) => action.allowConfigureFields);
|
||||
const fieldPermissions = collection?.fields?.map((field) => {
|
||||
const fieldPermissions = collection?.fields?.filter(field => field.interface)?.map((field) => {
|
||||
const permission = { ...field };
|
||||
for (const action of availableActionsWithFields) {
|
||||
permission[action.name] = inAction(action.name, field.name);
|
||||
@ -52,7 +63,7 @@ export const RolesResourcesActions = connect((props) => {
|
||||
} else {
|
||||
actionMap[actionName] = {
|
||||
name: actionName,
|
||||
fields: collection?.fields?.map?.((item) => item.name),
|
||||
fields: collection?.fields?.filter(field => field.interface)?.map?.((item) => item.name),
|
||||
};
|
||||
}
|
||||
onChange(Object.values(actionMap));
|
||||
@ -60,12 +71,15 @@ export const RolesResourcesActions = connect((props) => {
|
||||
const setScope = (actionName, scope) => {
|
||||
if (!actionMap[actionName]) {
|
||||
toggleAction(actionName);
|
||||
actionMap[actionName]['scope'] = scope;
|
||||
} else {
|
||||
actionMap[actionName]['scope'] = scope;
|
||||
onChange(Object.values(actionMap));
|
||||
}
|
||||
actionMap[actionName]['scope'] = scope;
|
||||
};
|
||||
const allChecked = {};
|
||||
for (const action of availableActionsWithFields) {
|
||||
allChecked[action.name] = collection?.fields?.length === actionMap?.[action.name]?.fields?.length;
|
||||
allChecked[action.name] = collection?.fields?.filter(field => field.interface)?.length === actionMap?.[action.name]?.fields?.length;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
@ -122,7 +136,9 @@ export const RolesResourcesActions = connect((props) => {
|
||||
let scope = null;
|
||||
if (actionMap[item.name]) {
|
||||
enabled = true;
|
||||
scope = actionMap[item.name]['scope'];
|
||||
if (!item.onNewRecord) {
|
||||
scope = actionMap[item.name]['scope'];
|
||||
}
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
|
@ -4,7 +4,7 @@ import { useRequest } from 'ahooks';
|
||||
import template from 'lodash/template';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { TableFieldResource, useAPIClient, useRecord } from '../';
|
||||
import { ACLCollectionProvider, TableFieldResource, useAPIClient, useRecord } from '../';
|
||||
import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager';
|
||||
import { useRecordIndex } from '../record-provider';
|
||||
|
||||
@ -89,7 +89,9 @@ export const useResourceAction = (props, opts = {}) => {
|
||||
const MaybeCollectionProvider = (props) => {
|
||||
const { collection } = props;
|
||||
return collection ? (
|
||||
<CollectionProvider collection={collection}>{props.children}</CollectionProvider>
|
||||
<CollectionProvider collection={collection}>
|
||||
<ACLCollectionProvider>{props.children}</ACLCollectionProvider>
|
||||
</CollectionProvider>
|
||||
) : (
|
||||
<>{props.children}</>
|
||||
);
|
||||
@ -183,5 +185,9 @@ export const RecordLink = (props) => {
|
||||
const record = useRecord();
|
||||
const { title, to, ...others } = props;
|
||||
const compiled = template(to || '');
|
||||
return <Link {...others} to={compiled({ record: record || {} })}>{field.title}</Link>;
|
||||
return (
|
||||
<Link {...others} to={compiled({ record: record || {} })}>
|
||||
{field.title}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ import { Layout } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import {
|
||||
ACLAllowConfigure, ACLRolesCheckProvider,
|
||||
CurrentUser,
|
||||
CurrentUserProvider,
|
||||
findByUid,
|
||||
@ -97,16 +98,18 @@ const InternalAdminLayout = (props: any) => {
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', top: 0, right: 0 }}>
|
||||
<PluginManager.Toolbar
|
||||
items={[
|
||||
{ component: 'DesignableSwitch', pin: true },
|
||||
{ component: 'CollectionManagerShortcut', pin: true },
|
||||
{ component: 'ACLShortcut', pin: true },
|
||||
{ component: 'WorkflowShortcut', pin: true },
|
||||
{ component: 'SchemaTemplateShortcut', pin: true },
|
||||
{ component: 'SystemSettingsShortcut' },
|
||||
]}
|
||||
/>
|
||||
<ACLAllowConfigure>
|
||||
<PluginManager.Toolbar
|
||||
items={[
|
||||
{ component: 'DesignableSwitch', pin: true },
|
||||
{ component: 'CollectionManagerShortcut', pin: true },
|
||||
{ component: 'ACLShortcut', pin: true },
|
||||
{ component: 'WorkflowShortcut', pin: true },
|
||||
{ component: 'SchemaTemplateShortcut', pin: true },
|
||||
{ component: 'SystemSettingsShortcut' },
|
||||
]}
|
||||
/>
|
||||
</ACLAllowConfigure>
|
||||
<CurrentUser />
|
||||
</div>
|
||||
</Layout.Header>
|
||||
@ -137,13 +140,15 @@ const InternalAdminLayout = (props: any) => {
|
||||
|
||||
export const AdminLayout = (props) => {
|
||||
return (
|
||||
<RemoteSchemaTemplateManagerProvider>
|
||||
<RemoteCollectionManagerProvider>
|
||||
<CurrentUserProvider>
|
||||
<InternalAdminLayout {...props} />
|
||||
</CurrentUserProvider>
|
||||
</RemoteCollectionManagerProvider>
|
||||
</RemoteSchemaTemplateManagerProvider>
|
||||
<CurrentUserProvider>
|
||||
<RemoteSchemaTemplateManagerProvider>
|
||||
<RemoteCollectionManagerProvider>
|
||||
<ACLRolesCheckProvider>
|
||||
<InternalAdminLayout {...props} />
|
||||
</ACLRolesCheckProvider>
|
||||
</RemoteCollectionManagerProvider>
|
||||
</RemoteSchemaTemplateManagerProvider>
|
||||
</CurrentUserProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -78,9 +78,16 @@ const InsertMenuItems = (props) => {
|
||||
type: 'void',
|
||||
title,
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-decorator': 'ACLMenuItemProvider',
|
||||
'x-component-props': {
|
||||
icon,
|
||||
},
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfCreate',
|
||||
method: 'bindMenuToRole',
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -112,9 +119,16 @@ const InsertMenuItems = (props) => {
|
||||
type: 'void',
|
||||
title,
|
||||
'x-component': 'Menu.Item',
|
||||
'x-decorator': 'ACLMenuItemProvider',
|
||||
'x-component-props': {
|
||||
icon,
|
||||
},
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfCreate',
|
||||
method: 'bindMenuToRole',
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
page: {
|
||||
type: 'void',
|
||||
@ -165,10 +179,17 @@ const InsertMenuItems = (props) => {
|
||||
type: 'void',
|
||||
title,
|
||||
'x-component': 'Menu.URL',
|
||||
'x-decorator': 'ACLMenuItemProvider',
|
||||
'x-component-props': {
|
||||
icon,
|
||||
href,
|
||||
},
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfCreate',
|
||||
method: 'bindMenuToRole',
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
@ -79,9 +79,16 @@ export const GroupItem = itemWrap((props) => {
|
||||
type: 'void',
|
||||
title,
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-decorator': 'ACLMenuItemProvider',
|
||||
'x-component-props': {
|
||||
icon,
|
||||
},
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfCreate',
|
||||
method: 'bindMenuToRole',
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -128,9 +135,16 @@ export const PageMenuItem = itemWrap((props) => {
|
||||
type: 'void',
|
||||
title,
|
||||
'x-component': 'Menu.Item',
|
||||
'x-decorator': 'ACLMenuItemProvider',
|
||||
'x-component-props': {
|
||||
icon,
|
||||
},
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfCreate',
|
||||
method: 'bindMenuToRole',
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
page: {
|
||||
type: 'void',
|
||||
@ -197,10 +211,17 @@ export const LinkMenuItem = itemWrap((props) => {
|
||||
type: 'void',
|
||||
title,
|
||||
'x-component': 'Menu.URL',
|
||||
'x-decorator': 'ACLMenuItemProvider',
|
||||
'x-component-props': {
|
||||
icon,
|
||||
href,
|
||||
},
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfCreate',
|
||||
method: 'bindMenuToRole',
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
@ -4,6 +4,7 @@ import React, { createContext, useContext, useState } from 'react';
|
||||
import { useTableSelectorProps as useTsp } from '../../../block-provider/TableSelectorProvider';
|
||||
import { CollectionProvider, useCollection } from '../../../collection-manager';
|
||||
import { FormProvider, SchemaComponentOptions } from '../../core';
|
||||
import { useCompile } from '../../hooks';
|
||||
import { ActionContext, useActionContext } from '../action';
|
||||
import { useFieldNames } from './useFieldNames';
|
||||
|
||||
@ -49,7 +50,7 @@ const useAssociation = (props) => {
|
||||
return association;
|
||||
}
|
||||
return getField(fieldSchema.name);
|
||||
}
|
||||
};
|
||||
|
||||
export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
const { value, multiple, onChange } = props;
|
||||
@ -57,7 +58,14 @@ export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collectionField = useAssociation(props);
|
||||
const options = Array.isArray(value) ? value : value ? [value] : [];
|
||||
const compile = useCompile();
|
||||
const options = (Array.isArray(value) ? value : value ? [value] : []).map(option => {
|
||||
const label = option[fieldNames.label];
|
||||
return {
|
||||
...option,
|
||||
[fieldNames.label]: compile(label),
|
||||
};
|
||||
});
|
||||
const [selectedRows, setSelectedRows] = useState(options);
|
||||
const values = options?.map((option) => option[fieldNames.value]);
|
||||
return (
|
||||
|
@ -11,6 +11,7 @@ export interface RemoteSchemaComponentProps {
|
||||
scope?: any;
|
||||
uid?: string;
|
||||
onSuccess?: any;
|
||||
components?: any;
|
||||
schemaTransform?: (schema: Schema) => Schema;
|
||||
render?: any;
|
||||
hidden?: any;
|
||||
@ -20,7 +21,7 @@ export interface RemoteSchemaComponentProps {
|
||||
const defaultTransform = (s: Schema) => s;
|
||||
|
||||
const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) => {
|
||||
const { onlyRenderProperties, hidden, scope, uid, onSuccess, schemaTransform = defaultTransform } = props;
|
||||
const { onlyRenderProperties, hidden, scope, uid, components, onSuccess, schemaTransform = defaultTransform } = props;
|
||||
const { reset } = useSchemaComponentContext();
|
||||
const conf = {
|
||||
url: `/uiSchemas:${onlyRenderProperties ? 'getProperties' : 'getJsonSchema'}/${uid}`,
|
||||
@ -41,7 +42,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
|
||||
}
|
||||
return (
|
||||
<FormProvider form={form}>
|
||||
<SchemaComponent memoized scope={scope} schema={schemaTransform(data?.data || {})} />
|
||||
<SchemaComponent memoized components={components} scope={scope} schema={schemaTransform(data?.data || {})} />
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
@ -67,6 +67,7 @@ export const CalendarActionInitializers = {
|
||||
component: 'CreateActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -44,6 +44,7 @@ export const TableActionColumnInitializers = (props: any) => {
|
||||
schema: {
|
||||
'x-component': 'Action.Link',
|
||||
'x-action': 'view',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -53,6 +54,7 @@ export const TableActionColumnInitializers = (props: any) => {
|
||||
schema: {
|
||||
'x-component': 'Action.Link',
|
||||
'x-action': 'update',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -62,6 +64,7 @@ export const TableActionColumnInitializers = (props: any) => {
|
||||
schema: {
|
||||
'x-component': 'Action.Link',
|
||||
'x-action': 'destroy',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -24,6 +24,7 @@ export const TableActionInitializers = {
|
||||
component: 'CreateActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -32,6 +33,7 @@ export const TableActionInitializers = {
|
||||
component: 'BulkDestroyActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -2,14 +2,22 @@ import { useCookieState } from 'ahooks';
|
||||
import { Menu, Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient } from '../api-client';
|
||||
import { useCurrentUserContext } from './CurrentUserProvider';
|
||||
|
||||
const useCurrentRoles = () => {
|
||||
const { data } = useCurrentUserContext();
|
||||
return data?.data?.roles || [];
|
||||
return [
|
||||
...(data?.data?.roles || []),
|
||||
{
|
||||
title: 'Anonymous',
|
||||
name: 'anonymous',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const SwitchRole = () => {
|
||||
const api = useAPIClient();
|
||||
const roles = useCurrentRoles();
|
||||
const { t } = useTranslation();
|
||||
const [roleName, setRoleName] = useCookieState('currentRoleName', {
|
||||
@ -30,8 +38,9 @@ export const SwitchRole = () => {
|
||||
}}
|
||||
options={roles}
|
||||
value={roleName}
|
||||
onChange={(roleName) => {
|
||||
onChange={async (roleName) => {
|
||||
setRoleName(roleName);
|
||||
await api.resource('users').setDefaultRole({ values: { roleName } });
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
|
@ -9,9 +9,10 @@ export async function checkAction(ctx, next) {
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
role: ctx.app.acl.getRole(currentRole).toJSON(),
|
||||
...ctx.app.acl.getRole(currentRole).toJSON(),
|
||||
allowAll: currentRole === 'root',
|
||||
allowConfigure: roleInstance.get('allowConfigure'),
|
||||
roleMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')),
|
||||
allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,11 @@ export default {
|
||||
name: 'default',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'hidden',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'allowConfigure',
|
||||
|
@ -245,9 +245,14 @@ export class PluginACL extends Plugin {
|
||||
if (plugin.constructor.name !== 'UsersPlugin') {
|
||||
return;
|
||||
}
|
||||
const repository = this.app.db.getRepository('roles');
|
||||
await repository.createMany({
|
||||
const roles = this.app.db.getRepository('roles');
|
||||
await roles.createMany({
|
||||
records: [
|
||||
{
|
||||
name: 'root',
|
||||
title: 'Root',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
name: 'admin',
|
||||
title: 'Admin',
|
||||
@ -263,8 +268,42 @@ export class PluginACL extends Plugin {
|
||||
},
|
||||
],
|
||||
});
|
||||
const rolesResourcesScopes = this.app.db.getRepository('rolesResourcesScopes');
|
||||
await rolesResourcesScopes.createMany({
|
||||
records: [
|
||||
{
|
||||
name: '{{t("All records")}}',
|
||||
scope: {},
|
||||
},
|
||||
{
|
||||
name: '{{t("Own records")}}',
|
||||
scope: {
|
||||
createdById: '{{ ctx.state.currentUser.id }}',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
this.app.acl.skip('roles.menuUiSchemas', 'set', 'logged-in');
|
||||
this.app.acl.skip('roles.menuUiSchemas', 'toggle', 'logged-in');
|
||||
this.app.acl.skip('roles.menuUiSchemas', 'list', 'logged-in');
|
||||
this.app.acl.skip('roles', 'check', 'logged-in');
|
||||
this.app.acl.skip('*', '*', (ctx) => {
|
||||
return ctx.state.currentRole === 'root';
|
||||
});
|
||||
|
||||
// root role
|
||||
this.app.resourcer.use(async (ctx, next) => {
|
||||
const { actionName, resourceName } = ctx.action.params;
|
||||
if (actionName === 'list' && resourceName === 'roles') {
|
||||
ctx.action.mergeParams({
|
||||
filter: {
|
||||
'name.$ne': 'root',
|
||||
},
|
||||
});
|
||||
}
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
async install() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bindMenuToRole } from './bind-menu-to-role';
|
||||
import { hookFactory } from './factory';
|
||||
import { removeSchema } from './remove-schema';
|
||||
import { bindMenuToRole } from './bind-menu-to-row';
|
||||
import { removeParentsIfNoChildren } from './remove-parents-if-no-children';
|
||||
import { removeSchema } from './remove-schema';
|
||||
|
||||
const hooks = [
|
||||
hookFactory('onCollectionDestroy', 'removeSchema', removeSchema),
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Database, { BelongsToManyRepository } from '@nocobase/database';
|
||||
import PluginACL from '@nocobase/plugin-acl';
|
||||
import UsersPlugin from '@nocobase/plugin-users';
|
||||
import { MockServer, mockServer } from '@nocobase/test';
|
||||
import { userPluginConfig } from './utils';
|
||||
import UsersPlugin from '@nocobase/plugin-users';
|
||||
|
||||
describe('role', () => {
|
||||
let api: MockServer;
|
||||
@ -108,12 +108,12 @@ describe('role', () => {
|
||||
.agent()
|
||||
.post('/users:setDefaultRole')
|
||||
.send({
|
||||
defaultRole: 'test2',
|
||||
roleName: 'test2',
|
||||
})
|
||||
.set({
|
||||
Authorization: `Bearer ${userToken}`,
|
||||
});
|
||||
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
|
||||
const userRoles = await userRolesRepo.find();
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
import { PasswordField } from '@nocobase/database';
|
||||
import UsersPlugin from '../server';
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
export async function check(ctx: Context, next: Next) {
|
||||
@ -154,9 +152,14 @@ export async function changePassword(ctx: Context, next: Next) {
|
||||
|
||||
export async function setDefaultRole(ctx: Context, next: Next) {
|
||||
const {
|
||||
values: { defaultRole },
|
||||
values: { roleName },
|
||||
} = ctx.action.params;
|
||||
|
||||
if (roleName == 'anonymous') {
|
||||
ctx.body = 'ok';
|
||||
return next();
|
||||
}
|
||||
|
||||
const currentUserId = ctx.state.currentUser.id;
|
||||
|
||||
await ctx.db.getRepository('rolesUsers').update({
|
||||
@ -171,7 +174,7 @@ export async function setDefaultRole(ctx: Context, next: Next) {
|
||||
await ctx.db.getRepository('rolesUsers').update({
|
||||
filter: {
|
||||
userId: currentUserId,
|
||||
roleName: defaultRole,
|
||||
roleName,
|
||||
},
|
||||
values: {
|
||||
default: true,
|
||||
|
@ -13,6 +13,13 @@ export function parseToken(options?: { plugin: UsersPlugin }) {
|
||||
}
|
||||
|
||||
function setCurrentRole(ctx, user) {
|
||||
const roleName = ctx.get('X-Role');
|
||||
|
||||
if (roleName === 'anonymous') {
|
||||
ctx.state.currentRole = roleName;
|
||||
return;
|
||||
}
|
||||
|
||||
const userRoles = user.get('roles');
|
||||
let userRole;
|
||||
|
||||
|
@ -118,7 +118,7 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
||||
nickname: adminNickname,
|
||||
email: adminEmail,
|
||||
password: adminPassword,
|
||||
roles: ['admin'],
|
||||
roles: ['root', 'admin'],
|
||||
},
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user