@nocobase/plugin-acl 2.1.0-alpha.2 → 2.1.0-alpha.21

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 (37) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/client-v2.d.ts +2 -0
  4. package/client-v2.js +1 -0
  5. package/dist/client/949.7ad4ad3b554e5452.js +10 -0
  6. package/dist/client/971.50ecf7b6ac572080.js +10 -0
  7. package/dist/client/index.js +1 -1
  8. package/dist/client-v2/139.929bc11d582ef7d4.js +10 -0
  9. package/dist/client-v2/193.3245b23f17b4c9f8.js +10 -0
  10. package/dist/client-v2/366.069b6cf12cfb9a67.js +10 -0
  11. package/dist/client-v2/627.ce101823deb86dd6.js +10 -0
  12. package/dist/client-v2/index.d.ts +9 -0
  13. package/dist/client-v2/index.js +10 -0
  14. package/dist/client-v2/plugin.d.ts +5 -0
  15. package/dist/client-v2/routes/AppInfoDemoRoute.d.ts +10 -0
  16. package/dist/client-v2/routes/DemoHomepageRoute.d.ts +10 -0
  17. package/dist/client-v2/routes/FlowSettingsComponentLoaderDemoRoute.d.ts +2 -0
  18. package/dist/client-v2/settings/DemoFlowSettingsLazyField.d.ts +10 -0
  19. package/dist/externalVersion.js +11 -9
  20. package/dist/server/index.d.ts +1 -0
  21. package/dist/server/index.js +2 -0
  22. package/dist/server/middlewares/check-association-operate.js +14 -5
  23. package/dist/server/middlewares/check-change-with-association.d.ts +21 -0
  24. package/dist/server/middlewares/check-change-with-association.js +327 -246
  25. package/dist/server/middlewares/check-query-permission.d.ts +10 -0
  26. package/dist/server/middlewares/check-query-permission.js +64 -0
  27. package/dist/server/middlewares/with-acl-meta.js +7 -2
  28. package/dist/server/migrations/20251119225252-update-member-default-permission.js +1 -1
  29. package/dist/server/query/apply-query-permission.d.ts +27 -0
  30. package/dist/server/query/apply-query-permission.js +242 -0
  31. package/dist/server/server.d.ts +6 -1
  32. package/dist/server/server.js +8 -1
  33. package/dist/swagger/index.d.ts +962 -143
  34. package/dist/swagger/index.js +854 -183
  35. package/package.json +5 -3
  36. package/dist/client/0655d5ded9f45bb1.js +0 -10
  37. package/dist/client/50204a14518b3a0d.js +0 -10
