diff --git a/list b/list new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts b/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts index c8d810a6fb2..311af4f2b3d 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts @@ -8,7 +8,7 @@ */ import lodash from 'lodash'; -import { snakeCase } from '@nocobase/database'; +import { Model, snakeCase } from '@nocobase/database'; import { NoPermissionError } from '@nocobase/acl'; function createWithACLMetaMiddleware() { @@ -165,6 +165,13 @@ function createWithACLMetaMiddleware() { ...params, context: actionCtx, }); + if (ctx.action?.params?.appends || queryParams?.appends) { + const queryParamsWithAppends = queryParams?.appends || []; + const ctxActionParamsWithAppends = ctx.action?.params?.appends || []; + const appends = queryParamsWithAppends.filter((x) => ctxActionParamsWithAppends.includes(x)); + queryParams.include = appends; + queryParams.appends = appends; + } const actionSql = ctx.db.sequelize.queryInterface.queryGenerator.selectQuery( Model.getTableName(), @@ -254,7 +261,10 @@ function createWithACLMetaMiddleware() { }); } } - + const includes = conditions + .map((c) => c.include) + .flat() + .map((x) => processIncludes(collection.model, x || [])); const results = await collection.model.findAll({ where: { [primaryKeyField]: ids, @@ -265,7 +275,7 @@ function createWithACLMetaMiddleware() { return [ctx.db.sequelize.literal(`CASE WHEN ${condition.whereCase} THEN 1 ELSE 0 END`), condition.action]; }), ], - include: conditions.map((condition) => condition.include).flat(), + include: includes, raw: true, }); @@ -297,4 +307,17 @@ function createWithACLMetaMiddleware() { }; } +function processIncludes(model: typeof Model, include: string) { + const association = model.associations[include]; + if (association?.['generateInclude']) { + const newInclude = { + association: include, + attributes: [], // out put empty fields by default + ...association['generateInclude'](), + }; + return newInclude; + } + return include; +} + export { createWithACLMetaMiddleware }; diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts index 96dc233cb15..5304c3dd9ca 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts @@ -620,6 +620,7 @@ export class PluginACLServer extends Plugin { await withACLMeta(ctx, next); } catch (error) { ctx.logger.error(error); + throw error; } }, { after: 'dataSource', group: 'with-acl-meta' }, diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/__tests__/belongs-to-array-field.test.ts b/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/__tests__/belongs-to-array-field.test.ts index 2fa2c42f66c..5bbe65bc236 100644 --- a/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/__tests__/belongs-to-array-field.test.ts +++ b/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/__tests__/belongs-to-array-field.test.ts @@ -17,7 +17,19 @@ describe('belongs to array field', () => { beforeEach(async () => { app = await createMockServer({ - plugins: ['field-m2m-array', 'data-source-manager', 'field-sort', 'data-source-main', 'error-handler'], + plugins: [ + 'acl', + 'users', + 'auth', + 'data-source-main', + 'field-m2m-array', + 'data-source-manager', + 'field-sort', + 'error-handler', + 'system-settings', + ], + registerActions: true, + acl: true, }); db = app.db; fieldRepo = db.getRepository('fields'); @@ -44,7 +56,7 @@ describe('belongs to array field', () => { }); await db.getRepository('collections').create({ values: { - name: 'users', + name: 'users1', fields: [ { name: 'id', @@ -83,7 +95,7 @@ describe('belongs to array field', () => { const field = await fieldRepo.create({ values: { interface: 'mbm', - collectionName: 'users', + collectionName: 'users1', name: 'tags', type: 'belongsToArray', foreignKey: 'tag_ids', @@ -102,7 +114,7 @@ describe('belongs to array field', () => { const field = await fieldRepo.create({ values: { interface: 'mbm', - collectionName: 'users', + collectionName: 'users1', name: 'tags', type: 'belongsToArray', foreignKey: 'username', @@ -113,7 +125,7 @@ describe('belongs to array field', () => { }); await field.load({ transaction }); }), - ).rejects.toThrow(/The type of foreign key "username" in collection "users" must be ARRAY, JSON or JSONB/); + ).rejects.toThrow(/The type of foreign key "username" in collection "users1" must be ARRAY, JSON or JSONB/); }); it('element type of foreign field must be match the type of target field', async () => { @@ -125,7 +137,7 @@ describe('belongs to array field', () => { const field = await fieldRepo.create({ values: { interface: 'mbm', - collectionName: 'users', + collectionName: 'users1', name: 'tags', type: 'belongsToArray', foreignKey: 'tag_ids', @@ -145,7 +157,7 @@ describe('belongs to array field', () => { const field = await fieldRepo.create({ values: { interface: 'mbm', - collectionName: 'users', + collectionName: 'users1', name: 'tags', type: 'belongsToArray', foreignKey: 'tag_ids', @@ -165,7 +177,7 @@ describe('belongs to array field', () => { const field = await fieldRepo.create({ values: { interface: 'mbm', - collectionName: 'users', + collectionName: 'users1', name: 'tag_ids_same', type: 'belongsToArray', foreignKey: 'tag_ids_same', @@ -179,4 +191,82 @@ describe('belongs to array field', () => { ).rejects.toThrow(/Naming collision/); }); }); + + describe('many-to-many acl test', async () => { + it('should list allowedActions when include many-to-many', async () => { + const field = await fieldRepo.create({ + values: { + interface: 'mbm', + collectionName: 'users1', + name: 'tags', + type: 'belongsToArray', + foreignKey: 'tag_ids', + target: 'tags', + targetKey: 'stringCode', + }, + }); + await field.load(); + await app.db.sync(); + + await app.db.getCollection('tags').repository.create({ + values: { + id: 1, + stringCode: '1', + title: 'Tag 1', + }, + }); + await app.db.getCollection('tags').repository.create({ + values: { + id: 2, + stringCode: '2', + title: 'Tag 2', + }, + }); + + await app.db.getCollection('users1').repository.create({ + values: { + username: 'testuser', + tag_ids: ['1', '2'], + }, + }); + await db.getRepository('roles').create({ + values: { + name: 'newRole', + }, + }); + const user = await db.getRepository('users').create({ + values: { + roles: ['newRole'], + }, + }); + + const result = await db.getRepository('roles.resources', 'newRole').create({ + values: { + name: 'users1', + usingActionConfig: true, + actions: [ + { + name: 'view', + fields: ['id', 'tags'], + scope: 1, + }, + ], + }, + }); + + const userAgent = (await app.agent().login(user, 'newRole')) as any; + const listResp = await userAgent + .set('X-With-ACL-Meta', true) + .resource('users1') + .list({ + pageSize: 2, + appends: ['tags'], + filter: { + 'tags.id': '1', + }, + }); + expect(listResp.statusCode).toEqual(200); + expect(listResp.body.meta.allowedActions).exist; + }); + }); });