@nocobase/plugin-acl 0.8.1-alpha.4 → 0.9.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +661 -201
  2. package/lib/actions/role-check.js +2 -0
  3. package/lib/actions/role-collections.js +39 -12
  4. package/lib/collections/roles.js +4 -0
  5. package/lib/collections/rolesResourcesActions.js +2 -1
  6. package/lib/collections/users.js +1 -0
  7. package/lib/migrations/20221214072638-set-role-snippets.d.ts +5 -0
  8. package/lib/migrations/20221214072638-set-role-snippets.js +49 -0
  9. package/lib/model/RoleModel.js +1 -0
  10. package/lib/model/RoleResourceActionModel.js +9 -3
  11. package/lib/server.js +393 -28
  12. package/package.json +7 -13
  13. package/src/__tests__/acl.test.ts +0 -548
  14. package/src/__tests__/association-field.test.ts +0 -308
  15. package/src/__tests__/configuration.test.ts +0 -74
  16. package/src/__tests__/middleware.test.ts +0 -228
  17. package/src/__tests__/own.test.ts +0 -133
  18. package/src/__tests__/prepare.ts +0 -20
  19. package/src/__tests__/role-check.test.ts +0 -41
  20. package/src/__tests__/role-resource.test.ts +0 -189
  21. package/src/__tests__/role-user.test.ts +0 -123
  22. package/src/__tests__/role.test.ts +0 -99
  23. package/src/__tests__/scope.test.ts +0 -59
  24. package/src/__tests__/setCurrentRole.test.ts +0 -83
  25. package/src/__tests__/users.test.ts +0 -52
  26. package/src/actions/available-actions.ts +0 -18
  27. package/src/actions/role-check.ts +0 -41
  28. package/src/actions/role-collections.ts +0 -65
  29. package/src/actions/user-setDefaultRole.ts +0 -45
  30. package/src/collections/roles-users.ts +0 -6
  31. package/src/collections/roles.ts +0 -79
  32. package/src/collections/rolesResources.ts +0 -31
  33. package/src/collections/rolesResourcesActions.ts +0 -28
  34. package/src/collections/rolesResourcesScopes.ts +0 -23
  35. package/src/collections/users.ts +0 -30
  36. package/src/index.ts +0 -2
  37. package/src/middlewares/setCurrentRole.ts +0 -32
  38. package/src/model/RoleModel.ts +0 -21
  39. package/src/model/RoleResourceActionModel.ts +0 -88
  40. package/src/model/RoleResourceModel.ts +0 -74
  41. package/src/server.ts +0 -463
