From 2b672fa30915653ee205c2cb8797f2a53b500e9f Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Tue, 16 Dec 2025 09:25:24 +0800 Subject: [PATCH] fix(users): disallow assigning the root role to users (#8180) * fix(users): disallow assigning the root role to users * fix: plugin * fix: test * fix: test --- .../src/server/__tests__/root.test.ts | 40 +++++++++++++++++++ .../@nocobase/plugin-acl/src/server/server.ts | 13 +++++- .../src/server/__tests__/actions.test.ts | 9 +---- .../server/__tests__/desktopRoutes.test.ts | 24 +++++++---- .../__tests__/data-source-with-acl.test.ts | 26 ++++++------ 5 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-acl/src/server/__tests__/root.test.ts diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/root.test.ts b/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/root.test.ts new file mode 100644 index 00000000000..33ac2baec6a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/root.test.ts @@ -0,0 +1,40 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { MockServer, createMockServer } from '@nocobase/test'; + +describe('root', async () => { + let app: MockServer; + beforeEach(async () => { + app = await createMockServer({ + plugins: ['users', 'field-sort', 'error-handler', 'acl', 'data-source-main', 'data-source-manager'], + }); + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('can not set root', async () => { + const initUser = await app.db.getRepository('users').findOne({ + appends: ['roles'], + }); + expect(initUser).toBeTruthy(); + expect(initUser.get('roles').map((role) => role.get('name'))).toContain('root'); + + await expect( + app.db.getRepository('users').create({ + values: { + username: 'test', + roles: ['root'], + }, + }), + ).rejects.toThrow(/No permissions/); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts index 68b6e481da9..d9b068d659e 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts @@ -9,7 +9,7 @@ import { Context, utils as actionUtils } from '@nocobase/actions'; import { Cache } from '@nocobase/cache'; -import { Collection, RelationField, Transaction } from '@nocobase/database'; +import { Collection, Model, RelationField, Transaction } from '@nocobase/database'; import { Plugin } from '@nocobase/server'; import lodash from 'lodash'; import { resolve } from 'path'; @@ -428,6 +428,17 @@ export class PluginACLServer extends Plugin { ], }); + this.app.on('afterLoad', async (app) => { + app.db.on('rolesUsers.beforeSave', async (model: Model) => { + if (!model._changed.has('roleName')) { + return; + } + if (model.roleName === 'root') { + throw new Error('No permissions'); + } + }); + }); + const rolesResourcesScopes = this.app.db.getRepository('dataSourcesRolesResourcesScopes'); await rolesResourcesScopes.createMany({ records: [ diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-api-keys/src/server/__tests__/actions.test.ts index d814d0b8a96..914eb6e3f51 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-api-keys/src/server/__tests__/actions.test.ts @@ -24,7 +24,7 @@ describe('actions', () => { let user; let testUser; - let role; + const role = { name: 'admin' }; let testRole; let createData; const expiresIn = 60 * 60 * 24; @@ -50,7 +50,7 @@ describe('actions', () => { testUser = await userRepo.create({ values: { nickname: 'test', - roles: user.roles, + roles: ['admin', 'member'], }, }); const roleRepo = await app.db.getRepository('roles'); @@ -60,11 +60,6 @@ describe('actions', () => { }, }); - role = await (app.db.getRepository('users.roles', user.id) as unknown as Repository).findOne({ - where: { - default: true, - }, - }); createData = { values: { name: 'TEST', diff --git a/packages/plugins/@nocobase/plugin-client/src/server/__tests__/desktopRoutes.test.ts b/packages/plugins/@nocobase/plugin-client/src/server/__tests__/desktopRoutes.test.ts index 3acef120899..330b1141ba0 100644 --- a/packages/plugins/@nocobase/plugin-client/src/server/__tests__/desktopRoutes.test.ts +++ b/packages/plugins/@nocobase/plugin-client/src/server/__tests__/desktopRoutes.test.ts @@ -49,8 +49,10 @@ describe('desktopRoutes:listAccessible', () => { }); it('should return all routes for root role', async () => { - const rootUser = await db.getRepository('users').create({ - values: { roles: ['root'] }, + const rootUser = await db.getRepository('users').findOne({ + filter: { + 'roles.name': 'root', + }, }); const agent = await app.agent().login(rootUser); @@ -82,8 +84,10 @@ describe('desktopRoutes:listAccessible', () => { it('should return filtered routes with children', async () => { // 使用 root 角色配置 member 的可访问路由 - const rootUser = await db.getRepository('users').create({ - values: { roles: ['root'] }, + const rootUser = await db.getRepository('users').findOne({ + filter: { + 'roles.name': 'root', + }, }); const rootAgent = await app.agent().login(rootUser); @@ -111,8 +115,10 @@ describe('desktopRoutes:listAccessible', () => { it('should return an empty response when there are no accessible routes', async () => { // 使用 root 角色配置 member 的可访问路由 - const rootUser = await db.getRepository('users').create({ - values: { roles: ['root'] }, + const rootUser = await db.getRepository('users').findOne({ + filter: { + 'roles.name': 'root', + }, }); const rootAgent = await app.agent().login(rootUser); @@ -151,8 +157,10 @@ describe('desktopRoutes:listAccessible', () => { // 配置 member 角色只能访问 page4 const routes = await db.getRepository('desktopRoutes').find({ limit: 6 }); - const rootUser = await db.getRepository('users').create({ - values: { roles: ['root'] }, + const rootUser = await db.getRepository('users').findOne({ + filter: { + 'roles.name': 'root', + }, }); const rootAgent = await app.agent().login(rootUser); await rootAgent.resource('roles.desktopRoutes', 'member').remove({ diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-with-acl.test.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-with-acl.test.ts index 1f10ff0be2c..30d62fddba5 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-with-acl.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-with-acl.test.ts @@ -112,9 +112,9 @@ describe('data source with acl', () => { await next(); }); - const adminUser = await app.db.getRepository('users').create({ - values: { - roles: ['root'], + const adminUser = await app.db.getRepository('users').findOne({ + filter: { + 'roles.name': 'root', }, }); @@ -125,9 +125,9 @@ describe('data source with acl', () => { }); it.skipIf(os.platform() === 'win32')('should allow root user', async () => { - const adminUser = await app.db.getRepository('users').create({ - values: { - roles: ['root'], + const adminUser = await app.db.getRepository('users').findOne({ + filter: { + 'roles.name': 'root', }, }); @@ -139,7 +139,7 @@ describe('data source with acl', () => { it('should update roles resources', async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, }); @@ -166,7 +166,7 @@ describe('data source with acl', () => { it('should set main data source strategy', async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, }); @@ -235,7 +235,7 @@ describe('data source with acl', () => { it('should create strategy', async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, }); @@ -297,7 +297,7 @@ describe('data source with acl', () => { it('should create resources', async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, }); @@ -414,7 +414,7 @@ describe('data source with acl', () => { it('should update roles strategy', async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, }); @@ -456,7 +456,7 @@ describe('data source with acl', () => { it(`should list response meta include new data sources`, async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, }); @@ -522,7 +522,7 @@ describe('data source with acl', () => { it(`should update data sources`, async () => { const adminUser = await app.db.getRepository('users').create({ values: { - roles: ['root'], + roles: ['admin'], }, });