fix: append acl resource params (#1923)
* test: append acl resource params * fix: test
This commit is contained in:
parent
7080db72eb
commit
710bd2dfd2
@ -1,5 +1,6 @@
|
|||||||
import { ACLRole, RoleActionParams } from './acl-role';
|
import { ACLRole, RoleActionParams } from './acl-role';
|
||||||
import { ACL, ListenerContext } from './acl';
|
import { ACL, ListenerContext } from './acl';
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
export type ResourceActions = { [key: string]: RoleActionParams };
|
export type ResourceActions = { [key: string]: RoleActionParams };
|
||||||
|
|
||||||
@ -35,7 +36,15 @@ export class ACLResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAction(name: string) {
|
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) {
|
setAction(name: string, params: RoleActionParams) {
|
||||||
|
@ -45,20 +45,15 @@ interface CanArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ACL extends EventEmitter {
|
export class ACL extends EventEmitter {
|
||||||
protected availableActions = new Map<string, ACLAvailableAction>();
|
|
||||||
public availableStrategy = new Map<string, ACLAvailableStrategy>();
|
public availableStrategy = new Map<string, ACLAvailableStrategy>();
|
||||||
protected fixedParamsManager = new FixedParamsManager();
|
|
||||||
|
|
||||||
protected middlewares: Toposort<any>;
|
|
||||||
|
|
||||||
public allowManager = new AllowManager(this);
|
public allowManager = new AllowManager(this);
|
||||||
public snippetManager = new SnippetManager();
|
public snippetManager = new SnippetManager();
|
||||||
|
|
||||||
roles = new Map<string, ACLRole>();
|
roles = new Map<string, ACLRole>();
|
||||||
|
|
||||||
actionAlias = new Map<string, string>();
|
actionAlias = new Map<string, string>();
|
||||||
|
|
||||||
configResources: string[] = [];
|
configResources: string[] = [];
|
||||||
|
protected availableActions = new Map<string, ACLAvailableAction>();
|
||||||
|
protected fixedParamsManager = new FixedParamsManager();
|
||||||
|
protected middlewares: Toposort<any>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -100,68 +95,6 @@ export class ACL extends EventEmitter {
|
|||||||
this.addCoreMiddleware();
|
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 {
|
define(options: DefineOptions): ACLRole {
|
||||||
const roleName = options.role;
|
const roleName = options.role;
|
||||||
const role = new ACLRole(this, roleName);
|
const role = new ACLRole(this, roleName);
|
||||||
@ -302,10 +235,6 @@ export class ACL extends EventEmitter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected isAvailableAction(actionName: string) {
|
|
||||||
return this.availableActions.has(this.resolveActionAlias(actionName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public resolveActionAlias(action: string) {
|
public resolveActionAlias(action: string) {
|
||||||
return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
|
return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
|
||||||
}
|
}
|
||||||
@ -360,7 +289,9 @@ export class ACL extends EventEmitter {
|
|||||||
const { resourceName, actionName } = ctx.action;
|
const { resourceName, actionName } = ctx.action;
|
||||||
|
|
||||||
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
||||||
return acl.can({ role: roleName, ...options });
|
const canResult = acl.can({ role: roleName, ...options });
|
||||||
|
|
||||||
|
return canResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.permission = {
|
ctx.permission = {
|
||||||
@ -397,4 +328,70 @@ export class ACL extends EventEmitter {
|
|||||||
registerSnippet(snippet: SnippetOptions) {
|
registerSnippet(snippet: SnippetOptions) {
|
||||||
this.snippetManager.register(snippet);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ describe('acl', () => {
|
|||||||
let admin;
|
let admin;
|
||||||
let adminAgent;
|
let adminAgent;
|
||||||
|
|
||||||
|
let userPlugin;
|
||||||
|
|
||||||
let uiSchemaRepository: UiSchemaRepository;
|
let uiSchemaRepository: UiSchemaRepository;
|
||||||
|
|
||||||
afterEach(async () => {
|
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(
|
adminAgent = app.agent().auth(
|
||||||
userPlugin.jwtService.sign({
|
userPlugin.jwtService.sign({
|
||||||
@ -42,6 +44,131 @@ describe('acl', () => {
|
|||||||
uiSchemaRepository = db.getRepository('uiSchemas');
|
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 () => {
|
it('should not have permission to list comments', async () => {
|
||||||
await db.getCollection('collections').repository.create({
|
await db.getCollection('collections').repository.create({
|
||||||
values: {
|
values: {
|
||||||
|
Loading…
Reference in New Issue
Block a user