feat: app.acl support
This commit is contained in:
parent
a2037d90c9
commit
ffbf4ecb66
@ -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';
|
|
||||||
|
16
packages/acl/src/skip-middleware.ts
Normal file
16
packages/acl/src/skip-middleware.ts
Normal 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;
|
||||||
|
}
|
@ -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 () => {
|
||||||
|
@ -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: {
|
||||||
|
@ -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: {
|
||||||
|
@ -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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -32,6 +32,8 @@ const roleCollectionsResource = {
|
|||||||
usingConfig,
|
usingConfig,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await next();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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());
|
||||||
|
@ -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: {
|
||||||
|
@ -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',
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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',
|
||||||
},
|
},
|
||||||
};
|
};
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user