@nocobase/plugin-acl 0.7.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 (98) hide show
  1. package/LICENSE +201 -0
  2. package/esm/actions/available-actions.d.ts +7 -0
  3. package/esm/actions/available-actions.js +26 -0
  4. package/esm/actions/available-actions.js.map +1 -0
  5. package/esm/actions/role-check.d.ts +1 -0
  6. package/esm/actions/role-check.js +38 -0
  7. package/esm/actions/role-check.js.map +1 -0
  8. package/esm/actions/role-collections.d.ts +7 -0
  9. package/esm/actions/role-collections.js +54 -0
  10. package/esm/actions/role-collections.js.map +1 -0
  11. package/esm/collections/roles.d.ts +3 -0
  12. package/esm/collections/roles.js +78 -0
  13. package/esm/collections/roles.js.map +1 -0
  14. package/esm/collections/rolesResources.d.ts +3 -0
  15. package/esm/collections/rolesResources.js +30 -0
  16. package/esm/collections/rolesResources.js.map +1 -0
  17. package/esm/collections/rolesResourcesActions.d.ts +3 -0
  18. package/esm/collections/rolesResourcesActions.js +27 -0
  19. package/esm/collections/rolesResourcesActions.js.map +1 -0
  20. package/esm/collections/rolesResourcesScopes.d.ts +3 -0
  21. package/esm/collections/rolesResourcesScopes.js +22 -0
  22. package/esm/collections/rolesResourcesScopes.js.map +1 -0
  23. package/esm/index.d.ts +1 -0
  24. package/esm/index.js +2 -0
  25. package/esm/index.js.map +1 -0
  26. package/esm/model/RoleModel.d.ts +7 -0
  27. package/esm/model/RoleModel.js +15 -0
  28. package/esm/model/RoleModel.js.map +1 -0
  29. package/esm/model/RoleResourceActionModel.d.ts +12 -0
  30. package/esm/model/RoleResourceActionModel.js +65 -0
  31. package/esm/model/RoleResourceActionModel.js.map +1 -0
  32. package/esm/model/RoleResourceModel.d.ts +16 -0
  33. package/esm/model/RoleResourceModel.js +55 -0
  34. package/esm/model/RoleResourceModel.js.map +1 -0
  35. package/esm/server.d.ts +33 -0
  36. package/esm/server.js +366 -0
  37. package/esm/server.js.map +1 -0
  38. package/lib/actions/available-actions.d.ts +7 -0
  39. package/lib/actions/available-actions.js +29 -0
  40. package/lib/actions/available-actions.js.map +1 -0
  41. package/lib/actions/role-check.d.ts +1 -0
  42. package/lib/actions/role-check.js +42 -0
  43. package/lib/actions/role-check.js.map +1 -0
  44. package/lib/actions/role-collections.d.ts +7 -0
  45. package/lib/actions/role-collections.js +57 -0
  46. package/lib/actions/role-collections.js.map +1 -0
  47. package/lib/collections/roles.d.ts +3 -0
  48. package/lib/collections/roles.js +80 -0
  49. package/lib/collections/roles.js.map +1 -0
  50. package/lib/collections/rolesResources.d.ts +3 -0
  51. package/lib/collections/rolesResources.js +32 -0
  52. package/lib/collections/rolesResources.js.map +1 -0
  53. package/lib/collections/rolesResourcesActions.d.ts +3 -0
  54. package/lib/collections/rolesResourcesActions.js +29 -0
  55. package/lib/collections/rolesResourcesActions.js.map +1 -0
  56. package/lib/collections/rolesResourcesScopes.d.ts +3 -0
  57. package/lib/collections/rolesResourcesScopes.js +24 -0
  58. package/lib/collections/rolesResourcesScopes.js.map +1 -0
  59. package/lib/index.d.ts +1 -0
  60. package/lib/index.js +9 -0
  61. package/lib/index.js.map +1 -0
  62. package/lib/model/RoleModel.d.ts +7 -0
  63. package/lib/model/RoleModel.js +19 -0
  64. package/lib/model/RoleModel.js.map +1 -0
  65. package/lib/model/RoleResourceActionModel.d.ts +12 -0
  66. package/lib/model/RoleResourceActionModel.js +69 -0
  67. package/lib/model/RoleResourceActionModel.js.map +1 -0
  68. package/lib/model/RoleResourceModel.d.ts +16 -0
  69. package/lib/model/RoleResourceModel.js +59 -0
  70. package/lib/model/RoleResourceModel.js.map +1 -0
  71. package/lib/server.d.ts +33 -0
  72. package/lib/server.js +371 -0
  73. package/lib/server.js.map +1 -0
  74. package/package.json +30 -0
  75. package/src/__tests__/acl.test.ts +533 -0
  76. package/src/__tests__/association-field.test.ts +297 -0
  77. package/src/__tests__/configuration.test.ts +45 -0
  78. package/src/__tests__/middleware.test.ts +233 -0
  79. package/src/__tests__/own.test.ts +140 -0
  80. package/src/__tests__/prepare.ts +39 -0
  81. package/src/__tests__/role-check.test.ts +35 -0
  82. package/src/__tests__/role-resource.test.ts +187 -0
  83. package/src/__tests__/role.test.ts +92 -0
  84. package/src/__tests__/scope.test.ts +51 -0
  85. package/src/actions/available-actions.ts +18 -0
  86. package/src/actions/role-check.ts +39 -0
  87. package/src/actions/role-collections.ts +55 -0
  88. package/src/collections/roles.ts +79 -0
  89. package/src/collections/rolesResources.ts +31 -0
  90. package/src/collections/rolesResourcesActions.ts +28 -0
  91. package/src/collections/rolesResourcesScopes.ts +23 -0
  92. package/src/index.ts +2 -0
  93. package/src/model/RoleModel.ts +21 -0
  94. package/src/model/RoleResourceActionModel.ts +80 -0
  95. package/src/model/RoleResourceModel.ts +61 -0
  96. package/src/server.ts +398 -0
  97. package/tsconfig.build.json +9 -0
  98. package/tsconfig.json +5 -0
