@nocobase/plugin-acl 0.11.1-alpha.5 → 0.12.0-alpha.2

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 (96) hide show
  1. package/client.d.ts +2 -3
  2. package/client.js +1 -1
  3. package/dist/client/index.js +11 -0
  4. package/{lib/server → dist}/index.d.ts +1 -0
  5. package/dist/index.js +18 -0
  6. package/{src/server/actions/available-actions.ts → dist/server/actions/available-actions.js} +7 -5
  7. package/{src/server/actions/role-check.ts → dist/server/actions/role-check.js} +17 -22
  8. package/dist/server/actions/role-collections.js +53 -0
  9. package/dist/server/actions/user-setDefaultRole.js +43 -0
  10. package/dist/server/collections/roles-users.js +10 -0
  11. package/dist/server/collections/roles.js +103 -0
  12. package/dist/server/collections/rolesResources.js +35 -0
  13. package/dist/server/collections/rolesResourcesActions.js +33 -0
  14. package/dist/server/collections/rolesResourcesScopes.js +27 -0
  15. package/dist/server/collections/users.js +35 -0
  16. package/dist/server/index.js +11 -0
  17. package/dist/server/middlewares/setCurrentRole.js +31 -0
  18. package/dist/server/migrations/20221214072638-set-role-snippets.js +25 -0
  19. package/dist/server/model/RoleModel.js +23 -0
  20. package/dist/server/model/RoleResourceActionModel.js +64 -0
  21. package/dist/server/model/RoleResourceModel.js +55 -0
  22. package/dist/server/server.js +709 -0
  23. package/package.json +12 -21
  24. package/server.d.ts +3 -3
  25. package/server.js +1 -1
  26. package/lib/client/index.js +0 -22
  27. package/lib/index.js +0 -13
  28. package/lib/server/actions/available-actions.js +0 -34
  29. package/lib/server/actions/role-check.js +0 -77
  30. package/lib/server/actions/role-collections.js +0 -98
  31. package/lib/server/actions/user-setDefaultRole.js +0 -52
  32. package/lib/server/collections/roles-users.js +0 -16
  33. package/lib/server/collections/roles.js +0 -92
  34. package/lib/server/collections/rolesResources.js +0 -31
  35. package/lib/server/collections/rolesResourcesActions.js +0 -31
  36. package/lib/server/collections/rolesResourcesScopes.js +0 -25
  37. package/lib/server/collections/users.js +0 -41
  38. package/lib/server/index.js +0 -13
  39. package/lib/server/middlewares/setCurrentRole.js +0 -45
  40. package/lib/server/migrations/20221214072638-set-role-snippets.js +0 -43
  41. package/lib/server/model/RoleModel.js +0 -35
  42. package/lib/server/model/RoleResourceActionModel.js +0 -91
  43. package/lib/server/model/RoleResourceModel.js +0 -106
  44. package/lib/server/server.js +0 -947
  45. package/src/client/index.ts +0 -8
  46. package/src/index.ts +0 -1
  47. package/src/server/__tests__/acl.test.ts +0 -835
  48. package/src/server/__tests__/actions.test.ts +0 -141
  49. package/src/server/__tests__/association-field.test.ts +0 -413
  50. package/src/server/__tests__/configuration.test.ts +0 -70
  51. package/src/server/__tests__/list-action.test.ts +0 -446
  52. package/src/server/__tests__/middleware.test.ts +0 -210
  53. package/src/server/__tests__/own.test.ts +0 -124
  54. package/src/server/__tests__/prepare.ts +0 -20
  55. package/src/server/__tests__/role-check.test.ts +0 -46
  56. package/src/server/__tests__/role-resource.test.ts +0 -177
  57. package/src/server/__tests__/role-user.test.ts +0 -127
  58. package/src/server/__tests__/role.test.ts +0 -118
  59. package/src/server/__tests__/scope.test.ts +0 -55
  60. package/src/server/__tests__/setCurrentRole.test.ts +0 -86
  61. package/src/server/__tests__/snippets.test.ts +0 -35
  62. package/src/server/__tests__/users.test.ts +0 -136
  63. package/src/server/__tests__/write-role-to-acl.test.ts +0 -41
  64. package/src/server/actions/role-collections.ts +0 -95
  65. package/src/server/actions/user-setDefaultRole.ts +0 -47
  66. package/src/server/collections/roles-users.ts +0 -8
  67. package/src/server/collections/roles.ts +0 -101
  68. package/src/server/collections/rolesResources.ts +0 -33
  69. package/src/server/collections/rolesResourcesActions.ts +0 -31
  70. package/src/server/collections/rolesResourcesScopes.ts +0 -25
  71. package/src/server/collections/users.ts +0 -31
  72. package/src/server/index.ts +0 -1
  73. package/src/server/middlewares/setCurrentRole.ts +0 -35
  74. package/src/server/migrations/20221214072638-set-role-snippets.ts +0 -23
  75. package/src/server/model/RoleModel.ts +0 -23
  76. package/src/server/model/RoleResourceActionModel.ts +0 -95
  77. package/src/server/model/RoleResourceModel.ts +0 -74
  78. package/src/server/server.ts +0 -854
  79. /package/{lib → dist}/client/index.d.ts +0 -0
  80. /package/{lib → dist}/server/actions/available-actions.d.ts +0 -0
  81. /package/{lib → dist}/server/actions/role-check.d.ts +0 -0
  82. /package/{lib → dist}/server/actions/role-collections.d.ts +0 -0
  83. /package/{lib → dist}/server/actions/user-setDefaultRole.d.ts +0 -0
  84. /package/{lib → dist}/server/collections/roles-users.d.ts +0 -0
  85. /package/{lib → dist}/server/collections/roles.d.ts +0 -0
  86. /package/{lib → dist}/server/collections/rolesResources.d.ts +0 -0
  87. /package/{lib → dist}/server/collections/rolesResourcesActions.d.ts +0 -0
  88. /package/{lib → dist}/server/collections/rolesResourcesScopes.d.ts +0 -0
  89. /package/{lib → dist}/server/collections/users.d.ts +0 -0
  90. /package/{lib → dist/server}/index.d.ts +0 -0
  91. /package/{lib → dist}/server/middlewares/setCurrentRole.d.ts +0 -0
  92. /package/{lib → dist}/server/migrations/20221214072638-set-role-snippets.d.ts +0 -0
  93. /package/{lib → dist}/server/model/RoleModel.d.ts +0 -0
  94. /package/{lib → dist}/server/model/RoleResourceActionModel.d.ts +0 -0
  95. /package/{lib → dist}/server/model/RoleResourceModel.d.ts +0 -0
  96. /package/{lib → dist}/server/server.d.ts +0 -0