@@ -1,83 +0,0 @@
1
- import Database from '@nocobase/database';
2
- import UsersPlugin from '@nocobase/plugin-users';
3
- import { MockServer } from '@nocobase/test';
4
- import { setCurrentRole } from '../middlewares/setCurrentRole';
5
- import { prepareApp } from './prepare';
6
-
7
- describe('role', () => {
8
- let api: MockServer;
9
- let db: Database;
10
-
11
- let usersPlugin: UsersPlugin;
12
- let ctx;
13
-
14
- beforeEach(async () => {
15
- api = await prepareApp();
16
-
17
- db = api.db;
18
- usersPlugin = api.getPlugin('users');
19
-
20
- ctx = {
21
- db,
22
- state: {
23
- currentRole: '',
24
- }
25
- }
26
- });
27
-
28
- afterEach(async () => {
29
- await api.destroy();
30
- });
31
-
32
- it('should set role with X-Role when exists', async () => {
33
- ctx.state.currentUser = await db.getRepository('users').findOne({
34
- appends: ['roles'],
35
- });
36
- ctx.get = function(name) {
37
- if (name === 'X-Role') {
38
- return 'admin';
39
- }
40
- };
41
- await setCurrentRole(ctx, () => {});
42
- expect(ctx.state.currentRole).toBe('admin');
43
- });
44
-
45
- it('should set role with default', async () => {
46
- ctx.state.currentUser = await db.getRepository('users').findOne({
47
- appends: ['roles'],
48
- });
49
- ctx.get = function (name) {
50
- if (name === 'X-Role') {
51
- return '';
52
- }
53
- };
54
- await setCurrentRole(ctx, () => {});
55
- expect(ctx.state.currentRole).toBe('root');
56
- });
57
-
58
- it('should set role with default when x-role does not exist', async () => {
59
- ctx.state.currentUser = await db.getRepository('users').findOne({
60
- appends: ['roles'],
61
- });
62
- ctx.get = function (name) {
63
- if (name === 'X-Role') {
64
- return 'abc';
65
- }
66
- };
67
- await setCurrentRole(ctx, () => {});
68
- expect(ctx.state.currentRole).toBe('root');
69
- });
70
-
71
- it('should set role with anonymous', async () => {
72
- ctx.state.currentUser = await db.getRepository('users').findOne({
73
- appends: ['roles'],
74
- });
75
- ctx.get = function (name) {
76
- if (name === 'X-Role') {
77
- return 'anonymous';
78
- }
79
- };
80
- await setCurrentRole(ctx, () => {});
81
- expect(ctx.state.currentRole).toBe('anonymous');
82
- });
83
- });
@@ -1,52 +0,0 @@
1
- import Database from '@nocobase/database';
2
- import { MockServer } from '@nocobase/test';
3
- import { prepareApp } from './prepare';
4
-
5
- describe('actions', () => {
6
- let app: MockServer;
7
- let db: Database;
8
- let adminUser;
9
- let agent;
10
- let adminAgent;
11
- let pluginUser;
12
-
13
- beforeEach(async () => {
14
- process.env.INIT_ROOT_EMAIL = 'test@nocobase.com';
15
- process.env.INIT_ROOT_PASSWORD = '123456';
16
- process.env.INIT_ROOT_NICKNAME = 'Test';
17
-
18
- app = await prepareApp();
19
- db = app.db;
20
-
21
- pluginUser = app.getPlugin('users');
22
- adminUser = await db.getRepository('users').findOne({
23
- filter: {
24
- email: process.env.INIT_ROOT_EMAIL
25
- },
26
- appends: ['roles']
27
- });
28
-
29
- agent = app.agent();
30
- adminAgent = app.agent().auth(
31
- pluginUser.jwtService.sign({
32
- userId: adminUser.get('id'),
33
- }),
34
- { type: 'bearer' },
35
- );
36
- });
37
-
38
- afterEach(async () => {
39
- await db.close();
40
- });
41
-
42
- it('update profile with roles', async () => {
43
- const res2 = await adminAgent.resource('users').updateProfile({
44
- filterByTk: adminUser.id,
45
- values: {
46
- nickname: 'a',
47
- roles: adminUser.roles
48
- }
49
- });
50
- expect(res2.status).toBe(200);
51
- });
52
- });
@@ -1,18 +0,0 @@
1
- const availableActionResource = {
2
- name: 'availableActions',
3
- actions: {
4
- async list(ctx, next) {
5
- const acl = ctx.app.acl;
6
- const availableActions = acl.getAvailableActions();
7
- ctx.body = Array.from(availableActions.entries()).map(([, { name, options }]) => {
8
- return {
9
- ...options,
10
- name,
11
- };
12
- });
13
- await next();
14
- },
15
- },
16
- };
17
-
18
- export { availableActionResource };
@@ -1,41 +0,0 @@
1
- const map2obj = (map: Map<string, string>) => {
2
- const obj = {};
3
- for(let [key, value] of map){
4
- obj[key] = value;
5
- }
6
- return obj;
7
- }
8
-
9
- export async function checkAction(ctx, next) {
10
- const currentRole = ctx.state.currentRole;
11
- if (currentRole) {
12
- const roleInstance = await ctx.db.getRepository('roles').findOne({
13
- filter: {
14
- name: currentRole,
15
- },
16
- appends: ['menuUiSchemas'],
17
- });
18
-
19
- const anonymous = await ctx.db.getRepository('roles').findOne({
20
- filter: {
21
- name: 'anonymous',
22
- },
23
- });
24
-
25
- const role = ctx.app.acl.getRole(currentRole);
26
-
27
- ctx.body = {
28
- ...role.toJSON(),
29
- resources: [...role.resources.keys()],
30
- actionAlias: map2obj(ctx.app.acl.actionAlias),
31
- allowAll: currentRole === 'root',
32
- allowConfigure: roleInstance.get('allowConfigure'),
33
- allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')),
34
- allowAnonymous: !!anonymous,
35
- };
36
- } else {
37
- throw new Error('Role not found');
38
- }
39
-
40
- await next();
41
- }
@@ -1,65 +0,0 @@
1
- import { Database } from '@nocobase/database';
2
-
3
- type UsingConfigType = 'strategy' | 'resourceAction';
4
-
5
- function totalPage(total, pageSize): number {
6
- return Math.ceil(total / pageSize);
7
- }
8
-
9
- const roleCollectionsResource = {
10
- name: 'roles.collections',
11
- actions: {
12
- async list(ctx, next) {
13
- const role = ctx.action.params.associatedIndex;
14
- const { page = 1, pageSize = 20 } = ctx.action.params;
15
-
16
- const db: Database = ctx.db;
17
- const collectionRepository = db.getRepository('collections');
18
-
19
- // all collections
20
- const [collections, count] = await collectionRepository.findAndCount({
21
- filter: ctx.action.params.filter,
22
- sort: 'sort',
23
- });
24
-
25
- // role collections
26
- const roleResources = await db.getRepository('rolesResources').find({
27
- filter: {
28
- roleName: role,
29
- },
30
- });
31
-
32
- // role collections
33
- const roleResourcesNames = roleResources.map((roleResource) => roleResource.get('name'));
34
- const roleResourceActionResourceNames = roleResources
35
- .filter((roleResources) => roleResources.get('usingActionsConfig'))
36
- .map((roleResources) => roleResources.get('name'));
37
-
38
- ctx.body = {
39
- count,
40
- rows: collections.map((collection) => {
41
- const exists = roleResourcesNames.includes(collection.get('name'));
42
-
43
- const usingConfig: UsingConfigType = roleResourceActionResourceNames.includes(collection.get('name'))
44
- ? 'resourceAction'
45
- : 'strategy';
46
-
47
- return {
48
- name: collection.get('name') as string,
49
- title: collection.get('title') as string,
50
- roleName: role,
51
- usingConfig,
52
- exists,
53
- };
54
- }),
55
- page: Number(page),
56
- pageSize: Number(pageSize),
57
- totalPage: totalPage(count, pageSize),
58
- };
59
-
60
- await next();
61
- },
62
- },
63
- };
64
-
65
- export { roleCollectionsResource };
@@ -1,45 +0,0 @@
1
- import { Context, Next } from '@nocobase/actions';
2
-
3
- export async function setDefaultRole(ctx: Context, next: Next) {
4
- const {
5
- values: { roleName },
6
- } = ctx.action.params;
7
-
8
- const {
9
- db,
10
- state: { currentUser },
11
- action: { params: { values } }
12
- } = ctx;
13
-
14
- if (values.roleName == 'anonymous') {
15
- return next();
16
- }
17
-
18
- const repository = db.getRepository('rolesUsers');
19
-
20
- await db.sequelize.transaction(async transaction => {
21
- await repository.update({
22
- filter: {
23
- userId: currentUser.get('id'),
24
- },
25
- values: {
26
- default: false,
27
- },
28
- transaction,
29
- });
30
- await repository.update({
31
- filter: {
32
- userId: currentUser.get('id'),
33
- roleName,
34
- },
35
- values: {
36
- default: true,
37
- },
38
- transaction,
39
- });
40
- });
41
-
42
- ctx.body = 'ok';
43
-
44
- await next();
45
- }
@@ -1,6 +0,0 @@
1
- import { CollectionOptions } from '@nocobase/database';
2
-
3
- export default {
4
- name: 'rolesUsers',
5
- fields: [{ type: 'boolean', name: 'default' }],
6
- } as CollectionOptions;
@@ -1,79 +0,0 @@
1
- import { CollectionOptions } from '@nocobase/database';
2
-
3
- export default {
4
- name: 'roles',
5
- title: '{{t("Roles")}}',
6
- autoGenId: false,
7
- model: 'RoleModel',
8
- filterTargetKey: 'name',
9
- // targetKey: 'name',
10
- sortable: true,
11
- fields: [
12
- {
13
- type: 'uid',
14
- name: 'name',
15
- prefix: 'r_',
16
- primaryKey: true,
17
- interface: 'input',
18
- uiSchema: {
19
- type: 'string',
20
- title: '{{t("Role UID")}}',
21
- 'x-component': 'Input',
22
- },
23
- },
24
- {
25
- type: 'string',
26
- name: 'title',
27
- unique: true,
28
- interface: 'input',
29
- uiSchema: {
30
- type: 'string',
31
- title: '{{t("Role name")}}',
32
- 'x-component': 'Input',
33
- },
34
- },
35
- {
36
- type: 'boolean',
37
- name: 'default',
38
- },
39
- {
40
- type: 'string',
41
- name: 'description',
42
- },
43
- {
44
- type: 'json',
45
- name: 'strategy',
46
- },
47
- {
48
- type: 'boolean',
49
- name: 'default',
50
- defaultValue: false,
51
- },
52
- {
53
- type: 'boolean',
54
- name: 'hidden',
55
- defaultValue: false,
56
- },
57
- {
58
- type: 'boolean',
59
- name: 'allowConfigure',
60
- },
61
- {
62
- type: 'boolean',
63
- name: 'allowNewMenu',
64
- },
65
- {
66
- type: 'belongsToMany',
67
- name: 'menuUiSchemas',
68
- target: 'uiSchemas',
69
- targetKey: 'x-uid',
70
- },
71
- {
72
- type: 'hasMany',
73
- name: 'resources',
74
- target: 'rolesResources',
75
- sourceKey: 'name',
76
- targetKey: 'name',
77
- },
78
- ],
79
- } as CollectionOptions;
@@ -1,31 +0,0 @@
1
- import { CollectionOptions } from '@nocobase/database';
2
-
3
- export default {
4
- name: 'rolesResources',
5
- model: 'RoleResourceModel',
6
- indexes: [
7
- {
8
- unique: true,
9
- fields: ['roleName', 'name'],
10
- },
11
- ],
12
- fields: [
13
- {
14
- type: 'belongsTo',
15
- name: 'role',
16
- },
17
- {
18
- type: 'string',
19
- name: 'name',
20
- },
21
- {
22
- type: 'boolean',
23
- name: 'usingActionsConfig',
24
- },
25
- {
26
- type: 'hasMany',
27
- name: 'actions',
28
- target: 'rolesResourcesActions',
29
- },
30
- ],
31
- } as CollectionOptions;
@@ -1,28 +0,0 @@
1
- import { CollectionOptions } from '@nocobase/database';
2
-
3
- export default {
4
- name: 'rolesResourcesActions',
5
- model: 'RoleResourceActionModel',
6
- fields: [
7
- {
8
- type: 'belongsTo',
9
- name: 'resource',
10
- foreignKey: 'rolesResourceId',
11
- target: 'rolesResources',
12
- },
13
- {
14
- type: 'string',
15
- name: 'name',
16
- },
17
- {
18
- type: 'array',
19
- name: 'fields',
20
- defaultValue: [],
21
- },
22
- {
23
- type: 'belongsTo',
24
- name: 'scope',
25
- target: 'rolesResourcesScopes',
26
- },
27
- ],
28
- } as CollectionOptions;
@@ -1,23 +0,0 @@
1
- import { CollectionOptions } from '@nocobase/database';
2
-
3
- export default {
4
- name: 'rolesResourcesScopes',
5
- fields: [
6
- {
7
- type: 'uid',
8
- name: 'key',
9
- },
10
- {
11
- type: 'string',
12
- name: 'name',
13
- },
14
- {
15
- type: 'string',
16
- name: 'resourceName',
17
- },
18
- {
19
- type: 'json',
20
- name: 'scope',
21
- },
22
- ],
23
- } as CollectionOptions;
@@ -1,30 +0,0 @@
1
- import { extend } from '@nocobase/database';
2
-
3
- export default extend({
4
- name: 'users',
5
- fields: [
6
- {
7
- interface: 'm2m',
8
- type: 'belongsToMany',
9
- name: 'roles',
10
- target: 'roles',
11
- foreignKey: 'userId',
12
- otherKey: 'roleName',
13
- sourceKey: 'id',
14
- targetKey: 'name',
15
- through: 'rolesUsers',
16
- uiSchema: {
17
- type: 'array',
18
- title: '{{t("Roles")}}',
19
- 'x-component': 'RecordPicker',
20
- 'x-component-props': {
21
- multiple: true,
22
- fieldNames: {
23
- label: 'title',
24
- value: 'name',
25
- },
26
- },
27
- },
28
- }
29
- ],
30
- });
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { default } from './server';
2
-
@@ -1,32 +0,0 @@
1
- export async function setCurrentRole(ctx, next) {
2
- let currentRole = ctx.get('X-Role');
3
-
4
- if (currentRole === 'anonymous') {
5
- ctx.state.currentRole = currentRole;
6
- return next();
7
- }
8
-
9
- if (!ctx.state.currentUser) {
10
- return next();
11
- }
12
-
13
- const repository = ctx.db.getRepository('users.roles', ctx.state.currentUser.id);
14
- const roles = await repository.find();
15
- ctx.state.currentUser.setDataValue('roles', roles);
16
-
17
- if (roles.length == 1) {
18
- currentRole = roles[0].name;
19
- } else if (roles.length > 1) {
20
- const role = roles.find((item) => item.name === currentRole);
21
- if (!role) {
22
- const defaultRole = roles.find((item) => item?.rolesUsers?.default);
23
- currentRole = (defaultRole || roles[0])?.name;
24
- }
25
- }
26
-
27
- if (currentRole) {
28
- ctx.state.currentRole = currentRole;
29
- }
30
-
31
- await next();
32
- }
@@ -1,21 +0,0 @@
1
- import { Model } from '@nocobase/database';
2
- import { ACL } from '@nocobase/acl';
3
-
4
- export class RoleModel extends Model {
5
- writeToAcl(options: { acl: ACL }) {
6
- const { acl } = options;
7
- const roleName = this.get('name') as string;
8
- let role = acl.getRole(roleName);
9
-
10
- if (!role) {
11
- role = acl.define({
12
- role: roleName,
13
- });
14
- }
15
-
16
- role.setStrategy({
17
- ...((this.get('strategy') as object) || {}),
18
- allowConfigure: this.get('allowConfigure') as boolean,
19
- });
20
- }
21
- }
@@ -1,88 +0,0 @@
1
- import { ACL, ACLRole } from '@nocobase/acl';
2
- import { Database, Model } from '@nocobase/database';
3
- import { AssociationFieldAction, AssociationFieldsActions, GrantHelper } from '../server';
4
-
5
- export class RoleResourceActionModel extends Model {
6
- async writeToACL(options: {
7
- acl: ACL;
8
- role: ACLRole;
9
- resourceName: string;
10
- associationFieldsActions: AssociationFieldsActions;
11
- grantHelper: GrantHelper;
12
- }) {
13
- // @ts-ignore
14
- const db: Database = this.constructor.database;
15
-
16
- const { resourceName, role, acl, associationFieldsActions, grantHelper } = options;
17
-
18
- const actionName = this.get('name') as string;
19
-
20
- const fields = this.get('fields') as any;
21
-
22
- const actionPath = `${resourceName}:${actionName}`;
23
- const actionParams = {
24
- fields,
25
- };
26
-
27
- // @ts-ignore
28
- const scope = await this.getScope();
29
-
30
- if (scope) {
31
- actionParams['own'] = scope.get('key') === 'own';
32
- actionParams['filter'] = scope.get('scope');
33
- }
34
-
35
- role.grantAction(actionPath, actionParams);
36
-
37
- const collection = db.getCollection(resourceName);
38
-
39
- if (!collection) {
40
- return;
41
- }
42
-
43
- const availableAction = acl.resolveActionAlias(actionName);
44
-
45
- for (const field of fields) {
46
- const collectionField = collection.getField(field);
47
-
48
- if (!collectionField) {
49
- console.log(`${field} does not exist`);
50
- continue;
51
- }
52
-
53
- const fieldType = collectionField.get('interface') as string;
54
-
55
- const fieldActions: AssociationFieldAction = associationFieldsActions?.[fieldType]?.[availableAction];
56
-
57
- const fieldTarget = collectionField.get('target');
58
-
59
- if (fieldActions) {
60
- // grant association actions to role
61
- const associationActions = fieldActions.associationActions || [];
62
- associationActions.forEach((associationAction) => {
63
- const actionName = `${resourceName}.${fieldTarget}:${associationAction}`;
64
- role.grantAction(actionName);
65
- });
66
-
67
- const targetActions = fieldActions.targetActions || [];
68
-
69
- targetActions.forEach((targetAction) => {
70
- const targetActionPath = `${fieldTarget}:${targetAction}`;
71
-
72
- // set resource target action with current resourceName
73
- grantHelper.resourceTargetActionMap.set(`${role.name}.${resourceName}`, [
74
- ...(grantHelper.resourceTargetActionMap.get(resourceName) || []),
75
- targetActionPath,
76
- ]);
77
-
78
- grantHelper.targetActionResourceMap.set(targetActionPath, [
79
- ...(grantHelper.targetActionResourceMap.get(targetActionPath) || []),
80
- `${role.name}.${resourceName}`,
81
- ]);
82
-
83
- role.grantAction(targetActionPath);
84
- });
85
- }
86
- }
87
- }
88
- }