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 { 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) {
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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: {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user