fix: resolve permission error for many-to-many(many) fields in data t… (#7028)

* fix: resolve permission error for many-to-many(many) fields in data tables
This commit is contained in:
ajie
2025-06-10 10:53:58 +08:00
committed by GitHub
parent f4c27ca7c4
commit 782a520420
4 changed files with 125 additions and 11 deletions
View File
@@ -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 };
@@ -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' },
@@ -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;
});
});
});