diff --git a/packages/acl/src/acl.ts b/packages/acl/src/acl.ts index 5e78402f2..3d141ec1a 100644 --- a/packages/acl/src/acl.ts +++ b/packages/acl/src/acl.ts @@ -219,6 +219,23 @@ export class ACL extends EventEmitter { middleware() { const acl = this; + const filterParams = (ctx, resourceName, params) => { + if (params?.filter?.createdById) { + const collection = ctx.db.getCollection(resourceName); + if (!collection.getField('createdById')) { + return lodash.omit(params, 'filter.createdById'); + } + } + + return params; + }; + + const ctxToObject = (ctx) => { + return { + state: JSON.parse(JSON.stringify(ctx.state)), + }; + }; + return async function ACLMiddleware(ctx, next) { const roleName = ctx.state.currentRole || 'anonymous'; const { resourceName, actionName } = ctx.action; @@ -248,7 +265,9 @@ export class ACL extends EventEmitter { const { params } = permission.can; if (params) { - resourcerAction.mergeParams(parse(params)({ ctx })); + const filteredParams = filterParams(ctx, resourceName, params); + const parsedParams = parse(filteredParams)({ ctx: ctxToObject(ctx) }); + resourcerAction.mergeParams(parsedParams); } await next(); diff --git a/packages/database/src/repository.ts b/packages/database/src/repository.ts index 23b1875bd..56feb320f 100644 --- a/packages/database/src/repository.ts +++ b/packages/database/src/repository.ts @@ -414,7 +414,10 @@ export class Repository instance.get(modelFilterKey) as TargetKey); if (filterByTk) { - pks = lodash.intersection(pks, filterByTk); + pks = lodash.intersection( + pks.map((i) => `${i}`), + filterByTk.map((i) => `${i}`), + ); } return await this.destroy({ diff --git a/packages/database/src/update-associations.ts b/packages/database/src/update-associations.ts index 4567c437c..15faba8e6 100644 --- a/packages/database/src/update-associations.ts +++ b/packages/database/src/update-associations.ts @@ -283,6 +283,7 @@ export async function updateSingleAssociation( if (value instanceof Model) { await model[setAccessor](value, { context, transaction }); model.setDataValue(key, value); + if (!options.transaction) { await transaction.commit(); } diff --git a/packages/plugin-acl/src/__tests__/own.test.ts b/packages/plugin-acl/src/__tests__/own.test.ts new file mode 100644 index 000000000..ddd4538b8 --- /dev/null +++ b/packages/plugin-acl/src/__tests__/own.test.ts @@ -0,0 +1,140 @@ +import { mockServer, MockServer } from '@nocobase/test'; +import { Database } from '@nocobase/database'; +import { ACL } from '@nocobase/acl'; +import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage'; +import PluginUiSchema from '@nocobase/plugin-ui-schema-storage'; +import PluginCollectionManager from '@nocobase/plugin-collection-manager'; +import PluginACL from '@nocobase/plugin-acl'; +import PluginUser from '@nocobase/plugin-users'; +import supertest from 'supertest'; + +describe('own test', () => { + let app: MockServer; + let db: Database; + let acl: ACL; + + let pluginUser: PluginUser; + let adminToken: string; + let userToken: string; + + let admin; + let user; + + let role; + let agent; + + afterEach(async () => { + await app.destroy(); + }); + + beforeEach(async () => { + app = mockServer({ + registerActions: true, + }); + + await app.cleanDb(); + + app.plugin(PluginUiSchema); + app.plugin(PluginCollectionManager); + app.plugin(PluginUser, { + jwt: { + secret: process.env.JWT_SECRET || '09f26e402586e2faa8da4c98a35f1b20d6b033c60', + }, + }); + + app.plugin(PluginACL); + await app.loadAndInstall(); + db = app.db; + + const PostCollection = db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { type: 'belongsToMany', name: 'tags' }, + ], + createdBy: true, + }); + + const TagCollection = db.collection({ + name: 'tags', + fields: [ + { type: 'string', name: 'name' }, + { type: 'belongsToMany', name: 'posts' }, + ], + createdBy: true, + }); + + const TestCollection = db.collection({ + name: 'tests', + fields: [{ type: 'string', name: 'name' }], + }); + + await app.db.sync(); + + agent = supertest.agent(app.callback()); + + acl = app.acl; + + role = await db.getRepository('roles').findOne({ + filter: { + name: 'admin', + }, + }); + + admin = await db.getRepository('users').findOne(); + + pluginUser = app.getPlugin('@nocobase/plugin-users'); + + adminToken = pluginUser.jwtService.sign({ userId: admin.get('id') }); + + user = await db.getRepository('users').create({ + values: { + nickname: 'test', + roles: ['admin'], + }, + }); + + userToken = pluginUser.jwtService.sign({ userId: user.get('id') }); + }); + + it('should list without createBy', async () => { + let response = await agent + .patch('/roles/admin') + .send({ + strategy: { + actions: ['view:own'], + }, + }) + .set({ Authorization: 'Bearer ' + adminToken }); + + response = await agent.get('/tests:list').set({ Authorization: 'Bearer ' + userToken }); + expect(response.statusCode).toEqual(200); + }); + + it('should delete with createdBy', async () => { + let response = await agent + .patch('/roles/admin') + .send({ + strategy: { + actions: ['view:own', 'create', 'destroy:own'], + }, + }) + .set({ Authorization: 'Bearer ' + adminToken }); + + response = await agent + .get('/posts:create') + .send({ + title: 't1', + }) + .set({ Authorization: 'Bearer ' + userToken }); + + expect(response.statusCode).toEqual(200); + + const data = response.body; + const id = data.data['id']; + + response = await agent.delete(`/posts/${id}`).set({ Authorization: 'Bearer ' + userToken }); + expect(response.statusCode).toEqual(200); + expect(await db.getRepository('posts').count()).toEqual(0); + }); +});