fix: meta acl with association query (#1695)

* fix: meta acl with association query

* fix: test
This commit is contained in:
ChengLei Shao 2023-04-12 17:44:33 +08:00 committed by GitHub
parent 2ed3565d43
commit 86de0733ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 17 deletions

View File

@ -1,8 +1,9 @@
import { Database } from '@nocobase/database'; import { Database } from '@nocobase/database';
import { prepareApp } from './prepare'; import { prepareApp } from './prepare';
import { MockServer } from '@nocobase/test';
describe('list action with acl', () => { describe('list action with acl', () => {
let app; let app: MockServer;
let Post; let Post;
@ -17,6 +18,11 @@ describe('list action with acl', () => {
type: 'bigInt', type: 'bigInt',
name: 'createdById', name: 'createdById',
}, },
{
type: 'belongsTo',
name: 'createdBy',
target: 'users',
},
], ],
}); });
@ -85,6 +91,7 @@ describe('list action with acl', () => {
}, },
); );
//@ts-ignore
const response = await app.agent().set('X-With-ACL-Meta', true).resource('tests').list({}); const response = await app.agent().set('X-With-ACL-Meta', true).resource('tests').list({});
const data = response.body; const data = response.body;
@ -93,6 +100,48 @@ describe('list action with acl', () => {
expect(data.meta.allowedActions.destroy).toEqual(['t3']); expect(data.meta.allowedActions.destroy).toEqual(['t3']);
}); });
it('should list items meta permissions by association field', async () => {
const userRole = app.acl.define({
role: 'user',
});
userRole.grantAction('posts:view', {});
userRole.grantAction('posts:update', {
filter: {
'createdBy.id': '{{ ctx.state.currentUser.id }}',
},
});
await Post.repository.create({
values: [
{ title: 'p1', createdById: 1 },
{ title: 'p2', createdById: 1 },
{ title: 'p3', createdById: 2 },
],
});
app.resourcer.use(
(ctx, next) => {
ctx.state.currentRole = 'user';
ctx.state.currentUser = {
id: 1,
};
return next();
},
{
before: 'acl',
},
);
const response = await (app as any).agent().set('X-With-ACL-Meta', true).resource('posts').list();
const data = response.body;
expect(data.meta.allowedActions.view).toEqual([1, 2, 3]);
expect(data.meta.allowedActions.update).toEqual([1, 2]);
expect(data.meta.allowedActions.destroy).toEqual([]);
});
it('should list items with meta permission', async () => { it('should list items with meta permission', async () => {
const userRole = app.acl.define({ const userRole = app.acl.define({
role: 'user', role: 'user',
@ -126,6 +175,7 @@ describe('list action with acl', () => {
}, },
); );
// @ts-ignore
const response = await app.agent().set('X-With-ACL-Meta', true).resource('posts').list({}); const response = await app.agent().set('X-With-ACL-Meta', true).resource('posts').list({});
const data = response.body; const data = response.body;
@ -167,6 +217,7 @@ describe('list action with acl', () => {
}, },
); );
// @ts-ignore
const getResponse = await app.agent().set('X-With-ACL-Meta', true).resource('posts').get({ const getResponse = await app.agent().set('X-With-ACL-Meta', true).resource('posts').get({
filterByTk: 1, filterByTk: 1,
}); });

View File

@ -1,7 +1,7 @@
import { mockServer } from '@nocobase/test'; import { mockServer, MockServer } from '@nocobase/test';
import PluginACL from '../server'; import PluginACL from '../server';
export async function prepareApp() { export async function prepareApp(): Promise<MockServer> {
const app = mockServer({ const app = mockServer({
registerActions: true, registerActions: true,
acl: true, acl: true,

View File

@ -585,20 +585,12 @@ export class PluginACL extends Plugin {
const withACLMeta = async (ctx: any, next) => { const withACLMeta = async (ctx: any, next) => {
await next(); await next();
if (!ctx.action) { if (!ctx.action || !ctx.get('X-With-ACL-Meta') || ctx.status !== 200) {
return; return;
} }
const { resourceName, actionName } = ctx.action; const { resourceName, actionName } = ctx.action;
if (!ctx.get('X-With-ACL-Meta')) {
return;
}
if (ctx.status !== 200) {
return;
}
if (!['list', 'get'].includes(actionName)) { if (!['list', 'get'].includes(actionName)) {
return; return;
} }
@ -620,11 +612,11 @@ export class PluginACL extends Plugin {
listData = lodash.castArray(listData); listData = lodash.castArray(listData);
} }
const actions = ['view', 'update', 'destroy']; const inspectActions = ['view', 'update', 'destroy'];
const actionsParams = []; const actionsParams = [];
for (const action of actions) { for (const action of inspectActions) {
const actionCtx: any = { const actionCtx: any = {
db: ctx.db, db: ctx.db,
action: { action: {
@ -707,10 +699,16 @@ export class PluginACL extends Plugin {
{ {
where: (() => { where: (() => {
const filterObj = queryParams.where; const filterObj = queryParams.where;
if (!this.db.options.underscored) { if (!this.db.options.underscored) {
return filterObj; return filterObj;
} }
const isAssociationKey = (key) => {
return key.startsWith('$') && key.endsWith('$');
};
// change camelCase to snake_case
const iterate = (rootObj, path = []) => { const iterate = (rootObj, path = []) => {
const obj = path.length == 0 ? rootObj : lodash.get(rootObj, path); const obj = path.length == 0 ? rootObj : lodash.get(rootObj, path);
@ -738,8 +736,21 @@ export class PluginACL extends Plugin {
} }
if (typeof key === 'string' && key !== snakeCase(key)) { if (typeof key === 'string' && key !== snakeCase(key)) {
lodash.set(rootObj, [...path, snakeCase(key)], lodash.cloneDeep(obj[key])); const setKey = isAssociationKey(key)
? (() => {
const parts = key.split('.');
parts[parts.length - 1] = lodash.snakeCase(parts[parts.length - 1]);
const result = parts.join('.');
return result.endsWith('$') ? result : `${result}$`;
})()
: snakeCase(key);
const setValue = lodash.cloneDeep(obj[key]);
lodash.unset(rootObj, [...path, key]); lodash.unset(rootObj, [...path, key]);
lodash.set(rootObj, [...path, setKey], setValue);
} }
}); });
}; };
@ -776,7 +787,7 @@ export class PluginACL extends Plugin {
include: conditions.map((condition) => condition.include).flat(), include: conditions.map((condition) => condition.include).flat(),
}); });
const allowedActions = actions const allowedActions = inspectActions
.map((action) => { .map((action) => {
if (allAllowed.includes(action)) { if (allAllowed.includes(action)) {
return [action, ids]; return [action, ids];