@nocobase/plugin-acl 2.1.0-alpha.1 → 2.1.0-alpha.11
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/LICENSE +201 -661
- package/README.md +79 -10
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/dist/client/a8ee1085b1883873.js +10 -0
- package/dist/client/d4b1aca44e7fa5bc.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/2d20c90fbb39ce2a.js +10 -0
- package/dist/client-v2/809ab4bace5cbbcf.js +10 -0
- package/dist/client-v2/90be8e43cfaeafd9.js +10 -0
- package/dist/client-v2/9b3f3766358e320c.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 +10 -8
- package/dist/server/middlewares/check-association-operate.js +14 -5
- package/dist/server/middlewares/check-change-with-association.d.ts +30 -0
- package/dist/server/middlewares/check-change-with-association.js +469 -0
- package/dist/server/middlewares/with-acl-meta.js +8 -2
- package/dist/server/migrations/20251119225252-update-member-default-permission.js +1 -1
- package/dist/server/server.d.ts +6 -1
- package/dist/server/server.js +14 -1
- package/dist/swagger/index.d.ts +795 -149
- package/dist/swagger/index.js +670 -183
- package/package.json +11 -4
- package/dist/client/0655d5ded9f45bb1.js +0 -10
- package/dist/client/50204a14518b3a0d.js +0 -10
|
@@ -31,7 +31,7 @@ __export(check_association_operate_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(check_association_operate_exports);
|
|
32
32
|
var import_acl = require("@nocobase/acl");
|
|
33
33
|
async function checkAssociationOperate(ctx, next) {
|
|
34
|
-
var _a;
|
|
34
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
35
35
|
const { actionName, resourceName, sourceId } = ctx.action;
|
|
36
36
|
if (!(resourceName.includes(".") && ["add", "set", "remove", "toggle"].includes(actionName))) {
|
|
37
37
|
return next();
|
|
@@ -59,12 +59,21 @@ async function checkAssociationOperate(ctx, next) {
|
|
|
59
59
|
}
|
|
60
60
|
if (params.filter) {
|
|
61
61
|
try {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
62
|
+
const timezone = ((_c = (_b = ctx.request) == null ? void 0 : _b.get) == null ? void 0 : _c.call(_b, "x-timezone")) ?? ((_e = (_d = ctx.request) == null ? void 0 : _d.header) == null ? void 0 : _e["x-timezone"]) ?? ((_g = (_f = ctx.req) == null ? void 0 : _f.headers) == null ? void 0 : _g["x-timezone"]);
|
|
63
|
+
const collection = (_i = (_h = ctx.database) == null ? void 0 : _h.getCollection) == null ? void 0 : _i.call(_h, resource);
|
|
64
|
+
(0, import_acl.checkFilterParams)(collection, params.filter);
|
|
65
|
+
const parsedFilter = await (0, import_acl.parseJsonTemplate)(params.filter, {
|
|
66
|
+
state: ctx.state,
|
|
67
|
+
timezone,
|
|
68
|
+
userProvider: (0, import_acl.createUserProvider)({
|
|
69
|
+
db: ctx.db,
|
|
70
|
+
currentUser: (_j = ctx.state) == null ? void 0 : _j.currentUser
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
const repo = ctx.database.getRepository(resource);
|
|
65
74
|
const record = await repo.findOne({
|
|
66
75
|
filterByTk: sourceId,
|
|
67
|
-
filter:
|
|
76
|
+
filter: parsedFilter ?? params.filter
|
|
68
77
|
});
|
|
69
78
|
if (!record) {
|
|
70
79
|
ctx.throw(403, "No permissions");
|
|
@@ -0,0 +1,30 @@
|
|
|
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, UserProvider } from '@nocobase/acl';
|
|
10
|
+
import { Context, Next } from '@nocobase/actions';
|
|
11
|
+
import { Collection } from '@nocobase/database';
|
|
12
|
+
export type SanitizeAssociationValuesOptions = {
|
|
13
|
+
acl?: ACL;
|
|
14
|
+
resourceName: string;
|
|
15
|
+
actionName: string;
|
|
16
|
+
values: any;
|
|
17
|
+
updateAssociationValues?: string[];
|
|
18
|
+
protectedKeys?: string[];
|
|
19
|
+
aclParams?: any;
|
|
20
|
+
roles?: string[];
|
|
21
|
+
currentRole?: string;
|
|
22
|
+
currentUser?: any;
|
|
23
|
+
collection?: Collection;
|
|
24
|
+
db?: any;
|
|
25
|
+
database?: any;
|
|
26
|
+
timezone?: string;
|
|
27
|
+
userProvider?: UserProvider;
|
|
28
|
+
};
|
|
29
|
+
export declare function sanitizeAssociationValues(options: SanitizeAssociationValuesOptions): Promise<any>;
|
|
30
|
+
export declare const checkChangesWithAssociation: (ctx: Context, next: Next) => Promise<any>;
|
|
@@ -0,0 +1,469 @@
|
|
|
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 __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
var check_change_with_association_exports = {};
|
|
38
|
+
__export(check_change_with_association_exports, {
|
|
39
|
+
checkChangesWithAssociation: () => checkChangesWithAssociation,
|
|
40
|
+
sanitizeAssociationValues: () => sanitizeAssociationValues
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(check_change_with_association_exports);
|
|
43
|
+
var import_acl = require("@nocobase/acl");
|
|
44
|
+
var import_lodash = __toESM(require("lodash"));
|
|
45
|
+
async function sanitizeAssociationValues(options) {
|
|
46
|
+
var _a, _b, _c;
|
|
47
|
+
const {
|
|
48
|
+
acl,
|
|
49
|
+
resourceName,
|
|
50
|
+
actionName,
|
|
51
|
+
values,
|
|
52
|
+
updateAssociationValues = [],
|
|
53
|
+
protectedKeys = [],
|
|
54
|
+
aclParams
|
|
55
|
+
} = options;
|
|
56
|
+
if (import_lodash.default.isEmpty(values)) {
|
|
57
|
+
return values;
|
|
58
|
+
}
|
|
59
|
+
const collection = options.collection ?? ((_b = (_a = options.database ?? options.db) == null ? void 0 : _a.getCollection) == null ? void 0 : _b.call(_a, resourceName));
|
|
60
|
+
if (!collection) {
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
const params = aclParams ?? (acl ? (_c = acl.fixedParamsManager) == null ? void 0 : _c.getParams(resourceName, actionName) : void 0);
|
|
64
|
+
const roles = options.roles;
|
|
65
|
+
const can = (canOptions) => (acl == null ? void 0 : acl.can({ roles: (roles == null ? void 0 : roles.length) ? roles : ["anonymous"], ...canOptions })) ?? null;
|
|
66
|
+
const parseOptions = {
|
|
67
|
+
timezone: options.timezone,
|
|
68
|
+
userProvider: options.userProvider,
|
|
69
|
+
state: {
|
|
70
|
+
currentRole: options.currentRole,
|
|
71
|
+
currentRoles: options.roles,
|
|
72
|
+
currentUser: options.currentUser
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return await processValues({
|
|
76
|
+
values,
|
|
77
|
+
updateAssociationValues,
|
|
78
|
+
aclParams: params,
|
|
79
|
+
collection,
|
|
80
|
+
lastFieldPath: "",
|
|
81
|
+
protectedKeys,
|
|
82
|
+
can,
|
|
83
|
+
parseOptions
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const checkChangesWithAssociation = async (ctx, next) => {
|
|
87
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
88
|
+
const timezone = ((_b = (_a = ctx.request) == null ? void 0 : _a.get) == null ? void 0 : _b.call(_a, "x-timezone")) ?? ((_d = (_c = ctx.request) == null ? void 0 : _c.header) == null ? void 0 : _d["x-timezone"]) ?? ((_f = (_e = ctx.req) == null ? void 0 : _e.headers) == null ? void 0 : _f["x-timezone"]);
|
|
89
|
+
const { resourceName, actionName } = ctx.action;
|
|
90
|
+
if (!["create", "firstOrCreate", "updateOrCreate", "update"].includes(actionName)) {
|
|
91
|
+
return next();
|
|
92
|
+
}
|
|
93
|
+
if ((_g = ctx.permission) == null ? void 0 : _g.skip) {
|
|
94
|
+
return next();
|
|
95
|
+
}
|
|
96
|
+
const roles = ctx.state.currentRoles;
|
|
97
|
+
if (roles.includes("root")) {
|
|
98
|
+
return next();
|
|
99
|
+
}
|
|
100
|
+
const acl = ctx.acl;
|
|
101
|
+
for (const role of roles) {
|
|
102
|
+
const aclRole = acl.getRole(role);
|
|
103
|
+
if (aclRole == null ? void 0 : aclRole.snippetAllowed(`${resourceName}:${actionName}`)) {
|
|
104
|
+
return next();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const params = ctx.action.params || {};
|
|
108
|
+
const rawValues = params.values;
|
|
109
|
+
if (import_lodash.default.isEmpty(rawValues)) {
|
|
110
|
+
return next();
|
|
111
|
+
}
|
|
112
|
+
const protectedKeys = ["firstOrCreate", "updateOrCreate"].includes(actionName) ? params.filterKeys || [] : [];
|
|
113
|
+
const collection = (_i = (_h = ctx.database ?? ctx.db) == null ? void 0 : _h.getCollection) == null ? void 0 : _i.call(_h, resourceName);
|
|
114
|
+
const processed = await sanitizeAssociationValues({
|
|
115
|
+
acl,
|
|
116
|
+
collection,
|
|
117
|
+
resourceName,
|
|
118
|
+
actionName,
|
|
119
|
+
values: rawValues,
|
|
120
|
+
updateAssociationValues: params.updateAssociationValues || [],
|
|
121
|
+
protectedKeys,
|
|
122
|
+
roles,
|
|
123
|
+
currentRole: ctx.state.currentRole,
|
|
124
|
+
currentUser: ctx.state.currentUser,
|
|
125
|
+
aclParams: (_k = (_j = ctx.permission) == null ? void 0 : _j.can) == null ? void 0 : _k.params,
|
|
126
|
+
timezone,
|
|
127
|
+
userProvider: (0, import_acl.createUserProvider)({
|
|
128
|
+
dataSourceManager: (_l = ctx.app) == null ? void 0 : _l.dataSourceManager,
|
|
129
|
+
currentUser: (_m = ctx.state) == null ? void 0 : _m.currentUser
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
ctx.action.params.values = processed;
|
|
133
|
+
await next();
|
|
134
|
+
};
|
|
135
|
+
async function processValues(options) {
|
|
136
|
+
var _a, _b;
|
|
137
|
+
const {
|
|
138
|
+
values,
|
|
139
|
+
updateAssociationValues,
|
|
140
|
+
aclParams,
|
|
141
|
+
collection,
|
|
142
|
+
lastFieldPath = "",
|
|
143
|
+
protectedKeys = [],
|
|
144
|
+
can,
|
|
145
|
+
parseOptions
|
|
146
|
+
} = options;
|
|
147
|
+
if (Array.isArray(values)) {
|
|
148
|
+
const result = [];
|
|
149
|
+
for (const item of values) {
|
|
150
|
+
if (!import_lodash.default.isPlainObject(item)) {
|
|
151
|
+
result.push(item);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const processed = await processValues({
|
|
155
|
+
values: item,
|
|
156
|
+
updateAssociationValues,
|
|
157
|
+
aclParams,
|
|
158
|
+
collection,
|
|
159
|
+
lastFieldPath,
|
|
160
|
+
protectedKeys,
|
|
161
|
+
can,
|
|
162
|
+
parseOptions
|
|
163
|
+
});
|
|
164
|
+
if (processed !== null && processed !== void 0) {
|
|
165
|
+
result.push(processed);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
if (!values || !import_lodash.default.isPlainObject(values)) {
|
|
171
|
+
return values;
|
|
172
|
+
}
|
|
173
|
+
if (!collection) {
|
|
174
|
+
return values;
|
|
175
|
+
}
|
|
176
|
+
let v = values;
|
|
177
|
+
if (aclParams == null ? void 0 : aclParams.whitelist) {
|
|
178
|
+
const combined = import_lodash.default.uniq([...aclParams.whitelist, ...protectedKeys]);
|
|
179
|
+
v = import_lodash.default.pick(values, combined);
|
|
180
|
+
}
|
|
181
|
+
for (const [fieldName, fieldValue] of Object.entries(v)) {
|
|
182
|
+
if (protectedKeys.includes(fieldName)) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const field = collection.getField(fieldName);
|
|
186
|
+
const isAssociation = field && ["hasOne", "hasMany", "belongsTo", "belongsToMany", "belongsToArray"].includes(field.type);
|
|
187
|
+
if (!isAssociation) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const targetCollection = collection.db.getCollection(field.target);
|
|
191
|
+
if (!targetCollection) {
|
|
192
|
+
delete v[fieldName];
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const fieldPath = lastFieldPath ? `${lastFieldPath}.${fieldName}` : fieldName;
|
|
196
|
+
const recordKey = field.type === "hasOne" ? targetCollection.model.primaryKeyAttribute : field.targetKey;
|
|
197
|
+
const canUpdateAssociation = updateAssociationValues.includes(fieldPath);
|
|
198
|
+
if (!canUpdateAssociation) {
|
|
199
|
+
const normalized = normalizeAssociationValue(fieldValue, recordKey);
|
|
200
|
+
if (normalized === void 0 && !protectedKeys.includes(fieldName)) {
|
|
201
|
+
delete v[fieldName];
|
|
202
|
+
} else {
|
|
203
|
+
v[fieldName] = normalized;
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const createParams = can == null ? void 0 : can({
|
|
208
|
+
resource: field.target,
|
|
209
|
+
action: "create"
|
|
210
|
+
});
|
|
211
|
+
const updateParams = can == null ? void 0 : can({
|
|
212
|
+
resource: field.target,
|
|
213
|
+
action: "update"
|
|
214
|
+
});
|
|
215
|
+
if (Array.isArray(fieldValue)) {
|
|
216
|
+
const processed = [];
|
|
217
|
+
let allowedRecordKeys;
|
|
218
|
+
let existingRecordKeys;
|
|
219
|
+
if (updateParams) {
|
|
220
|
+
const allowedResult = await collectAllowedRecordKeys(
|
|
221
|
+
fieldValue,
|
|
222
|
+
recordKey,
|
|
223
|
+
(_a = updateParams == null ? void 0 : updateParams.params) == null ? void 0 : _a.filter,
|
|
224
|
+
targetCollection,
|
|
225
|
+
parseOptions
|
|
226
|
+
);
|
|
227
|
+
allowedRecordKeys = allowedResult == null ? void 0 : allowedResult.allowedKeys;
|
|
228
|
+
if (createParams && ((_b = allowedResult == null ? void 0 : allowedResult.missingKeys) == null ? void 0 : _b.size)) {
|
|
229
|
+
existingRecordKeys = await collectExistingRecordKeys(recordKey, targetCollection, allowedResult.missingKeys);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const item of fieldValue) {
|
|
233
|
+
const r2 = await processAssociationChild({
|
|
234
|
+
value: item,
|
|
235
|
+
recordKey,
|
|
236
|
+
updateAssociationValues,
|
|
237
|
+
createParams,
|
|
238
|
+
updateParams,
|
|
239
|
+
target: targetCollection,
|
|
240
|
+
fieldPath,
|
|
241
|
+
allowedRecordKeys,
|
|
242
|
+
existingRecordKeys,
|
|
243
|
+
can,
|
|
244
|
+
parseOptions
|
|
245
|
+
});
|
|
246
|
+
if (r2 !== null && r2 !== void 0) {
|
|
247
|
+
processed.push(r2);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
v[fieldName] = processed;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const r = await processAssociationChild({
|
|
254
|
+
value: fieldValue,
|
|
255
|
+
recordKey,
|
|
256
|
+
updateAssociationValues,
|
|
257
|
+
createParams,
|
|
258
|
+
updateParams,
|
|
259
|
+
target: targetCollection,
|
|
260
|
+
fieldPath,
|
|
261
|
+
can,
|
|
262
|
+
parseOptions
|
|
263
|
+
});
|
|
264
|
+
v[fieldName] = r;
|
|
265
|
+
}
|
|
266
|
+
return v;
|
|
267
|
+
}
|
|
268
|
+
function normalizeAssociationValue(value, recordKey) {
|
|
269
|
+
if (!value) {
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
if (Array.isArray(value)) {
|
|
273
|
+
const result = value.map((v) => typeof v === "number" || typeof v === "string" ? v : v[recordKey]).filter((v) => v !== null && v !== void 0);
|
|
274
|
+
return result.length > 0 ? result : void 0;
|
|
275
|
+
}
|
|
276
|
+
return typeof value === "number" || typeof value === "string" ? value : value[recordKey];
|
|
277
|
+
}
|
|
278
|
+
async function collectAllowedRecordKeys(items, recordKey, filter, collection, parseOptions) {
|
|
279
|
+
if (!collection) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const { repository } = collection;
|
|
283
|
+
const keys = items.map((item) => import_lodash.default.isPlainObject(item) ? item[recordKey] : void 0).filter((key) => key !== void 0 && key !== null);
|
|
284
|
+
if (!keys.length) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
(0, import_acl.checkFilterParams)(collection, filter);
|
|
289
|
+
const scopedFilter = filter ? await (0, import_acl.parseJsonTemplate)(filter, parseOptions) : {};
|
|
290
|
+
const records = await repository.find({
|
|
291
|
+
filter: {
|
|
292
|
+
...scopedFilter,
|
|
293
|
+
[`${recordKey}.$in`]: keys
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
const allowedKeys = /* @__PURE__ */ new Set();
|
|
297
|
+
for (const record of records) {
|
|
298
|
+
const key = typeof record.get === "function" ? record.get(recordKey) : record[recordKey];
|
|
299
|
+
if (key !== void 0 && key !== null) {
|
|
300
|
+
allowedKeys.add(key);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const missingKeys = new Set(keys.filter((key) => !allowedKeys.has(key)));
|
|
304
|
+
return { allowedKeys, missingKeys };
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (e instanceof import_acl.NoPermissionError) {
|
|
307
|
+
return {
|
|
308
|
+
allowedKeys: /* @__PURE__ */ new Set(),
|
|
309
|
+
missingKeys: new Set(keys)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
throw e;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function collectExistingRecordKeys(recordKey, collection, keys) {
|
|
316
|
+
const { repository } = collection;
|
|
317
|
+
if (!repository) {
|
|
318
|
+
return /* @__PURE__ */ new Set();
|
|
319
|
+
}
|
|
320
|
+
const keyList = Array.from(keys);
|
|
321
|
+
if (!keyList.length) {
|
|
322
|
+
return /* @__PURE__ */ new Set();
|
|
323
|
+
}
|
|
324
|
+
const records = await repository.find({
|
|
325
|
+
filter: {
|
|
326
|
+
[`${recordKey}.$in`]: keyList
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
330
|
+
for (const record of records) {
|
|
331
|
+
const key = typeof record.get === "function" ? record.get(recordKey) : record[recordKey];
|
|
332
|
+
if (key !== void 0 && key !== null) {
|
|
333
|
+
existingKeys.add(key);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return existingKeys;
|
|
337
|
+
}
|
|
338
|
+
async function recordExistsWithoutScope(collection, recordKey, keyValue) {
|
|
339
|
+
const { repository } = collection;
|
|
340
|
+
if (!repository) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
const record = await repository.findOne({
|
|
344
|
+
filter: {
|
|
345
|
+
[recordKey]: keyValue
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
return Boolean(record);
|
|
349
|
+
}
|
|
350
|
+
async function processAssociationChild(options) {
|
|
351
|
+
var _a, _b;
|
|
352
|
+
const {
|
|
353
|
+
value,
|
|
354
|
+
recordKey,
|
|
355
|
+
updateAssociationValues,
|
|
356
|
+
createParams,
|
|
357
|
+
updateParams,
|
|
358
|
+
target,
|
|
359
|
+
fieldPath,
|
|
360
|
+
allowedRecordKeys,
|
|
361
|
+
existingRecordKeys,
|
|
362
|
+
can,
|
|
363
|
+
parseOptions
|
|
364
|
+
} = options;
|
|
365
|
+
const keyValue = value == null ? void 0 : value[recordKey];
|
|
366
|
+
const fallbackToCreate = async () => {
|
|
367
|
+
if (!createParams) {
|
|
368
|
+
return keyValue;
|
|
369
|
+
}
|
|
370
|
+
return await processValues({
|
|
371
|
+
values: value,
|
|
372
|
+
updateAssociationValues,
|
|
373
|
+
aclParams: createParams.params,
|
|
374
|
+
collection: target,
|
|
375
|
+
lastFieldPath: fieldPath,
|
|
376
|
+
protectedKeys: [],
|
|
377
|
+
can,
|
|
378
|
+
parseOptions
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const tryFallbackToCreate = async (reason, knownExists) => {
|
|
382
|
+
if (!createParams) {
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
const recordExists = typeof knownExists === "boolean" ? knownExists : await recordExistsWithoutScope(target, recordKey, keyValue);
|
|
386
|
+
if (!recordExists) {
|
|
387
|
+
return await fallbackToCreate();
|
|
388
|
+
}
|
|
389
|
+
return void 0;
|
|
390
|
+
};
|
|
391
|
+
if (keyValue !== void 0 && keyValue !== null) {
|
|
392
|
+
if (!updateParams) {
|
|
393
|
+
const created = await tryFallbackToCreate(`No permission to update association, try create not exist record`);
|
|
394
|
+
if (created !== void 0) {
|
|
395
|
+
return created;
|
|
396
|
+
}
|
|
397
|
+
return keyValue;
|
|
398
|
+
}
|
|
399
|
+
const { repository } = target;
|
|
400
|
+
if (!repository) {
|
|
401
|
+
return keyValue;
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
if (allowedRecordKeys) {
|
|
405
|
+
if (!allowedRecordKeys.has(keyValue)) {
|
|
406
|
+
const created = await tryFallbackToCreate(
|
|
407
|
+
`No permission to update association due to scope, try create not exist record`,
|
|
408
|
+
existingRecordKeys ? existingRecordKeys.has(keyValue) : void 0
|
|
409
|
+
);
|
|
410
|
+
if (created !== void 0) {
|
|
411
|
+
return created;
|
|
412
|
+
}
|
|
413
|
+
return keyValue;
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
(0, import_acl.checkFilterParams)(target, (_a = updateParams.params) == null ? void 0 : _a.filter);
|
|
417
|
+
const filter = await (0, import_acl.parseJsonTemplate)((_b = updateParams.params) == null ? void 0 : _b.filter, parseOptions);
|
|
418
|
+
const record = await repository.findOne({
|
|
419
|
+
filter: {
|
|
420
|
+
...filter,
|
|
421
|
+
[recordKey]: keyValue
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
if (!record) {
|
|
425
|
+
const created = await tryFallbackToCreate(
|
|
426
|
+
`No permission to update association due to scope, try create not exist record`
|
|
427
|
+
);
|
|
428
|
+
if (created !== void 0) {
|
|
429
|
+
return created;
|
|
430
|
+
}
|
|
431
|
+
return keyValue;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return await processValues({
|
|
435
|
+
values: value,
|
|
436
|
+
updateAssociationValues,
|
|
437
|
+
aclParams: updateParams.params,
|
|
438
|
+
collection: target,
|
|
439
|
+
lastFieldPath: fieldPath,
|
|
440
|
+
protectedKeys: [],
|
|
441
|
+
can,
|
|
442
|
+
parseOptions
|
|
443
|
+
});
|
|
444
|
+
} catch (e) {
|
|
445
|
+
if (e instanceof import_acl.NoPermissionError) {
|
|
446
|
+
return keyValue;
|
|
447
|
+
}
|
|
448
|
+
throw e;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (createParams) {
|
|
452
|
+
return await processValues({
|
|
453
|
+
values: value,
|
|
454
|
+
updateAssociationValues,
|
|
455
|
+
aclParams: createParams.params,
|
|
456
|
+
collection: target,
|
|
457
|
+
lastFieldPath: fieldPath,
|
|
458
|
+
protectedKeys: [],
|
|
459
|
+
can,
|
|
460
|
+
parseOptions
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
466
|
+
0 && (module.exports = {
|
|
467
|
+
checkChangesWithAssociation,
|
|
468
|
+
sanitizeAssociationValues
|
|
469
|
+
});
|
|
@@ -71,7 +71,11 @@ function createWithACLMetaMiddleware() {
|
|
|
71
71
|
if (collection.isMultiFilterTargetKey()) {
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
const
|
|
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
|
},
|
|
@@ -113,6 +118,7 @@ function createWithACLMetaMiddleware() {
|
|
|
113
118
|
}
|
|
114
119
|
},
|
|
115
120
|
state: {
|
|
121
|
+
...ctx.state,
|
|
116
122
|
currentRole: ctx.state.currentRole,
|
|
117
123
|
currentRoles: ctx.state.currentRoles,
|
|
118
124
|
currentUser: (() => {
|
|
@@ -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.
|
|
36
|
+
appVersion = "<2.1.0";
|
|
37
37
|
async up() {
|
|
38
38
|
const repo = this.db.getRepository("roles");
|
|
39
39
|
const role = await repo.findOne({
|
package/dist/server/server.d.ts
CHANGED
|
@@ -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():
|
|
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>;
|
package/dist/server/server.js
CHANGED
|
@@ -56,10 +56,17 @@ var import_RoleResourceActionModel = require("./model/RoleResourceActionModel");
|
|
|
56
56
|
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
|
+
var import_check_change_with_association = require("./middlewares/check-change-with-association");
|
|
59
60
|
class PluginACLServer extends import_server.Plugin {
|
|
60
61
|
get acl() {
|
|
61
62
|
return this.app.acl;
|
|
62
63
|
}
|
|
64
|
+
async sanitizeAssociationValues(options) {
|
|
65
|
+
return (0, import_check_change_with_association.sanitizeAssociationValues)({
|
|
66
|
+
...options,
|
|
67
|
+
acl: options.acl ?? this.acl
|
|
68
|
+
});
|
|
69
|
+
}
|
|
63
70
|
async writeResourceToACL(resourceModel, transaction) {
|
|
64
71
|
await resourceModel.writeToACL({
|
|
65
72
|
acl: this.acl,
|
|
@@ -144,6 +151,7 @@ class PluginACLServer extends import_server.Plugin {
|
|
|
144
151
|
"roles.dataSourcesCollections:*",
|
|
145
152
|
"roles.dataSourceResources:*",
|
|
146
153
|
"dataSourcesRolesResourcesScopes:*",
|
|
154
|
+
"dataSourcesRolesResourcesActions:*",
|
|
147
155
|
"rolesResourcesScopes:*"
|
|
148
156
|
]
|
|
149
157
|
});
|
|
@@ -503,7 +511,6 @@ class PluginACLServer extends import_server.Plugin {
|
|
|
503
511
|
}
|
|
504
512
|
return next();
|
|
505
513
|
});
|
|
506
|
-
const parseJsonTemplate = this.app.acl.parseJsonTemplate;
|
|
507
514
|
this.app.acl.beforeGrantAction(async (ctx) => {
|
|
508
515
|
const actionName = this.app.acl.resolveActionAlias(ctx.actionName);
|
|
509
516
|
if (import_lodash.default.isPlainObject(ctx.params)) {
|
|
@@ -575,6 +582,12 @@ class PluginACLServer extends import_server.Plugin {
|
|
|
575
582
|
dataSource.acl.use(import_check_association_operate.checkAssociationOperate, {
|
|
576
583
|
before: "core"
|
|
577
584
|
});
|
|
585
|
+
if (dataSource.options.acl !== false && dataSource.options.useACL !== false) {
|
|
586
|
+
dataSource.resourceManager.registerPreActionHandler("create", import_check_change_with_association.checkChangesWithAssociation);
|
|
587
|
+
dataSource.resourceManager.registerPreActionHandler("firstOrCreate", import_check_change_with_association.checkChangesWithAssociation);
|
|
588
|
+
dataSource.resourceManager.registerPreActionHandler("updateOrCreate", import_check_change_with_association.checkChangesWithAssociation);
|
|
589
|
+
dataSource.resourceManager.registerPreActionHandler("update", import_check_change_with_association.checkChangesWithAssociation);
|
|
590
|
+
}
|
|
578
591
|
});
|
|
579
592
|
this.db.on("afterUpdateCollection", async (collection) => {
|
|
580
593
|
if (collection.options.loadedFromCollectionManager || collection.options.asStrategyResource) {
|