From ac7abedbb8ea74992dddf843b4fa79575445a960 Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 11 Feb 2022 19:31:53 +0800 Subject: [PATCH] feat: acl middleware support --- packages/acl/src/acl.ts | 53 +++++++++++++++++++--------- packages/plugin-acl/src/server.ts | 3 ++ packages/plugin-client/src/plugin.ts | 11 ++++++ 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/acl/src/acl.ts b/packages/acl/src/acl.ts index 0d4641745..66d36b22e 100644 --- a/packages/acl/src/acl.ts +++ b/packages/acl/src/acl.ts @@ -1,9 +1,10 @@ +import { Action } from '@nocobase/resourcer'; +import EventEmitter from 'events'; +import compose from 'koa-compose'; import lodash from 'lodash'; +import { AclAvailableAction, AvailableActionOptions } from './acl-available-action'; import { ACLAvailableStrategy, AvailableStrategyOptions, predicate } from './acl-available-strategy'; 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'); interface CanResult { @@ -43,6 +44,7 @@ interface CanArgs { export class ACL extends EventEmitter { protected availableActions = new Map(); protected availableStrategy = new Map(); + protected middlewares = []; roles = new Map(); @@ -145,6 +147,11 @@ export class ACL extends EventEmitter { can({ role, resource, action }: CanArgs): CanResult | null { const aclRole = this.roles.get(role); + + if (!aclRole) { + return null; + } + const aclResource = aclRole.getResource(resource); if (aclResource) { @@ -196,33 +203,47 @@ export class ACL extends EventEmitter { return this.actionAlias.get(action) ? this.actionAlias.get(action) : action; } + use(fn: any) { + this.middlewares.push(fn); + } + middleware() { - const aclInstance = this; + const acl = this; return async function ACLMiddleware(ctx, next) { - const roleName = ctx.state.currentRole; + const roleName = ctx.state.currentRole || 'anonymous'; const { resourceName, actionName } = ctx.action; const resourcerAction: Action = ctx.action; ctx.can = (options: Omit) => { - 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) { - ctx.throw(403, 'no permission'); - return; - } + return compose(acl.middlewares)(ctx, async () => { + const permission = ctx.permission; - if (lodash.get(canResult, 'params')) { - const template = parse(canResult.params); + if (permission.skip) { + 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(); + }); }; } } diff --git a/packages/plugin-acl/src/server.ts b/packages/plugin-acl/src/server.ts index f1e01d3dc..9bf3fc460 100644 --- a/packages/plugin-acl/src/server.ts +++ b/packages/plugin-acl/src/server.ts @@ -86,6 +86,9 @@ export class PluginACL extends Plugin { const acl = createACL(); this.acl = acl; + // @ts-ignore + this.app.acl = acl; + this.app.db.registerModels({ RoleResourceActionModel, RoleResourceModel, diff --git a/packages/plugin-client/src/plugin.ts b/packages/plugin-client/src/plugin.ts index 0651f0d9e..5330f4c79 100644 --- a/packages/plugin-client/src/plugin.ts +++ b/packages/plugin-client/src/plugin.ts @@ -18,6 +18,17 @@ export class ClientPlugin extends Plugin { } 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({ name: 'app', actions: {