feat: app.acl support

This commit is contained in:
chenos 2022-02-11 23:59:03 +08:00
parent a2037d90c9
commit ffbf4ecb66
18 changed files with 80 additions and 56 deletions

View File

@ -1,5 +1,7 @@
export * from './acl';
export * from './acl-available-action';
export * from './acl-available-strategy';
export * from './acl-resource'; export * from './acl-resource';
export * from './acl-role'; export * from './acl-role';
export * from './acl-available-strategy'; export * from './skip-middleware';
export * from './acl-available-action';
export * from './acl';

View File

@ -0,0 +1,16 @@
export const skip = (options: ACLSkipOptions) => {
return async function ACLSkipMiddleware(ctx, next) {
const { resourceName, actionName } = ctx.action;
if (resourceName === options.resourceName && actionName === options.actionName) {
ctx.permission = {
skip: true,
};
}
await next();
};
};
interface ACLSkipOptions {
resourceName: string;
actionName: string;
}

View File

@ -1,8 +1,7 @@
import { ACL } from '@nocobase/acl';
import { Database } from '@nocobase/database';
import { MockServer } from '@nocobase/test'; import { MockServer } from '@nocobase/test';
import { prepareApp } from './prepare'; import { prepareApp } from './prepare';
import { Database } from '@nocobase/database';
import { ACL } from '@nocobase/acl';
import PluginACL from '../server';
describe('acl', () => { describe('acl', () => {
let app: MockServer; let app: MockServer;
@ -16,9 +15,7 @@ describe('acl', () => {
beforeEach(async () => { beforeEach(async () => {
app = await prepareApp(); app = await prepareApp();
db = app.db; db = app.db;
const aclPlugin = app.getPlugin<PluginACL>('PluginACL'); acl = app.acl;
acl = aclPlugin.getACL();
}); });
it('should works with universal actions', async () => { it('should works with universal actions', async () => {

View File

@ -1,8 +1,7 @@
import { MockServer } from '@nocobase/test';
import { Database, HasManyRepository, Model } from '@nocobase/database';
import { ACL } from '@nocobase/acl'; import { ACL } from '@nocobase/acl';
import { Database, HasManyRepository, Model } from '@nocobase/database';
import { MockServer } from '@nocobase/test';
import { prepareApp } from './prepare'; import { prepareApp } from './prepare';
import PluginACL from '@nocobase/plugin-acl';
describe('association field acl', () => { describe('association field acl', () => {
let app: MockServer; let app: MockServer;
@ -18,9 +17,7 @@ describe('association field acl', () => {
beforeEach(async () => { beforeEach(async () => {
app = await prepareApp(); app = await prepareApp();
db = app.db; db = app.db;
const aclPlugin = app.getPlugin<PluginACL>('PluginACL'); acl = app.acl;
acl = aclPlugin.getACL();
role = await db.getRepository('roles').create({ role = await db.getRepository('roles').create({
values: { values: {

View File

@ -1,8 +1,7 @@
import { ACL } from '@nocobase/acl';
import { Database, Model } from '@nocobase/database';
import { MockServer } from '@nocobase/test'; import { MockServer } from '@nocobase/test';
import { changeMockUser, prepareApp } from './prepare'; import { changeMockUser, prepareApp } from './prepare';
import { Database, Model } from '@nocobase/database';
import { ACL } from '@nocobase/acl';
import PluginACL from '@nocobase/plugin-acl';
describe('middleware', () => { describe('middleware', () => {
let app: MockServer; let app: MockServer;
@ -13,7 +12,7 @@ describe('middleware', () => {
beforeEach(async () => { beforeEach(async () => {
app = await prepareApp(); app = await prepareApp();
db = app.db; db = app.db;
acl = app.getPlugin<PluginACL>('PluginACL').getACL(); acl = app.acl;
await db.getRepository('roles').create({ await db.getRepository('roles').create({
values: { values: {

View File

@ -1,13 +1,12 @@
import PluginACL from '..';
const availableActionResource = { const availableActionResource = {
name: 'availableActions', name: 'availableActions',
actions: { actions: {
list(ctx, next) { async list(ctx, next) {
const aclPlugin: PluginACL = ctx.app.getPlugin('PluginACL'); const acl = ctx.app.acl;
const acl = aclPlugin.getACL();
const availableActions = acl.getAvailableActions(); const availableActions = acl.getAvailableActions();
ctx.body = Array.from(availableActions.entries()).map((item) => item[1]); ctx.body = Array.from(availableActions.entries()).map((item) => item[1]);
await next();
}, },
}, },
}; };

View File

@ -32,6 +32,8 @@ const roleCollectionsResource = {
usingConfig, usingConfig,
}; };
}); });
await next();
}, },
}, },
}; };

View File

@ -1,7 +1,5 @@
import { ACL } from '@nocobase/acl';
import { Plugin } from '@nocobase/server'; import { Plugin } from '@nocobase/server';
import path from 'path'; import { resolve } from 'path';
import { createACL } from './acl';
import { availableActionResource } from './actions/available-actions'; import { availableActionResource } from './actions/available-actions';
import { roleCollectionsResource } from './actions/role-collections'; import { roleCollectionsResource } from './actions/role-collections';
import { RoleResourceActionModel } from './model/RoleResourceActionModel'; import { RoleResourceActionModel } from './model/RoleResourceActionModel';
@ -28,18 +26,17 @@ export class GrantHelper {
} }
export class PluginACL extends Plugin { export class PluginACL extends Plugin {
acl: ACL;
associationFieldsActions: AssociationFieldsActions = {}; associationFieldsActions: AssociationFieldsActions = {};
grantHelper = new GrantHelper(); grantHelper = new GrantHelper();
registerAssociationFieldAction(associationType: string, value: AssociationFieldActions) { get acl() {
this.associationFieldsActions[associationType] = value; return this.app.acl;
} }
getACL() { registerAssociationFieldAction(associationType: string, value: AssociationFieldActions) {
return this.acl; this.associationFieldsActions[associationType] = value;
} }
registerAssociationFieldsActions() { registerAssociationFieldsActions() {
@ -104,11 +101,6 @@ export class PluginACL extends Plugin {
} }
async beforeLoad() { async beforeLoad() {
const acl = createACL();
this.acl = acl;
// @ts-ignore
this.app.acl = acl;
this.app.db.registerModels({ this.app.db.registerModels({
RoleResourceActionModel, RoleResourceActionModel,
@ -123,10 +115,10 @@ export class PluginACL extends Plugin {
this.app.db.on('roles.afterSave', async (model, options) => { this.app.db.on('roles.afterSave', async (model, options) => {
const { transaction } = options; const { transaction } = options;
const roleName = model.get('name'); const roleName = model.get('name');
let role = acl.getRole(roleName); let role = this.acl.getRole(roleName);
if (!role) { if (!role) {
role = acl.define({ role = this.acl.define({
role: model.get('name'), role: model.get('name'),
}); });
} }
@ -153,7 +145,7 @@ export class PluginACL extends Plugin {
this.app.db.on('roles.afterDestroy', (model) => { this.app.db.on('roles.afterDestroy', (model) => {
const roleName = model.get('name'); const roleName = model.get('name');
acl.removeRole(roleName); this.acl.removeRole(roleName);
}); });
this.app.db.on('rolesResources.afterSaveWithAssociations', async (model: RoleResourceModel, options) => { this.app.db.on('rolesResources.afterSaveWithAssociations', async (model: RoleResourceModel, options) => {
@ -241,7 +233,7 @@ export class PluginACL extends Plugin {
async load() { async load() {
await this.app.db.import({ await this.app.db.import({
directory: path.resolve(__dirname, 'collections'), directory: resolve(__dirname, 'collections'),
}); });
this.app.resourcer.use(this.acl.middleware()); this.app.resourcer.use(this.acl.middleware());

View File

@ -1,3 +1,4 @@
import { skip } from '@nocobase/acl';
import { Plugin } from '@nocobase/server'; import { Plugin } from '@nocobase/server';
export class ClientPlugin extends Plugin { export class ClientPlugin extends Plugin {
@ -18,17 +19,12 @@ export class ClientPlugin extends Plugin {
} }
async load() { async load() {
// @ts-ignore this.app.acl.use(
this.app.acl.use(async (ctx, next) => { skip({
const { resourceName } = ctx.action; resourceName: 'app',
if (resourceName === 'app') { actionName: 'getLang',
ctx.permission = { }),
skip: true, );
};
}
await next();
});
this.app.resource({ this.app.resource({
name: 'app', name: 'app',
actions: { actions: {

View File

@ -1,3 +1,4 @@
import { skip } from '@nocobase/acl';
import { Plugin } from '@nocobase/server'; import { Plugin } from '@nocobase/server';
import { resolve } from 'path'; import { resolve } from 'path';
@ -23,6 +24,12 @@ export class SystemSettingsPlugin extends Plugin {
await this.app.db.import({ await this.app.db.import({
directory: resolve(__dirname, 'collections'), directory: resolve(__dirname, 'collections'),
}); });
this.app.acl.use(
skip({
resourceName: 'systemSettings',
actionName: 'get',
}),
);
} }
} }

View File

@ -1,3 +1,4 @@
import { skip } from '@nocobase/acl';
import { MagicAttributeModel } from '@nocobase/database'; import { MagicAttributeModel } from '@nocobase/database';
import { Plugin } from '@nocobase/server'; import { Plugin } from '@nocobase/server';
import { resolve } from 'path'; import { resolve } from 'path';
@ -130,6 +131,12 @@ export class UiRoutesStoragePlugin extends Plugin {
this.app.db.import({ this.app.db.import({
directory: resolve(__dirname, 'collections'), directory: resolve(__dirname, 'collections'),
}); });
this.app.acl.use(
skip({
resourceName: 'uiRoutes',
actionName: 'getAccessible',
}),
);
} }
} }

View File

@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@koa/cors": "^3.1.0", "@koa/cors": "^3.1.0",
"@koa/router": "^9.4.0", "@koa/router": "^9.4.0",
"@nocobase/acl": "0.6.0-alpha.0",
"@nocobase/actions": "^0.6.0-alpha.0", "@nocobase/actions": "^0.6.0-alpha.0",
"@nocobase/database": "^0.6.0-alpha.0", "@nocobase/database": "^0.6.0-alpha.0",
"@nocobase/resourcer": "^0.6.0-alpha.0", "@nocobase/resourcer": "^0.6.0-alpha.0",

View File

@ -4,24 +4,28 @@ const availableActions: {
[key: string]: AvailableActionOptions; [key: string]: AvailableActionOptions;
} = { } = {
create: { create: {
displayName: 't("create")', displayName: 't("Create")',
type: 'new-data', type: 'new-data',
}, },
import: { import: {
displayName: 't("import")', displayName: 't("Import")',
type: 'new-data',
},
export: {
displayName: 't("Import")',
type: 'new-data', type: 'new-data',
}, },
view: { view: {
displayName: 't("view")', displayName: 't("View")',
type: 'old-data', type: 'old-data',
aliases: ['get', 'list'], aliases: ['get', 'list'],
}, },
update: { update: {
displayName: 't("edit")', displayName: 't("Edit")',
type: 'old-data', type: 'old-data',
}, },
destroy: { destroy: {
displayName: 't("destroy")', displayName: 't("Delete")',
type: 'old-data', type: 'old-data',
}, },
}; };

View File

@ -1,3 +1,4 @@
import { ACL } from '@nocobase/acl';
import { registerActions } from '@nocobase/actions'; import { registerActions } from '@nocobase/actions';
import Database, { CleanOptions, CollectionOptions, DatabaseOptions, SyncOptions } from '@nocobase/database'; import Database, { CleanOptions, CollectionOptions, DatabaseOptions, SyncOptions } from '@nocobase/database';
import Resourcer, { ResourceOptions } from '@nocobase/resourcer'; import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
@ -7,6 +8,7 @@ import { Server } from 'http';
import { i18n, InitOptions } from 'i18next'; import { i18n, InitOptions } from 'i18next';
import Koa from 'koa'; import Koa from 'koa';
import { isBoolean } from 'lodash'; import { isBoolean } from 'lodash';
import { createACL } from './acl';
import { createCli, createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper'; import { createCli, createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
import { PluginManager } from './plugin-manager'; import { PluginManager } from './plugin-manager';
@ -86,6 +88,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
public readonly pm: PluginManager; public readonly pm: PluginManager;
public readonly acl: ACL;
protected plugins = new Map<string, Plugin>(); protected plugins = new Map<string, Plugin>();
public listenServer: Server; public listenServer: Server;
@ -93,6 +97,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
constructor(options: ApplicationOptions) { constructor(options: ApplicationOptions) {
super(); super();
this.acl = createACL();
this.db = createDatabase(options); this.db = createDatabase(options);
this.resourcer = createResourcer(options); this.resourcer = createResourcer(options);
this.cli = createCli(this, options); this.cli = createCli(this, options);