@@ -0,0 +1,64 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var check_query_permission_exports = {};
28
+ __export(check_query_permission_exports, {
29
+ checkQueryPermission: () => checkQueryPermission
30
+ });
31
+ module.exports = __toCommonJS(check_query_permission_exports);
32
+ var import_acl = require("@nocobase/acl");
33
+ var import_apply_query_permission = require("../query/apply-query-permission");
34
+ async function checkQueryPermission(ctx, next) {
35
+ var _a, _b, _c, _d;
36
+ const query = { ...ctx.action.params.values };
37
+ try {
38
+ const result = await (0, import_apply_query_permission.applyQueryPermission)({
39
+ acl: ctx.acl,
40
+ db: ctx.database,
41
+ resourceName: ctx.action.resourceName,
42
+ query,
43
+ currentUser: (_a = ctx.state) == null ? void 0 : _a.currentUser,
44
+ currentRole: (_b = ctx.state) == null ? void 0 : _b.currentRole,
45
+ currentRoles: (_c = ctx.state) == null ? void 0 : _c.currentRoles,
46
+ timezone: (_d = ctx.get) == null ? void 0 : _d.call(ctx, "x-timezone"),
47
+ state: ctx.state
48
+ });
49
+ ctx.action.params = {
50
+ ...ctx.action.params,
51
+ values: result.query
52
+ };
53
+ } catch (error) {
54
+ if (error instanceof import_acl.NoPermissionError) {
55
+ ctx.throw(403, "No permissions");
56
+ }
57
+ throw error;
58
+ }
59
+ await next();
60
+ }
61
+ // Annotate the CommonJS export names for ESM import in node:
62
+ 0 && (module.exports = {
63
+ checkQueryPermission
64
+ });
@@ -71,7 +71,11 @@ function createWithACLMetaMiddleware() {
71
71
  if (collection.isMultiFilterTargetKey()) {
72
72
  return;
73
73
  }
74
- const primaryKeyField = Model.primaryKeyField || Model.primaryKeyAttribute;
74
+ const filterTargetKey = collection.filterTargetKey;
75
+ if (!filterTargetKey || typeof filterTargetKey !== "string") {
76
+ return;
77
+ }
78
+ const primaryKeyField = filterTargetKey;
75
79
  let listData;
76
80
  if ((_a = ctx.body) == null ? void 0 : _a.data) {
77
81
  listData = ctx.data;
@@ -90,7 +94,8 @@ function createWithACLMetaMiddleware() {
90
94
  const actionsParams = [];
91
95
  for (const action of inspectActions) {
92
96
  const actionCtx = {
93
- db,
97
+ db: ctx.db,
98
+ database: db,
94
99
  get: () => {
95
100
  return void 0;
96
101
  },
@@ -33,7 +33,7 @@ var import_server = require("@nocobase/server");
33
33
  class update_member_default_permission_default extends import_server.Migration {
34
34
  on = "afterLoad";
35
35
  // 'beforeLoad' or 'afterLoad'
36
- appVersion = "<2.0.0";
36
+ appVersion = "<2.1.0";
37
37
  async up() {
38
38
  const repo = this.db.getRepository("roles");
39
39
  const role = await repo.findOne({
@@ -0,0 +1,27 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { ACL } from '@nocobase/acl';
10
+ import type { QueryOptions } from '@nocobase/database';
11
+ import { Database } from '@nocobase/database';
12
+ export type QueryPermissionQuery = QueryOptions & Record<string, any>;
13
+ export type ApplyQueryPermissionOptions = {
14
+ acl: ACL;
15
+ db: Database;
16
+ resourceName: string;
17
+ query: QueryPermissionQuery;
18
+ currentUser?: any;
19
+ currentRole?: string;
20
+ currentRoles?: string[];
21
+ timezone?: string;
22
+ state?: any;
23
+ };
24
+ export declare function applyQueryPermission(options: ApplyQueryPermissionOptions): Promise<{
25
+ permission: import("@nocobase/acl").CanResult;
26
+ query: QueryPermissionQuery;
27
+ }>;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var apply_query_permission_exports = {};
28
+ __export(apply_query_permission_exports, {
29
+ applyQueryPermission: () => applyQueryPermission
30
+ });
31
+ module.exports = __toCommonJS(apply_query_permission_exports);
32
+ var import_acl = require("@nocobase/acl");
33
+ var import_utils = require("@nocobase/utils");
34
+ function normalizeFieldPath(field) {
35
+ if (!field) {
36
+ return null;
37
+ }
38
+ const parts = Array.isArray(field) ? field : field.split(".");
39
+ const normalized = parts.filter(Boolean);
40
+ return normalized.length ? normalized : null;
41
+ }
42
+ function stringifyFieldPath(field) {
43
+ const path = normalizeFieldPath(field);
44
+ if (!path) {
45
+ return null;
46
+ }
47
+ return path.join(".");
48
+ }
49
+ function resolveQueryFieldKey(resourceName, field) {
50
+ const path = normalizeFieldPath(field);
51
+ if (!path) {
52
+ return null;
53
+ }
54
+ if (path.length === 1) {
55
+ return `${resourceName}.${path[0]}`;
56
+ }
57
+ return path.join(".");
58
+ }
59
+ function pruneEmptyArray(items) {
60
+ return items && items.length > 0 ? items : void 0;
61
+ }
62
+ function getRoleNames(options) {
63
+ var _a;
64
+ if ((_a = options.currentRoles) == null ? void 0 : _a.length) {
65
+ return options.currentRoles;
66
+ }
67
+ if (options.currentRole) {
68
+ return [options.currentRole];
69
+ }
70
+ return ["anonymous"];
71
+ }
72
+ function getSelectableFields(permission) {
73
+ var _a, _b;
74
+ const fields = (_a = permission == null ? void 0 : permission.params) == null ? void 0 : _a.fields;
75
+ const appends = (_b = permission == null ? void 0 : permission.params) == null ? void 0 : _b.appends;
76
+ if (fields === void 0 && appends === void 0) {
77
+ return null;
78
+ }
79
+ return Array.from(/* @__PURE__ */ new Set([...fields || [], ...appends || []]));
80
+ }
81
+ function isAllowedFieldPath(acl, db, roles, rootCollectionName, field) {
82
+ var _a;
83
+ const fieldPath = normalizeFieldPath(field);
84
+ if (!fieldPath) {
85
+ return false;
86
+ }
87
+ let collectionName = rootCollectionName;
88
+ let permission = acl.can({
89
+ roles,
90
+ resource: collectionName,
91
+ action: "view"
92
+ });
93
+ if (!permission) {
94
+ return false;
95
+ }
96
+ for (let index = 0; index < fieldPath.length; index++) {
97
+ const fieldName = fieldPath[index];
98
+ const field2 = (_a = db.getCollection(collectionName)) == null ? void 0 : _a.getField(fieldName);
99
+ if (!field2) {
100
+ return false;
101
+ }
102
+ const selectableFields = getSelectableFields(permission);
103
+ if (selectableFields && !selectableFields.includes(fieldName)) {
104
+ return false;
105
+ }
106
+ if (index === fieldPath.length - 1) {
107
+ return true;
108
+ }
109
+ if (!field2.target) {
110
+ return false;
111
+ }
112
+ collectionName = field2.target;
113
+ permission = acl.can({
114
+ roles,
115
+ resource: collectionName,
116
+ action: "view"
117
+ });
118
+ if (!permission) {
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ }
124
+ function pruneSelections(items, acl, db, roles, resourceName) {
125
+ return pruneEmptyArray((items || []).filter((item) => isAllowedFieldPath(acl, db, roles, resourceName, item.field)));
126
+ }
127
+ function getAvailableSelectionKeys(resourceName, query) {
128
+ const keys = /* @__PURE__ */ new Set();
129
+ for (const item of [...query.measures || [], ...query.dimensions || []]) {
130
+ if (item.alias) {
131
+ keys.add(item.alias);
132
+ }
133
+ const fieldPathKey = stringifyFieldPath(item.field);
134
+ if (fieldPathKey) {
135
+ keys.add(fieldPathKey);
136
+ }
137
+ const qualifiedFieldKey = resolveQueryFieldKey(resourceName, item.field);
138
+ if (qualifiedFieldKey) {
139
+ keys.add(qualifiedFieldKey);
140
+ }
141
+ }
142
+ return keys;
143
+ }
144
+ function pruneOrders(items, acl, db, roles, resourceName, availableSelectionKeys) {
145
+ return pruneEmptyArray(
146
+ (items || []).filter((item) => {
147
+ if (isAllowedFieldPath(acl, db, roles, resourceName, item.field)) {
148
+ return true;
149
+ }
150
+ const fieldKey = stringifyFieldPath(item.field);
151
+ return !!fieldKey && availableSelectionKeys.has(fieldKey);
152
+ })
153
+ );
154
+ }
155
+ function pruneHavingNode(node, availableKeys) {
156
+ if (!(0, import_utils.isPlainObject)(node)) {
157
+ return void 0;
158
+ }
159
+ const result = {};
160
+ for (const [key, value] of Object.entries(node)) {
161
+ if ((key === "$and" || key === "$or") && Array.isArray(value)) {
162
+ const items = value.map((item) => pruneHavingNode(item, availableKeys)).filter((item) => item !== void 0);
163
+ if (items.length > 0) {
164
+ result[key] = items;
165
+ }
166
+ continue;
167
+ }
168
+ if (!availableKeys.has(key)) {
169
+ continue;
170
+ }
171
+ result[key] = value;
172
+ }
173
+ return Object.keys(result).length ? result : void 0;
174
+ }
175
+ async function getParsedPermissionFilter(options, permission) {
176
+ var _a;
177
+ const filterParams = (_a = permission == null ? void 0 : permission.params) == null ? void 0 : _a.filter;
178
+ if (!filterParams) {
179
+ return void 0;
180
+ }
181
+ (0, import_acl.checkFilterParams)(options.db.getCollection(options.resourceName), filterParams);
182
+ return await (0, import_acl.parseJsonTemplate)(filterParams, {
183
+ state: options.state,
184
+ timezone: options.timezone,
185
+ userProvider: (0, import_acl.createUserProvider)({
186
+ db: options.db,
187
+ currentUser: options.currentUser
188
+ })
189
+ }) ?? filterParams;
190
+ }
191
+ async function applyQueryPermission(options) {
192
+ var _a, _b, _c, _d, _e, _f;
193
+ const { acl, db, resourceName, query: sourceQuery, currentUser, state, timezone } = options;
194
+ const roles = getRoleNames(options);
195
+ const rootPermission = acl.can({
196
+ roles,
197
+ resource: resourceName,
198
+ action: "view"
199
+ });
200
+ if (!rootPermission) {
201
+ throw new import_acl.NoPermissionError(`No permission for resource: ${resourceName}`);
202
+ }
203
+ const query = {
204
+ ...sourceQuery,
205
+ measures: pruneSelections(sourceQuery.measures, acl, db, roles, resourceName),
206
+ dimensions: pruneSelections(sourceQuery.dimensions, acl, db, roles, resourceName)
207
+ };
208
+ const availableSelectionKeys = getAvailableSelectionKeys(resourceName, query);
209
+ query.orders = pruneOrders(sourceQuery.orders, acl, db, roles, resourceName, availableSelectionKeys);
210
+ query.having = pruneHavingNode(query.having, availableSelectionKeys);
211
+ const aclFilter = await getParsedPermissionFilter(
212
+ {
213
+ ...options,
214
+ acl,
215
+ db,
216
+ resourceName,
217
+ query,
218
+ currentUser,
219
+ state,
220
+ timezone
221
+ },
222
+ rootPermission
223
+ );
224
+ if (aclFilter) {
225
+ query.filter = (0, import_utils.assign)(query.filter || {}, aclFilter, {
226
+ filter: "andMerge"
227
+ });
228
+ }
229
+ const hasSelection = (((_a = options.query.measures) == null ? void 0 : _a.length) || 0) > 0 || (((_b = options.query.dimensions) == null ? void 0 : _b.length) || 0) > 0 || (((_c = options.query.orders) == null ? void 0 : _c.length) || 0) > 0;
230
+ const hasRemainingSelection = (((_d = query.measures) == null ? void 0 : _d.length) || 0) > 0 || (((_e = query.dimensions) == null ? void 0 : _e.length) || 0) > 0 || (((_f = query.orders) == null ? void 0 : _f.length) || 0) > 0;
231
+ if (hasSelection && !hasRemainingSelection) {
232
+ throw new import_acl.NoPermissionError(`No permission for query fields: ${options.resourceName}`);
233
+ }
234
+ return {
235
+ permission: rootPermission,
236
+ query
237
+ };
238
+ }
239
+ // Annotate the CommonJS export names for ESM import in node:
240
+ 0 && (module.exports = {
241
+ applyQueryPermission
242
+ });
@@ -11,8 +11,13 @@ import { Plugin } from '@nocobase/server';
11
11
  import { RoleModel } from './model/RoleModel';
12
12
  import { RoleResourceActionModel } from './model/RoleResourceActionModel';
13
13
  import { RoleResourceModel } from './model/RoleResourceModel';
14
+ import { SanitizeAssociationValuesOptions } from './middlewares/check-change-with-association';
15
+ import type { ACL } from '@nocobase/acl';
14
16
  export declare class PluginACLServer extends Plugin {
15
- get acl(): import("@nocobase/acl").ACL;
17
+ get acl(): ACL;
18
+ sanitizeAssociationValues(options: SanitizeAssociationValuesOptions & {
19
+ acl?: ACL;
20
+ }): Promise<any>;
16
21
  writeResourceToACL(resourceModel: RoleResourceModel, transaction: Transaction): Promise<void>;
17
22
  writeActionToACL(actionModel: RoleResourceActionModel, transaction: Transaction): Promise<void>;
18
23
  handleSyncMessage(message: any): Promise<void>;
@@ -57,10 +57,17 @@ var import_RoleResourceModel = require("./model/RoleResourceModel");
57
57
  var import_union_role = require("./actions/union-role");
58
58
  var import_check_association_operate = require("./middlewares/check-association-operate");
59
59
  var import_check_change_with_association = require("./middlewares/check-change-with-association");
60
+ var import_check_query_permission = require("./middlewares/check-query-permission");
60
61
  class PluginACLServer extends import_server.Plugin {
61
62
  get acl() {
62
63
  return this.app.acl;
63
64
  }
65
+ async sanitizeAssociationValues(options) {
66
+ return (0, import_check_change_with_association.sanitizeAssociationValues)({
67
+ ...options,
68
+ acl: options.acl ?? this.acl
69
+ });
70
+ }
64
71
  async writeResourceToACL(resourceModel, transaction) {
65
72
  await resourceModel.writeToACL({
66
73
  acl: this.acl,
@@ -505,7 +512,6 @@ class PluginACLServer extends import_server.Plugin {
505
512
  }
506
513
  return next();
507
514
  });
508
- const parseJsonTemplate = this.app.acl.parseJsonTemplate;
509
515
  this.app.acl.beforeGrantAction(async (ctx) => {
510
516
  const actionName = this.app.acl.resolveActionAlias(ctx.actionName);
511
517
  if (import_lodash.default.isPlainObject(ctx.params)) {
@@ -578,6 +584,7 @@ class PluginACLServer extends import_server.Plugin {
578
584
  before: "core"
579
585
  });
580
586
  if (dataSource.options.acl !== false && dataSource.options.useACL !== false) {
587
+ dataSource.resourceManager.registerPreActionHandler("query", import_check_query_permission.checkQueryPermission);
581
588
  dataSource.resourceManager.registerPreActionHandler("create", import_check_change_with_association.checkChangesWithAssociation);
582
589
  dataSource.resourceManager.registerPreActionHandler("firstOrCreate", import_check_change_with_association.checkChangesWithAssociation);
583
590
  dataSource.resourceManager.registerPreActionHandler("updateOrCreate", import_check_change_with_association.checkChangesWithAssociation);