feat: acl middleware support
This commit is contained in:
		
							parent
							
								
									a507267b72
								
							
						
					
					
						commit
						ac7abedbb8
					
				@ -1,9 +1,10 @@
 | 
				
			|||||||
 | 
					import { Action } from '@nocobase/resourcer';
 | 
				
			||||||
 | 
					import EventEmitter from 'events';
 | 
				
			||||||
 | 
					import compose from 'koa-compose';
 | 
				
			||||||
import lodash from 'lodash';
 | 
					import lodash from 'lodash';
 | 
				
			||||||
 | 
					import { AclAvailableAction, AvailableActionOptions } from './acl-available-action';
 | 
				
			||||||
import { ACLAvailableStrategy, AvailableStrategyOptions, predicate } from './acl-available-strategy';
 | 
					import { ACLAvailableStrategy, AvailableStrategyOptions, predicate } from './acl-available-strategy';
 | 
				
			||||||
import { ACLRole, RoleActionParams } from './acl-role';
 | 
					import { ACLRole, RoleActionParams } from './acl-role';
 | 
				
			||||||
import { AclAvailableAction, AvailableActionOptions } from './acl-available-action';
 | 
					 | 
				
			||||||
import EventEmitter from 'events';
 | 
					 | 
				
			||||||
import { Action } from '@nocobase/resourcer';
 | 
					 | 
				
			||||||
const parse = require('json-templates');
 | 
					const parse = require('json-templates');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface CanResult {
 | 
					interface CanResult {
 | 
				
			||||||
@ -43,6 +44,7 @@ interface CanArgs {
 | 
				
			|||||||
export class ACL extends EventEmitter {
 | 
					export class ACL extends EventEmitter {
 | 
				
			||||||
  protected availableActions = new Map<string, AclAvailableAction>();
 | 
					  protected availableActions = new Map<string, AclAvailableAction>();
 | 
				
			||||||
  protected availableStrategy = new Map<string, ACLAvailableStrategy>();
 | 
					  protected availableStrategy = new Map<string, ACLAvailableStrategy>();
 | 
				
			||||||
 | 
					  protected middlewares = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  roles = new Map<string, ACLRole>();
 | 
					  roles = new Map<string, ACLRole>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -145,6 +147,11 @@ export class ACL extends EventEmitter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  can({ role, resource, action }: CanArgs): CanResult | null {
 | 
					  can({ role, resource, action }: CanArgs): CanResult | null {
 | 
				
			||||||
    const aclRole = this.roles.get(role);
 | 
					    const aclRole = this.roles.get(role);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!aclRole) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const aclResource = aclRole.getResource(resource);
 | 
					    const aclResource = aclRole.getResource(resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (aclResource) {
 | 
					    if (aclResource) {
 | 
				
			||||||
@ -196,33 +203,47 @@ export class ACL extends EventEmitter {
 | 
				
			|||||||
    return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
 | 
					    return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  use(fn: any) {
 | 
				
			||||||
 | 
					    this.middlewares.push(fn);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  middleware() {
 | 
					  middleware() {
 | 
				
			||||||
    const aclInstance = this;
 | 
					    const acl = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return async function ACLMiddleware(ctx, next) {
 | 
					    return async function ACLMiddleware(ctx, next) {
 | 
				
			||||||
      const roleName = ctx.state.currentRole;
 | 
					      const roleName = ctx.state.currentRole || 'anonymous';
 | 
				
			||||||
      const { resourceName, actionName } = ctx.action;
 | 
					      const { resourceName, actionName } = ctx.action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const resourcerAction: Action = ctx.action;
 | 
					      const resourcerAction: Action = ctx.action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      ctx.can = (options: Omit<CanArgs, 'role'>) => {
 | 
					      ctx.can = (options: Omit<CanArgs, 'role'>) => {
 | 
				
			||||||
        return aclInstance.can({ role: roleName, ...options });
 | 
					        return acl.can({ role: roleName, ...options });
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const canResult = ctx.can({ resource: resourceName, action: actionName });
 | 
					      ctx.permission = {
 | 
				
			||||||
 | 
					        can: ctx.can({ resource: resourceName, action: actionName }),
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!canResult) {
 | 
					      return compose(acl.middlewares)(ctx, async () => {
 | 
				
			||||||
        ctx.throw(403, 'no permission');
 | 
					        const permission = ctx.permission;
 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (lodash.get(canResult, 'params')) {
 | 
					        if (permission.skip) {
 | 
				
			||||||
        const template = parse(canResult.params);
 | 
					          return next();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        resourcerAction.mergeParams(template({ ctx }));
 | 
					        if (!permission.can || typeof permission.can !== 'object') {
 | 
				
			||||||
      }
 | 
					          ctx.throw(403, 'no permission');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await next();
 | 
					        const { params } = permission.can;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (params) {
 | 
				
			||||||
 | 
					          resourcerAction.mergeParams(parse(params)({ ctx }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await next();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,9 @@ export class PluginACL extends Plugin {
 | 
				
			|||||||
    const acl = createACL();
 | 
					    const acl = createACL();
 | 
				
			||||||
    this.acl = acl;
 | 
					    this.acl = acl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // @ts-ignore
 | 
				
			||||||
 | 
					    this.app.acl = acl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.app.db.registerModels({
 | 
					    this.app.db.registerModels({
 | 
				
			||||||
      RoleResourceActionModel,
 | 
					      RoleResourceActionModel,
 | 
				
			||||||
      RoleResourceModel,
 | 
					      RoleResourceModel,
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,17 @@ export class ClientPlugin extends Plugin {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async load() {
 | 
					  async load() {
 | 
				
			||||||
 | 
					    // @ts-ignore
 | 
				
			||||||
 | 
					    this.app.acl.use(async (ctx, next) => {
 | 
				
			||||||
 | 
					      const { resourceName } = ctx.action;
 | 
				
			||||||
 | 
					      if (resourceName === 'app') {
 | 
				
			||||||
 | 
					        ctx.permission = {
 | 
				
			||||||
 | 
					          skip: true,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await next();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.app.resource({
 | 
					    this.app.resource({
 | 
				
			||||||
      name: 'app',
 | 
					      name: 'app',
 | 
				
			||||||
      actions: {
 | 
					      actions: {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user