@nocobase/plugin-acl 2.1.0-beta.14 → 2.1.0-beta.16

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 (33) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/dist/client/949.7ad4ad3b554e5452.js +10 -0
  4. package/dist/client/971.50ecf7b6ac572080.js +10 -0
  5. package/dist/client/index.js +1 -1
  6. package/dist/client-v2/139.929bc11d582ef7d4.js +10 -0
  7. package/dist/client-v2/193.3245b23f17b4c9f8.js +10 -0
  8. package/dist/client-v2/366.069b6cf12cfb9a67.js +10 -0
  9. package/dist/client-v2/627.ce101823deb86dd6.js +10 -0
  10. package/dist/client-v2/index.d.ts +9 -0
  11. package/dist/client-v2/index.js +10 -0
  12. package/dist/client-v2/plugin.d.ts +5 -0
  13. package/dist/client-v2/routes/AppInfoDemoRoute.d.ts +10 -0
  14. package/dist/client-v2/routes/DemoHomepageRoute.d.ts +10 -0
  15. package/dist/client-v2/routes/FlowSettingsComponentLoaderDemoRoute.d.ts +2 -0
  16. package/dist/client-v2/settings/DemoFlowSettingsLazyField.d.ts +10 -0
  17. package/dist/externalVersion.js +11 -9
  18. package/dist/server/actions/apply-data-permissions.d.ts +10 -0
  19. package/dist/server/actions/apply-data-permissions.js +208 -0
  20. package/dist/server/actions/data-source-compat.d.ts +13 -0
  21. package/dist/server/actions/data-source-compat.js +189 -0
  22. package/dist/server/index.d.ts +1 -0
  23. package/dist/server/index.js +2 -0
  24. package/dist/server/middlewares/check-query-permission.d.ts +10 -0
  25. package/dist/server/middlewares/check-query-permission.js +64 -0
  26. package/dist/server/query/apply-query-permission.d.ts +27 -0
  27. package/dist/server/query/apply-query-permission.js +242 -0
  28. package/dist/server/server.js +18 -0
  29. package/dist/swagger/index.d.ts +1113 -145
  30. package/dist/swagger/index.js +986 -180
  31. package/package.json +4 -2
  32. package/dist/client/646.e67b75c43441bcc0.js +0 -10
  33. package/dist/client/86.45758388a34b8834.js +0 -10
@@ -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
+ });
@@ -46,6 +46,8 @@ var import_server = require("@nocobase/server");
46
46
  var import_lodash = __toESM(require("lodash"));
47
47
  var import_path = require("path");
48
48
  var import_available_actions = require("./actions/available-actions");
49
+ var import_apply_data_permissions = require("./actions/apply-data-permissions");
50
+ var import_data_source_compat = require("./actions/data-source-compat");
49
51
  var import_role_check = require("./actions/role-check");
50
52
  var import_role_collections = require("./actions/role-collections");
51
53
  var import_user_setDefaultRole = require("./actions/user-setDefaultRole");
@@ -57,6 +59,7 @@ var import_RoleResourceModel = require("./model/RoleResourceModel");
57
59
  var import_union_role = require("./actions/union-role");
58
60
  var import_check_association_operate = require("./middlewares/check-association-operate");
59
61
  var import_check_change_with_association = require("./middlewares/check-change-with-association");
62
+ var import_check_query_permission = require("./middlewares/check-query-permission");
60
63
  class PluginACLServer extends import_server.Plugin {
61
64
  get acl() {
62
65
  return this.app.acl;
@@ -180,7 +183,21 @@ class PluginACLServer extends import_server.Plugin {
180
183
  this.app.resourcer.define(import_available_actions.availableActionResource);
181
184
  this.app.resourcer.define(import_role_collections.roleCollectionsResource);
182
185
  this.app.resourcer.registerActionHandler("roles:setSystemRoleMode", import_union_role.setSystemRoleMode);
186
+ this.app.resourcer.registerActionHandler("roles:applyDataPermissions", import_apply_data_permissions.applyDataPermissions);
183
187
  this.app.resourcer.registerActionHandler("roles:check", import_role_check.checkAction);
188
+ this.app.resourcer.registerPreActionHandler(
189
+ "roles.dataSourcesCollections:list",
190
+ import_data_source_compat.guardRolesDataSourcesCollectionsList
191
+ );
192
+ this.app.resourcer.registerPreActionHandler(
193
+ "roles.dataSourceResources:create",
194
+ import_data_source_compat.guardRolesDataSourceResourcesCreate
195
+ );
196
+ this.app.resourcer.registerPreActionHandler("roles.dataSourceResources:get", import_data_source_compat.guardRolesDataSourceResourcesGet);
197
+ this.app.resourcer.registerPreActionHandler(
198
+ "roles.dataSourceResources:update",
199
+ import_data_source_compat.guardRolesDataSourceResourcesUpdate
200
+ );
184
201
  this.app.resourcer.registerActionHandler(`users:setDefaultRole`, import_user_setDefaultRole.setDefaultRole);
185
202
  this.db.on("users.afterCreateWithAssociations", async (model, options) => {
186
203
  const { transaction } = options;
@@ -583,6 +600,7 @@ class PluginACLServer extends import_server.Plugin {
583
600
  before: "core"
584
601
  });
585
602
  if (dataSource.options.acl !== false && dataSource.options.useACL !== false) {
603
+ dataSource.resourceManager.registerPreActionHandler("query", import_check_query_permission.checkQueryPermission);
586
604
  dataSource.resourceManager.registerPreActionHandler("create", import_check_change_with_association.checkChangesWithAssociation);
587
605
  dataSource.resourceManager.registerPreActionHandler("firstOrCreate", import_check_change_with_association.checkChangesWithAssociation);
588
606
  dataSource.resourceManager.registerPreActionHandler("updateOrCreate", import_check_change_with_association.checkChangesWithAssociation);