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 () => {
|
||||||
|
const permission = ctx.permission;
|
||||||
|
|
||||||
|
if (permission.skip) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permission.can || typeof permission.can !== 'object') {
|
||||||
ctx.throw(403, 'no permission');
|
ctx.throw(403, 'no permission');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lodash.get(canResult, 'params')) {
|
const { params } = permission.can;
|
||||||
const template = parse(canResult.params);
|
|
||||||
|
|
||||||
resourcerAction.mergeParams(template({ ctx }));
|
if (params) {
|
||||||
|
resourcerAction.mergeParams(parse(params)({ ctx }));
|
||||||
}
|
}
|
||||||
|
|
||||||
await next();
|
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