mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
fix: correct role union calculation logic (#6605)
* refactor: enhance role merging logic: prioritize strategy actions over existing actions * refactor: union role acl * refactor: union role acl * refactor: union role acl * fix: assign getKeys * fix: assign * refactor: switch role set default role record * refactor: switch role set default role record * refactor: switch role set default role record * refactor: switch role set default role record * refactor: add setCurrentRole cache * fix: e2e test * fix: setCurrentRole * refactor: switch role --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
2d9545a719
commit
41961fb923
@ -30,9 +30,65 @@ export function mergeRole(roles: ACLRole[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.snippets = mergeRoleSnippets(allSnippets);
|
result.snippets = mergeRoleSnippets(allSnippets);
|
||||||
|
adjustActionByStrategy(roles, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When merging permissions from multiple roles, if strategy.actions allows certain actions, then those actions have higher priority.
|
||||||
|
* For example, [
|
||||||
|
* {
|
||||||
|
* actions: {
|
||||||
|
* 'users:view': {...},
|
||||||
|
* 'users:create': {...}
|
||||||
|
* },
|
||||||
|
* strategy: {
|
||||||
|
* actions: ['view']
|
||||||
|
* }
|
||||||
|
* }]
|
||||||
|
* finally result: [{
|
||||||
|
* actions: {
|
||||||
|
* 'users:create': {...},
|
||||||
|
* 'users:view': {} // all view
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* strategy: {
|
||||||
|
* actions: ['view']
|
||||||
|
* }]
|
||||||
|
**/
|
||||||
|
function adjustActionByStrategy(
|
||||||
|
roles,
|
||||||
|
result: {
|
||||||
|
actions?: Record<string, object>;
|
||||||
|
strategy?: { actions?: string[] };
|
||||||
|
resources?: string[];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { actions, strategy } = result;
|
||||||
|
const actionSet = getAdjustActions(roles);
|
||||||
|
if (!_.isEmpty(actions) && !_.isEmpty(strategy?.actions) && !_.isEmpty(result.resources)) {
|
||||||
|
for (const resource of result.resources) {
|
||||||
|
for (const action of strategy.actions) {
|
||||||
|
if (actionSet.has(action)) {
|
||||||
|
actions[`${resource}:${action}`] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAdjustActions(roles: ACLRole[]) {
|
||||||
|
const actionSet = new Set<string>();
|
||||||
|
for (const role of roles) {
|
||||||
|
const jsonRole = role.toJSON();
|
||||||
|
// Within the same role, actions have higher priority than strategy.actions.
|
||||||
|
if (!_.isEmpty(jsonRole.strategy?.['actions']) && _.isEmpty(jsonRole.actions)) {
|
||||||
|
jsonRole.strategy['actions'].forEach((x) => !x.includes('own') && actionSet.add(x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionSet;
|
||||||
|
}
|
||||||
|
|
||||||
function mergeRoleNames(sourceRoleNames, newRoleName) {
|
function mergeRoleNames(sourceRoleNames, newRoleName) {
|
||||||
return newRoleName ? sourceRoleNames.concat(newRoleName) : sourceRoleNames;
|
return newRoleName ? sourceRoleNames.concat(newRoleName) : sourceRoleNames;
|
||||||
}
|
}
|
||||||
|
@ -315,6 +315,34 @@ describe('merge strategy', () => {
|
|||||||
key1: 'val1 + val2',
|
key1: 'val1 + val2',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('case 2', () => {
|
||||||
|
const obj = assign(
|
||||||
|
{
|
||||||
|
filter: { a: 'a2' },
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
filter: () => '123',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(obj).toMatchObject({
|
||||||
|
filter: '123',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('case 3', () => {
|
||||||
|
const obj = assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
filter: { a: 'a2' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filter: () => '123',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(obj).toMatchObject({
|
||||||
|
filter: '123',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('merge', () => {
|
describe('merge', () => {
|
||||||
@ -416,4 +444,32 @@ describe('merge strategy', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('source is empty', () => {
|
||||||
|
it('case 1', () => {
|
||||||
|
const obj = assign(
|
||||||
|
{
|
||||||
|
resourceName: 'uiSchemas',
|
||||||
|
resourceIndex: 'n0jylid5rqa',
|
||||||
|
actionName: 'getJsonSchema',
|
||||||
|
values: {},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
filter: 'andMerge',
|
||||||
|
fields: 'intersect',
|
||||||
|
except: 'union',
|
||||||
|
whitelist: 'intersect',
|
||||||
|
blacklist: 'intersect',
|
||||||
|
sort: 'overwrite',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(obj).toMatchObject({
|
||||||
|
resourceName: 'uiSchemas',
|
||||||
|
resourceIndex: 'n0jylid5rqa',
|
||||||
|
actionName: 'getJsonSchema',
|
||||||
|
values: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import lodash from 'lodash';
|
import _ from 'lodash';
|
||||||
import { isPlainObject } from './common';
|
import { isPlainObject } from './common';
|
||||||
|
|
||||||
type MergeStrategyType = 'merge' | 'deepMerge' | 'overwrite' | 'andMerge' | 'orMerge' | 'intersect' | 'union';
|
type MergeStrategyType = 'merge' | 'deepMerge' | 'overwrite' | 'andMerge' | 'orMerge' | 'intersect' | 'union';
|
||||||
@ -88,7 +88,7 @@ mergeStrategies.set('union', (x, y) => {
|
|||||||
if (typeof y === 'string') {
|
if (typeof y === 'string') {
|
||||||
y = y.split(',');
|
y = y.split(',');
|
||||||
}
|
}
|
||||||
return lodash.uniq((x || []).concat(y || [])).filter(Boolean);
|
return _.uniq((x || []).concat(y || [])).filter(Boolean);
|
||||||
});
|
});
|
||||||
|
|
||||||
mergeStrategies.set('intersect', (x, y) =>
|
mergeStrategies.set('intersect', (x, y) =>
|
||||||
@ -110,15 +110,22 @@ mergeStrategies.set('intersect', (x, y) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function assign(target: any, source: any, strategies: MergeStrategies = {}) {
|
export function assign(target: any, source: any, strategies: MergeStrategies = {}) {
|
||||||
getKeys(source).forEach((sourceKey) => {
|
const sourceKeys = getKeys(source);
|
||||||
|
const targetKeys = getKeys(target);
|
||||||
|
_.uniq([...sourceKeys, ...targetKeys]).forEach((sourceKey) => {
|
||||||
const strategy = strategies[sourceKey];
|
const strategy = strategies[sourceKey];
|
||||||
let func = mergeStrategies.get('deepMerge');
|
let func: any;
|
||||||
if (typeof strategy === 'function') {
|
if (typeof strategy === 'function') {
|
||||||
func = strategy;
|
func = strategy;
|
||||||
} else if (typeof strategy === 'string' && mergeStrategies.has(strategy as any)) {
|
} else if (typeof strategy === 'string' && mergeStrategies.has(strategy as any)) {
|
||||||
func = mergeStrategies.get(strategy as any);
|
func = mergeStrategies.get(strategy as any);
|
||||||
}
|
}
|
||||||
target[sourceKey] = func(target[sourceKey], source[sourceKey]);
|
if (func) {
|
||||||
|
target[sourceKey] = func(target[sourceKey], source[sourceKey]);
|
||||||
|
} else if (sourceKeys.includes(sourceKey)) {
|
||||||
|
const func = mergeStrategies.get('deepMerge');
|
||||||
|
target[sourceKey] = func(target[sourceKey], source[sourceKey]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,20 @@ import {
|
|||||||
useCurrentRoleMode,
|
useCurrentRoleMode,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Divider } from 'antd';
|
import { Divider } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export const SwitchRole = () => {
|
export const SwitchRole = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const roles = useCurrentRoles();
|
const roles = _.cloneDeep(useCurrentRoles());
|
||||||
const roleMode = useCurrentRoleMode();
|
const roleMode = useCurrentRoleMode();
|
||||||
const currentRole = roles.find((role) => role.name === api.auth.role)?.name;
|
const currentRole = roles.find((role) => role.name === api.auth.role)?.name;
|
||||||
|
if (roleMode === 'allow-use-union') {
|
||||||
|
roles.unshift({
|
||||||
|
name: '__union__',
|
||||||
|
title: t('Full permissions', { ns: 'acl' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
// 当角色数量小于等于1 或者 是仅使用合并角色模式时,不显示切换角色选项
|
// 当角色数量小于等于1 或者 是仅使用合并角色模式时,不显示切换角色选项
|
||||||
if (roles.length <= 1 || roleMode === 'only-use-union') {
|
if (roles.length <= 1 || roleMode === 'only-use-union') {
|
||||||
return null;
|
return null;
|
||||||
|
@ -11,6 +11,8 @@ import Database, { BelongsToManyRepository } from '@nocobase/database';
|
|||||||
import UsersPlugin from '@nocobase/plugin-users';
|
import UsersPlugin from '@nocobase/plugin-users';
|
||||||
import { createMockServer, MockServer } from '@nocobase/test';
|
import { createMockServer, MockServer } from '@nocobase/test';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { SystemRoleMode } from '../enum';
|
||||||
|
import { UNION_ROLE_KEY } from '../constants';
|
||||||
|
|
||||||
describe('role', () => {
|
describe('role', () => {
|
||||||
let api: MockServer;
|
let api: MockServer;
|
||||||
@ -20,7 +22,7 @@ describe('role', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
api = await createMockServer({
|
api = await createMockServer({
|
||||||
plugins: ['field-sort', 'users', 'acl', 'auth', 'data-source-manager', 'system-settings'],
|
plugins: ['field-sort', 'users', 'acl', 'auth', 'data-source-manager', 'system-settings', 'ui-schema-storage'],
|
||||||
});
|
});
|
||||||
db = api.db;
|
db = api.db;
|
||||||
usersPlugin = api.getPlugin('users');
|
usersPlugin = api.getPlugin('users');
|
||||||
@ -163,4 +165,77 @@ describe('role', () => {
|
|||||||
|
|
||||||
expect(defaultRole['name']).toEqual('test2');
|
expect(defaultRole['name']).toEqual('test2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set users default role is __union__', async () => {
|
||||||
|
const role = await db.getRepository('roles').create({
|
||||||
|
values: {
|
||||||
|
name: 'test',
|
||||||
|
title: 'test user',
|
||||||
|
allowConfigure: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const user = await db.getRepository('users').create({
|
||||||
|
values: {
|
||||||
|
token: '123',
|
||||||
|
roles: [role.name],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const repo = db.getRepository('systemSettings');
|
||||||
|
await repo.update({
|
||||||
|
filter: { id: 1 },
|
||||||
|
values: {
|
||||||
|
roleMode: SystemRoleMode.allowUseUnion,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const userToken = jwt.sign({ userId: user.get('id') }, 'test-key');
|
||||||
|
const response = await api
|
||||||
|
.agent()
|
||||||
|
.post('/users:setDefaultRole')
|
||||||
|
.send({
|
||||||
|
roleName: UNION_ROLE_KEY,
|
||||||
|
})
|
||||||
|
.set({
|
||||||
|
Authorization: `Bearer ${userToken}`,
|
||||||
|
'X-Authenticator': 'basic',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toEqual(200);
|
||||||
|
let userRole = await db.getRepository('rolesUsers').findOne({ where: { userId: user.get('id'), default: true } });
|
||||||
|
expect(userRole.roleName).toEqual(UNION_ROLE_KEY);
|
||||||
|
|
||||||
|
// switch
|
||||||
|
const response1 = await api
|
||||||
|
.agent()
|
||||||
|
.post('/users:setDefaultRole')
|
||||||
|
.send({
|
||||||
|
roleName: role.name,
|
||||||
|
})
|
||||||
|
.set({
|
||||||
|
Authorization: `Bearer ${userToken}`,
|
||||||
|
'X-Authenticator': 'basic',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response1.statusCode).toEqual(200);
|
||||||
|
userRole = await db.getRepository('rolesUsers').findOne({ where: { userId: user.get('id'), default: true } });
|
||||||
|
expect(userRole.roleName).toEqual(role.name);
|
||||||
|
|
||||||
|
const response2 = await api
|
||||||
|
.agent()
|
||||||
|
.post('/users:setDefaultRole')
|
||||||
|
.send({
|
||||||
|
roleName: UNION_ROLE_KEY,
|
||||||
|
})
|
||||||
|
.set({
|
||||||
|
Authorization: `Bearer ${userToken}`,
|
||||||
|
'X-Authenticator': 'basic',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response2.statusCode).toEqual(200);
|
||||||
|
userRole = await db.getRepository('rolesUsers').findOne({ where: { userId: user.get('id'), default: true } });
|
||||||
|
expect(userRole.roleName).toEqual(UNION_ROLE_KEY);
|
||||||
|
const agent = await api.agent().login(user);
|
||||||
|
const response3 = await agent.resource('roles').check();
|
||||||
|
expect(response3.statusCode).toEqual(200);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -530,4 +530,150 @@ describe('union role: full permissions', async () => {
|
|||||||
expect(createRoleResponse.statusCode).toBe(200);
|
expect(createRoleResponse.statusCode).toBe(200);
|
||||||
expect(createRoleResponse.body.data.role).not.toBe(UNION_ROLE_KEY);
|
expect(createRoleResponse.body.data.role).not.toBe(UNION_ROLE_KEY);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should general action permissions override specific resource permissions when using union role #1924', async () => {
|
||||||
|
const rootAgent = await app.agent().login(rootUser);
|
||||||
|
await rootAgent
|
||||||
|
.post(`/dataSources/main/roles:update`)
|
||||||
|
.query({
|
||||||
|
filterByTk: role1.name,
|
||||||
|
})
|
||||||
|
.send({
|
||||||
|
roleName: role1.name,
|
||||||
|
strategy: {
|
||||||
|
actions: ['view'],
|
||||||
|
},
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ownDataSourceScopeRole = await db.getRepository('dataSourcesRolesResourcesScopes').findOne({
|
||||||
|
where: {
|
||||||
|
key: 'own',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const scopeFields = ['id', 'createdBy', 'createdById'];
|
||||||
|
const dataSourceResourcesResponse = await rootAgent
|
||||||
|
.post(`/roles/${role2.name}/dataSourceResources:create`)
|
||||||
|
.query({
|
||||||
|
filterByTk: 'users',
|
||||||
|
filter: {
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
name: 'users',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.send({
|
||||||
|
usingActionsConfig: true,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'view',
|
||||||
|
fields: scopeFields,
|
||||||
|
scope: {
|
||||||
|
id: ownDataSourceScopeRole.id,
|
||||||
|
createdAt: '2025-02-19T08:57:17.385Z',
|
||||||
|
updatedAt: '2025-02-19T08:57:17.385Z',
|
||||||
|
key: 'own',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
name: '{{t("Own records")}}',
|
||||||
|
resourceName: null,
|
||||||
|
scope: {
|
||||||
|
createdById: '{{ ctx.state.currentUser.id }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'users',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
});
|
||||||
|
expect(dataSourceResourcesResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
|
agent = await app.agent().login(user, UNION_ROLE_KEY);
|
||||||
|
const rolesResponse = await agent.resource('roles').check();
|
||||||
|
expect(rolesResponse.status).toBe(200);
|
||||||
|
expect(rolesResponse.body.data.actions['users:view']).toStrictEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify actions configuration for union role with specific scopes', async () => {
|
||||||
|
const rootAgent = await app.agent().login(rootUser);
|
||||||
|
await rootAgent
|
||||||
|
.post(`/dataSources/main/roles:update`)
|
||||||
|
.query({
|
||||||
|
filterByTk: role1.name,
|
||||||
|
})
|
||||||
|
.send({
|
||||||
|
roleName: role1.name,
|
||||||
|
strategy: {
|
||||||
|
actions: ['view', 'create:own', 'update'],
|
||||||
|
},
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ownDataSourceScopeRole = await db.getRepository('dataSourcesRolesResourcesScopes').findOne({
|
||||||
|
where: {
|
||||||
|
key: 'own',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const scopeFields = ['id', 'createdBy', 'createdById'];
|
||||||
|
const dataSourceResourcesResponse = await rootAgent
|
||||||
|
.post(`/roles/${role2.name}/dataSourceResources:create`)
|
||||||
|
.query({
|
||||||
|
filterByTk: 'users',
|
||||||
|
filter: {
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
name: 'users',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.send({
|
||||||
|
usingActionsConfig: true,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'view',
|
||||||
|
fields: scopeFields,
|
||||||
|
scope: {
|
||||||
|
id: ownDataSourceScopeRole.id,
|
||||||
|
createdAt: '2025-02-19T08:57:17.385Z',
|
||||||
|
updatedAt: '2025-02-19T08:57:17.385Z',
|
||||||
|
key: 'own',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
name: '{{t("Own records")}}',
|
||||||
|
resourceName: null,
|
||||||
|
scope: {
|
||||||
|
createdById: '{{ ctx.state.currentUser.id }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create',
|
||||||
|
fields: scopeFields,
|
||||||
|
scope: {
|
||||||
|
id: ownDataSourceScopeRole.id,
|
||||||
|
createdAt: '2025-02-19T08:57:17.385Z',
|
||||||
|
updatedAt: '2025-02-19T08:57:17.385Z',
|
||||||
|
key: 'own',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
name: '{{t("Own records")}}',
|
||||||
|
resourceName: null,
|
||||||
|
scope: {
|
||||||
|
createdById: '{{ ctx.state.currentUser.id }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'users',
|
||||||
|
dataSourceKey: 'main',
|
||||||
|
});
|
||||||
|
expect(dataSourceResourcesResponse.statusCode).toBe(200);
|
||||||
|
|
||||||
|
agent = await app.agent().login(user, UNION_ROLE_KEY);
|
||||||
|
const rolesResponse = await agent.resource('roles').check();
|
||||||
|
expect(rolesResponse.status).toBe(200);
|
||||||
|
expect(rolesResponse.body.data.actions).toHaveProperty('users:create');
|
||||||
|
expect(rolesResponse.body.data.actions).toHaveProperty('users:view');
|
||||||
|
expect(rolesResponse.body.data.actions['users:view']).toStrictEqual({});
|
||||||
|
expect(rolesResponse.body.data.actions).not.toHaveProperty('users:create:own');
|
||||||
|
expect(rolesResponse.body.data.actions['users:create']).toHaveProperty('filter');
|
||||||
|
expect(rolesResponse.body.data.actions['users:create']).toHaveProperty('whitelist');
|
||||||
|
expect(rolesResponse.body.data.actions['users:update']).toStrictEqual({});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,25 +29,47 @@ export async function setDefaultRole(ctx: Context, next: Next) {
|
|||||||
const repository = db.getRepository('rolesUsers');
|
const repository = db.getRepository('rolesUsers');
|
||||||
|
|
||||||
await db.sequelize.transaction(async (transaction) => {
|
await db.sequelize.transaction(async (transaction) => {
|
||||||
await repository.update({
|
const currentUserDefaultRole = await repository.findOne({
|
||||||
filter: {
|
filter: {
|
||||||
userId: currentUser.id,
|
userId: currentUser.id,
|
||||||
},
|
|
||||||
values: {
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
await repository.update({
|
|
||||||
filter: {
|
|
||||||
userId: currentUser.id,
|
|
||||||
roleName,
|
|
||||||
},
|
|
||||||
values: {
|
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (currentUserDefaultRole?.roleName === roleName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUserDefaultRole) {
|
||||||
|
await repository.model.update(
|
||||||
|
{ default: false },
|
||||||
|
{ where: { userId: currentUser.id, roleName: currentUserDefaultRole.roleName }, transaction },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetUserRole = await repository.findOne({
|
||||||
|
filter: {
|
||||||
|
userId: currentUser.id,
|
||||||
|
roleName,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
let model;
|
||||||
|
if (targetUserRole) {
|
||||||
|
await repository.model.update({ default: true }, { where: { userId: currentUser.id, roleName }, transaction });
|
||||||
|
model = targetUserRole.set('default', true);
|
||||||
|
} else {
|
||||||
|
model = await repository.create({
|
||||||
|
values: {
|
||||||
|
userId: currentUser.id,
|
||||||
|
roleName,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
db.emitAsync('rolesUsers.afterSave', model);
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.body = 'ok';
|
ctx.body = 'ok';
|
||||||
|
@ -47,7 +47,9 @@ export async function setCurrentRole(ctx: Context, next) {
|
|||||||
roles.forEach((role: any) => rolesMap.set(role.name, role));
|
roles.forEach((role: any) => rolesMap.set(role.name, role));
|
||||||
const userRoles = Array.from(rolesMap.values());
|
const userRoles = Array.from(rolesMap.values());
|
||||||
ctx.state.currentUser.roles = userRoles;
|
ctx.state.currentUser.roles = userRoles;
|
||||||
const systemSettings = await ctx.db.getRepository('systemSettings').findOne();
|
const systemSettings = (await cache.wrap(`app:systemSettings`, () =>
|
||||||
|
ctx.db.getRepository('systemSettings').findOne(),
|
||||||
|
)) as Model;
|
||||||
const roleMode = systemSettings?.get('roleMode') || SystemRoleMode.default;
|
const roleMode = systemSettings?.get('roleMode') || SystemRoleMode.default;
|
||||||
if ([currentRole, ctx.state.currentRole].includes(UNION_ROLE_KEY) && roleMode === SystemRoleMode.default) {
|
if ([currentRole, ctx.state.currentRole].includes(UNION_ROLE_KEY) && roleMode === SystemRoleMode.default) {
|
||||||
currentRole = userRoles[0].name;
|
currentRole = userRoles[0].name;
|
||||||
@ -58,12 +60,6 @@ export async function setCurrentRole(ctx: Context, next) {
|
|||||||
ctx.headers['x-role'] = UNION_ROLE_KEY;
|
ctx.headers['x-role'] = UNION_ROLE_KEY;
|
||||||
ctx.state.currentRoles = userRoles.map((role) => role.name);
|
ctx.state.currentRoles = userRoles.map((role) => role.name);
|
||||||
return next();
|
return next();
|
||||||
} else if (roleMode === SystemRoleMode.allowUseUnion) {
|
|
||||||
userRoles.unshift({
|
|
||||||
name: UNION_ROLE_KEY,
|
|
||||||
title: ctx.t('Full permissions', { ns: 'acl' }),
|
|
||||||
});
|
|
||||||
ctx.state.currentUser.roles = userRoles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentRole === UNION_ROLE_KEY) {
|
if (currentRole === UNION_ROLE_KEY) {
|
||||||
@ -85,11 +81,13 @@ export async function setCurrentRole(ctx: Context, next) {
|
|||||||
}
|
}
|
||||||
// 2. If the X-Role is not set, or the X-Role does not belong to the user, use the default role
|
// 2. If the X-Role is not set, or the X-Role does not belong to the user, use the default role
|
||||||
if (!role) {
|
if (!role) {
|
||||||
const defaultRole = userRoles.find((role) => role?.rolesUsers?.default);
|
const defaultRoleModel = await cache.wrap(`roles:${ctx.state.currentUser.id}:defaultRole`, () =>
|
||||||
role = (defaultRole || userRoles.find((x) => x.name !== UNION_ROLE_KEY))?.name;
|
ctx.db.getRepository('rolesUsers').findOne({ where: { userId: ctx.state.currentUser.id, default: true } }),
|
||||||
|
);
|
||||||
|
role = defaultRoleModel?.roleName || userRoles[0]?.name;
|
||||||
}
|
}
|
||||||
ctx.state.currentRole = role;
|
ctx.state.currentRole = role;
|
||||||
ctx.state.currentRoles = [role];
|
ctx.state.currentRoles = role === UNION_ROLE_KEY ? [userRoles[0]?.name] : [role];
|
||||||
if (!ctx.state.currentRoles.length) {
|
if (!ctx.state.currentRoles.length) {
|
||||||
return ctx.throw(401, {
|
return ctx.throw(401, {
|
||||||
code: 'ROLE_NOT_FOUND_ERR',
|
code: 'ROLE_NOT_FOUND_ERR',
|
||||||
|
@ -341,10 +341,16 @@ export class PluginACLServer extends Plugin {
|
|||||||
this.app.db.on('rolesUsers.afterSave', async (model) => {
|
this.app.db.on('rolesUsers.afterSave', async (model) => {
|
||||||
const cache = this.app.cache as Cache;
|
const cache = this.app.cache as Cache;
|
||||||
await cache.del(`roles:${model.get('userId')}`);
|
await cache.del(`roles:${model.get('userId')}`);
|
||||||
|
await cache.del(`roles:${model.get('userId')}:defaultRole`);
|
||||||
|
});
|
||||||
|
this.app.db.on('systemSettings.afterSave', async (model) => {
|
||||||
|
const cache = this.app.cache as Cache;
|
||||||
|
await cache.del(`app:systemSettings`);
|
||||||
});
|
});
|
||||||
this.app.db.on('rolesUsers.afterDestroy', async (model) => {
|
this.app.db.on('rolesUsers.afterDestroy', async (model) => {
|
||||||
const cache = this.app.cache as Cache;
|
const cache = this.app.cache as Cache;
|
||||||
await cache.del(`roles:${model.get('userId')}`);
|
await cache.del(`roles:${model.get('userId')}`);
|
||||||
|
await cache.del(`roles:${model.get('userId')}:defaultRole`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const writeRolesToACL = async (app, options) => {
|
const writeRolesToACL = async (app, options) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user