@@ -0,0 +1,709 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var acl = require('@nocobase/acl');
6
+ var actions = require('@nocobase/actions');
7
+ var database = require('@nocobase/database');
8
+ var server = require('@nocobase/server');
9
+ var lodash = require('lodash');
10
+ var path = require('path');
11
+ var availableActions = require('./actions/available-actions');
12
+ var roleCheck = require('./actions/role-check');
13
+ var roleCollections = require('./actions/role-collections');
14
+ var userSetDefaultRole = require('./actions/user-setDefaultRole');
15
+ var setCurrentRole = require('./middlewares/setCurrentRole');
16
+ var RoleModel = require('./model/RoleModel');
17
+ var RoleResourceActionModel = require('./model/RoleResourceActionModel');
18
+ var RoleResourceModel = require('./model/RoleResourceModel');
19
+
20
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
+
22
+ var lodash__default = /*#__PURE__*/_interopDefault(lodash);
23
+
24
+ class GrantHelper {
25
+ resourceTargetActionMap = /* @__PURE__ */ new Map();
26
+ targetActionResourceMap = /* @__PURE__ */ new Map();
27
+ constructor() {
28
+ }
29
+ }
30
+ class PluginACL extends server.Plugin {
31
+ // association field actions config
32
+ associationFieldsActions = {};
33
+ grantHelper = new GrantHelper();
34
+ get acl() {
35
+ return this.app.acl;
36
+ }
37
+ registerAssociationFieldAction(associationType, value) {
38
+ this.associationFieldsActions[associationType] = value;
39
+ }
40
+ registerAssociationFieldsActions() {
41
+ this.registerAssociationFieldAction("hasOne", {
42
+ view: {
43
+ associationActions: ["list", "get", "view"]
44
+ },
45
+ create: {
46
+ associationActions: ["create", "set"]
47
+ },
48
+ update: {
49
+ associationActions: ["update", "remove", "set"]
50
+ }
51
+ });
52
+ this.registerAssociationFieldAction("hasMany", {
53
+ view: {
54
+ associationActions: ["list", "get", "view"]
55
+ },
56
+ create: {
57
+ associationActions: ["create", "set", "add"]
58
+ },
59
+ update: {
60
+ associationActions: ["update", "remove", "set"]
61
+ }
62
+ });
63
+ this.registerAssociationFieldAction("belongsTo", {
64
+ view: {
65
+ associationActions: ["list", "get", "view"]
66
+ },
67
+ create: {
68
+ associationActions: ["create", "set"]
69
+ },
70
+ update: {
71
+ associationActions: ["update", "remove", "set"]
72
+ }
73
+ });
74
+ this.registerAssociationFieldAction("belongsToMany", {
75
+ view: {
76
+ associationActions: ["list", "get", "view"]
77
+ },
78
+ create: {
79
+ associationActions: ["create", "set", "add"]
80
+ },
81
+ update: {
82
+ associationActions: ["update", "remove", "set", "toggle"]
83
+ }
84
+ });
85
+ }
86
+ async writeResourceToACL(resourceModel, transaction) {
87
+ await resourceModel.writeToACL({
88
+ acl: this.acl,
89
+ associationFieldsActions: this.associationFieldsActions,
90
+ transaction,
91
+ grantHelper: this.grantHelper
92
+ });
93
+ }
94
+ async writeActionToACL(actionModel, transaction) {
95
+ const resource = actionModel.get("resource");
96
+ const role = this.acl.getRole(resource.get("roleName"));
97
+ await actionModel.writeToACL({
98
+ acl: this.acl,
99
+ role,
100
+ resourceName: resource.get("name"),
101
+ associationFieldsActions: this.associationFieldsActions,
102
+ grantHelper: this.grantHelper
103
+ });
104
+ }
105
+ async writeRolesToACL() {
106
+ const roles = await this.app.db.getRepository("roles").find({
107
+ appends: ["resources", "resources.actions"]
108
+ });
109
+ for (const role of roles) {
110
+ await this.writeRoleToACL(role);
111
+ }
112
+ }
113
+ async writeRoleToACL(role, transaction = null) {
114
+ role.writeToAcl({ acl: this.acl });
115
+ let resources = role.get("resources");
116
+ if (!resources) {
117
+ resources = await role.getResources({ transaction });
118
+ }
119
+ for (const resource of resources) {
120
+ await this.writeResourceToACL(resource, transaction);
121
+ }
122
+ }
123
+ async beforeLoad() {
124
+ this.db.addMigrations({
125
+ namespace: this.name,
126
+ directory: path.resolve(__dirname, "./migrations"),
127
+ context: {
128
+ plugin: this
129
+ }
130
+ });
131
+ this.app.db.registerModels({
132
+ RoleResourceActionModel: RoleResourceActionModel.RoleResourceActionModel,
133
+ RoleResourceModel: RoleResourceModel.RoleResourceModel,
134
+ RoleModel: RoleModel.RoleModel
135
+ });
136
+ this.app.acl.registerSnippet({
137
+ name: `pm.${this.name}.roles`,
138
+ actions: [
139
+ "roles:*",
140
+ "roles.snippets:*",
141
+ "availableActions:list",
142
+ "roles.collections:list",
143
+ "roles.resources:*",
144
+ "uiSchemas:getProperties",
145
+ "roles.menuUiSchemas:*"
146
+ ]
147
+ });
148
+ this.app.acl.beforeGrantAction((ctx) => {
149
+ const actionName = this.app.acl.resolveActionAlias(ctx.actionName);
150
+ const collection = this.app.db.getCollection(ctx.resourceName);
151
+ if (!collection) {
152
+ return;
153
+ }
154
+ const fieldsParams = ctx.params.fields;
155
+ if (!fieldsParams) {
156
+ return;
157
+ }
158
+ if (actionName == "view" || actionName == "export") {
159
+ const associationsFields = fieldsParams.filter((fieldName) => {
160
+ const field = collection.getField(fieldName);
161
+ return field instanceof database.RelationField;
162
+ });
163
+ ctx.params = {
164
+ ...ctx.params,
165
+ fields: lodash__default.default.difference(fieldsParams, associationsFields),
166
+ appends: associationsFields
167
+ };
168
+ }
169
+ });
170
+ this.registerAssociationFieldsActions();
171
+ this.app.resourcer.define(availableActions.availableActionResource);
172
+ this.app.resourcer.define(roleCollections.roleCollectionsResource);
173
+ this.app.resourcer.registerActionHandler("roles:check", roleCheck.checkAction);
174
+ this.app.resourcer.registerActionHandler(`users:setDefaultRole`, userSetDefaultRole.setDefaultRole);
175
+ this.db.on("users.afterCreateWithAssociations", async (model, options) => {
176
+ const { transaction } = options;
177
+ const repository = this.app.db.getRepository("roles");
178
+ const defaultRole = await repository.findOne({
179
+ filter: {
180
+ default: true
181
+ },
182
+ transaction
183
+ });
184
+ if (defaultRole && await model.countRoles({ transaction }) == 0) {
185
+ await model.addRoles(defaultRole, { transaction });
186
+ }
187
+ });
188
+ this.app.on("acl:writeRoleToACL", async (roleModel) => {
189
+ await this.writeRoleToACL(roleModel);
190
+ });
191
+ this.app.db.on("roles.afterSaveWithAssociations", async (model, options) => {
192
+ const { transaction } = options;
193
+ await this.writeRoleToACL(model, transaction);
194
+ if (model.get("default")) {
195
+ await this.app.db.getRepository("roles").update({
196
+ values: {
197
+ default: false
198
+ },
199
+ filter: {
200
+ "name.$ne": model.get("name")
201
+ },
202
+ hooks: false,
203
+ transaction
204
+ });
205
+ }
206
+ });
207
+ this.app.db.on("roles.afterDestroy", (model) => {
208
+ const roleName = model.get("name");
209
+ this.acl.removeRole(roleName);
210
+ });
211
+ this.app.db.on("rolesResources.afterSaveWithAssociations", async (model, options) => {
212
+ await this.writeResourceToACL(model, options.transaction);
213
+ });
214
+ this.app.db.on("rolesResourcesActions.afterUpdateWithAssociations", async (model, options) => {
215
+ const { transaction } = options;
216
+ const resource = await model.getResource({
217
+ transaction
218
+ });
219
+ await this.writeResourceToACL(resource, transaction);
220
+ });
221
+ this.app.db.on("rolesResources.afterDestroy", async (model, options) => {
222
+ const role = this.acl.getRole(model.get("roleName"));
223
+ if (role) {
224
+ role.revokeResource(model.get("name"));
225
+ }
226
+ });
227
+ this.app.db.on("collections.afterDestroy", async (model, options) => {
228
+ const { transaction } = options;
229
+ await this.app.db.getRepository("rolesResources").destroy({
230
+ filter: {
231
+ name: model.get("name")
232
+ },
233
+ transaction
234
+ });
235
+ });
236
+ this.app.db.on("fields.afterCreate", async (model, options) => {
237
+ const { transaction } = options;
238
+ const collectionName = model.get("collectionName");
239
+ const fieldName = model.get("name");
240
+ const resourceActions = await this.app.db.getRepository("rolesResourcesActions").find({
241
+ filter: {
242
+ "resource.name": collectionName
243
+ },
244
+ transaction,
245
+ appends: ["resource"]
246
+ });
247
+ for (const resourceAction of resourceActions) {
248
+ const fields = resourceAction.get("fields");
249
+ const newFields = [...fields, fieldName];
250
+ await this.app.db.getRepository("rolesResourcesActions").update({
251
+ filterByTk: resourceAction.get("id"),
252
+ values: {
253
+ fields: newFields
254
+ },
255
+ transaction
256
+ });
257
+ }
258
+ });
259
+ this.app.db.on("fields.afterDestroy", async (model, options) => {
260
+ const collectionName = model.get("collectionName");
261
+ const fieldName = model.get("name");
262
+ const resourceActions = await this.app.db.getRepository("rolesResourcesActions").find({
263
+ filter: {
264
+ "resource.name": collectionName,
265
+ "fields.$anyOf": [fieldName]
266
+ },
267
+ transaction: options.transaction
268
+ });
269
+ for (const resourceAction of resourceActions) {
270
+ const fields = resourceAction.get("fields");
271
+ const newFields = fields.filter((field) => field != fieldName);
272
+ await this.app.db.getRepository("rolesResourcesActions").update({
273
+ filterByTk: resourceAction.get("id"),
274
+ values: {
275
+ fields: newFields
276
+ },
277
+ transaction: options.transaction
278
+ });
279
+ }
280
+ });
281
+ this.app.on("afterLoad", async (app, options) => {
282
+ if ((options == null ? void 0 : options.method) === "install" || (options == null ? void 0 : options.method) === "upgrade") {
283
+ return;
284
+ }
285
+ const exists = await this.app.db.collectionExistsInDb("roles");
286
+ if (exists) {
287
+ await this.writeRolesToACL();
288
+ }
289
+ });
290
+ this.app.on("afterInstall", async (app, options) => {
291
+ const exists = await this.app.db.collectionExistsInDb("roles");
292
+ if (exists) {
293
+ await this.writeRolesToACL();
294
+ }
295
+ });
296
+ this.app.on("afterInstallPlugin", async (plugin) => {
297
+ if (plugin.getName() !== "users") {
298
+ return;
299
+ }
300
+ const User = this.db.getCollection("users");
301
+ await User.repository.update({
302
+ values: {
303
+ roles: ["root", "admin", "member"]
304
+ },
305
+ forceUpdate: true
306
+ });
307
+ const RolesUsers = this.db.getCollection("rolesUsers");
308
+ await RolesUsers.repository.update({
309
+ filter: {
310
+ userId: 1,
311
+ roleName: "root"
312
+ },
313
+ values: {
314
+ default: true
315
+ }
316
+ });
317
+ });
318
+ this.app.on("beforeInstallPlugin", async (plugin) => {
319
+ if (plugin.getName() !== "users") {
320
+ return;
321
+ }
322
+ const roles = this.app.db.getRepository("roles");
323
+ await roles.createMany({
324
+ records: [
325
+ {
326
+ name: "root",
327
+ title: '{{t("Root")}}',
328
+ hidden: true,
329
+ snippets: ["ui.*", "pm", "pm.*"]
330
+ },
331
+ {
332
+ name: "admin",
333
+ title: '{{t("Admin")}}',
334
+ allowConfigure: true,
335
+ allowNewMenu: true,
336
+ strategy: { actions: ["create", "view", "update", "destroy"] },
337
+ snippets: ["ui.*", "pm", "pm.*"]
338
+ },
339
+ {
340
+ name: "member",
341
+ title: '{{t("Member")}}',
342
+ allowNewMenu: true,
343
+ strategy: { actions: ["view", "update:own", "destroy:own", "create"] },
344
+ default: true,
345
+ snippets: ["!ui.*", "!pm", "!pm.*"]
346
+ }
347
+ ]
348
+ });
349
+ const rolesResourcesScopes = this.app.db.getRepository("rolesResourcesScopes");
350
+ await rolesResourcesScopes.createMany({
351
+ records: [
352
+ {
353
+ key: "all",
354
+ name: '{{t("All records")}}',
355
+ scope: {}
356
+ },
357
+ {
358
+ key: "own",
359
+ name: '{{t("Own records")}}',
360
+ scope: {
361
+ createdById: "{{ ctx.state.currentUser.id }}"
362
+ }
363
+ }
364
+ ]
365
+ });
366
+ });
367
+ this.app.resourcer.use(setCurrentRole.setCurrentRole, { tag: "setCurrentRole", before: "acl", after: "auth" });
368
+ this.app.acl.allow("users", "setDefaultRole", "loggedIn");
369
+ this.app.acl.allow("roles", "check", "loggedIn");
370
+ this.app.acl.allow("*", "*", (ctx) => {
371
+ return ctx.state.currentRole === "root";
372
+ });
373
+ this.app.acl.addFixedParams("collections", "destroy", () => {
374
+ return {
375
+ filter: {
376
+ $and: [{ "name.$ne": "roles" }, { "name.$ne": "rolesUsers" }]
377
+ }
378
+ };
379
+ });
380
+ this.app.acl.addFixedParams("rolesResourcesScopes", "destroy", () => {
381
+ return {
382
+ filter: {
383
+ $and: [{ "key.$ne": "all" }, { "key.$ne": "own" }]
384
+ }
385
+ };
386
+ });
387
+ this.app.acl.addFixedParams("rolesResourcesScopes", "update", () => {
388
+ return {
389
+ filter: {
390
+ $and: [{ "key.$ne": "all" }, { "key.$ne": "own" }]
391
+ }
392
+ };
393
+ });
394
+ this.app.acl.addFixedParams("roles", "destroy", () => {
395
+ return {
396
+ filter: {
397
+ $and: [{ "name.$ne": "root" }, { "name.$ne": "admin" }, { "name.$ne": "member" }]
398
+ }
399
+ };
400
+ });
401
+ this.app.resourcer.use(async (ctx, next) => {
402
+ const { actionName, resourceName, params } = ctx.action;
403
+ const { showAnonymous } = params || {};
404
+ if (actionName === "list" && resourceName === "roles") {
405
+ if (!showAnonymous) {
406
+ ctx.action.mergeParams({
407
+ filter: {
408
+ "name.$ne": "anonymous"
409
+ }
410
+ });
411
+ }
412
+ }
413
+ if (actionName === "update" && resourceName === "roles.resources") {
414
+ ctx.action.mergeParams({
415
+ updateAssociationValues: ["actions"]
416
+ });
417
+ }
418
+ await next();
419
+ });
420
+ this.app.acl.use(async (ctx, next) => {
421
+ var _a, _b, _c, _d, _e;
422
+ const { actionName, resourceName } = ctx.action;
423
+ if (actionName === "get" || actionName === "list") {
424
+ if (!Array.isArray((_c = (_b = (_a = ctx == null ? void 0 : ctx.permission) == null ? void 0 : _a.can) == null ? void 0 : _b.params) == null ? void 0 : _c.fields)) {
425
+ return next();
426
+ }
427
+ let collection;
428
+ if (resourceName.includes(".")) {
429
+ const [collectionName, associationName] = resourceName.split(".");
430
+ const field = (_e = (_d = ctx.db.getCollection(collectionName)) == null ? void 0 : _d.getField) == null ? void 0 : _e.call(_d, associationName);
431
+ if (field.target) {
432
+ collection = ctx.db.getCollection(field.target);
433
+ }
434
+ } else {
435
+ collection = ctx.db.getCollection(resourceName);
436
+ }
437
+ if (collection && collection.hasField("createdById")) {
438
+ ctx.permission.can.params.fields.push("createdById");
439
+ }
440
+ }
441
+ return next();
442
+ });
443
+ const parseJsonTemplate = this.app.acl.parseJsonTemplate;
444
+ this.app.acl.use(
445
+ async (ctx, next) => {
446
+ var _a, _b, _c, _d;
447
+ const { actionName, resourceName, resourceOf } = ctx.action;
448
+ if (resourceName.includes(".") && resourceOf) {
449
+ if (!((_b = (_a = ctx == null ? void 0 : ctx.permission) == null ? void 0 : _a.can) == null ? void 0 : _b.params)) {
450
+ return next();
451
+ }
452
+ delete ctx.permission.can.params.filter;
453
+ const [collectionName] = resourceName.split(".");
454
+ const action = ctx.can({ resource: collectionName, action: actionName });
455
+ const availableAction = this.app.acl.getAvailableAction(actionName);
456
+ if ((_c = availableAction == null ? void 0 : availableAction.options) == null ? void 0 : _c.onNewRecord) {
457
+ if (action) {
458
+ ctx.permission.skip = true;
459
+ } else {
460
+ ctx.permission.can = false;
461
+ }
462
+ } else {
463
+ const filter = await parseJsonTemplate(((_d = action == null ? void 0 : action.params) == null ? void 0 : _d.filter) || {}, ctx);
464
+ const sourceInstance = await ctx.db.getRepository(collectionName).findOne({
465
+ filterByTk: resourceOf,
466
+ filter
467
+ });
468
+ if (!sourceInstance) {
469
+ ctx.permission.can = false;
470
+ }
471
+ }
472
+ }
473
+ await next();
474
+ },
475
+ {
476
+ before: "core"
477
+ }
478
+ );
479
+ this.app.acl.use(
480
+ async (ctx, next) => {
481
+ var _a, _b;
482
+ const action = (_b = (_a = ctx.permission) == null ? void 0 : _a.can) == null ? void 0 : _b.action;
483
+ if (action == "destroy" && !ctx.action.resourceName.includes(".")) {
484
+ const repository = actions.utils.getRepositoryFromParams(ctx);
485
+ const filteredCount = await repository.count(ctx.permission.mergedParams);
486
+ const queryCount = await repository.count(ctx.permission.rawParams);
487
+ if (queryCount > filteredCount) {
488
+ ctx.throw(403, "No permissions");
489
+ return;
490
+ }
491
+ }
492
+ await next();
493
+ },
494
+ {
495
+ after: "core",
496
+ group: "after"
497
+ }
498
+ );
499
+ const withACLMeta = async (ctx, next) => {
500
+ var _a, _b, _c;
501
+ await next();
502
+ if (!ctx.action || !ctx.get("X-With-ACL-Meta") || ctx.status !== 200) {
503
+ return;
504
+ }
505
+ const { resourceName, actionName } = ctx.action;
506
+ if (!["list", "get"].includes(actionName)) {
507
+ return;
508
+ }
509
+ const collection = ctx.db.getCollection(resourceName);
510
+ if (!collection) {
511
+ return;
512
+ }
513
+ const Model = collection.model;
514
+ const primaryKeyField = Model.primaryKeyField || Model.primaryKeyAttribute;
515
+ const dataPath = ((_a = ctx.body) == null ? void 0 : _a.rows) ? "body.rows" : "body";
516
+ let listData = lodash__default.default.get(ctx, dataPath);
517
+ if (actionName == "get") {
518
+ listData = lodash__default.default.castArray(listData);
519
+ }
520
+ const inspectActions = ["view", "update", "destroy"];
521
+ const actionsParams = [];
522
+ for (const action of inspectActions) {
523
+ const actionCtx = {
524
+ db: ctx.db,
525
+ action: {
526
+ actionName: action,
527
+ name: action,
528
+ params: {},
529
+ resourceName: ctx.action.resourceName,
530
+ resourceOf: ctx.action.resourceOf,
531
+ mergeParams() {
532
+ }
533
+ },
534
+ state: {
535
+ currentRole: ctx.state.currentRole,
536
+ currentUser: (() => {
537
+ var _a2;
538
+ if (!ctx.state.currentUser) {
539
+ return null;
540
+ }
541
+ if (ctx.state.currentUser.toJSON) {
542
+ return (_a2 = ctx.state.currentUser) == null ? void 0 : _a2.toJSON();
543
+ }
544
+ return ctx.state.currentUser;
545
+ })()
546
+ },
547
+ permission: {},
548
+ throw(...args) {
549
+ throw new acl.NoPermissionError(...args);
550
+ }
551
+ };
552
+ try {
553
+ await this.app.acl.getActionParams(actionCtx);
554
+ } catch (e) {
555
+ if (e instanceof acl.NoPermissionError) {
556
+ continue;
557
+ }
558
+ throw e;
559
+ }
560
+ actionsParams.push([
561
+ action,
562
+ ((_b = actionCtx.permission) == null ? void 0 : _b.can) === null && !actionCtx.permission.skip ? null : ((_c = actionCtx.permission) == null ? void 0 : _c.parsedParams) || {},
563
+ actionCtx
564
+ ]);
565
+ }
566
+ const ids = (() => {
567
+ if (collection.options.tree) {
568
+ if (listData.length == 0)
569
+ return [];
570
+ const getAllNodeIds = (data) => [data[primaryKeyField], ...(data.children || []).flatMap(getAllNodeIds)];
571
+ return listData.map((tree) => getAllNodeIds(tree.toJSON())).flat();
572
+ }
573
+ return listData.map((item) => item[primaryKeyField]);
574
+ })();
575
+ const conditions = [];
576
+ const allAllowed = [];
577
+ for (const [action, params, actionCtx] of actionsParams) {
578
+ if (!params) {
579
+ continue;
580
+ }
581
+ if (lodash__default.default.isEmpty(params) || lodash__default.default.isEmpty(params.filter)) {
582
+ allAllowed.push(action);
583
+ continue;
584
+ }
585
+ const queryParams = collection.repository.buildQueryOptions({
586
+ ...params,
587
+ context: actionCtx
588
+ });
589
+ const actionSql = ctx.db.sequelize.queryInterface.queryGenerator.selectQuery(
590
+ Model.getTableName(),
591
+ {
592
+ where: (() => {
593
+ const filterObj = queryParams.where;
594
+ if (!this.db.options.underscored) {
595
+ return filterObj;
596
+ }
597
+ const isAssociationKey = (key) => {
598
+ return key.startsWith("$") && key.endsWith("$");
599
+ };
600
+ const iterate = (rootObj, path = []) => {
601
+ const obj = path.length == 0 ? rootObj : lodash__default.default.get(rootObj, path);
602
+ if (Array.isArray(obj)) {
603
+ for (let i = 0; i < obj.length; i++) {
604
+ if (obj[i] === null) {
605
+ continue;
606
+ }
607
+ if (typeof obj[i] === "object") {
608
+ iterate(rootObj, [...path, i]);
609
+ }
610
+ }
611
+ return;
612
+ }
613
+ Reflect.ownKeys(obj).forEach((key) => {
614
+ if (Array.isArray(obj) && key == "length") {
615
+ return;
616
+ }
617
+ if (typeof obj[key] === "object" && obj[key] !== null || typeof obj[key] === "symbol") {
618
+ iterate(rootObj, [...path, key]);
619
+ }
620
+ if (typeof key === "string" && key !== database.snakeCase(key)) {
621
+ const setKey = isAssociationKey(key) ? (() => {
622
+ const parts = key.split(".");
623
+ parts[parts.length - 1] = lodash__default.default.snakeCase(parts[parts.length - 1]);
624
+ const result = parts.join(".");
625
+ return result.endsWith("$") ? result : `${result}$`;
626
+ })() : database.snakeCase(key);
627
+ const setValue = lodash__default.default.cloneDeep(obj[key]);
628
+ lodash__default.default.unset(rootObj, [...path, key]);
629
+ lodash__default.default.set(rootObj, [...path, setKey], setValue);
630
+ }
631
+ });
632
+ };
633
+ iterate(filterObj);
634
+ return filterObj;
635
+ })(),
636
+ attributes: [primaryKeyField],
637
+ includeIgnoreAttributes: false
638
+ },
639
+ Model
640
+ );
641
+ const whereCase = actionSql.match(/WHERE (.*?);/)[1];
642
+ conditions.push({
643
+ whereCase,
644
+ action,
645
+ include: queryParams.include
646
+ });
647
+ }
648
+ const results = await collection.model.findAll({
649
+ where: {
650
+ [primaryKeyField]: ids
651
+ },
652
+ attributes: [
653
+ primaryKeyField,
654
+ ...conditions.map((condition) => {
655
+ return [ctx.db.sequelize.literal(`CASE WHEN ${condition.whereCase} THEN 1 ELSE 0 END`), condition.action];
656
+ })
657
+ ],
658
+ include: conditions.map((condition) => condition.include).flat()
659
+ });
660
+ const allowedActions = inspectActions.map((action) => {
661
+ if (allAllowed.includes(action)) {
662
+ return [action, ids];
663
+ }
664
+ return [action, results.filter((item) => Boolean(item.get(action))).map((item) => item.get(primaryKeyField))];
665
+ }).reduce((acc, [action, ids2]) => {
666
+ acc[action] = ids2;
667
+ return acc;
668
+ }, {});
669
+ if (actionName == "get") {
670
+ ctx.bodyMeta = {
671
+ ...ctx.bodyMeta || {},
672
+ allowedActions
673
+ };
674
+ }
675
+ if (actionName == "list") {
676
+ ctx.body.allowedActions = allowedActions;
677
+ }
678
+ };
679
+ this.app.use(
680
+ async (ctx, next) => {
681
+ try {
682
+ await withACLMeta(ctx, next);
683
+ } catch (error) {
684
+ ctx.logger.error(error);
685
+ }
686
+ },
687
+ { after: "restApi", group: "after" }
688
+ );
689
+ }
690
+ async install() {
691
+ const repo = this.db.getRepository("collections");
692
+ if (repo) {
693
+ await repo.db2cm("roles");
694
+ }
695
+ }
696
+ async load() {
697
+ await this.importCollections(path.resolve(__dirname, "collections"));
698
+ this.db.extendCollection({
699
+ name: "rolesUischemas",
700
+ namespace: "acl.acl",
701
+ duplicator: "required"
702
+ });
703
+ }
704
+ }
705
+ var server_default = PluginACL;
706
+
707
+ exports.GrantHelper = GrantHelper;
708
+ exports.PluginACL = PluginACL;
709
+ exports.default = server_default;