fix: append acl resource params (#1923)

* test: append acl resource params

* fix: test
This commit is contained in:
ChengLei Shao 2023-05-24 21:31:12 +08:00 committed by GitHub
parent 7080db72eb
commit 710bd2dfd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 210 additions and 77 deletions

View File

@ -1,5 +1,6 @@
import { ACLRole, RoleActionParams } from './acl-role';
import { ACL, ListenerContext } from './acl';
import lodash from 'lodash';
export type ResourceActions = { [key: string]: RoleActionParams };
@ -35,7 +36,15 @@ export class ACLResource {
}
getAction(name: string) {
return this.actions.get(name) || this.actions.get(this.acl.resolveActionAlias(name));
const result = this.actions.get(name) || this.actions.get(this.acl.resolveActionAlias(name));
if (!result) {
return null;
}
if (Array.isArray(result.fields) && result.fields.length > 0) {
result.fields = lodash.uniq(result.fields);
}
return lodash.cloneDeep(result);
}
setAction(name: string, params: RoleActionParams) {

View File

@ -45,20 +45,15 @@ interface CanArgs {
}
export class ACL extends EventEmitter {
protected availableActions = new Map<string, ACLAvailableAction>();
public availableStrategy = new Map<string, ACLAvailableStrategy>();
protected fixedParamsManager = new FixedParamsManager();
protected middlewares: Toposort<any>;
public allowManager = new AllowManager(this);
public snippetManager = new SnippetManager();
roles = new Map<string, ACLRole>();
actionAlias = new Map<string, string>();
configResources: string[] = [];
protected availableActions = new Map<string, ACLAvailableAction>();
protected fixedParamsManager = new FixedParamsManager();
protected middlewares: Toposort<any>;
constructor() {
super();
@ -100,68 +95,6 @@ export class ACL extends EventEmitter {
this.addCoreMiddleware();
}
protected addCoreMiddleware() {
const acl = this;
const filterParams = (ctx, resourceName, params) => {
if (params?.filter?.createdById) {
const collection = ctx.db.getCollection(resourceName);
if (!collection || !collection.getField('createdById')) {
return lodash.omit(params, 'filter.createdById');
}
}
return params;
};
this.middlewares.add(
async (ctx, next) => {
const resourcerAction: Action = ctx.action;
const { resourceName, actionName } = ctx.action;
const permission = ctx.permission;
ctx.log?.info && ctx.log.info('ctx permission', permission);
if ((!permission.can || typeof permission.can !== 'object') && !permission.skip) {
ctx.throw(403, 'No permissions');
return;
}
const params = permission.can?.params || acl.fixedParamsManager.getParams(resourceName, actionName);
ctx.log?.info && ctx.log.info('acl params', params);
if (params && resourcerAction.mergeParams) {
const filteredParams = filterParams(ctx, resourceName, params);
const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx);
ctx.permission.parsedParams = parsedParams;
ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams);
ctx.permission.rawParams = lodash.cloneDeep(resourcerAction.params);
resourcerAction.mergeParams(parsedParams, {
appends: (x, y) => {
if (!x) {
return [];
}
if (!y) {
return x;
}
return (x as any[]).filter((i) => y.includes(i.split('.').shift()));
},
});
ctx.permission.mergedParams = lodash.cloneDeep(resourcerAction.params);
}
await next();
},
{
tag: 'core',
group: 'core',
},
);
}
define(options: DefineOptions): ACLRole {
const roleName = options.role;
const role = new ACLRole(this, roleName);
@ -302,10 +235,6 @@ export class ACL extends EventEmitter {
return null;
}
protected isAvailableAction(actionName: string) {
return this.availableActions.has(this.resolveActionAlias(actionName));
}
public resolveActionAlias(action: string) {
return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
}
@ -360,7 +289,9 @@ export class ACL extends EventEmitter {
const { resourceName, actionName } = ctx.action;
ctx.can = (options: Omit<CanArgs, 'role'>) => {
return acl.can({ role: roleName, ...options });
const canResult = acl.can({ role: roleName, ...options });
return canResult;
};
ctx.permission = {
@ -397,4 +328,70 @@ export class ACL extends EventEmitter {
registerSnippet(snippet: SnippetOptions) {
this.snippetManager.register(snippet);
}
protected addCoreMiddleware() {
const acl = this;
const filterParams = (ctx, resourceName, params) => {
if (params?.filter?.createdById) {
const collection = ctx.db.getCollection(resourceName);
if (!collection || !collection.getField('createdById')) {
return lodash.omit(params, 'filter.createdById');
}
}
return params;
};
this.middlewares.add(
async (ctx, next) => {
const resourcerAction: Action = ctx.action;
const { resourceName, actionName } = ctx.action;
const permission = ctx.permission;
ctx.log?.info && ctx.log.info('ctx permission', permission);
if ((!permission.can || typeof permission.can !== 'object') && !permission.skip) {
ctx.throw(403, 'No permissions');
return;
}
const params = permission.can?.params || acl.fixedParamsManager.getParams(resourceName, actionName);
ctx.log?.info && ctx.log.info('acl params', params);
if (params && resourcerAction.mergeParams) {
const filteredParams = filterParams(ctx, resourceName, params);
const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx);
ctx.permission.parsedParams = parsedParams;
ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams);
ctx.permission.rawParams = lodash.cloneDeep(resourcerAction.params);
resourcerAction.mergeParams(parsedParams, {
appends: (x, y) => {
if (!x) {
return [];
}
if (!y) {
return x;
}
return (x as any[]).filter((i) => y.includes(i.split('.').shift()));
},
});
ctx.permission.mergedParams = lodash.cloneDeep(resourcerAction.params);
}
await next();
},
{
tag: 'core',
group: 'core',
},
);
}
protected isAvailableAction(actionName: string) {
return this.availableActions.has(this.resolveActionAlias(actionName));
}
}

View File

@ -12,6 +12,8 @@ describe('acl', () => {
let admin;
let adminAgent;
let userPlugin;
let uiSchemaRepository: UiSchemaRepository;
afterEach(async () => {
@ -30,7 +32,7 @@ describe('acl', () => {
},
});
const userPlugin = app.getPlugin('users') as UsersPlugin;
userPlugin = app.getPlugin('users') as UsersPlugin;
adminAgent = app.agent().auth(
userPlugin.jwtService.sign({
@ -42,6 +44,131 @@ describe('acl', () => {
uiSchemaRepository = db.getRepository('uiSchemas');
});
test('append createById', async () => {
const Company = await db.getRepository('collections').create({
context: {},
values: {
name: 'companies',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasMany',
name: 'users',
},
],
},
});
const Repair = await db.getRepository('collections').create({
context: {},
values: {
name: 'repairs',
createdBy: true,
fields: [
{
type: 'belongsTo',
name: 'company',
},
{
type: 'string',
name: 'name',
},
],
},
});
const c1 = await db.getRepository('companies').create({
values: {
name: 'c1',
},
});
await db.getRepository('roles').create({
values: {
name: 'test-role',
},
});
await adminAgent.resource('roles.resources', 'test-role').create({
values: {
name: 'repairs',
usingActionsConfig: true,
actions: [
{
name: 'list',
fields: ['id'],
},
],
},
});
const u1 = await db.getRepository('users').create({
values: {
name: 'u1',
company: { id: c1.get('id') },
roles: ['test-role'],
},
});
const r1 = await db.getRepository('repairs').create({
values: {
name: 'r1',
company: { id: c1.get('id') },
},
});
userPlugin = app.getPlugin('users') as UsersPlugin;
const testAgent = app.agent().auth(
userPlugin.jwtService.sign({
userId: u1.get('id'),
}),
{ type: 'bearer' },
);
// @ts-ignore
const response1 = await testAgent.resource('repairs').list({
filter: {
company: {
id: {
$isVar: 'currentUser.company.id',
},
},
},
});
// @ts-ignore
const response2 = await testAgent.resource('repairs').list({
filter: {
company: {
id: {
$isVar: 'currentUser.company.id',
},
},
},
});
// @ts-ignore
const response3 = await testAgent.resource('repairs').list({
filter: {
company: {
id: {
$isVar: 'currentUser.company.id',
},
},
},
});
const acl = app.acl;
const canResult = acl.can({ role: 'test-role', resource: 'repairs', action: 'list' });
const params = canResult['params'];
expect(params['fields']).toHaveLength(3);
});
it('should not have permission to list comments', async () => {
await db.getCollection('collections').repository.create({
values: {