@@ -0,0 +1,533 @@
1
+ import { ACL } from '@nocobase/acl';
2
+ import { Database } from '@nocobase/database';
3
+ import { MockServer } from '@nocobase/test';
4
+ import { changeMockRole, prepareApp } from './prepare';
5
+ import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
6
+
7
+ describe('acl', () => {
8
+ let app: MockServer;
9
+ let db: Database;
10
+ let acl: ACL;
11
+
12
+ let uiSchemaRepository: UiSchemaRepository;
13
+
14
+ afterEach(async () => {
15
+ await app.destroy();
16
+ });
17
+
18
+ beforeEach(async () => {
19
+ app = await prepareApp();
20
+ db = app.db;
21
+ acl = app.acl;
22
+
23
+ uiSchemaRepository = db.getRepository('uiSchemas');
24
+ });
25
+
26
+ it('should works with universal actions', async () => {
27
+ await db.getRepository('roles').create({
28
+ values: {
29
+ name: 'admin',
30
+ title: 'Admin User',
31
+ allowConfigure: true,
32
+ },
33
+ });
34
+
35
+ const role = await db.getRepository('roles').findOne({
36
+ filter: {
37
+ name: 'admin',
38
+ },
39
+ });
40
+
41
+ expect(
42
+ acl.can({
43
+ role: 'admin',
44
+ resource: 'posts',
45
+ action: 'create',
46
+ }),
47
+ ).toBeNull();
48
+
49
+ // grant universal action
50
+ await app
51
+ .agent()
52
+ .resource('roles')
53
+ .update({
54
+ resourceIndex: 'admin',
55
+ values: {
56
+ strategy: {
57
+ actions: ['create'],
58
+ },
59
+ },
60
+ });
61
+
62
+ expect(
63
+ acl.can({
64
+ role: 'admin',
65
+ resource: 'posts',
66
+ action: 'create',
67
+ }),
68
+ ).toMatchObject({
69
+ role: 'admin',
70
+ resource: 'posts',
71
+ action: 'create',
72
+ });
73
+ });
74
+
75
+ it('should works with resources actions', async () => {
76
+ const role = await db.getRepository('roles').create({
77
+ values: {
78
+ name: 'admin',
79
+ title: 'Admin User',
80
+ allowConfigure: true,
81
+ strategy: {
82
+ actions: ['list'],
83
+ },
84
+ },
85
+ });
86
+
87
+ changeMockRole('admin');
88
+
89
+ // create c1 collection
90
+ await db.getRepository('collections').create({
91
+ values: {
92
+ name: 'c1',
93
+ title: 'table1',
94
+ },
95
+ });
96
+
97
+ // create c2 collection
98
+ await db.getRepository('collections').create({
99
+ values: {
100
+ name: 'c2',
101
+ title: 'table2',
102
+ },
103
+ });
104
+
105
+ // create c1 published scope
106
+ await app
107
+ .agent()
108
+ .resource('rolesResourcesScopes')
109
+ .create({
110
+ values: {
111
+ resourceName: 'c1',
112
+ name: 'published',
113
+ scope: {
114
+ published: true,
115
+ },
116
+ },
117
+ });
118
+
119
+ const publishedScope = await db.getRepository('rolesResourcesScopes').findOne();
120
+
121
+ // set admin resources
122
+ await app
123
+ .agent()
124
+ .resource('roles.resources', 'admin')
125
+ .create({
126
+ values: {
127
+ name: 'c1',
128
+ usingActionsConfig: true,
129
+ actions: [
130
+ {
131
+ name: 'create',
132
+ scope: publishedScope.get('id'),
133
+ },
134
+ {
135
+ name: 'view',
136
+ fields: ['title', 'age'],
137
+ },
138
+ ],
139
+ },
140
+ });
141
+
142
+ expect(
143
+ acl.can({
144
+ role: 'admin',
145
+ resource: 'c1',
146
+ action: 'create',
147
+ }),
148
+ ).toMatchObject({
149
+ role: 'admin',
150
+ resource: 'c1',
151
+ action: 'create',
152
+ params: {
153
+ filter: { published: true },
154
+ },
155
+ });
156
+
157
+ expect(
158
+ acl.can({
159
+ role: 'admin',
160
+ resource: 'c1',
161
+ action: 'view',
162
+ }),
163
+ ).toMatchObject({
164
+ role: 'admin',
165
+ resource: 'c1',
166
+ action: 'view',
167
+ params: {
168
+ fields: ['age', 'title', 'id', 'createdAt', 'updatedAt'],
169
+ },
170
+ });
171
+
172
+ // revoke action
173
+ const response = await app
174
+ .agent()
175
+ .resource('roles.resources', role.get('name'))
176
+ .list({
177
+ appends: ['actions'],
178
+ });
179
+
180
+ expect(response.statusCode).toEqual(200);
181
+
182
+ const actions = response.body.data[0].actions;
183
+ const collectionName = response.body.data[0].name;
184
+
185
+ await app
186
+ .agent()
187
+ .resource('roles.resources', role.get('name'))
188
+ .update({
189
+ filterByTk: collectionName,
190
+ values: {
191
+ name: 'c1',
192
+ usingActionsConfig: true,
193
+ actions: [
194
+ {
195
+ name: 'view',
196
+ fields: ['title', 'age'],
197
+ },
198
+ ],
199
+ },
200
+ });
201
+
202
+ expect(
203
+ acl.can({
204
+ role: 'admin',
205
+ resource: 'c1',
206
+ action: 'create',
207
+ }),
208
+ ).toBeNull();
209
+ });
210
+
211
+ it('should revoke resource when collection destroy', async () => {
212
+ const role = await db.getRepository('roles').create({
213
+ values: {
214
+ name: 'admin',
215
+ title: 'Admin User',
216
+ allowConfigure: true,
217
+ },
218
+ });
219
+
220
+ await db.getRepository('collections').create({
221
+ values: {
222
+ name: 'posts',
223
+ },
224
+ });
225
+
226
+ await db.getRepository('fields').create({
227
+ values: {
228
+ collectionName: 'posts',
229
+ type: 'string',
230
+ name: 'title',
231
+ },
232
+ });
233
+
234
+ await app
235
+ .agent()
236
+ .resource('roles.resources')
237
+ .create({
238
+ associatedIndex: role.get('name') as string,
239
+ values: {
240
+ name: 'posts',
241
+ usingActionsConfig: true,
242
+ actions: [
243
+ {
244
+ name: 'view',
245
+ fields: ['title'],
246
+ },
247
+ ],
248
+ },
249
+ });
250
+
251
+ expect(
252
+ acl.can({
253
+ role: 'admin',
254
+ resource: 'posts',
255
+ action: 'view',
256
+ }),
257
+ ).not.toBeNull();
258
+
259
+ await db.getRepository('collections').destroy({
260
+ filter: {
261
+ name: 'posts',
262
+ },
263
+ });
264
+
265
+ expect(
266
+ acl.can({
267
+ role: 'admin',
268
+ resource: 'posts',
269
+ action: 'view',
270
+ }),
271
+ ).toBeNull();
272
+ });
273
+
274
+ it('should revoke actions when not using actions config', async () => {
275
+ await db.getRepository('roles').create({
276
+ values: {
277
+ name: 'admin',
278
+ title: 'Admin User',
279
+ allowConfigure: true,
280
+ },
281
+ });
282
+
283
+ const role = await db.getRepository('roles').findOne({
284
+ filter: {
285
+ name: 'admin',
286
+ },
287
+ });
288
+
289
+ await db.getRepository('collections').create({
290
+ values: {
291
+ name: 'posts',
292
+ title: 'posts',
293
+ },
294
+ });
295
+
296
+ await app
297
+ .agent()
298
+ .resource('roles.resources')
299
+ .create({
300
+ associatedIndex: role.get('name') as string,
301
+ values: {
302
+ name: 'posts',
303
+ usingActionsConfig: true,
304
+ actions: [
305
+ {
306
+ name: 'create',
307
+ },
308
+ ],
309
+ },
310
+ });
311
+
312
+ expect(
313
+ acl.can({
314
+ role: 'admin',
315
+ resource: 'posts',
316
+ action: 'create',
317
+ }),
318
+ ).toMatchObject({
319
+ role: 'admin',
320
+ resource: 'posts',
321
+ action: 'create',
322
+ });
323
+
324
+ await app
325
+ .agent()
326
+ .resource('roles.resources', role.get('name'))
327
+ .update({
328
+ filterByTk: (
329
+ await db.getRepository('rolesResources').findOne({
330
+ filter: {
331
+ name: 'posts',
332
+ roleName: 'admin',
333
+ },
334
+ })
335
+ ).get('name') as string,
336
+ values: {
337
+ usingActionsConfig: false,
338
+ },
339
+ });
340
+
341
+ expect(
342
+ acl.can({
343
+ role: 'admin',
344
+ resource: 'posts',
345
+ action: 'create',
346
+ }),
347
+ ).toBeNull();
348
+
349
+ await app
350
+ .agent()
351
+ .resource('roles.resources', role.get('name'))
352
+ .update({
353
+ filterByTk: (
354
+ await db.getRepository('rolesResources').findOne({
355
+ filter: {
356
+ name: 'posts',
357
+ roleName: 'admin',
358
+ },
359
+ })
360
+ ).get('name') as string,
361
+ values: {
362
+ usingActionsConfig: true,
363
+ },
364
+ });
365
+
366
+ expect(
367
+ acl.can({
368
+ role: 'admin',
369
+ resource: 'posts',
370
+ action: 'create',
371
+ }),
372
+ ).toMatchObject({
373
+ role: 'admin',
374
+ resource: 'posts',
375
+ action: 'create',
376
+ });
377
+ });
378
+
379
+ it('should add fields when field created', async () => {
380
+ const role = await db.getRepository('roles').create({
381
+ values: {
382
+ name: 'admin',
383
+ title: 'Admin User',
384
+ allowConfigure: true,
385
+ },
386
+ });
387
+
388
+ await db.getRepository('collections').create({
389
+ values: {
390
+ name: 'posts',
391
+ },
392
+ });
393
+
394
+ await db.getRepository('fields').create({
395
+ values: {
396
+ collectionName: 'posts',
397
+ type: 'string',
398
+ name: 'title',
399
+ },
400
+ });
401
+
402
+ await app
403
+ .agent()
404
+ .resource('roles.resources')
405
+ .create({
406
+ associatedIndex: role.get('name') as string,
407
+ values: {
408
+ name: 'posts',
409
+ usingActionsConfig: true,
410
+ actions: [
411
+ {
412
+ name: 'view',
413
+ fields: ['title'],
414
+ },
415
+ ],
416
+ },
417
+ });
418
+
419
+ const allowFields = acl.can({
420
+ role: 'admin',
421
+ resource: 'posts',
422
+ action: 'view',
423
+ })['params']['fields'];
424
+
425
+ expect(allowFields.includes('title')).toBeTruthy();
426
+
427
+ await db.getRepository('fields').create({
428
+ values: {
429
+ collectionName: 'posts',
430
+ type: 'string',
431
+ name: 'description',
432
+ },
433
+ });
434
+
435
+ const newAllowFields = acl.can({
436
+ role: 'admin',
437
+ resource: 'posts',
438
+ action: 'view',
439
+ })['params']['fields'];
440
+
441
+ expect(newAllowFields.includes('description')).toBeTruthy();
442
+ });
443
+
444
+ it('should get role menus', async () => {
445
+ const role = await db.getRepository('roles').create({
446
+ values: {
447
+ name: 'admin',
448
+ title: 'Admin User',
449
+ allowConfigure: true,
450
+ strategy: {
451
+ actions: ['view'],
452
+ },
453
+ },
454
+ });
455
+
456
+ changeMockRole('admin');
457
+
458
+ const menuResponse = await app.agent().resource('roles.menuUiSchemas', 'admin').list();
459
+
460
+ expect(menuResponse.statusCode).toEqual(200);
461
+ });
462
+
463
+ it('should toggle role menus', async () => {
464
+ const role = await db.getRepository('roles').create({
465
+ values: {
466
+ name: 'admin',
467
+ title: 'Admin User',
468
+ allowConfigure: true,
469
+ strategy: {
470
+ actions: ['*'],
471
+ },
472
+ },
473
+ });
474
+
475
+ changeMockRole('admin');
476
+
477
+ const schema = {
478
+ 'x-uid': 'test',
479
+ };
480
+
481
+ await uiSchemaRepository.insert(schema);
482
+
483
+ const response = await app
484
+ .agent()
485
+ .resource('roles.menuUiSchemas', 'admin')
486
+ .toggle({
487
+ values: { tk: 'test' },
488
+ });
489
+
490
+ expect(response.statusCode).toEqual(200);
491
+ });
492
+
493
+ it('should sync data to acl before app start', async () => {
494
+ const role = await db.getRepository('roles').create({
495
+ values: {
496
+ name: 'admin',
497
+ title: 'Admin User',
498
+ allowConfigure: true,
499
+ resources: [
500
+ {
501
+ name: 'posts',
502
+ usingActionsConfig: true,
503
+ actions: [
504
+ {
505
+ name: 'view',
506
+ fields: ['title'],
507
+ },
508
+ ],
509
+ },
510
+ ],
511
+ },
512
+ hooks: false,
513
+ });
514
+
515
+ expect(acl.getRole('admin')).toBeUndefined();
516
+
517
+ await app.start();
518
+
519
+ expect(acl.getRole('admin')).toBeDefined();
520
+
521
+ expect(
522
+ acl.can({
523
+ role: 'admin',
524
+ resource: 'posts',
525
+ action: 'view',
526
+ }),
527
+ ).toMatchObject({
528
+ role: 'admin',
529
+ resource: 'posts',
530
+ action: 'view',
531
+ });
532
+ });
533
+ });