refactor: permissions plugin
This commit is contained in:
parent
8f58e3ec27
commit
fe12c67ba6
7
packages/plugin-permissions-v04/.npmignore
Normal file
7
packages/plugin-permissions-v04/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
|
__tests__
|
||||||
|
tsconfig.json
|
||||||
|
src
|
||||||
|
.fatherrc.ts
|
13
packages/plugin-permissions-v04/package.json
Normal file
13
packages/plugin-permissions-v04/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-permissions-v04",
|
||||||
|
"version": "0.4.0-alpha.7",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@nocobase/actions": "^0.4.0-alpha.7",
|
||||||
|
"@nocobase/database": "^0.4.0-alpha.7",
|
||||||
|
"@nocobase/resourcer": "^0.4.0-alpha.7",
|
||||||
|
"@nocobase/server": "^0.4.0-alpha.7"
|
||||||
|
},
|
||||||
|
"gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e"
|
||||||
|
}
|
194
packages/plugin-permissions-v04/src/collections/roles.ts
Normal file
194
packages/plugin-permissions-v04/src/collections/roles.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
import { ROLE_TYPE_ROOT, ROLE_TYPE_USER, ROLE_TYPE_ANONYMOUS } from '../constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'roles',
|
||||||
|
title: '权限组配置',
|
||||||
|
developerMode: true,
|
||||||
|
internal: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
interface: 'string',
|
||||||
|
title: '权限组名称',
|
||||||
|
comment: '角色名称',
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInForm: true,
|
||||||
|
showInDetail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'select',
|
||||||
|
title: '角色类型',
|
||||||
|
type: 'integer',
|
||||||
|
name: 'type',
|
||||||
|
developerMode: true,
|
||||||
|
dataSource: [
|
||||||
|
{ value: ROLE_TYPE_ROOT, label: '系统角色' },
|
||||||
|
{ value: ROLE_TYPE_ANONYMOUS, label: '匿名角色' },
|
||||||
|
{ value: ROLE_TYPE_USER, label: '自定义角色' },
|
||||||
|
],
|
||||||
|
defaultValue: ROLE_TYPE_USER,
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInDetail: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'boolean',
|
||||||
|
title: '默认权限组',
|
||||||
|
type: 'radio',
|
||||||
|
name: 'default',
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInForm: true,
|
||||||
|
showInDetail: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'textarea',
|
||||||
|
title: '描述',
|
||||||
|
type: 'text',
|
||||||
|
name: 'description',
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInForm: true,
|
||||||
|
showInDetail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO(feature): 用户组后续考虑
|
||||||
|
// TODO(feature): 用户表应通过插件配置关联,考虑到今后会有多账户系统的情况
|
||||||
|
{
|
||||||
|
interface: 'linkTo',
|
||||||
|
title: '用户',
|
||||||
|
comment: '关联的用户表',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'users',
|
||||||
|
target: 'users',
|
||||||
|
through: 'users_roles'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'linkTo',
|
||||||
|
title: '数据表',
|
||||||
|
comment: '包含的以数据表分组的权限集',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'collections',
|
||||||
|
through: 'permissions',
|
||||||
|
targetKey: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'linkTo',
|
||||||
|
title: '页面',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'menus',
|
||||||
|
target: 'menus',
|
||||||
|
through: 'menus_permissions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: '权限集(方便访问)',
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'permissions'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'list',
|
||||||
|
title: '查看',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// type: 'get',
|
||||||
|
// name: 'get',
|
||||||
|
// title: '详情',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
type: 'create',
|
||||||
|
name: 'create',
|
||||||
|
title: '新增',
|
||||||
|
viewName: 'form',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'update',
|
||||||
|
name: 'update',
|
||||||
|
title: '编辑',
|
||||||
|
viewName: 'form',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'destroy',
|
||||||
|
name: 'destroy',
|
||||||
|
title: '删除',
|
||||||
|
filter: {
|
||||||
|
type: ROLE_TYPE_USER,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
views_v2: [
|
||||||
|
{
|
||||||
|
developerMode: true,
|
||||||
|
type: 'table',
|
||||||
|
name: 'table',
|
||||||
|
title: '全部数据',
|
||||||
|
labelField: 'title',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'create',
|
||||||
|
type: 'create',
|
||||||
|
title: '新增',
|
||||||
|
viewName: 'form',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'destroy',
|
||||||
|
type: 'destroy',
|
||||||
|
title: '删除',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: ['title', 'description', 'default'],
|
||||||
|
detailsOpenMode: 'drawer', // window
|
||||||
|
details: ['descriptions', 'collections', 'pages'],
|
||||||
|
sort: ['id'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
developerMode: true,
|
||||||
|
type: 'descriptions',
|
||||||
|
name: 'descriptions',
|
||||||
|
title: '详情',
|
||||||
|
fields: ['title', 'description', 'default'],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'update',
|
||||||
|
type: 'update',
|
||||||
|
title: '编辑',
|
||||||
|
viewName: 'form',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
developerMode: true,
|
||||||
|
type: 'form',
|
||||||
|
name: 'form',
|
||||||
|
title: '表单',
|
||||||
|
fields: ['title', 'description', 'default'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
developerMode: true,
|
||||||
|
type: 'table',
|
||||||
|
dataSourceType: 'association',
|
||||||
|
name: 'collections',
|
||||||
|
title: '数据操作权限',
|
||||||
|
targetViewName: 'permissions_table',
|
||||||
|
targetFieldName: 'collections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
developerMode: true,
|
||||||
|
type: 'table',
|
||||||
|
dataSourceType: 'association',
|
||||||
|
name: 'pages',
|
||||||
|
title: '菜单和页面权限',
|
||||||
|
targetViewName: 'permissions_table',
|
||||||
|
targetFieldName: 'menus',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as TableOptions;
|
166
packages/plugin-permissions-v04/src/server.ts
Normal file
166
packages/plugin-permissions-v04/src/server.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import { Operator } from '@nocobase/database';
|
||||||
|
import * as collectionsRolesActions from './actions/collections.roles';
|
||||||
|
import * as rolesCollectionsActions from './actions/roles.collections';
|
||||||
|
import AccessController, { PermissionParams } from './AccessController';
|
||||||
|
|
||||||
|
// API
|
||||||
|
// const permissions = ctx.app.getPluginInstance('permissions');
|
||||||
|
// const result: boolean = permissions.can(key, options);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class Permissions {
|
||||||
|
readonly app: Application;
|
||||||
|
readonly options: any;
|
||||||
|
|
||||||
|
constructor(app: Application, options) {
|
||||||
|
this.app = app;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
const { database, resourcer } = app;
|
||||||
|
|
||||||
|
database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(collectionsRolesActions).forEach(actionName => {
|
||||||
|
resourcer.registerActionHandler(`collections.roles:${actionName}`, collectionsRolesActions[actionName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(rolesCollectionsActions).forEach(actionName => {
|
||||||
|
resourcer.registerActionHandler(`roles.collections:${actionName}`, rolesCollectionsActions[actionName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
database.getModel('collections').addHook('afterCreate', async (model: any, options) => {
|
||||||
|
await model.updateAssociations({
|
||||||
|
scopes: [
|
||||||
|
{
|
||||||
|
title: '全部数据',
|
||||||
|
filter: {},
|
||||||
|
locked: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户自己的数据',
|
||||||
|
filter: {
|
||||||
|
"created_by_id.$currentUser": true,
|
||||||
|
},
|
||||||
|
locked: true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
database.getModel('users').addHook('afterCreate', async(model, options) => {
|
||||||
|
const { transaction = await database.sequelize.transaction() } = options;
|
||||||
|
const Role = database.getModel('roles');
|
||||||
|
const defaultRole = await Role.findOne({ where: { default: true }, transaction });
|
||||||
|
if (defaultRole) {
|
||||||
|
// @ts-ignore
|
||||||
|
await model.addRole(defaultRole, { transaction });
|
||||||
|
}
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 针对“自己创建的” scope 添加特殊的操作符以生成查询条件
|
||||||
|
if (!Operator.has('$currentUser')) {
|
||||||
|
Operator.register('$currentUser', (value, { ctx }) => {
|
||||||
|
const user = ctx.state.currentUser;
|
||||||
|
return { [Op.eq]: user[user.constructor.primaryKeyAttribute] };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resourcer.use(this.injection);
|
||||||
|
resourcer.use(this.middleware);
|
||||||
|
|
||||||
|
resourcer.use(async (ctx, next) => {
|
||||||
|
const { resourceName } = ctx.action.params;
|
||||||
|
if (resourceName === 'action_logs' && !await ctx.ac.isRoot()) {
|
||||||
|
const collections = await ctx.ac.getCollections();
|
||||||
|
ctx.action.mergeParams({
|
||||||
|
filter: {
|
||||||
|
'collection_name.in': collections
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
injection = async (ctx, next) => {
|
||||||
|
ctx.ac = new AccessController(ctx);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
|
||||||
|
middleware = async (ctx, next) => {
|
||||||
|
const {
|
||||||
|
associatedName,
|
||||||
|
resourceField,
|
||||||
|
resourceName,
|
||||||
|
actionName
|
||||||
|
} = ctx.action.params;
|
||||||
|
|
||||||
|
let result: PermissionParams = false;
|
||||||
|
|
||||||
|
// 关系数据的权限
|
||||||
|
if (associatedName && resourceField) {
|
||||||
|
if (resourceField.options.id && resourceField.options.interface === 'subTable') {
|
||||||
|
if (await ctx.ac.isRoot()) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const permissions = await ctx.ac.getPermissions();
|
||||||
|
const FieldPermission = ctx.db.getModel('fields_permissions');
|
||||||
|
const fps = await FieldPermission.findAll({
|
||||||
|
where: {
|
||||||
|
field_id: resourceField.options.id,
|
||||||
|
permission_id: {
|
||||||
|
[Op.in]: permissions.map(p => p.id),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (fps.length) {
|
||||||
|
for (const fp of fps) {
|
||||||
|
if (Array.isArray(fp.actions) && fp.actions.includes(`${resourceField.options.collection_name}:${actionName}`)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.reject(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = await ctx.ac.can(resourceField.options.target).act(actionName).any();
|
||||||
|
} else {
|
||||||
|
result = await ctx.ac.can(resourceName).act(actionName).any();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return this.reject(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result === true) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.action.mergeParams({
|
||||||
|
filter: result.filter,
|
||||||
|
// TODO: 在 fields 改进之前,先注释掉
|
||||||
|
// fields: result.fields.map(item => item.get('field').get('name'))
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
|
||||||
|
reject(ctx) {
|
||||||
|
ctx.throw(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function (options = {}) {
|
||||||
|
const instance = new Permissions(this, options);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'action_permissions',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'actionName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'scope',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'collection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'role',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'fieldPermissions',
|
||||||
|
target: 'field_permissions',
|
||||||
|
foreignKey: 'action_permission_id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'fields',
|
||||||
|
through: 'field_permissions',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as TableOptions;
|
@ -1,194 +1,24 @@
|
|||||||
import { TableOptions } from '@nocobase/database';
|
import { TableOptions } from '@nocobase/database';
|
||||||
import { ROLE_TYPE_ROOT, ROLE_TYPE_USER, ROLE_TYPE_ANONYMOUS } from '../constants';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'roles',
|
name: 'roles',
|
||||||
title: '权限组配置',
|
sortable: 'sort',
|
||||||
developerMode: true,
|
|
||||||
internal: true,
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
interface: 'string',
|
type: 'uid',
|
||||||
title: '权限组名称',
|
name: 'name',
|
||||||
comment: '角色名称',
|
primaryKey: true,
|
||||||
|
prefix: 't_',
|
||||||
|
},
|
||||||
|
{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'title',
|
name: 'title',
|
||||||
component: {
|
|
||||||
showInTable: true,
|
|
||||||
showInForm: true,
|
|
||||||
showInDetail: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
interface: 'select',
|
|
||||||
title: '角色类型',
|
|
||||||
type: 'integer',
|
|
||||||
name: 'type',
|
|
||||||
developerMode: true,
|
|
||||||
dataSource: [
|
|
||||||
{ value: ROLE_TYPE_ROOT, label: '系统角色' },
|
|
||||||
{ value: ROLE_TYPE_ANONYMOUS, label: '匿名角色' },
|
|
||||||
{ value: ROLE_TYPE_USER, label: '自定义角色' },
|
|
||||||
],
|
|
||||||
defaultValue: ROLE_TYPE_USER,
|
|
||||||
component: {
|
|
||||||
showInTable: true,
|
|
||||||
showInDetail: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
interface: 'boolean',
|
|
||||||
title: '默认权限组',
|
|
||||||
type: 'radio',
|
|
||||||
name: 'default',
|
|
||||||
component: {
|
|
||||||
showInTable: true,
|
|
||||||
showInForm: true,
|
|
||||||
showInDetail: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
interface: 'textarea',
|
|
||||||
title: '描述',
|
|
||||||
type: 'text',
|
|
||||||
name: 'description',
|
|
||||||
component: {
|
|
||||||
showInTable: true,
|
|
||||||
showInForm: true,
|
|
||||||
showInDetail: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// TODO(feature): 用户组后续考虑
|
|
||||||
// TODO(feature): 用户表应通过插件配置关联,考虑到今后会有多账户系统的情况
|
|
||||||
{
|
|
||||||
interface: 'linkTo',
|
|
||||||
title: '用户',
|
|
||||||
comment: '关联的用户表',
|
|
||||||
type: 'belongsToMany',
|
type: 'belongsToMany',
|
||||||
name: 'users',
|
name: 'users',
|
||||||
target: 'users',
|
target: 'users',
|
||||||
through: 'users_roles'
|
through: 'roles_users'
|
||||||
},
|
|
||||||
{
|
|
||||||
interface: 'linkTo',
|
|
||||||
title: '数据表',
|
|
||||||
comment: '包含的以数据表分组的权限集',
|
|
||||||
type: 'belongsToMany',
|
|
||||||
name: 'collections',
|
|
||||||
through: 'permissions',
|
|
||||||
targetKey: 'name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
interface: 'linkTo',
|
|
||||||
title: '页面',
|
|
||||||
type: 'belongsToMany',
|
|
||||||
name: 'menus',
|
|
||||||
target: 'menus',
|
|
||||||
through: 'menus_permissions',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
comment: '权限集(方便访问)',
|
|
||||||
type: 'hasMany',
|
|
||||||
name: 'permissions'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'list',
|
|
||||||
title: '查看',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// type: 'get',
|
|
||||||
// name: 'get',
|
|
||||||
// title: '详情',
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
type: 'create',
|
|
||||||
name: 'create',
|
|
||||||
title: '新增',
|
|
||||||
viewName: 'form',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'update',
|
|
||||||
name: 'update',
|
|
||||||
title: '编辑',
|
|
||||||
viewName: 'form',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'destroy',
|
|
||||||
name: 'destroy',
|
|
||||||
title: '删除',
|
|
||||||
filter: {
|
|
||||||
type: ROLE_TYPE_USER,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
views_v2: [
|
|
||||||
{
|
|
||||||
developerMode: true,
|
|
||||||
type: 'table',
|
|
||||||
name: 'table',
|
|
||||||
title: '全部数据',
|
|
||||||
labelField: 'title',
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
name: 'create',
|
|
||||||
type: 'create',
|
|
||||||
title: '新增',
|
|
||||||
viewName: 'form',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'destroy',
|
|
||||||
type: 'destroy',
|
|
||||||
title: '删除',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fields: ['title', 'description', 'default'],
|
|
||||||
detailsOpenMode: 'drawer', // window
|
|
||||||
details: ['descriptions', 'collections', 'pages'],
|
|
||||||
sort: ['id'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
developerMode: true,
|
|
||||||
type: 'descriptions',
|
|
||||||
name: 'descriptions',
|
|
||||||
title: '详情',
|
|
||||||
fields: ['title', 'description', 'default'],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
name: 'update',
|
|
||||||
type: 'update',
|
|
||||||
title: '编辑',
|
|
||||||
viewName: 'form',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
developerMode: true,
|
|
||||||
type: 'form',
|
|
||||||
name: 'form',
|
|
||||||
title: '表单',
|
|
||||||
fields: ['title', 'description', 'default'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
developerMode: true,
|
|
||||||
type: 'table',
|
|
||||||
dataSourceType: 'association',
|
|
||||||
name: 'collections',
|
|
||||||
title: '数据操作权限',
|
|
||||||
targetViewName: 'permissions_table',
|
|
||||||
targetFieldName: 'collections',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
developerMode: true,
|
|
||||||
type: 'table',
|
|
||||||
dataSourceType: 'association',
|
|
||||||
name: 'pages',
|
|
||||||
title: '菜单和页面权限',
|
|
||||||
targetViewName: 'permissions_table',
|
|
||||||
targetFieldName: 'menus',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as TableOptions;
|
} as TableOptions;
|
||||||
|
20
packages/plugin-permissions/src/collections/scopes.ts
Normal file
20
packages/plugin-permissions/src/collections/scopes.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'scopes',
|
||||||
|
sortable: 'sort',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'json',
|
||||||
|
name: 'filter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'collection',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as TableOptions;
|
@ -1,166 +1,12 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Op } from 'sequelize';
|
import Database from '@nocobase/database';
|
||||||
import { Application } from '@nocobase/server';
|
import Resourcer from '@nocobase/resourcer';
|
||||||
import { Operator } from '@nocobase/database';
|
|
||||||
import * as collectionsRolesActions from './actions/collections.roles';
|
|
||||||
import * as rolesCollectionsActions from './actions/roles.collections';
|
|
||||||
import AccessController, { PermissionParams } from './AccessController';
|
|
||||||
|
|
||||||
// API
|
export default async function () {
|
||||||
// const permissions = ctx.app.getPluginInstance('permissions');
|
const database: Database = this.database;
|
||||||
// const result: boolean = permissions.can(key, options);
|
const resourcer: Resourcer = this.resourcer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Permissions {
|
|
||||||
readonly app: Application;
|
|
||||||
readonly options: any;
|
|
||||||
|
|
||||||
constructor(app: Application, options) {
|
|
||||||
this.app = app;
|
|
||||||
this.options = options;
|
|
||||||
|
|
||||||
const { database, resourcer } = app;
|
|
||||||
|
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(collectionsRolesActions).forEach(actionName => {
|
|
||||||
resourcer.registerActionHandler(`collections.roles:${actionName}`, collectionsRolesActions[actionName]);
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(rolesCollectionsActions).forEach(actionName => {
|
|
||||||
resourcer.registerActionHandler(`roles.collections:${actionName}`, rolesCollectionsActions[actionName]);
|
|
||||||
});
|
|
||||||
|
|
||||||
database.getModel('collections').addHook('afterCreate', async (model: any, options) => {
|
|
||||||
await model.updateAssociations({
|
|
||||||
scopes: [
|
|
||||||
{
|
|
||||||
title: '全部数据',
|
|
||||||
filter: {},
|
|
||||||
locked: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '用户自己的数据',
|
|
||||||
filter: {
|
|
||||||
"created_by_id.$currentUser": true,
|
|
||||||
},
|
|
||||||
locked: true
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
database.getModel('users').addHook('afterCreate', async(model, options) => {
|
|
||||||
const { transaction = await database.sequelize.transaction() } = options;
|
|
||||||
const Role = database.getModel('roles');
|
|
||||||
const defaultRole = await Role.findOne({ where: { default: true }, transaction });
|
|
||||||
if (defaultRole) {
|
|
||||||
// @ts-ignore
|
|
||||||
await model.addRole(defaultRole, { transaction });
|
|
||||||
}
|
|
||||||
if (!options.transaction) {
|
|
||||||
await transaction.commit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 针对“自己创建的” scope 添加特殊的操作符以生成查询条件
|
|
||||||
if (!Operator.has('$currentUser')) {
|
|
||||||
Operator.register('$currentUser', (value, { ctx }) => {
|
|
||||||
const user = ctx.state.currentUser;
|
|
||||||
return { [Op.eq]: user[user.constructor.primaryKeyAttribute] };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resourcer.use(this.injection);
|
|
||||||
resourcer.use(this.middleware);
|
|
||||||
|
|
||||||
resourcer.use(async (ctx, next) => {
|
|
||||||
const { resourceName } = ctx.action.params;
|
|
||||||
if (resourceName === 'action_logs' && !await ctx.ac.isRoot()) {
|
|
||||||
const collections = await ctx.ac.getCollections();
|
|
||||||
ctx.action.mergeParams({
|
|
||||||
filter: {
|
|
||||||
'collection_name.in': collections
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
injection = async (ctx, next) => {
|
|
||||||
ctx.ac = new AccessController(ctx);
|
|
||||||
|
|
||||||
return next();
|
|
||||||
};
|
|
||||||
|
|
||||||
middleware = async (ctx, next) => {
|
|
||||||
const {
|
|
||||||
associatedName,
|
|
||||||
resourceField,
|
|
||||||
resourceName,
|
|
||||||
actionName
|
|
||||||
} = ctx.action.params;
|
|
||||||
|
|
||||||
let result: PermissionParams = false;
|
|
||||||
|
|
||||||
// 关系数据的权限
|
|
||||||
if (associatedName && resourceField) {
|
|
||||||
if (resourceField.options.id && resourceField.options.interface === 'subTable') {
|
|
||||||
if (await ctx.ac.isRoot()) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
const permissions = await ctx.ac.getPermissions();
|
|
||||||
const FieldPermission = ctx.db.getModel('fields_permissions');
|
|
||||||
const fps = await FieldPermission.findAll({
|
|
||||||
where: {
|
|
||||||
field_id: resourceField.options.id,
|
|
||||||
permission_id: {
|
|
||||||
[Op.in]: permissions.map(p => p.id),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (fps.length) {
|
|
||||||
for (const fp of fps) {
|
|
||||||
if (Array.isArray(fp.actions) && fp.actions.includes(`${resourceField.options.collection_name}:${actionName}`)) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.reject(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = await ctx.ac.can(resourceField.options.target).act(actionName).any();
|
|
||||||
} else {
|
|
||||||
result = await ctx.ac.can(resourceName).act(actionName).any();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return this.reject(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result === true) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.action.mergeParams({
|
|
||||||
filter: result.filter,
|
|
||||||
// TODO: 在 fields 改进之前,先注释掉
|
|
||||||
// fields: result.fields.map(item => item.get('field').get('name'))
|
|
||||||
});
|
|
||||||
|
|
||||||
return next();
|
|
||||||
};
|
|
||||||
|
|
||||||
reject(ctx) {
|
|
||||||
ctx.throw(404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function (options = {}) {
|
|
||||||
const instance = new Permissions(this, options);
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user