@nocobase/plugin-acl 2.1.0-beta.15 → 2.1.0-beta.17
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.
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/dist/client/949.7ad4ad3b554e5452.js +10 -0
- package/dist/client/971.50ecf7b6ac572080.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/139.929bc11d582ef7d4.js +10 -0
- package/dist/client-v2/193.3245b23f17b4c9f8.js +10 -0
- package/dist/client-v2/366.069b6cf12cfb9a67.js +10 -0
- package/dist/client-v2/627.ce101823deb86dd6.js +10 -0
- package/dist/client-v2/index.d.ts +9 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/client-v2/plugin.d.ts +5 -0
- package/dist/client-v2/routes/AppInfoDemoRoute.d.ts +10 -0
- package/dist/client-v2/routes/DemoHomepageRoute.d.ts +10 -0
- package/dist/client-v2/routes/FlowSettingsComponentLoaderDemoRoute.d.ts +2 -0
- package/dist/client-v2/settings/DemoFlowSettingsLazyField.d.ts +10 -0
- package/dist/externalVersion.js +11 -9
- package/dist/server/actions/apply-data-permissions.d.ts +10 -0
- package/dist/server/actions/apply-data-permissions.js +208 -0
- package/dist/server/actions/data-source-compat.d.ts +13 -0
- package/dist/server/actions/data-source-compat.js +189 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/middlewares/check-query-permission.d.ts +10 -0
- package/dist/server/middlewares/check-query-permission.js +64 -0
- package/dist/server/query/apply-query-permission.d.ts +27 -0
- package/dist/server/query/apply-query-permission.js +242 -0
- package/dist/server/server.js +18 -0
- package/dist/swagger/index.d.ts +1113 -145
- package/dist/swagger/index.js +986 -180
- package/package.json +4 -2
- package/dist/client/646.e67b75c43441bcc0.js +0 -10
- 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
|
+
});
|
package/dist/server/server.js
CHANGED
|
@@ -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);
|