From 43f3cb7cfeede78771aa96068a9682db5d3d8d37 Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Wed, 7 Jun 2023 15:01:50 +0800 Subject: [PATCH] =?UTF-8?q?chore(acl):=20write=20role=20to=20acl=20if=20it?= =?UTF-8?q?=20exists=20in=20database=20and=20not=20found=20=E2=80=A6=20(#2?= =?UTF-8?q?001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(acl): write role to acl if it exists in database and not found in acl * fix: test --- .../plugins/acl/src/__tests__/users.test.ts | 2 +- .../src/__tests__/write-role-to-acl.test.ts | 48 +++++++++++++ .../plugins/acl/src/actions/role-check.ts | 68 +++++++++++-------- packages/plugins/acl/src/server.ts | 30 +++++--- 4 files changed, 107 insertions(+), 41 deletions(-) create mode 100644 packages/plugins/acl/src/__tests__/write-role-to-acl.test.ts diff --git a/packages/plugins/acl/src/__tests__/users.test.ts b/packages/plugins/acl/src/__tests__/users.test.ts index c09f10276..4ab735bb9 100644 --- a/packages/plugins/acl/src/__tests__/users.test.ts +++ b/packages/plugins/acl/src/__tests__/users.test.ts @@ -99,7 +99,7 @@ describe('actions', () => { const rolesCheckResponse2 = (await loggedAgent.set('Accept', 'application/json').get('/roles:check')) as any; expect(rolesCheckResponse2.status).toEqual(500); - expect(rolesCheckResponse2.body.errors[0].message).toEqual('Role not found'); + expect(rolesCheckResponse2.body.errors[0].message).toEqual('User role not found'); }); it('should destroy through table record when destroy role', async () => { diff --git a/packages/plugins/acl/src/__tests__/write-role-to-acl.test.ts b/packages/plugins/acl/src/__tests__/write-role-to-acl.test.ts new file mode 100644 index 000000000..2a27d9a34 --- /dev/null +++ b/packages/plugins/acl/src/__tests__/write-role-to-acl.test.ts @@ -0,0 +1,48 @@ +import { MockServer } from '@nocobase/test'; +import { Database } from '@nocobase/database'; +import { prepareApp } from './prepare'; +import UsersPlugin from '@nocobase/plugin-users'; + +describe('write role to acl', () => { + let app: MockServer; + let db: Database; + + beforeEach(async () => { + app = await prepareApp(); + db = app.db; + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('should write role to acl if role instance exists in db', async () => { + const role = await db.getRepository('roles').create({ + values: { + name: 'test', + }, + }); + + // remove role from acl + app.acl.removeRole('test'); + + const user = await db.getRepository('users').create({ + values: { + roles: ['test'], + }, + }); + + const userPlugin = app.getPlugin('users') as UsersPlugin; + const agent = app.agent().auth( + userPlugin.jwtService.sign({ + userId: user.get('id'), + }), + { type: 'bearer' }, + ); + + // @ts-ignore + const response = await agent.resource('roles').check(); + + expect(response.statusCode).toEqual(200); + }); +}); diff --git a/packages/plugins/acl/src/actions/role-check.ts b/packages/plugins/acl/src/actions/role-check.ts index 68c9cd4b8..dc9dc310d 100644 --- a/packages/plugins/acl/src/actions/role-check.ts +++ b/packages/plugins/acl/src/actions/role-check.ts @@ -8,36 +8,46 @@ const map2obj = (map: Map) => { export async function checkAction(ctx, next) { const currentRole = ctx.state.currentRole; - if (currentRole) { - const roleInstance = await ctx.db.getRepository('roles').findOne({ - filter: { - name: currentRole, - }, - appends: ['menuUiSchemas'], - }); - - const anonymous = await ctx.db.getRepository('roles').findOne({ - filter: { - name: 'anonymous', - }, - }); - - const role = ctx.app.acl.getRole(currentRole); - const availableActions = ctx.app.acl.getAvailableActions(); - - ctx.body = { - ...role.toJSON(), - availableActions: [...availableActions.keys()], - resources: [...role.resources.keys()], - actionAlias: map2obj(ctx.app.acl.actionAlias), - allowAll: currentRole === 'root', - allowConfigure: roleInstance.get('allowConfigure'), - allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')), - allowAnonymous: !!anonymous, - }; - } else { - throw new Error('Role not found'); + if (!currentRole) { + throw new Error('User role not found'); } + const roleInstance = await ctx.db.getRepository('roles').findOne({ + filter: { + name: currentRole, + }, + appends: ['menuUiSchemas'], + }); + + if (!roleInstance) { + throw new Error(`Role ${currentRole} not exists`); + } + + const anonymous = await ctx.db.getRepository('roles').findOne({ + filter: { + name: 'anonymous', + }, + }); + + let role = ctx.app.acl.getRole(currentRole); + + if (!role) { + await ctx.app.emit('acl:writeRoleToACL', roleInstance); + role = ctx.app.acl.getRole(currentRole); + } + + const availableActions = ctx.app.acl.getAvailableActions(); + + ctx.body = { + ...role.toJSON(), + availableActions: [...availableActions.keys()], + resources: [...role.resources.keys()], + actionAlias: map2obj(ctx.app.acl.actionAlias), + allowAll: currentRole === 'root', + allowConfigure: roleInstance.get('allowConfigure'), + allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')), + allowAnonymous: !!anonymous, + }; + await next(); } diff --git a/packages/plugins/acl/src/server.ts b/packages/plugins/acl/src/server.ts index 0070a9621..53e3428c8 100644 --- a/packages/plugins/acl/src/server.ts +++ b/packages/plugins/acl/src/server.ts @@ -128,11 +128,21 @@ export class PluginACL extends Plugin { })) as RoleModel[]; for (const role of roles) { - role.writeToAcl({ acl: this.acl }); + await this.writeRoleToACL(role); + } + } - for (const resource of role.get('resources') as RoleResourceModel[]) { - await this.writeResourceToACL(resource, null); - } + async writeRoleToACL(role: RoleModel, transaction: any = null) { + role.writeToAcl({ acl: this.acl }); + + let resources = role.get('resources') as RoleResourceModel[]; + + if (!resources) { + resources = await role.getResources({ transaction }); + } + + for (const resource of resources as RoleResourceModel[]) { + await this.writeResourceToACL(resource, transaction); } } @@ -217,16 +227,14 @@ export class PluginACL extends Plugin { } }); + this.app.on('acl:writeRoleToACL', async (roleModel: RoleModel) => { + await this.writeRoleToACL(roleModel); + }); + this.app.db.on('roles.afterSaveWithAssociations', async (model, options) => { const { transaction } = options; - model.writeToAcl({ - acl: this.acl, - }); - - for (const resource of (await model.getResources({ transaction })) as RoleResourceModel[]) { - await this.writeResourceToACL(resource, transaction); - } + await this.writeRoleToACL(model, transaction); // model is default if (model.get('default')) {