diff --git a/packages/core/acl/src/acl-resource.ts b/packages/core/acl/src/acl-resource.ts index f3592deeb..f16e94ba8 100644 --- a/packages/core/acl/src/acl-resource.ts +++ b/packages/core/acl/src/acl-resource.ts @@ -1,5 +1,6 @@ import { ACLRole, RoleActionParams } from './acl-role'; import { ACL, ListenerContext } from './acl'; +import lodash from 'lodash'; export type ResourceActions = { [key: string]: RoleActionParams }; @@ -35,7 +36,15 @@ export class ACLResource { } getAction(name: string) { - return this.actions.get(name) || this.actions.get(this.acl.resolveActionAlias(name)); + const result = this.actions.get(name) || this.actions.get(this.acl.resolveActionAlias(name)); + if (!result) { + return null; + } + + if (Array.isArray(result.fields) && result.fields.length > 0) { + result.fields = lodash.uniq(result.fields); + } + return lodash.cloneDeep(result); } setAction(name: string, params: RoleActionParams) { diff --git a/packages/core/acl/src/acl.ts b/packages/core/acl/src/acl.ts index 9d682cbe0..678587f64 100644 --- a/packages/core/acl/src/acl.ts +++ b/packages/core/acl/src/acl.ts @@ -45,20 +45,15 @@ interface CanArgs { } export class ACL extends EventEmitter { - protected availableActions = new Map(); public availableStrategy = new Map(); - protected fixedParamsManager = new FixedParamsManager(); - - protected middlewares: Toposort; - public allowManager = new AllowManager(this); public snippetManager = new SnippetManager(); - roles = new Map(); - actionAlias = new Map(); - configResources: string[] = []; + protected availableActions = new Map(); + protected fixedParamsManager = new FixedParamsManager(); + protected middlewares: Toposort; constructor() { super(); @@ -100,68 +95,6 @@ export class ACL extends EventEmitter { this.addCoreMiddleware(); } - protected addCoreMiddleware() { - const acl = this; - - const filterParams = (ctx, resourceName, params) => { - if (params?.filter?.createdById) { - const collection = ctx.db.getCollection(resourceName); - if (!collection || !collection.getField('createdById')) { - return lodash.omit(params, 'filter.createdById'); - } - } - - return params; - }; - - this.middlewares.add( - async (ctx, next) => { - const resourcerAction: Action = ctx.action; - const { resourceName, actionName } = ctx.action; - - const permission = ctx.permission; - - ctx.log?.info && ctx.log.info('ctx permission', permission); - - if ((!permission.can || typeof permission.can !== 'object') && !permission.skip) { - ctx.throw(403, 'No permissions'); - return; - } - - const params = permission.can?.params || acl.fixedParamsManager.getParams(resourceName, actionName); - - ctx.log?.info && ctx.log.info('acl params', params); - - if (params && resourcerAction.mergeParams) { - const filteredParams = filterParams(ctx, resourceName, params); - const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx); - - ctx.permission.parsedParams = parsedParams; - ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams); - ctx.permission.rawParams = lodash.cloneDeep(resourcerAction.params); - resourcerAction.mergeParams(parsedParams, { - appends: (x, y) => { - if (!x) { - return []; - } - if (!y) { - return x; - } - return (x as any[]).filter((i) => y.includes(i.split('.').shift())); - }, - }); - ctx.permission.mergedParams = lodash.cloneDeep(resourcerAction.params); - } - - await next(); - }, - { - tag: 'core', - group: 'core', - }, - ); - } - define(options: DefineOptions): ACLRole { const roleName = options.role; const role = new ACLRole(this, roleName); @@ -302,10 +235,6 @@ export class ACL extends EventEmitter { return null; } - protected isAvailableAction(actionName: string) { - return this.availableActions.has(this.resolveActionAlias(actionName)); - } - public resolveActionAlias(action: string) { return this.actionAlias.get(action) ? this.actionAlias.get(action) : action; } @@ -360,7 +289,9 @@ export class ACL extends EventEmitter { const { resourceName, actionName } = ctx.action; ctx.can = (options: Omit) => { - return acl.can({ role: roleName, ...options }); + const canResult = acl.can({ role: roleName, ...options }); + + return canResult; }; ctx.permission = { @@ -397,4 +328,70 @@ export class ACL extends EventEmitter { registerSnippet(snippet: SnippetOptions) { this.snippetManager.register(snippet); } + + protected addCoreMiddleware() { + const acl = this; + + const filterParams = (ctx, resourceName, params) => { + if (params?.filter?.createdById) { + const collection = ctx.db.getCollection(resourceName); + if (!collection || !collection.getField('createdById')) { + return lodash.omit(params, 'filter.createdById'); + } + } + + return params; + }; + + this.middlewares.add( + async (ctx, next) => { + const resourcerAction: Action = ctx.action; + const { resourceName, actionName } = ctx.action; + + const permission = ctx.permission; + + ctx.log?.info && ctx.log.info('ctx permission', permission); + + if ((!permission.can || typeof permission.can !== 'object') && !permission.skip) { + ctx.throw(403, 'No permissions'); + return; + } + + const params = permission.can?.params || acl.fixedParamsManager.getParams(resourceName, actionName); + + ctx.log?.info && ctx.log.info('acl params', params); + + if (params && resourcerAction.mergeParams) { + const filteredParams = filterParams(ctx, resourceName, params); + const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx); + + ctx.permission.parsedParams = parsedParams; + ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams); + ctx.permission.rawParams = lodash.cloneDeep(resourcerAction.params); + resourcerAction.mergeParams(parsedParams, { + appends: (x, y) => { + if (!x) { + return []; + } + if (!y) { + return x; + } + return (x as any[]).filter((i) => y.includes(i.split('.').shift())); + }, + }); + ctx.permission.mergedParams = lodash.cloneDeep(resourcerAction.params); + } + + await next(); + }, + { + tag: 'core', + group: 'core', + }, + ); + } + + protected isAvailableAction(actionName: string) { + return this.availableActions.has(this.resolveActionAlias(actionName)); + } } diff --git a/packages/plugins/acl/src/__tests__/acl.test.ts b/packages/plugins/acl/src/__tests__/acl.test.ts index cb8de2908..7f40c3c00 100644 --- a/packages/plugins/acl/src/__tests__/acl.test.ts +++ b/packages/plugins/acl/src/__tests__/acl.test.ts @@ -12,6 +12,8 @@ describe('acl', () => { let admin; let adminAgent; + let userPlugin; + let uiSchemaRepository: UiSchemaRepository; afterEach(async () => { @@ -30,7 +32,7 @@ describe('acl', () => { }, }); - const userPlugin = app.getPlugin('users') as UsersPlugin; + userPlugin = app.getPlugin('users') as UsersPlugin; adminAgent = app.agent().auth( userPlugin.jwtService.sign({ @@ -42,6 +44,131 @@ describe('acl', () => { uiSchemaRepository = db.getRepository('uiSchemas'); }); + test('append createById', async () => { + const Company = await db.getRepository('collections').create({ + context: {}, + values: { + name: 'companies', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'hasMany', + name: 'users', + }, + ], + }, + }); + + const Repair = await db.getRepository('collections').create({ + context: {}, + values: { + name: 'repairs', + createdBy: true, + fields: [ + { + type: 'belongsTo', + name: 'company', + }, + { + type: 'string', + name: 'name', + }, + ], + }, + }); + + const c1 = await db.getRepository('companies').create({ + values: { + name: 'c1', + }, + }); + + await db.getRepository('roles').create({ + values: { + name: 'test-role', + }, + }); + + await adminAgent.resource('roles.resources', 'test-role').create({ + values: { + name: 'repairs', + usingActionsConfig: true, + actions: [ + { + name: 'list', + fields: ['id'], + }, + ], + }, + }); + + const u1 = await db.getRepository('users').create({ + values: { + name: 'u1', + company: { id: c1.get('id') }, + roles: ['test-role'], + }, + }); + + const r1 = await db.getRepository('repairs').create({ + values: { + name: 'r1', + company: { id: c1.get('id') }, + }, + }); + + userPlugin = app.getPlugin('users') as UsersPlugin; + + const testAgent = app.agent().auth( + userPlugin.jwtService.sign({ + userId: u1.get('id'), + }), + { type: 'bearer' }, + ); + + // @ts-ignore + const response1 = await testAgent.resource('repairs').list({ + filter: { + company: { + id: { + $isVar: 'currentUser.company.id', + }, + }, + }, + }); + + // @ts-ignore + const response2 = await testAgent.resource('repairs').list({ + filter: { + company: { + id: { + $isVar: 'currentUser.company.id', + }, + }, + }, + }); + + // @ts-ignore + const response3 = await testAgent.resource('repairs').list({ + filter: { + company: { + id: { + $isVar: 'currentUser.company.id', + }, + }, + }, + }); + + const acl = app.acl; + const canResult = acl.can({ role: 'test-role', resource: 'repairs', action: 'list' }); + const params = canResult['params']; + + expect(params['fields']).toHaveLength(3); + }); + it('should not have permission to list comments', async () => { await db.getCollection('collections').repository.create({ values: {