oak-domain 4.2.2 → 4.2.4
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/lib/base-app-domain/ActionDefDict.d.ts +8 -8
- package/lib/base-app-domain/Oper/Schema.d.ts +158 -158
- package/lib/base-app-domain/Oper/Storage.js +56 -56
- package/lib/base-app-domain/User/Action.d.ts +11 -11
- package/lib/base-app-domain/User/Action.js +12 -12
- package/lib/base-app-domain/UserEntityGrant/Action.d.ts +5 -5
- package/lib/base-app-domain/UserEntityGrant/Action.js +5 -5
- package/lib/compiler/schemalBuilder.js +4167 -4167
- package/lib/entities/Oper.d.ts +12 -12
- package/lib/entities/Oper.js +36 -36
- package/lib/entities/User.d.ts +18 -18
- package/lib/entities/User.js +32 -32
- package/lib/index.d.ts +1 -1
- package/lib/index.js +3 -3
- package/lib/store/AsyncRowStore.d.ts +66 -66
- package/lib/store/CascadeStore.d.ts +109 -109
- package/lib/store/CascadeStore.js +1728 -1726
- package/lib/store/RelationAuth.js +1209 -1209
- package/lib/store/TriggerExecutor.js +468 -468
- package/lib/store/actionDef.js +278 -278
- package/lib/store/checker.js +487 -487
- package/lib/store/relation.d.ts +12 -12
- package/lib/store/relation.js +74 -74
- package/lib/triggers/index.d.ts +5 -5
- package/lib/triggers/index.js +28 -28
- package/lib/types/Configuration.d.ts +42 -42
- package/lib/types/Configuration.js +3 -3
- package/lib/types/Connector.d.ts +39 -38
- package/lib/types/Entity.d.ts +209 -209
- package/lib/types/Sync.d.ts +74 -69
- package/lib/types/Sync.js +9 -9
- package/lib/types/index.d.ts +27 -27
- package/lib/types/index.js +30 -30
- package/lib/utils/SimpleConnector.d.ts +81 -64
- package/lib/utils/SimpleConnector.js +217 -206
- package/lib/utils/assert.d.ts +5 -5
- package/lib/utils/projection.d.ts +4 -4
- package/lib/utils/relationPath.d.ts +31 -31
- package/lib/utils/relationPath.js +202 -202
- package/package.json +51 -51
- package/src/entities/ActionAuth.ts +41 -41
- package/src/entities/I18n.ts +45 -45
- package/src/entities/Modi.ts +69 -69
- package/src/entities/ModiEntity.ts +26 -26
- package/src/entities/Oper.ts +48 -48
- package/src/entities/OperEntity.ts +27 -27
- package/src/entities/Path.ts +43 -43
- package/src/entities/Relation.ts +43 -43
- package/src/entities/RelationAuth.ts +44 -44
- package/src/entities/User.ts +48 -48
- package/src/entities/UserEntityClaim.ts +29 -29
- package/src/entities/UserEntityGrant.ts +24 -24
- package/src/entities/UserRelation.ts +50 -50
|
@@ -1,1209 +1,1209 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUserRelationsByActions = exports.RelationAuth = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
6
|
-
const types_1 = require("../types");
|
|
7
|
-
const AsyncRowStore_1 = require("./AsyncRowStore");
|
|
8
|
-
const filter_1 = require("./filter");
|
|
9
|
-
const relation_1 = require("./relation");
|
|
10
|
-
const action_1 = require("../actions/action");
|
|
11
|
-
const lodash_1 = require("../utils/lodash");
|
|
12
|
-
const entities_1 = require("../compiler/entities");
|
|
13
|
-
const relationPath_1 = require("../utils/relationPath");
|
|
14
|
-
class RelationAuth {
|
|
15
|
-
authDeduceRelationMap;
|
|
16
|
-
schema;
|
|
17
|
-
static SPECIAL_ENTITIES = entities_1.SYSTEM_RESERVE_ENTITIES;
|
|
18
|
-
selectFreeEntities;
|
|
19
|
-
updateFreeDict;
|
|
20
|
-
constructor(schema, authDeduceRelationMap, selectFreeEntities, updateFreeDict) {
|
|
21
|
-
this.schema = schema;
|
|
22
|
-
this.selectFreeEntities = selectFreeEntities || [];
|
|
23
|
-
this.updateFreeDict = updateFreeDict || {};
|
|
24
|
-
this.authDeduceRelationMap = Object.assign({}, authDeduceRelationMap, {
|
|
25
|
-
modi: 'entity',
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
// 前台检查filter是否满足relation约束
|
|
29
|
-
checkRelationSync(entity, operation, context) {
|
|
30
|
-
if (context.isRoot()) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
this.checkActions2(entity, operation, context);
|
|
34
|
-
}
|
|
35
|
-
// 后台检查filter是否满足relation约束
|
|
36
|
-
async checkRelationAsync(entity, operation, context) {
|
|
37
|
-
if (context.isRoot()) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
await this.checkActions2(entity, operation, context);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* 检查当前用户有无权限对filter约束的userRelation进行action操作
|
|
44
|
-
* @param context
|
|
45
|
-
* @param action
|
|
46
|
-
* @param filter
|
|
47
|
-
* @returns
|
|
48
|
-
*/
|
|
49
|
-
checkUserRelation(context, action, filter) {
|
|
50
|
-
const userId = context.getCurrentUserId();
|
|
51
|
-
/**
|
|
52
|
-
* 检查对某一个relationId是否有操作资格
|
|
53
|
-
* @param destRelationId
|
|
54
|
-
* @returns
|
|
55
|
-
*/
|
|
56
|
-
const checkOnRelationId = (entity, destRelationId, filter) => {
|
|
57
|
-
/**
|
|
58
|
-
* 找到能创建此relation的所有父级relation,只要user和其中一个有关联即可以通过
|
|
59
|
-
*/
|
|
60
|
-
const relationAuths = context.select('relationAuth', {
|
|
61
|
-
data: {
|
|
62
|
-
id: 1,
|
|
63
|
-
path: {
|
|
64
|
-
id: 1,
|
|
65
|
-
sourceEntity: 1,
|
|
66
|
-
destEntity: 1,
|
|
67
|
-
value: 1,
|
|
68
|
-
recursive: 1,
|
|
69
|
-
},
|
|
70
|
-
sourceRelationId: 1,
|
|
71
|
-
sourceRelation: {
|
|
72
|
-
id: 1,
|
|
73
|
-
entity: 1,
|
|
74
|
-
entityId: 1,
|
|
75
|
-
},
|
|
76
|
-
destRelationId: 1,
|
|
77
|
-
destRelation: {
|
|
78
|
-
id: 1,
|
|
79
|
-
entity: 1,
|
|
80
|
-
entityId: 1,
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
filter: {
|
|
84
|
-
destRelationId,
|
|
85
|
-
},
|
|
86
|
-
}, { dontCollect: true });
|
|
87
|
-
const checkRelationAuth = (relationAuth) => {
|
|
88
|
-
const { destRelation, sourceRelationId, path } = relationAuth;
|
|
89
|
-
(0, assert_1.default)(entity === destRelation.entity);
|
|
90
|
-
let destEntityFilter = this.makePathFilter(entity, path, this.schema, {
|
|
91
|
-
userRelation$entity: {
|
|
92
|
-
userId,
|
|
93
|
-
relationId: sourceRelationId,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
if (filter) {
|
|
97
|
-
destEntityFilter = (0, filter_1.combineFilters)(entity, this.schema, [destEntityFilter, filter]);
|
|
98
|
-
}
|
|
99
|
-
return context.count(destRelation.entity, {
|
|
100
|
-
filter: destEntityFilter,
|
|
101
|
-
}, { ignoreAttrMiss: true });
|
|
102
|
-
};
|
|
103
|
-
if (relationAuths instanceof Promise) {
|
|
104
|
-
return relationAuths.then((ras) => Promise.all(ras.map(ra => checkRelationAuth(ra)))).then((result) => !!result.find(ele => {
|
|
105
|
-
(0, assert_1.default)(typeof ele === 'number');
|
|
106
|
-
return ele > 0;
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
const result = relationAuths.map(ra => checkRelationAuth(ra));
|
|
110
|
-
return !!result.find(ele => ele > 0);
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* 检查对超过一个的relationId是否有操作资格
|
|
114
|
-
* @param relationFilter 限定relationId的条件
|
|
115
|
-
* @param intersection 是否交集(对每个relationId都得有权限)
|
|
116
|
-
* @param entityFilter 限定entity的条件
|
|
117
|
-
* @param entity 对应的entity
|
|
118
|
-
* @attention 这里为了代码复用,有可能是要通过本函数内部来查询确定entity;所以要保证,如果传入的relationFilter就可以确定relationId,则这里的entity参数必传。
|
|
119
|
-
* @returns
|
|
120
|
-
*/
|
|
121
|
-
const checkOnMultipleRelations = (relationFilter, intersection, entityFilter, entity) => {
|
|
122
|
-
let entity2 = entity;
|
|
123
|
-
const getRelationIds = () => {
|
|
124
|
-
const relevantIds = (0, filter_1.getRelevantIds)(relationFilter);
|
|
125
|
-
if (relevantIds.length > 0) {
|
|
126
|
-
return relevantIds;
|
|
127
|
-
}
|
|
128
|
-
const relations = context.select('relation', {
|
|
129
|
-
data: {
|
|
130
|
-
id: 1,
|
|
131
|
-
entity: 1,
|
|
132
|
-
entityId: 1,
|
|
133
|
-
},
|
|
134
|
-
filter: relationFilter
|
|
135
|
-
}, { dontCollect: true });
|
|
136
|
-
if (relations instanceof Promise) {
|
|
137
|
-
return relations.then((rs) => {
|
|
138
|
-
if (!entity2) {
|
|
139
|
-
entity2 = rs[0]?.entity;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
(0, assert_1.default)(entity2 === rs[0]?.entity);
|
|
143
|
-
}
|
|
144
|
-
return rs.map(ele => ele.id);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
if (!entity2) {
|
|
148
|
-
entity2 = relations[0]?.entity;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
(0, assert_1.default)(entity2 === relations[0]?.entity);
|
|
152
|
-
}
|
|
153
|
-
return relations.map(ele => ele.id);
|
|
154
|
-
};
|
|
155
|
-
const relationIds = getRelationIds();
|
|
156
|
-
if (relationIds instanceof Promise) {
|
|
157
|
-
return relationIds.then((ids) => {
|
|
158
|
-
return Promise.all(ids.map(ele => checkOnRelationId(entity2, ele, entityFilter))).then((value) => {
|
|
159
|
-
if (intersection) {
|
|
160
|
-
return !(value.includes(false));
|
|
161
|
-
}
|
|
162
|
-
return value.includes(true);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
const value = relationIds.map(ele => checkOnRelationId(entity2, ele, entityFilter));
|
|
167
|
-
if (intersection) {
|
|
168
|
-
return !(value.includes(false));
|
|
169
|
-
}
|
|
170
|
-
return value.includes(true);
|
|
171
|
-
};
|
|
172
|
-
if (action === 'create') {
|
|
173
|
-
const { entity, entityId, relationId } = filter;
|
|
174
|
-
(0, assert_1.default)(typeof entity === 'string');
|
|
175
|
-
let entityFilter;
|
|
176
|
-
if (entityId) {
|
|
177
|
-
entityFilter = {
|
|
178
|
-
id: entityId,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
// userEntityGrant会有这种情况,限定某个对象的范围进行授权
|
|
183
|
-
entityFilter = filter[entity];
|
|
184
|
-
}
|
|
185
|
-
if (relationId) {
|
|
186
|
-
// 如果指定relation,则测试该relation上是否可行
|
|
187
|
-
// 目前可能会有多个relationIds传入(userEntityGrant做测试),但一定是可以确定的relationId集合
|
|
188
|
-
return checkOnMultipleRelations({ id: relationId }, true, entityFilter, entity);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
// 否则为测试“能否”有权限管理的资格,此时只要有一个就可以
|
|
192
|
-
// 这是为上层的menu所有,真正的创建不可能走到这里
|
|
193
|
-
// bug fixed,目前框架不支持entityId为null,所以这里暂时只支持entityId一种方式的测试
|
|
194
|
-
(0, assert_1.default)(entityId);
|
|
195
|
-
return checkOnMultipleRelations({
|
|
196
|
-
entity,
|
|
197
|
-
$or: [
|
|
198
|
-
{
|
|
199
|
-
entityId: {
|
|
200
|
-
$exists: false,
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
entityId,
|
|
205
|
-
}
|
|
206
|
-
]
|
|
207
|
-
}, false, entityFilter, entity);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
(0, assert_1.default)(action === 'remove');
|
|
212
|
-
// 有可能是删除多个userRelation,这时必须检查每一个relation都有对应的权限(有一个不能删除那就不能删除)
|
|
213
|
-
return checkOnMultipleRelations({
|
|
214
|
-
userRelation$relation: filter,
|
|
215
|
-
}, false, {
|
|
216
|
-
userRelation$entity: filter,
|
|
217
|
-
}, filter.entity);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
checkOperateSpecialEntities2(entity2, action, filter, context) {
|
|
221
|
-
switch (entity2) {
|
|
222
|
-
case 'userRelation': {
|
|
223
|
-
(0, assert_1.default)(!(filter instanceof Array));
|
|
224
|
-
return this.checkUserRelation(context, action, filter);
|
|
225
|
-
}
|
|
226
|
-
case 'user': {
|
|
227
|
-
// 对用户的操作由应用自己去管理权限,这里只检查grant/revoke
|
|
228
|
-
if (['grant', 'revoke'].includes(action)) {
|
|
229
|
-
// assert(filter && Object.keys(filter).length === 1, 'grant/revoke只能操作userRelation$user');
|
|
230
|
-
// assert(filter!.hasOwnProperty('userRelation$user'), 'grant/revoke只能操作userRelation$user');
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
// 应用允许用户操作其它用户的逻辑请通过编写类型为relation的checker去控制,在这里不能加以限制
|
|
235
|
-
return true;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
case 'modi': {
|
|
239
|
-
/** 正常情况下对modi的生成是在触发器下openRootMode,不会走到这里
|
|
240
|
-
* 但是有些例外,如extraFile如果在modi中创建,上传成功之后需要显式生成一条modi,这时对modi的
|
|
241
|
-
* 检查可以转化为对其父entity的update权限检查
|
|
242
|
-
*/
|
|
243
|
-
(0, assert_1.default)(action === 'create');
|
|
244
|
-
const { entity, entityId } = filter;
|
|
245
|
-
return this.checkOperation(entity, {
|
|
246
|
-
action: 'update',
|
|
247
|
-
data: {},
|
|
248
|
-
filter: {
|
|
249
|
-
id: entityId,
|
|
250
|
-
},
|
|
251
|
-
}, context);
|
|
252
|
-
}
|
|
253
|
-
case 'relation': {
|
|
254
|
-
// 创建relation目前不支持,以后再说
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
case 'userEntityGrant': {
|
|
258
|
-
// userEntityGrant的创建相当于授权,领取相当于赋权
|
|
259
|
-
if (['create', 'update', 'remove'].includes(action)) {
|
|
260
|
-
if (action === 'create') {
|
|
261
|
-
const { relationEntity, relationEntityFilter, relationIds } = filter;
|
|
262
|
-
return this.checkOperateSpecialEntities2('userRelation', 'create', {
|
|
263
|
-
entity: relationEntity,
|
|
264
|
-
[relationEntity]: relationEntityFilter,
|
|
265
|
-
relationId: {
|
|
266
|
-
$in: relationIds,
|
|
267
|
-
},
|
|
268
|
-
}, context);
|
|
269
|
-
}
|
|
270
|
-
return this.checkOperateSpecialEntities2('userRelation', 'action', {
|
|
271
|
-
relation: {
|
|
272
|
-
userEntityGrant$relation: filter,
|
|
273
|
-
},
|
|
274
|
-
}, context);
|
|
275
|
-
}
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
default: {
|
|
279
|
-
(0, assert_1.default)(false, `对象${entity2}的权限控制没有加以控制`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
getDeducedEntityFilters(entity, filter, actions, context) {
|
|
284
|
-
const entityFilters = [
|
|
285
|
-
{
|
|
286
|
-
entity,
|
|
287
|
-
filter,
|
|
288
|
-
actions,
|
|
289
|
-
}
|
|
290
|
-
];
|
|
291
|
-
if (this.authDeduceRelationMap[entity]) {
|
|
292
|
-
(0, assert_1.default)(this.authDeduceRelationMap[entity] === 'entity');
|
|
293
|
-
let { entity: deduceEntity, entityId: deduceEntityId } = filter;
|
|
294
|
-
let deduceFilter = {};
|
|
295
|
-
if (deduceEntity && deduceEntityId) {
|
|
296
|
-
deduceFilter = { id: deduceEntityId };
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
// 也可能是用cascade方式进行查找,这里有时候filter上会带有两个不同的entity目标,尚未处理(todo!)
|
|
300
|
-
const { ref } = this.schema[entity].attributes.entity;
|
|
301
|
-
(0, assert_1.default)(ref instanceof Array);
|
|
302
|
-
for (const refEntity of ref) {
|
|
303
|
-
if (filter[refEntity]) {
|
|
304
|
-
deduceEntity = refEntity;
|
|
305
|
-
deduceFilter = filter[refEntity];
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const getRecursiveDeducedFilters = (deduceEntity, deduceFilter) => {
|
|
311
|
-
const excludeActions = action_1.readOnlyActions.concat([ /* 'create', 'remove' */]);
|
|
312
|
-
const updateActions = this.schema[deduceEntity].actions.filter((a) => !excludeActions.includes(a));
|
|
313
|
-
/* if (!RelationAuth.SPECIAL_ENTITIES.includes(deduceEntity as string)) {
|
|
314
|
-
return this.getDeducedEntityFilters(deduceEntity, deduceFilter, actions[0] === 'select' ? actions : updateActions, context);
|
|
315
|
-
}
|
|
316
|
-
return []; */
|
|
317
|
-
return this.getDeducedEntityFilters(deduceEntity, deduceFilter, actions[0] === 'select' ? actions : updateActions, context);
|
|
318
|
-
};
|
|
319
|
-
if (deduceEntity && deduceFilter) {
|
|
320
|
-
const deducedSelections = getRecursiveDeducedFilters(deduceEntity, deduceFilter);
|
|
321
|
-
if (deducedSelections instanceof Promise) {
|
|
322
|
-
return deducedSelections.then((ds) => {
|
|
323
|
-
entityFilters.push(...ds);
|
|
324
|
-
return entityFilters;
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
entityFilters.push(...deducedSelections);
|
|
328
|
-
return entityFilters;
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
/**
|
|
332
|
-
* 这种情况说明从filter中无法确定相应的deduceFilter,需要查找该实体对应的entity/entityId来进行推导。
|
|
333
|
-
* 这种情况一般发生在entity1 -> entity2上,此时entity2应该是一个固定id查询的filter
|
|
334
|
-
* 在这里先假设如果碰到了list类型的filter,直接不使用deduce路径上的对象来推导
|
|
335
|
-
*/
|
|
336
|
-
const rows2 = context.select(entity, {
|
|
337
|
-
data: {
|
|
338
|
-
id: 1,
|
|
339
|
-
entity: 1,
|
|
340
|
-
entityId: 1,
|
|
341
|
-
},
|
|
342
|
-
filter,
|
|
343
|
-
indexFrom: 0,
|
|
344
|
-
count: 10,
|
|
345
|
-
}, { dontCollect: true, blockTrigger: true });
|
|
346
|
-
const dealWithData = (rows) => {
|
|
347
|
-
// 这里如果entity指向不同的实体,一般出现这样的查询,则其权限应当不由这条deduce路径处理
|
|
348
|
-
// 同上,如果找到的行数大于1行,说明deduce路径上的对象不确定,也暂不处理 by Xc 20230725
|
|
349
|
-
if (rows.length > 1 || rows.length === 0) {
|
|
350
|
-
if (process.env.NODE_ENV === 'development') {
|
|
351
|
-
console.warn(`进行deduce推导时找到了${rows.length}行${entity}数据`);
|
|
352
|
-
}
|
|
353
|
-
return entityFilters;
|
|
354
|
-
}
|
|
355
|
-
const { entity: deducedEntity, entityId: deducedEntityId } = rows[0];
|
|
356
|
-
if (!deducedEntity || !deducedEntityId) {
|
|
357
|
-
// 这种情况会出现在前台缓存里
|
|
358
|
-
return entityFilters;
|
|
359
|
-
}
|
|
360
|
-
const result = getRecursiveDeducedFilters(deducedEntity, {
|
|
361
|
-
id: deducedEntityId,
|
|
362
|
-
});
|
|
363
|
-
if (result instanceof Promise) {
|
|
364
|
-
return result.then((r2) => {
|
|
365
|
-
entityFilters.push(...r2);
|
|
366
|
-
return entityFilters;
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
entityFilters.push(...result);
|
|
370
|
-
return entityFilters;
|
|
371
|
-
};
|
|
372
|
-
if (rows2 instanceof Promise) {
|
|
373
|
-
return rows2.then((r2) => dealWithData(r2));
|
|
374
|
-
}
|
|
375
|
-
return dealWithData(rows2);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return entityFilters;
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* 对于selection,解构出最底层的对象,如果最底层的对象可以被访问,则父对象一定可以
|
|
382
|
-
* 但对于deduce的子对象,不必再向底层查看(假设deduce对象一般都位于树的最底层附近)
|
|
383
|
-
* @param entity
|
|
384
|
-
* @param operation
|
|
385
|
-
*/
|
|
386
|
-
destructSelection(entity, selection) {
|
|
387
|
-
const leafSelections = [];
|
|
388
|
-
const destructInner = (entity2, selection2) => {
|
|
389
|
-
const { data, filter } = selection2;
|
|
390
|
-
let hasOneToMany = false;
|
|
391
|
-
for (const attr in data) {
|
|
392
|
-
const rel = (0, relation_1.judgeRelation)(this.schema, entity2, attr);
|
|
393
|
-
if (rel instanceof Array) {
|
|
394
|
-
const [e, foreignKey] = rel;
|
|
395
|
-
if (foreignKey) {
|
|
396
|
-
(0, assert_1.default)(!this.authDeduceRelationMap[e]);
|
|
397
|
-
hasOneToMany = true;
|
|
398
|
-
destructInner(e, {
|
|
399
|
-
data: data[attr].data,
|
|
400
|
-
filter: (0, filter_1.combineFilters)(e, this.schema, [{
|
|
401
|
-
[foreignKey.slice(0, foreignKey.length - 2)]: filter,
|
|
402
|
-
}, data[attr].filter || {}]),
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
if (!this.authDeduceRelationMap[e]) {
|
|
407
|
-
hasOneToMany = true;
|
|
408
|
-
destructInner(e, {
|
|
409
|
-
data: data[attr].data,
|
|
410
|
-
filter: (0, filter_1.combineFilters)(e, this.schema, [{
|
|
411
|
-
[entity2]: filter,
|
|
412
|
-
}, data[attr].filter || {}]),
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
(0, assert_1.default)(this.authDeduceRelationMap[e] === 'entity');
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
if (!hasOneToMany) {
|
|
422
|
-
leafSelections.push({
|
|
423
|
-
entity: entity2,
|
|
424
|
-
filter,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
destructInner(entity, (0, lodash_1.cloneDeep)(selection));
|
|
429
|
-
return leafSelections;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* 对于operation,解构出一个树形结构,以方便自顶向下的进行访问
|
|
433
|
-
* 但对于deduce的子对象,不必再向底层查看
|
|
434
|
-
* @param entity
|
|
435
|
-
* @param selection
|
|
436
|
-
*/
|
|
437
|
-
destructOperation(entity2, operation2, userId) {
|
|
438
|
-
/**
|
|
439
|
-
* 对create动作,把data中的cascade部分剔除后作为filter参与后续的检验
|
|
440
|
-
* @param operation
|
|
441
|
-
* @returns
|
|
442
|
-
*/
|
|
443
|
-
const makeCreateFilter = (entity, operation) => {
|
|
444
|
-
const { data, filter } = operation;
|
|
445
|
-
(0, assert_1.default)(!(data instanceof Array));
|
|
446
|
-
if (data) {
|
|
447
|
-
const data2 = {};
|
|
448
|
-
for (const attr in data) {
|
|
449
|
-
const rel = (0, relation_1.judgeRelation)(this.schema, entity, attr);
|
|
450
|
-
if (rel === 1) {
|
|
451
|
-
// 只需要记住id和各种外键属性,不这样处理有些古怪的属性比如coordinate,其作为createdata和作为filter并不同构
|
|
452
|
-
/* if ((['id', 'entity', 'entityId'].includes(attr) || this.schema[entity].attributes[attr as any]?.type === 'ref') && typeof data[attr] === 'string') {
|
|
453
|
-
data2[attr] = data[attr];
|
|
454
|
-
} */
|
|
455
|
-
// 假设不再成立,userEntityGrant需要relationEntity这样的属性
|
|
456
|
-
if (!['geometry', 'geography', 'st_geometry', 'st_point'].includes(this.schema[entity].attributes[attr]?.type)) {
|
|
457
|
-
data2[attr] = data[attr];
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return data2;
|
|
462
|
-
}
|
|
463
|
-
return filter;
|
|
464
|
-
};
|
|
465
|
-
const addChild = (node, path, child) => {
|
|
466
|
-
// 在这里要把可以被node deduce出来的child处理掉
|
|
467
|
-
const paths = path.split('$');
|
|
468
|
-
(0, assert_1.default)(paths.length >= 2);
|
|
469
|
-
if (this.authDeduceRelationMap[child.entity] === paths[1]) {
|
|
470
|
-
(0, assert_1.default)(paths[1] === 'entity', '当前只支持entity外键上的deduce');
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
if (node.children[path]) {
|
|
474
|
-
if (node.children[path] instanceof Array) {
|
|
475
|
-
node.children[path].push(child);
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
node.children[path] = [node.children[path], child];
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
Object.assign(node.children, {
|
|
483
|
-
[path]: child,
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
return true;
|
|
487
|
-
};
|
|
488
|
-
const destructInner = (entity, operation,
|
|
489
|
-
// extraFilter?: ED[T2]['Selection']['filter'],
|
|
490
|
-
path, child, hasParent, extraFilter) => {
|
|
491
|
-
const { action, data, filter } = operation;
|
|
492
|
-
const filter2 = action === 'create' ? makeCreateFilter(entity, operation) : (0, lodash_1.cloneDeep)(filter);
|
|
493
|
-
(0, assert_1.default)(filter2);
|
|
494
|
-
if (extraFilter) {
|
|
495
|
-
Object.assign(filter2, extraFilter);
|
|
496
|
-
}
|
|
497
|
-
// const filter3 = extraFilter ? combineFilters(entity, schema, [filter2, extraFilter]) : filter2;
|
|
498
|
-
const me = {
|
|
499
|
-
entity: entity,
|
|
500
|
-
filter: filter2,
|
|
501
|
-
children: {},
|
|
502
|
-
action,
|
|
503
|
-
};
|
|
504
|
-
let root = me;
|
|
505
|
-
// 如果当前对象是一个toModi的,意味着它的cascadeUpdate会全部被变为modi去缓存,因此不需要再向下检查了
|
|
506
|
-
// modi被apply时,这些modi产生的更新才会被实际检查
|
|
507
|
-
// 这里可能有问题,再思考思考 by Xc 20231111
|
|
508
|
-
const isModiUpdate = this.schema[entity].toModi && action !== 'remove';
|
|
509
|
-
if (child) {
|
|
510
|
-
(0, assert_1.default)(path);
|
|
511
|
-
addChild(me, path, child);
|
|
512
|
-
}
|
|
513
|
-
(0, assert_1.default)(!(data instanceof Array));
|
|
514
|
-
for (const attr in data) {
|
|
515
|
-
const rel = (0, relation_1.judgeRelation)(this.schema, entity, attr);
|
|
516
|
-
if (rel === 2 && !isModiUpdate) {
|
|
517
|
-
(0, assert_1.default)(root === me && !hasParent, 'cascadeUpdate必须是树结构,避免森林');
|
|
518
|
-
const mtoOperation = data[attr];
|
|
519
|
-
root = destructInner(attr, mtoOperation, `${entity}$entity`, me);
|
|
520
|
-
}
|
|
521
|
-
else if (typeof rel === 'string' && !isModiUpdate) {
|
|
522
|
-
(0, assert_1.default)(root === me && !hasParent, 'cascadeUpdate必须是树结构,避免森林');
|
|
523
|
-
root = destructInner(rel, data[attr], `${entity}$${attr}`, me);
|
|
524
|
-
}
|
|
525
|
-
else if (rel instanceof Array && !isModiUpdate) {
|
|
526
|
-
const [e, f] = rel;
|
|
527
|
-
const otmOperations = data[attr];
|
|
528
|
-
/**
|
|
529
|
-
* 这里目前在cascadeUpdate的过程中,只有当一对多个userRelation的操作需要将entity和entityId复制到子对象上
|
|
530
|
-
* 因为对userRelation的判断是走的特殊路径,无法利用父对象的actionAuth
|
|
531
|
-
* 其它对象情况不需要复制,因为应用中必须要能保证(前台传来的)父对象的filter不依赖于子对象的条件
|
|
532
|
-
*/
|
|
533
|
-
let extraFilter = undefined;
|
|
534
|
-
if (e === 'userRelation' && entity !== 'user') {
|
|
535
|
-
me.userRelations = [];
|
|
536
|
-
extraFilter = {
|
|
537
|
-
entity,
|
|
538
|
-
entityId: filter2.id,
|
|
539
|
-
};
|
|
540
|
-
const dealWithUserRelation = (userRelation) => {
|
|
541
|
-
const { action, data } = userRelation;
|
|
542
|
-
if (action === 'create') {
|
|
543
|
-
const attrs = Object.keys(data);
|
|
544
|
-
(0, assert_1.default)((0, lodash_1.difference)(attrs, Object.keys(this.schema.userRelation.attributes).concat('id')).length === 0);
|
|
545
|
-
if (data.userId === userId) {
|
|
546
|
-
me.userRelations?.push(data);
|
|
547
|
-
}
|
|
548
|
-
(0, assert_1.default)(filter2.id);
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
if (otmOperations instanceof Array) {
|
|
552
|
-
otmOperations.forEach((otmOperation) => dealWithUserRelation(otmOperation));
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
dealWithUserRelation(otmOperations);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
if (otmOperations instanceof Array) {
|
|
559
|
-
otmOperations.forEach((otmOperation) => {
|
|
560
|
-
const son = destructInner(e, otmOperation, undefined, undefined, true, extraFilter);
|
|
561
|
-
addChild(me, attr, son);
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
const son = destructInner(e, otmOperations, undefined, undefined, true, extraFilter);
|
|
566
|
-
addChild(me, attr, son);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
return root;
|
|
571
|
-
};
|
|
572
|
-
return destructInner(entity2, operation2);
|
|
573
|
-
}
|
|
574
|
-
makePathFilter(entity, path, schema, filter) {
|
|
575
|
-
const { value, recursive } = path;
|
|
576
|
-
if (value === '') {
|
|
577
|
-
(0, assert_1.default)(!recursive);
|
|
578
|
-
return filter;
|
|
579
|
-
}
|
|
580
|
-
const paths = value.split('.');
|
|
581
|
-
const makeRecursiveFilter = (recursiveDepth) => {
|
|
582
|
-
if (recursiveDepth > 0) {
|
|
583
|
-
return {
|
|
584
|
-
$or: [
|
|
585
|
-
filter,
|
|
586
|
-
{
|
|
587
|
-
parent: makeRecursiveFilter(recursiveDepth - 1)
|
|
588
|
-
}
|
|
589
|
-
]
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
return filter;
|
|
593
|
-
};
|
|
594
|
-
const makeInner = (idx, e2) => {
|
|
595
|
-
const attr = paths[idx];
|
|
596
|
-
if (idx === paths.length) {
|
|
597
|
-
if (!recursive) {
|
|
598
|
-
return filter;
|
|
599
|
-
}
|
|
600
|
-
else {
|
|
601
|
-
// 在最后一个对象上存在递归,用or连接处理
|
|
602
|
-
const { recursiveDepth } = schema[e2];
|
|
603
|
-
(0, assert_1.default)(recursiveDepth > 0);
|
|
604
|
-
return makeRecursiveFilter(recursiveDepth);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
608
|
-
const rel = (0, relation_1.judgeRelation)(schema, e2, attr);
|
|
609
|
-
let e3;
|
|
610
|
-
if (rel === 2) {
|
|
611
|
-
e3 = attr;
|
|
612
|
-
}
|
|
613
|
-
else if (typeof rel === 'string') {
|
|
614
|
-
e3 = rel;
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
(0, assert_1.default)(rel instanceof Array);
|
|
618
|
-
e3 = rel[0];
|
|
619
|
-
}
|
|
620
|
-
const f = makeInner(idx + 1, e3);
|
|
621
|
-
return {
|
|
622
|
-
[attr]: f,
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
return makeInner(0, entity);
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* 对所有满足操作要求的actionAuth加以判断,找到可以满足当前用户身份的actionAuth
|
|
630
|
-
* @param entity
|
|
631
|
-
* @param filter
|
|
632
|
-
* @param actionAuths
|
|
633
|
-
* @param context
|
|
634
|
-
* @return
|
|
635
|
-
*/
|
|
636
|
-
filterActionAuths(entity, filter, actionAuths, context) {
|
|
637
|
-
const result = actionAuths.map((ele) => {
|
|
638
|
-
const { path, relation, relationId } = ele;
|
|
639
|
-
// 在cache中,可能出现relation外键指向的对象为null的情况,要容错
|
|
640
|
-
if (relationId) {
|
|
641
|
-
if (relation) {
|
|
642
|
-
const { userRelation$relation: userRelations } = relation;
|
|
643
|
-
if (userRelations.length > 0) {
|
|
644
|
-
const entityIds = (0, lodash_1.uniq)(userRelations.map(ele => ele.entityId));
|
|
645
|
-
const pathFilter = this.makePathFilter(entity, path, this.schema, {
|
|
646
|
-
id: entityIds.length > 0 ? {
|
|
647
|
-
$in: entityIds,
|
|
648
|
-
} : entityIds[0],
|
|
649
|
-
});
|
|
650
|
-
const contains = (0, filter_1.checkFilterContains)(entity, context, pathFilter, filter, true);
|
|
651
|
-
if (contains instanceof Promise) {
|
|
652
|
-
return contains.then((c) => {
|
|
653
|
-
if (c) {
|
|
654
|
-
return ele;
|
|
655
|
-
}
|
|
656
|
-
return;
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
if (contains) {
|
|
660
|
-
return ele;
|
|
661
|
-
}
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
// 说明是通过userId关联
|
|
668
|
-
const pathFilter = this.makePathFilter(entity, path, this.schema, {
|
|
669
|
-
id: context.getCurrentUserId(),
|
|
670
|
-
});
|
|
671
|
-
const contains = (0, filter_1.checkFilterContains)(entity, context, pathFilter, filter, true);
|
|
672
|
-
if (contains instanceof Promise) {
|
|
673
|
-
return contains.then((c) => {
|
|
674
|
-
if (c) {
|
|
675
|
-
return ele;
|
|
676
|
-
}
|
|
677
|
-
return;
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
if (contains) {
|
|
681
|
-
return ele;
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
if (result.find(ele => ele instanceof Promise)) {
|
|
685
|
-
return Promise.all(result).then((r2) => r2.filter(ele => !!ele));
|
|
686
|
-
}
|
|
687
|
-
return result.filter(ele => !!ele);
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* 对于有些特殊的查询(带很多$or的查询,多发生在系统级别),单个actionAuth无法满足,需要共同加以判定
|
|
691
|
-
* @param entity
|
|
692
|
-
* @param filter
|
|
693
|
-
* @param actionAuths
|
|
694
|
-
* @param context
|
|
695
|
-
* @param actions
|
|
696
|
-
*/
|
|
697
|
-
checkActionAuthInGroup(entity, filter, actionAuths, context) {
|
|
698
|
-
const filters = actionAuths.filter(ele => ele.path.destEntity === entity).map((ele) => {
|
|
699
|
-
const { path, relation, relationId } = ele;
|
|
700
|
-
if (relationId) {
|
|
701
|
-
const pathFilter = this.makePathFilter(entity, path, this.schema, {
|
|
702
|
-
userRelation$entity: {
|
|
703
|
-
userId: context.getCurrentUserId(),
|
|
704
|
-
relationId,
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
return pathFilter;
|
|
708
|
-
}
|
|
709
|
-
// 说明是通过userId关联
|
|
710
|
-
return this.makePathFilter(entity, path, this.schema, {
|
|
711
|
-
id: context.getCurrentUserId(),
|
|
712
|
-
});
|
|
713
|
-
});
|
|
714
|
-
const groupFilter = (0, filter_1.combineFilters)(entity, this.schema, filters, true);
|
|
715
|
-
if (groupFilter) {
|
|
716
|
-
return (0, filter_1.checkFilterContains)(entity, context, groupFilter, filter, true);
|
|
717
|
-
}
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
checkSelection(entity, selection, context) {
|
|
721
|
-
const leafSelections = this.destructSelection(entity, selection);
|
|
722
|
-
const deducedLeafSelections = leafSelections.map(({ entity, filter }) => this.getDeducedEntityFilters(entity, filter, ['select'], context));
|
|
723
|
-
const checkDeducedLeafSelections = (dlSelections2) => {
|
|
724
|
-
const dlSelections = dlSelections2.filter((ele) => {
|
|
725
|
-
const entities = ele.map(ele2 => ele2.entity);
|
|
726
|
-
// 同一个leaf的deducedSelections中只要有一个能通过就足够了
|
|
727
|
-
if ((0, lodash_1.intersection)(this.selectFreeEntities, entities).length > 0) {
|
|
728
|
-
return false;
|
|
729
|
-
}
|
|
730
|
-
if ((0, lodash_1.intersection)(RelationAuth.SPECIAL_ENTITIES, entities).length > 0) {
|
|
731
|
-
// todo
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
return true;
|
|
735
|
-
});
|
|
736
|
-
if (dlSelections.length === 0) {
|
|
737
|
-
return true;
|
|
738
|
-
}
|
|
739
|
-
if (!context.getCurrentUserId()) {
|
|
740
|
-
throw new types_1.OakUnloggedInException();
|
|
741
|
-
}
|
|
742
|
-
const allEntities = [];
|
|
743
|
-
dlSelections.forEach((ele) => ele.forEach(({ entity }) => {
|
|
744
|
-
allEntities.push(entity);
|
|
745
|
-
}));
|
|
746
|
-
const actionAuths = context.select('actionAuth', {
|
|
747
|
-
data: {
|
|
748
|
-
id: 1,
|
|
749
|
-
path: {
|
|
750
|
-
id: 1,
|
|
751
|
-
value: 1,
|
|
752
|
-
sourceEntity: 1,
|
|
753
|
-
destEntity: 1,
|
|
754
|
-
recursive: 1,
|
|
755
|
-
},
|
|
756
|
-
deActions: 1,
|
|
757
|
-
relationId: 1,
|
|
758
|
-
},
|
|
759
|
-
filter: {
|
|
760
|
-
deActions: {
|
|
761
|
-
$contains: 'select',
|
|
762
|
-
},
|
|
763
|
-
path: {
|
|
764
|
-
destEntity: {
|
|
765
|
-
$in: allEntities,
|
|
766
|
-
},
|
|
767
|
-
},
|
|
768
|
-
}
|
|
769
|
-
}, { dontCollect: true, ignoreAttrMiss: true });
|
|
770
|
-
/**
|
|
771
|
-
* 返回的结果中,第一层为leafNode,必须全通过,第二层为单个leafNode上的deduce,通过一个就可以
|
|
772
|
-
* @param result
|
|
773
|
-
* @returns
|
|
774
|
-
*/
|
|
775
|
-
const checkResult = (result) => {
|
|
776
|
-
let idx = 0;
|
|
777
|
-
for (const r1 of result) {
|
|
778
|
-
const r2 = r1.find(ele => ele === true);
|
|
779
|
-
if (!r2) {
|
|
780
|
-
if (process.env.NODE_ENV === 'development') {
|
|
781
|
-
console.warn('对象的select权限被否决,请检查', dlSelections[idx]);
|
|
782
|
-
}
|
|
783
|
-
idx++;
|
|
784
|
-
return false;
|
|
785
|
-
}
|
|
786
|
-
idx++;
|
|
787
|
-
}
|
|
788
|
-
return true;
|
|
789
|
-
};
|
|
790
|
-
if (actionAuths instanceof Promise) {
|
|
791
|
-
(0, assert_1.default)(context instanceof AsyncRowStore_1.AsyncContext);
|
|
792
|
-
return actionAuths.then((aas) => Promise.all(dlSelections.map((ele) => Promise.all(ele.map((ele2) => this.checkActionAuthInGroup(ele2.entity, ele2.filter, aas, context))))).then((result) => checkResult(result)));
|
|
793
|
-
}
|
|
794
|
-
return checkResult(dlSelections.map(ele => ele.map(ele2 => this.checkActionAuthInGroup(ele2.entity, ele2.filter, actionAuths, context))));
|
|
795
|
-
};
|
|
796
|
-
if (deducedLeafSelections[0] instanceof Promise) {
|
|
797
|
-
return Promise.all(deducedLeafSelections)
|
|
798
|
-
.then((dls) => checkDeducedLeafSelections(dls));
|
|
799
|
-
}
|
|
800
|
-
return checkDeducedLeafSelections(deducedLeafSelections);
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* 此函数判定一个结点是否能通过权限检测,同时寻找该结点本身对象上成立的actionAuth,用于本结点子孙结点的快速检测
|
|
804
|
-
* 如果结点因其deduce的对象通过了检测,其被推断对象的actionAuth无法用于更低对象的权限检测
|
|
805
|
-
* @param node
|
|
806
|
-
* @param context
|
|
807
|
-
* @returns
|
|
808
|
-
*/
|
|
809
|
-
findActionAuthsOnNode(node, context) {
|
|
810
|
-
const { entity, filter, action, userRelations } = node;
|
|
811
|
-
const deducedEntityFilters2 = this.getDeducedEntityFilters(entity, filter, [action], context);
|
|
812
|
-
/**
|
|
813
|
-
* 搜索判定是否允许自建对象,自建的条件是 path = '',destEntity === entity
|
|
814
|
-
* @param actionAuths
|
|
815
|
-
* @returns
|
|
816
|
-
*/
|
|
817
|
-
const findOwnCreateUserRelation = (actionAuths) => {
|
|
818
|
-
if (userRelations && userRelations.length > 0) {
|
|
819
|
-
const ars = actionAuths.filter((ar) => !!userRelations.find((ur) => ur.relationId === ar.relationId) && ar.path.value === '' && ar.path.destEntity === entity);
|
|
820
|
-
if (ars.length > 0) {
|
|
821
|
-
return ars;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
};
|
|
825
|
-
const actionAuthOnEntities = [];
|
|
826
|
-
const dealWithDeducedEntityFilters = (deducedEntityFilters) => {
|
|
827
|
-
const specialEntities = deducedEntityFilters.filter(ele => RelationAuth.SPECIAL_ENTITIES.includes(ele.entity));
|
|
828
|
-
const unspecicalEntities = deducedEntityFilters.filter(ele => !RelationAuth.SPECIAL_ENTITIES.includes(ele.entity));
|
|
829
|
-
const result = [];
|
|
830
|
-
if (specialEntities.length > 0) {
|
|
831
|
-
// 对于deduce出来的special对象,直接判定create应该问题不大,否则写起来太烦琐(具体情况遇到了再调试)
|
|
832
|
-
result.push(...specialEntities.map(ele => this.checkOperateSpecialEntities2(ele.entity, ele.entity === entity ? node.action : 'create', ele.filter, context)));
|
|
833
|
-
}
|
|
834
|
-
if (unspecicalEntities.length > 0) {
|
|
835
|
-
const allEntities = unspecicalEntities.map(ele => ele.entity);
|
|
836
|
-
const allActions = (0, lodash_1.uniq)(unspecicalEntities.map(ele => ele.actions).flat());
|
|
837
|
-
const actionAuths2 = context.select('actionAuth', {
|
|
838
|
-
data: {
|
|
839
|
-
id: 1,
|
|
840
|
-
path: {
|
|
841
|
-
id: 1,
|
|
842
|
-
destEntity: 1,
|
|
843
|
-
sourceEntity: 1,
|
|
844
|
-
value: 1,
|
|
845
|
-
recursive: 1,
|
|
846
|
-
},
|
|
847
|
-
deActions: 1,
|
|
848
|
-
relation: {
|
|
849
|
-
id: 1,
|
|
850
|
-
userRelation$relation: {
|
|
851
|
-
$entity: 'userRelation',
|
|
852
|
-
data: {
|
|
853
|
-
id: 1,
|
|
854
|
-
entity: 1,
|
|
855
|
-
entityId: 1,
|
|
856
|
-
},
|
|
857
|
-
filter: {
|
|
858
|
-
userId: context.getCurrentUserId(),
|
|
859
|
-
},
|
|
860
|
-
},
|
|
861
|
-
},
|
|
862
|
-
},
|
|
863
|
-
filter: {
|
|
864
|
-
path: {
|
|
865
|
-
destEntity: {
|
|
866
|
-
$in: allEntities,
|
|
867
|
-
}
|
|
868
|
-
},
|
|
869
|
-
deActions: {
|
|
870
|
-
$overlaps: allActions,
|
|
871
|
-
},
|
|
872
|
-
}
|
|
873
|
-
}, { dontCollect: true, ignoreAttrMiss: true });
|
|
874
|
-
const checkActionAuths = (actionAuths) => {
|
|
875
|
-
const created = findOwnCreateUserRelation(actionAuths);
|
|
876
|
-
if (created) {
|
|
877
|
-
actionAuthOnEntities.push(...created);
|
|
878
|
-
return true;
|
|
879
|
-
}
|
|
880
|
-
const result = deducedEntityFilters.map((ele) => {
|
|
881
|
-
const ars2 = actionAuths.filter(ele2 => ele2.path.destEntity === ele.entity && (0, lodash_1.intersection)(ele2.deActions, ele.actions).length > 0 // 这里只要overlap就可以了
|
|
882
|
-
);
|
|
883
|
-
const ars3 = this.filterActionAuths(ele.entity, ele.filter, ars2, context);
|
|
884
|
-
const checkFilteredArs = (actionAuths2) => {
|
|
885
|
-
if (actionAuths2.length > 0) {
|
|
886
|
-
if (ele.entity === entity) {
|
|
887
|
-
actionAuthOnEntities.push(...actionAuths2);
|
|
888
|
-
}
|
|
889
|
-
return true;
|
|
890
|
-
}
|
|
891
|
-
return false;
|
|
892
|
-
};
|
|
893
|
-
if (ars3 instanceof Promise) {
|
|
894
|
-
return ars3.then((ars4) => checkFilteredArs(ars4));
|
|
895
|
-
}
|
|
896
|
-
return checkFilteredArs(ars3);
|
|
897
|
-
});
|
|
898
|
-
if (result.find(ele => ele instanceof Promise)) {
|
|
899
|
-
return Promise.all(result).then((r2) => r2.includes(true));
|
|
900
|
-
}
|
|
901
|
-
return result.includes(true);
|
|
902
|
-
};
|
|
903
|
-
if (actionAuths2 instanceof Promise) {
|
|
904
|
-
result.push(actionAuths2.then((ars2) => checkActionAuths(ars2)));
|
|
905
|
-
}
|
|
906
|
-
else {
|
|
907
|
-
result.push(checkActionAuths(actionAuths2));
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
if (result.find(ele => ele instanceof Promise)) {
|
|
911
|
-
return Promise.all(result).then((r2) => {
|
|
912
|
-
// r2中只有一个通过就能通过
|
|
913
|
-
if (r2.includes(true)) {
|
|
914
|
-
return actionAuthOnEntities;
|
|
915
|
-
}
|
|
916
|
-
return false;
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
if (result.includes(true)) {
|
|
920
|
-
return actionAuthOnEntities;
|
|
921
|
-
}
|
|
922
|
-
return false;
|
|
923
|
-
};
|
|
924
|
-
if (deducedEntityFilters2 instanceof Promise) {
|
|
925
|
-
return deducedEntityFilters2.then((def2) => dealWithDeducedEntityFilters(def2));
|
|
926
|
-
}
|
|
927
|
-
return dealWithDeducedEntityFilters(deducedEntityFilters2);
|
|
928
|
-
}
|
|
929
|
-
checkOperationTree2(tree, context) {
|
|
930
|
-
const checkNode = (node, actionAuths) => {
|
|
931
|
-
const checkChildren = (legalPaths) => {
|
|
932
|
-
const { children } = node;
|
|
933
|
-
const childPath = Object.keys(children);
|
|
934
|
-
if (childPath.length === 0) {
|
|
935
|
-
return true;
|
|
936
|
-
}
|
|
937
|
-
const childResult = childPath.map((childPath) => {
|
|
938
|
-
const child = children[childPath];
|
|
939
|
-
const childEntity = child instanceof Array ? child[0].entity : child.entity;
|
|
940
|
-
// 这里如果该子结点能deduce到父,则直接通过
|
|
941
|
-
if (this.authDeduceRelationMap[childEntity]) {
|
|
942
|
-
(0, assert_1.default)(this.authDeduceRelationMap[childEntity] === 'entity');
|
|
943
|
-
const rel = (0, relation_1.judgeRelation)(this.schema, childEntity, childPath);
|
|
944
|
-
if (rel === 2) {
|
|
945
|
-
return true;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
const pathToParent = childPath.endsWith('$entity') ? node.entity : childPath.split('$')[1];
|
|
949
|
-
if (child instanceof Array) {
|
|
950
|
-
const childActions = child.map(ele => ele.action);
|
|
951
|
-
const childLegalAuths = legalPaths.map((ele) => {
|
|
952
|
-
const { path: { value: pv }, relationId } = ele;
|
|
953
|
-
const pv2 = pv ? `${pathToParent}.${pv}` : pathToParent;
|
|
954
|
-
return context.select('actionAuth', {
|
|
955
|
-
data: {
|
|
956
|
-
id: 1,
|
|
957
|
-
},
|
|
958
|
-
filter: {
|
|
959
|
-
path: {
|
|
960
|
-
value: pv2,
|
|
961
|
-
destEntity: childEntity,
|
|
962
|
-
},
|
|
963
|
-
deActions: {
|
|
964
|
-
$overlaps: childActions,
|
|
965
|
-
},
|
|
966
|
-
relationId: relationId || {
|
|
967
|
-
$exists: false,
|
|
968
|
-
},
|
|
969
|
-
}
|
|
970
|
-
}, { dontCollect: true });
|
|
971
|
-
});
|
|
972
|
-
if (childLegalAuths[0] instanceof Promise) {
|
|
973
|
-
return Promise.all(childLegalAuths).then((clas) => child.map((c) => checkNode(c, clas.flat())));
|
|
974
|
-
}
|
|
975
|
-
return child.map((c) => checkNode(c, childLegalAuths.flat()));
|
|
976
|
-
}
|
|
977
|
-
const childLegalAuths = legalPaths.map((ele) => {
|
|
978
|
-
const { path: { value: pv }, relationId } = ele;
|
|
979
|
-
const pv2 = pv ? `${pathToParent}.${pv}` : pathToParent;
|
|
980
|
-
return context.select('actionAuth', {
|
|
981
|
-
data: {
|
|
982
|
-
id: 1,
|
|
983
|
-
},
|
|
984
|
-
filter: {
|
|
985
|
-
path: {
|
|
986
|
-
value: pv2,
|
|
987
|
-
destEntity: childEntity,
|
|
988
|
-
},
|
|
989
|
-
deActions: {
|
|
990
|
-
$overlaps: child.action,
|
|
991
|
-
},
|
|
992
|
-
relationId: relationId || {
|
|
993
|
-
$exists: false,
|
|
994
|
-
},
|
|
995
|
-
}
|
|
996
|
-
}, { dontCollect: true });
|
|
997
|
-
}).flat();
|
|
998
|
-
if (childLegalAuths[0] instanceof Promise) {
|
|
999
|
-
return Promise.all(childLegalAuths).then((clas) => checkNode(child, clas.flat()));
|
|
1000
|
-
}
|
|
1001
|
-
return checkNode(child, childLegalAuths);
|
|
1002
|
-
}).flat();
|
|
1003
|
-
if (childResult[0] instanceof Promise) {
|
|
1004
|
-
return Promise.all(childResult).then((r) => !r.includes(false));
|
|
1005
|
-
}
|
|
1006
|
-
return !childResult.includes(false);
|
|
1007
|
-
};
|
|
1008
|
-
// 先根据parent传下来的合法auths来搜寻,只需要查找actionAuth,降低开销
|
|
1009
|
-
// 这里有可能父结点根据actionAuths能通过,但子结点需要重新搜索父结点上的新actionAuths才能通过吗?应该不存在这种情况。 by Xc 20230824
|
|
1010
|
-
if (actionAuths && actionAuths.length > 0) {
|
|
1011
|
-
return checkChildren(actionAuths);
|
|
1012
|
-
}
|
|
1013
|
-
// 没有能根据父亲传下来的actionAuth判定,只能自己找
|
|
1014
|
-
const result = this.findActionAuthsOnNode(node, context);
|
|
1015
|
-
const checkResult = (result2) => {
|
|
1016
|
-
if (result2 === false) {
|
|
1017
|
-
return false;
|
|
1018
|
-
}
|
|
1019
|
-
// 如果是对user对象操作通过,需要增添一条虚假的的actionAuth
|
|
1020
|
-
if (node.entity === 'user') {
|
|
1021
|
-
result2.push({
|
|
1022
|
-
id: 'temp',
|
|
1023
|
-
pathId: 'path_temp',
|
|
1024
|
-
path: {
|
|
1025
|
-
id: 'path_temp',
|
|
1026
|
-
destEntity: 'user',
|
|
1027
|
-
sourceEntity: 'any',
|
|
1028
|
-
value: '',
|
|
1029
|
-
$$createAt$$: 1,
|
|
1030
|
-
$$updateAt$$: 1,
|
|
1031
|
-
$$seq$$: 123,
|
|
1032
|
-
recursive: false,
|
|
1033
|
-
},
|
|
1034
|
-
$$createAt$$: 1,
|
|
1035
|
-
$$updateAt$$: 1,
|
|
1036
|
-
$$seq$$: 123,
|
|
1037
|
-
deActions: [node.action],
|
|
1038
|
-
});
|
|
1039
|
-
}
|
|
1040
|
-
return checkChildren(result2);
|
|
1041
|
-
};
|
|
1042
|
-
if (result instanceof Promise) {
|
|
1043
|
-
return result.then((r2) => checkResult(r2));
|
|
1044
|
-
}
|
|
1045
|
-
return checkResult(result);
|
|
1046
|
-
};
|
|
1047
|
-
return checkNode(tree);
|
|
1048
|
-
}
|
|
1049
|
-
checkOperation(entity, operation, context) {
|
|
1050
|
-
const { action, filter, data } = operation;
|
|
1051
|
-
if (this.updateFreeDict[entity] && this.updateFreeDict[entity].includes(action)) {
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
|
-
const userId = context.getCurrentUserId();
|
|
1055
|
-
if (!userId) {
|
|
1056
|
-
throw new types_1.OakUnloggedInException();
|
|
1057
|
-
}
|
|
1058
|
-
if (!filter && (!data || action !== 'create')) {
|
|
1059
|
-
if (process.env.NODE_ENV === 'development') {
|
|
1060
|
-
console.warn('operation不能没有限制条件', operation);
|
|
1061
|
-
}
|
|
1062
|
-
return false;
|
|
1063
|
-
}
|
|
1064
|
-
const updateTree = this.destructOperation(entity, operation, userId);
|
|
1065
|
-
return this.checkOperationTree2(updateTree, context);
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* 检查一个operation是否能被通过权限测试
|
|
1069
|
-
* 一个cascadeOperation是一棵树形结构:
|
|
1070
|
-
* * 对于select,只要叶子通过其父结点必然通过;
|
|
1071
|
-
* * 对于update,自顶向下进行检查,若父亲被权限S通过,则只需要检查子对于S有没有相对路径上的actionAuth
|
|
1072
|
-
* 另外在update中,还需要考虑自建userRelation的case(例如在电子商务网站上购买商品,创建订单同时创建用户和订单的关系)
|
|
1073
|
-
* @param entity
|
|
1074
|
-
* @param operation
|
|
1075
|
-
* @param context
|
|
1076
|
-
* @param actions
|
|
1077
|
-
* @returns
|
|
1078
|
-
*/
|
|
1079
|
-
checkActions2(entity, operation, context, actions) {
|
|
1080
|
-
const { action } = operation;
|
|
1081
|
-
if (!action || action_1.readOnlyActions.includes(action)) {
|
|
1082
|
-
const result = this.checkSelection(entity, operation, context);
|
|
1083
|
-
if (result instanceof Promise) {
|
|
1084
|
-
return result.then((r) => {
|
|
1085
|
-
if (!r) {
|
|
1086
|
-
throw new types_1.OakUserInvisibleException(entity, operation);
|
|
1087
|
-
}
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
if (!result) {
|
|
1091
|
-
throw new types_1.OakUserInvisibleException(entity, operation);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
else {
|
|
1095
|
-
const result = this.checkOperation(entity, operation, context);
|
|
1096
|
-
if (result instanceof Promise) {
|
|
1097
|
-
return result.then((r) => {
|
|
1098
|
-
if (!r) {
|
|
1099
|
-
throw new types_1.OakUserUnpermittedException(entity, operation);
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
if (!result) {
|
|
1104
|
-
throw new types_1.OakUserUnpermittedException(entity, operation);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
exports.RelationAuth = RelationAuth;
|
|
1110
|
-
;
|
|
1111
|
-
/**
|
|
1112
|
-
* 获取有对entity进行actions操作权限的userRelation关系
|
|
1113
|
-
* @param params
|
|
1114
|
-
* @param context
|
|
1115
|
-
* todo paths改成复数以后这里还未充分测试过
|
|
1116
|
-
*/
|
|
1117
|
-
async function getUserRelationsByActions(params, context) {
|
|
1118
|
-
const { entity, filter, actions, overlap } = params;
|
|
1119
|
-
const actionAuthfilter = {
|
|
1120
|
-
path: {
|
|
1121
|
-
destEntity: entity,
|
|
1122
|
-
},
|
|
1123
|
-
};
|
|
1124
|
-
if (overlap) {
|
|
1125
|
-
Object.assign(actionAuthfilter, {
|
|
1126
|
-
deActions: {
|
|
1127
|
-
$overlaps: actions,
|
|
1128
|
-
},
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
else {
|
|
1132
|
-
Object.assign(actionAuthfilter, {
|
|
1133
|
-
deActions: {
|
|
1134
|
-
$contains: actions,
|
|
1135
|
-
},
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
const actionAuths = await context.select('actionAuth', {
|
|
1139
|
-
data: {
|
|
1140
|
-
id: 1,
|
|
1141
|
-
path: {
|
|
1142
|
-
id: 1,
|
|
1143
|
-
value: 1,
|
|
1144
|
-
destEntity: 1,
|
|
1145
|
-
recursive: 1,
|
|
1146
|
-
},
|
|
1147
|
-
relationId: 1,
|
|
1148
|
-
relation: {
|
|
1149
|
-
id: 1,
|
|
1150
|
-
entity: 1,
|
|
1151
|
-
},
|
|
1152
|
-
},
|
|
1153
|
-
filter: actionAuthfilter,
|
|
1154
|
-
}, { dontCollect: true });
|
|
1155
|
-
const getUserRelations = async (urAuths) => {
|
|
1156
|
-
// 相同的path可以groupBy掉
|
|
1157
|
-
const urAuthDict2 = {};
|
|
1158
|
-
urAuths.forEach((auth) => {
|
|
1159
|
-
const { path, relationId } = auth;
|
|
1160
|
-
const { value, recursive } = path;
|
|
1161
|
-
if (!urAuthDict2[value]) {
|
|
1162
|
-
urAuthDict2[value] = [[relationId], recursive];
|
|
1163
|
-
}
|
|
1164
|
-
else if (!urAuthDict2[value][0].includes(relationId)) {
|
|
1165
|
-
(0, assert_1.default)(urAuthDict2[value][1] === recursive);
|
|
1166
|
-
urAuthDict2[value][0].push(relationId);
|
|
1167
|
-
}
|
|
1168
|
-
});
|
|
1169
|
-
const userRelations = await Promise.all(Object.keys(urAuthDict2).map(async (path) => {
|
|
1170
|
-
const [relationIds, recursive] = urAuthDict2[path];
|
|
1171
|
-
const { projection, getData } = (0, relationPath_1.destructRelationPath)(context.getSchema(), entity, path, {
|
|
1172
|
-
relationId: {
|
|
1173
|
-
$in: relationIds,
|
|
1174
|
-
},
|
|
1175
|
-
}, recursive);
|
|
1176
|
-
const rows = await context.select(entity, {
|
|
1177
|
-
data: projection,
|
|
1178
|
-
filter,
|
|
1179
|
-
}, { dontCollect: true });
|
|
1180
|
-
const urs = rows.map(ele => getData(ele)).flat().filter(ele => !!ele);
|
|
1181
|
-
return urs;
|
|
1182
|
-
}));
|
|
1183
|
-
return userRelations.flat();
|
|
1184
|
-
};
|
|
1185
|
-
const getDirectUserEntities = async (directAuths) => {
|
|
1186
|
-
const userEntities = await Promise.all(directAuths.map(async ({ path }) => {
|
|
1187
|
-
const { value, recursive } = path;
|
|
1188
|
-
(0, assert_1.default)(!recursive);
|
|
1189
|
-
const { getData, projection } = (0, relationPath_1.destructDirectPath)(context.getSchema(), entity, value, recursive);
|
|
1190
|
-
const rows = await context.select(entity, {
|
|
1191
|
-
data: projection,
|
|
1192
|
-
filter,
|
|
1193
|
-
}, { dontCollect: true });
|
|
1194
|
-
const userEntities = rows.map(ele => getData(ele)).flat().filter(ele => !!ele);
|
|
1195
|
-
return userEntities;
|
|
1196
|
-
}));
|
|
1197
|
-
return userEntities.flat();
|
|
1198
|
-
};
|
|
1199
|
-
const urAuths2 = actionAuths.filter(ele => !!ele.relationId // 有relation说明通过userRelation关联
|
|
1200
|
-
);
|
|
1201
|
-
const directAuths2 = actionAuths.filter(ele => !ele.relationId // 没relation说明通过user关联
|
|
1202
|
-
);
|
|
1203
|
-
const [userRelations, userEntities] = await Promise.all([getUserRelations(urAuths2), getDirectUserEntities(directAuths2)]);
|
|
1204
|
-
return {
|
|
1205
|
-
userRelations,
|
|
1206
|
-
userEntities,
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
exports.getUserRelationsByActions = getUserRelationsByActions;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUserRelationsByActions = exports.RelationAuth = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
6
|
+
const types_1 = require("../types");
|
|
7
|
+
const AsyncRowStore_1 = require("./AsyncRowStore");
|
|
8
|
+
const filter_1 = require("./filter");
|
|
9
|
+
const relation_1 = require("./relation");
|
|
10
|
+
const action_1 = require("../actions/action");
|
|
11
|
+
const lodash_1 = require("../utils/lodash");
|
|
12
|
+
const entities_1 = require("../compiler/entities");
|
|
13
|
+
const relationPath_1 = require("../utils/relationPath");
|
|
14
|
+
class RelationAuth {
|
|
15
|
+
authDeduceRelationMap;
|
|
16
|
+
schema;
|
|
17
|
+
static SPECIAL_ENTITIES = entities_1.SYSTEM_RESERVE_ENTITIES;
|
|
18
|
+
selectFreeEntities;
|
|
19
|
+
updateFreeDict;
|
|
20
|
+
constructor(schema, authDeduceRelationMap, selectFreeEntities, updateFreeDict) {
|
|
21
|
+
this.schema = schema;
|
|
22
|
+
this.selectFreeEntities = selectFreeEntities || [];
|
|
23
|
+
this.updateFreeDict = updateFreeDict || {};
|
|
24
|
+
this.authDeduceRelationMap = Object.assign({}, authDeduceRelationMap, {
|
|
25
|
+
modi: 'entity',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// 前台检查filter是否满足relation约束
|
|
29
|
+
checkRelationSync(entity, operation, context) {
|
|
30
|
+
if (context.isRoot()) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.checkActions2(entity, operation, context);
|
|
34
|
+
}
|
|
35
|
+
// 后台检查filter是否满足relation约束
|
|
36
|
+
async checkRelationAsync(entity, operation, context) {
|
|
37
|
+
if (context.isRoot()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await this.checkActions2(entity, operation, context);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 检查当前用户有无权限对filter约束的userRelation进行action操作
|
|
44
|
+
* @param context
|
|
45
|
+
* @param action
|
|
46
|
+
* @param filter
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
checkUserRelation(context, action, filter) {
|
|
50
|
+
const userId = context.getCurrentUserId();
|
|
51
|
+
/**
|
|
52
|
+
* 检查对某一个relationId是否有操作资格
|
|
53
|
+
* @param destRelationId
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
const checkOnRelationId = (entity, destRelationId, filter) => {
|
|
57
|
+
/**
|
|
58
|
+
* 找到能创建此relation的所有父级relation,只要user和其中一个有关联即可以通过
|
|
59
|
+
*/
|
|
60
|
+
const relationAuths = context.select('relationAuth', {
|
|
61
|
+
data: {
|
|
62
|
+
id: 1,
|
|
63
|
+
path: {
|
|
64
|
+
id: 1,
|
|
65
|
+
sourceEntity: 1,
|
|
66
|
+
destEntity: 1,
|
|
67
|
+
value: 1,
|
|
68
|
+
recursive: 1,
|
|
69
|
+
},
|
|
70
|
+
sourceRelationId: 1,
|
|
71
|
+
sourceRelation: {
|
|
72
|
+
id: 1,
|
|
73
|
+
entity: 1,
|
|
74
|
+
entityId: 1,
|
|
75
|
+
},
|
|
76
|
+
destRelationId: 1,
|
|
77
|
+
destRelation: {
|
|
78
|
+
id: 1,
|
|
79
|
+
entity: 1,
|
|
80
|
+
entityId: 1,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
filter: {
|
|
84
|
+
destRelationId,
|
|
85
|
+
},
|
|
86
|
+
}, { dontCollect: true });
|
|
87
|
+
const checkRelationAuth = (relationAuth) => {
|
|
88
|
+
const { destRelation, sourceRelationId, path } = relationAuth;
|
|
89
|
+
(0, assert_1.default)(entity === destRelation.entity);
|
|
90
|
+
let destEntityFilter = this.makePathFilter(entity, path, this.schema, {
|
|
91
|
+
userRelation$entity: {
|
|
92
|
+
userId,
|
|
93
|
+
relationId: sourceRelationId,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
if (filter) {
|
|
97
|
+
destEntityFilter = (0, filter_1.combineFilters)(entity, this.schema, [destEntityFilter, filter]);
|
|
98
|
+
}
|
|
99
|
+
return context.count(destRelation.entity, {
|
|
100
|
+
filter: destEntityFilter,
|
|
101
|
+
}, { ignoreAttrMiss: true });
|
|
102
|
+
};
|
|
103
|
+
if (relationAuths instanceof Promise) {
|
|
104
|
+
return relationAuths.then((ras) => Promise.all(ras.map(ra => checkRelationAuth(ra)))).then((result) => !!result.find(ele => {
|
|
105
|
+
(0, assert_1.default)(typeof ele === 'number');
|
|
106
|
+
return ele > 0;
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
const result = relationAuths.map(ra => checkRelationAuth(ra));
|
|
110
|
+
return !!result.find(ele => ele > 0);
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* 检查对超过一个的relationId是否有操作资格
|
|
114
|
+
* @param relationFilter 限定relationId的条件
|
|
115
|
+
* @param intersection 是否交集(对每个relationId都得有权限)
|
|
116
|
+
* @param entityFilter 限定entity的条件
|
|
117
|
+
* @param entity 对应的entity
|
|
118
|
+
* @attention 这里为了代码复用,有可能是要通过本函数内部来查询确定entity;所以要保证,如果传入的relationFilter就可以确定relationId,则这里的entity参数必传。
|
|
119
|
+
* @returns
|
|
120
|
+
*/
|
|
121
|
+
const checkOnMultipleRelations = (relationFilter, intersection, entityFilter, entity) => {
|
|
122
|
+
let entity2 = entity;
|
|
123
|
+
const getRelationIds = () => {
|
|
124
|
+
const relevantIds = (0, filter_1.getRelevantIds)(relationFilter);
|
|
125
|
+
if (relevantIds.length > 0) {
|
|
126
|
+
return relevantIds;
|
|
127
|
+
}
|
|
128
|
+
const relations = context.select('relation', {
|
|
129
|
+
data: {
|
|
130
|
+
id: 1,
|
|
131
|
+
entity: 1,
|
|
132
|
+
entityId: 1,
|
|
133
|
+
},
|
|
134
|
+
filter: relationFilter
|
|
135
|
+
}, { dontCollect: true });
|
|
136
|
+
if (relations instanceof Promise) {
|
|
137
|
+
return relations.then((rs) => {
|
|
138
|
+
if (!entity2) {
|
|
139
|
+
entity2 = rs[0]?.entity;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
(0, assert_1.default)(entity2 === rs[0]?.entity);
|
|
143
|
+
}
|
|
144
|
+
return rs.map(ele => ele.id);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (!entity2) {
|
|
148
|
+
entity2 = relations[0]?.entity;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
(0, assert_1.default)(entity2 === relations[0]?.entity);
|
|
152
|
+
}
|
|
153
|
+
return relations.map(ele => ele.id);
|
|
154
|
+
};
|
|
155
|
+
const relationIds = getRelationIds();
|
|
156
|
+
if (relationIds instanceof Promise) {
|
|
157
|
+
return relationIds.then((ids) => {
|
|
158
|
+
return Promise.all(ids.map(ele => checkOnRelationId(entity2, ele, entityFilter))).then((value) => {
|
|
159
|
+
if (intersection) {
|
|
160
|
+
return !(value.includes(false));
|
|
161
|
+
}
|
|
162
|
+
return value.includes(true);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const value = relationIds.map(ele => checkOnRelationId(entity2, ele, entityFilter));
|
|
167
|
+
if (intersection) {
|
|
168
|
+
return !(value.includes(false));
|
|
169
|
+
}
|
|
170
|
+
return value.includes(true);
|
|
171
|
+
};
|
|
172
|
+
if (action === 'create') {
|
|
173
|
+
const { entity, entityId, relationId } = filter;
|
|
174
|
+
(0, assert_1.default)(typeof entity === 'string');
|
|
175
|
+
let entityFilter;
|
|
176
|
+
if (entityId) {
|
|
177
|
+
entityFilter = {
|
|
178
|
+
id: entityId,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// userEntityGrant会有这种情况,限定某个对象的范围进行授权
|
|
183
|
+
entityFilter = filter[entity];
|
|
184
|
+
}
|
|
185
|
+
if (relationId) {
|
|
186
|
+
// 如果指定relation,则测试该relation上是否可行
|
|
187
|
+
// 目前可能会有多个relationIds传入(userEntityGrant做测试),但一定是可以确定的relationId集合
|
|
188
|
+
return checkOnMultipleRelations({ id: relationId }, true, entityFilter, entity);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// 否则为测试“能否”有权限管理的资格,此时只要有一个就可以
|
|
192
|
+
// 这是为上层的menu所有,真正的创建不可能走到这里
|
|
193
|
+
// bug fixed,目前框架不支持entityId为null,所以这里暂时只支持entityId一种方式的测试
|
|
194
|
+
(0, assert_1.default)(entityId);
|
|
195
|
+
return checkOnMultipleRelations({
|
|
196
|
+
entity,
|
|
197
|
+
$or: [
|
|
198
|
+
{
|
|
199
|
+
entityId: {
|
|
200
|
+
$exists: false,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
entityId,
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}, false, entityFilter, entity);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
(0, assert_1.default)(action === 'remove');
|
|
212
|
+
// 有可能是删除多个userRelation,这时必须检查每一个relation都有对应的权限(有一个不能删除那就不能删除)
|
|
213
|
+
return checkOnMultipleRelations({
|
|
214
|
+
userRelation$relation: filter,
|
|
215
|
+
}, false, {
|
|
216
|
+
userRelation$entity: filter,
|
|
217
|
+
}, filter.entity);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
checkOperateSpecialEntities2(entity2, action, filter, context) {
|
|
221
|
+
switch (entity2) {
|
|
222
|
+
case 'userRelation': {
|
|
223
|
+
(0, assert_1.default)(!(filter instanceof Array));
|
|
224
|
+
return this.checkUserRelation(context, action, filter);
|
|
225
|
+
}
|
|
226
|
+
case 'user': {
|
|
227
|
+
// 对用户的操作由应用自己去管理权限,这里只检查grant/revoke
|
|
228
|
+
if (['grant', 'revoke'].includes(action)) {
|
|
229
|
+
// assert(filter && Object.keys(filter).length === 1, 'grant/revoke只能操作userRelation$user');
|
|
230
|
+
// assert(filter!.hasOwnProperty('userRelation$user'), 'grant/revoke只能操作userRelation$user');
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// 应用允许用户操作其它用户的逻辑请通过编写类型为relation的checker去控制,在这里不能加以限制
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
case 'modi': {
|
|
239
|
+
/** 正常情况下对modi的生成是在触发器下openRootMode,不会走到这里
|
|
240
|
+
* 但是有些例外,如extraFile如果在modi中创建,上传成功之后需要显式生成一条modi,这时对modi的
|
|
241
|
+
* 检查可以转化为对其父entity的update权限检查
|
|
242
|
+
*/
|
|
243
|
+
(0, assert_1.default)(action === 'create');
|
|
244
|
+
const { entity, entityId } = filter;
|
|
245
|
+
return this.checkOperation(entity, {
|
|
246
|
+
action: 'update',
|
|
247
|
+
data: {},
|
|
248
|
+
filter: {
|
|
249
|
+
id: entityId,
|
|
250
|
+
},
|
|
251
|
+
}, context);
|
|
252
|
+
}
|
|
253
|
+
case 'relation': {
|
|
254
|
+
// 创建relation目前不支持,以后再说
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
case 'userEntityGrant': {
|
|
258
|
+
// userEntityGrant的创建相当于授权,领取相当于赋权
|
|
259
|
+
if (['create', 'update', 'remove'].includes(action)) {
|
|
260
|
+
if (action === 'create') {
|
|
261
|
+
const { relationEntity, relationEntityFilter, relationIds } = filter;
|
|
262
|
+
return this.checkOperateSpecialEntities2('userRelation', 'create', {
|
|
263
|
+
entity: relationEntity,
|
|
264
|
+
[relationEntity]: relationEntityFilter,
|
|
265
|
+
relationId: {
|
|
266
|
+
$in: relationIds,
|
|
267
|
+
},
|
|
268
|
+
}, context);
|
|
269
|
+
}
|
|
270
|
+
return this.checkOperateSpecialEntities2('userRelation', 'action', {
|
|
271
|
+
relation: {
|
|
272
|
+
userEntityGrant$relation: filter,
|
|
273
|
+
},
|
|
274
|
+
}, context);
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
default: {
|
|
279
|
+
(0, assert_1.default)(false, `对象${entity2}的权限控制没有加以控制`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
getDeducedEntityFilters(entity, filter, actions, context) {
|
|
284
|
+
const entityFilters = [
|
|
285
|
+
{
|
|
286
|
+
entity,
|
|
287
|
+
filter,
|
|
288
|
+
actions,
|
|
289
|
+
}
|
|
290
|
+
];
|
|
291
|
+
if (this.authDeduceRelationMap[entity]) {
|
|
292
|
+
(0, assert_1.default)(this.authDeduceRelationMap[entity] === 'entity');
|
|
293
|
+
let { entity: deduceEntity, entityId: deduceEntityId } = filter;
|
|
294
|
+
let deduceFilter = {};
|
|
295
|
+
if (deduceEntity && deduceEntityId) {
|
|
296
|
+
deduceFilter = { id: deduceEntityId };
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// 也可能是用cascade方式进行查找,这里有时候filter上会带有两个不同的entity目标,尚未处理(todo!)
|
|
300
|
+
const { ref } = this.schema[entity].attributes.entity;
|
|
301
|
+
(0, assert_1.default)(ref instanceof Array);
|
|
302
|
+
for (const refEntity of ref) {
|
|
303
|
+
if (filter[refEntity]) {
|
|
304
|
+
deduceEntity = refEntity;
|
|
305
|
+
deduceFilter = filter[refEntity];
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const getRecursiveDeducedFilters = (deduceEntity, deduceFilter) => {
|
|
311
|
+
const excludeActions = action_1.readOnlyActions.concat([ /* 'create', 'remove' */]);
|
|
312
|
+
const updateActions = this.schema[deduceEntity].actions.filter((a) => !excludeActions.includes(a));
|
|
313
|
+
/* if (!RelationAuth.SPECIAL_ENTITIES.includes(deduceEntity as string)) {
|
|
314
|
+
return this.getDeducedEntityFilters(deduceEntity, deduceFilter, actions[0] === 'select' ? actions : updateActions, context);
|
|
315
|
+
}
|
|
316
|
+
return []; */
|
|
317
|
+
return this.getDeducedEntityFilters(deduceEntity, deduceFilter, actions[0] === 'select' ? actions : updateActions, context);
|
|
318
|
+
};
|
|
319
|
+
if (deduceEntity && deduceFilter) {
|
|
320
|
+
const deducedSelections = getRecursiveDeducedFilters(deduceEntity, deduceFilter);
|
|
321
|
+
if (deducedSelections instanceof Promise) {
|
|
322
|
+
return deducedSelections.then((ds) => {
|
|
323
|
+
entityFilters.push(...ds);
|
|
324
|
+
return entityFilters;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
entityFilters.push(...deducedSelections);
|
|
328
|
+
return entityFilters;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
/**
|
|
332
|
+
* 这种情况说明从filter中无法确定相应的deduceFilter,需要查找该实体对应的entity/entityId来进行推导。
|
|
333
|
+
* 这种情况一般发生在entity1 -> entity2上,此时entity2应该是一个固定id查询的filter
|
|
334
|
+
* 在这里先假设如果碰到了list类型的filter,直接不使用deduce路径上的对象来推导
|
|
335
|
+
*/
|
|
336
|
+
const rows2 = context.select(entity, {
|
|
337
|
+
data: {
|
|
338
|
+
id: 1,
|
|
339
|
+
entity: 1,
|
|
340
|
+
entityId: 1,
|
|
341
|
+
},
|
|
342
|
+
filter,
|
|
343
|
+
indexFrom: 0,
|
|
344
|
+
count: 10,
|
|
345
|
+
}, { dontCollect: true, blockTrigger: true });
|
|
346
|
+
const dealWithData = (rows) => {
|
|
347
|
+
// 这里如果entity指向不同的实体,一般出现这样的查询,则其权限应当不由这条deduce路径处理
|
|
348
|
+
// 同上,如果找到的行数大于1行,说明deduce路径上的对象不确定,也暂不处理 by Xc 20230725
|
|
349
|
+
if (rows.length > 1 || rows.length === 0) {
|
|
350
|
+
if (process.env.NODE_ENV === 'development') {
|
|
351
|
+
console.warn(`进行deduce推导时找到了${rows.length}行${entity}数据`);
|
|
352
|
+
}
|
|
353
|
+
return entityFilters;
|
|
354
|
+
}
|
|
355
|
+
const { entity: deducedEntity, entityId: deducedEntityId } = rows[0];
|
|
356
|
+
if (!deducedEntity || !deducedEntityId) {
|
|
357
|
+
// 这种情况会出现在前台缓存里
|
|
358
|
+
return entityFilters;
|
|
359
|
+
}
|
|
360
|
+
const result = getRecursiveDeducedFilters(deducedEntity, {
|
|
361
|
+
id: deducedEntityId,
|
|
362
|
+
});
|
|
363
|
+
if (result instanceof Promise) {
|
|
364
|
+
return result.then((r2) => {
|
|
365
|
+
entityFilters.push(...r2);
|
|
366
|
+
return entityFilters;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
entityFilters.push(...result);
|
|
370
|
+
return entityFilters;
|
|
371
|
+
};
|
|
372
|
+
if (rows2 instanceof Promise) {
|
|
373
|
+
return rows2.then((r2) => dealWithData(r2));
|
|
374
|
+
}
|
|
375
|
+
return dealWithData(rows2);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return entityFilters;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 对于selection,解构出最底层的对象,如果最底层的对象可以被访问,则父对象一定可以
|
|
382
|
+
* 但对于deduce的子对象,不必再向底层查看(假设deduce对象一般都位于树的最底层附近)
|
|
383
|
+
* @param entity
|
|
384
|
+
* @param operation
|
|
385
|
+
*/
|
|
386
|
+
destructSelection(entity, selection) {
|
|
387
|
+
const leafSelections = [];
|
|
388
|
+
const destructInner = (entity2, selection2) => {
|
|
389
|
+
const { data, filter } = selection2;
|
|
390
|
+
let hasOneToMany = false;
|
|
391
|
+
for (const attr in data) {
|
|
392
|
+
const rel = (0, relation_1.judgeRelation)(this.schema, entity2, attr);
|
|
393
|
+
if (rel instanceof Array) {
|
|
394
|
+
const [e, foreignKey] = rel;
|
|
395
|
+
if (foreignKey) {
|
|
396
|
+
(0, assert_1.default)(!this.authDeduceRelationMap[e]);
|
|
397
|
+
hasOneToMany = true;
|
|
398
|
+
destructInner(e, {
|
|
399
|
+
data: data[attr].data,
|
|
400
|
+
filter: (0, filter_1.combineFilters)(e, this.schema, [{
|
|
401
|
+
[foreignKey.slice(0, foreignKey.length - 2)]: filter,
|
|
402
|
+
}, data[attr].filter || {}]),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
if (!this.authDeduceRelationMap[e]) {
|
|
407
|
+
hasOneToMany = true;
|
|
408
|
+
destructInner(e, {
|
|
409
|
+
data: data[attr].data,
|
|
410
|
+
filter: (0, filter_1.combineFilters)(e, this.schema, [{
|
|
411
|
+
[entity2]: filter,
|
|
412
|
+
}, data[attr].filter || {}]),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
(0, assert_1.default)(this.authDeduceRelationMap[e] === 'entity');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!hasOneToMany) {
|
|
422
|
+
leafSelections.push({
|
|
423
|
+
entity: entity2,
|
|
424
|
+
filter,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
destructInner(entity, (0, lodash_1.cloneDeep)(selection));
|
|
429
|
+
return leafSelections;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 对于operation,解构出一个树形结构,以方便自顶向下的进行访问
|
|
433
|
+
* 但对于deduce的子对象,不必再向底层查看
|
|
434
|
+
* @param entity
|
|
435
|
+
* @param selection
|
|
436
|
+
*/
|
|
437
|
+
destructOperation(entity2, operation2, userId) {
|
|
438
|
+
/**
|
|
439
|
+
* 对create动作,把data中的cascade部分剔除后作为filter参与后续的检验
|
|
440
|
+
* @param operation
|
|
441
|
+
* @returns
|
|
442
|
+
*/
|
|
443
|
+
const makeCreateFilter = (entity, operation) => {
|
|
444
|
+
const { data, filter } = operation;
|
|
445
|
+
(0, assert_1.default)(!(data instanceof Array));
|
|
446
|
+
if (data) {
|
|
447
|
+
const data2 = {};
|
|
448
|
+
for (const attr in data) {
|
|
449
|
+
const rel = (0, relation_1.judgeRelation)(this.schema, entity, attr);
|
|
450
|
+
if (rel === 1) {
|
|
451
|
+
// 只需要记住id和各种外键属性,不这样处理有些古怪的属性比如coordinate,其作为createdata和作为filter并不同构
|
|
452
|
+
/* if ((['id', 'entity', 'entityId'].includes(attr) || this.schema[entity].attributes[attr as any]?.type === 'ref') && typeof data[attr] === 'string') {
|
|
453
|
+
data2[attr] = data[attr];
|
|
454
|
+
} */
|
|
455
|
+
// 假设不再成立,userEntityGrant需要relationEntity这样的属性
|
|
456
|
+
if (!['geometry', 'geography', 'st_geometry', 'st_point'].includes(this.schema[entity].attributes[attr]?.type)) {
|
|
457
|
+
data2[attr] = data[attr];
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return data2;
|
|
462
|
+
}
|
|
463
|
+
return filter;
|
|
464
|
+
};
|
|
465
|
+
const addChild = (node, path, child) => {
|
|
466
|
+
// 在这里要把可以被node deduce出来的child处理掉
|
|
467
|
+
const paths = path.split('$');
|
|
468
|
+
(0, assert_1.default)(paths.length >= 2);
|
|
469
|
+
if (this.authDeduceRelationMap[child.entity] === paths[1]) {
|
|
470
|
+
(0, assert_1.default)(paths[1] === 'entity', '当前只支持entity外键上的deduce');
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
if (node.children[path]) {
|
|
474
|
+
if (node.children[path] instanceof Array) {
|
|
475
|
+
node.children[path].push(child);
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
node.children[path] = [node.children[path], child];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
Object.assign(node.children, {
|
|
483
|
+
[path]: child,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
return true;
|
|
487
|
+
};
|
|
488
|
+
const destructInner = (entity, operation,
|
|
489
|
+
// extraFilter?: ED[T2]['Selection']['filter'],
|
|
490
|
+
path, child, hasParent, extraFilter) => {
|
|
491
|
+
const { action, data, filter } = operation;
|
|
492
|
+
const filter2 = action === 'create' ? makeCreateFilter(entity, operation) : (0, lodash_1.cloneDeep)(filter);
|
|
493
|
+
(0, assert_1.default)(filter2);
|
|
494
|
+
if (extraFilter) {
|
|
495
|
+
Object.assign(filter2, extraFilter);
|
|
496
|
+
}
|
|
497
|
+
// const filter3 = extraFilter ? combineFilters(entity, schema, [filter2, extraFilter]) : filter2;
|
|
498
|
+
const me = {
|
|
499
|
+
entity: entity,
|
|
500
|
+
filter: filter2,
|
|
501
|
+
children: {},
|
|
502
|
+
action,
|
|
503
|
+
};
|
|
504
|
+
let root = me;
|
|
505
|
+
// 如果当前对象是一个toModi的,意味着它的cascadeUpdate会全部被变为modi去缓存,因此不需要再向下检查了
|
|
506
|
+
// modi被apply时,这些modi产生的更新才会被实际检查
|
|
507
|
+
// 这里可能有问题,再思考思考 by Xc 20231111
|
|
508
|
+
const isModiUpdate = this.schema[entity].toModi && action !== 'remove';
|
|
509
|
+
if (child) {
|
|
510
|
+
(0, assert_1.default)(path);
|
|
511
|
+
addChild(me, path, child);
|
|
512
|
+
}
|
|
513
|
+
(0, assert_1.default)(!(data instanceof Array));
|
|
514
|
+
for (const attr in data) {
|
|
515
|
+
const rel = (0, relation_1.judgeRelation)(this.schema, entity, attr);
|
|
516
|
+
if (rel === 2 && !isModiUpdate) {
|
|
517
|
+
(0, assert_1.default)(root === me && !hasParent, 'cascadeUpdate必须是树结构,避免森林');
|
|
518
|
+
const mtoOperation = data[attr];
|
|
519
|
+
root = destructInner(attr, mtoOperation, `${entity}$entity`, me);
|
|
520
|
+
}
|
|
521
|
+
else if (typeof rel === 'string' && !isModiUpdate) {
|
|
522
|
+
(0, assert_1.default)(root === me && !hasParent, 'cascadeUpdate必须是树结构,避免森林');
|
|
523
|
+
root = destructInner(rel, data[attr], `${entity}$${attr}`, me);
|
|
524
|
+
}
|
|
525
|
+
else if (rel instanceof Array && !isModiUpdate) {
|
|
526
|
+
const [e, f] = rel;
|
|
527
|
+
const otmOperations = data[attr];
|
|
528
|
+
/**
|
|
529
|
+
* 这里目前在cascadeUpdate的过程中,只有当一对多个userRelation的操作需要将entity和entityId复制到子对象上
|
|
530
|
+
* 因为对userRelation的判断是走的特殊路径,无法利用父对象的actionAuth
|
|
531
|
+
* 其它对象情况不需要复制,因为应用中必须要能保证(前台传来的)父对象的filter不依赖于子对象的条件
|
|
532
|
+
*/
|
|
533
|
+
let extraFilter = undefined;
|
|
534
|
+
if (e === 'userRelation' && entity !== 'user') {
|
|
535
|
+
me.userRelations = [];
|
|
536
|
+
extraFilter = {
|
|
537
|
+
entity,
|
|
538
|
+
entityId: filter2.id,
|
|
539
|
+
};
|
|
540
|
+
const dealWithUserRelation = (userRelation) => {
|
|
541
|
+
const { action, data } = userRelation;
|
|
542
|
+
if (action === 'create') {
|
|
543
|
+
const attrs = Object.keys(data);
|
|
544
|
+
(0, assert_1.default)((0, lodash_1.difference)(attrs, Object.keys(this.schema.userRelation.attributes).concat('id')).length === 0);
|
|
545
|
+
if (data.userId === userId) {
|
|
546
|
+
me.userRelations?.push(data);
|
|
547
|
+
}
|
|
548
|
+
(0, assert_1.default)(filter2.id);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
if (otmOperations instanceof Array) {
|
|
552
|
+
otmOperations.forEach((otmOperation) => dealWithUserRelation(otmOperation));
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
dealWithUserRelation(otmOperations);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (otmOperations instanceof Array) {
|
|
559
|
+
otmOperations.forEach((otmOperation) => {
|
|
560
|
+
const son = destructInner(e, otmOperation, undefined, undefined, true, extraFilter);
|
|
561
|
+
addChild(me, attr, son);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const son = destructInner(e, otmOperations, undefined, undefined, true, extraFilter);
|
|
566
|
+
addChild(me, attr, son);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return root;
|
|
571
|
+
};
|
|
572
|
+
return destructInner(entity2, operation2);
|
|
573
|
+
}
|
|
574
|
+
makePathFilter(entity, path, schema, filter) {
|
|
575
|
+
const { value, recursive } = path;
|
|
576
|
+
if (value === '') {
|
|
577
|
+
(0, assert_1.default)(!recursive);
|
|
578
|
+
return filter;
|
|
579
|
+
}
|
|
580
|
+
const paths = value.split('.');
|
|
581
|
+
const makeRecursiveFilter = (recursiveDepth) => {
|
|
582
|
+
if (recursiveDepth > 0) {
|
|
583
|
+
return {
|
|
584
|
+
$or: [
|
|
585
|
+
filter,
|
|
586
|
+
{
|
|
587
|
+
parent: makeRecursiveFilter(recursiveDepth - 1)
|
|
588
|
+
}
|
|
589
|
+
]
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
return filter;
|
|
593
|
+
};
|
|
594
|
+
const makeInner = (idx, e2) => {
|
|
595
|
+
const attr = paths[idx];
|
|
596
|
+
if (idx === paths.length) {
|
|
597
|
+
if (!recursive) {
|
|
598
|
+
return filter;
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
// 在最后一个对象上存在递归,用or连接处理
|
|
602
|
+
const { recursiveDepth } = schema[e2];
|
|
603
|
+
(0, assert_1.default)(recursiveDepth > 0);
|
|
604
|
+
return makeRecursiveFilter(recursiveDepth);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
const rel = (0, relation_1.judgeRelation)(schema, e2, attr);
|
|
609
|
+
let e3;
|
|
610
|
+
if (rel === 2) {
|
|
611
|
+
e3 = attr;
|
|
612
|
+
}
|
|
613
|
+
else if (typeof rel === 'string') {
|
|
614
|
+
e3 = rel;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
(0, assert_1.default)(rel instanceof Array);
|
|
618
|
+
e3 = rel[0];
|
|
619
|
+
}
|
|
620
|
+
const f = makeInner(idx + 1, e3);
|
|
621
|
+
return {
|
|
622
|
+
[attr]: f,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
return makeInner(0, entity);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* 对所有满足操作要求的actionAuth加以判断,找到可以满足当前用户身份的actionAuth
|
|
630
|
+
* @param entity
|
|
631
|
+
* @param filter
|
|
632
|
+
* @param actionAuths
|
|
633
|
+
* @param context
|
|
634
|
+
* @return
|
|
635
|
+
*/
|
|
636
|
+
filterActionAuths(entity, filter, actionAuths, context) {
|
|
637
|
+
const result = actionAuths.map((ele) => {
|
|
638
|
+
const { path, relation, relationId } = ele;
|
|
639
|
+
// 在cache中,可能出现relation外键指向的对象为null的情况,要容错
|
|
640
|
+
if (relationId) {
|
|
641
|
+
if (relation) {
|
|
642
|
+
const { userRelation$relation: userRelations } = relation;
|
|
643
|
+
if (userRelations.length > 0) {
|
|
644
|
+
const entityIds = (0, lodash_1.uniq)(userRelations.map(ele => ele.entityId));
|
|
645
|
+
const pathFilter = this.makePathFilter(entity, path, this.schema, {
|
|
646
|
+
id: entityIds.length > 0 ? {
|
|
647
|
+
$in: entityIds,
|
|
648
|
+
} : entityIds[0],
|
|
649
|
+
});
|
|
650
|
+
const contains = (0, filter_1.checkFilterContains)(entity, context, pathFilter, filter, true);
|
|
651
|
+
if (contains instanceof Promise) {
|
|
652
|
+
return contains.then((c) => {
|
|
653
|
+
if (c) {
|
|
654
|
+
return ele;
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
if (contains) {
|
|
660
|
+
return ele;
|
|
661
|
+
}
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
// 说明是通过userId关联
|
|
668
|
+
const pathFilter = this.makePathFilter(entity, path, this.schema, {
|
|
669
|
+
id: context.getCurrentUserId(),
|
|
670
|
+
});
|
|
671
|
+
const contains = (0, filter_1.checkFilterContains)(entity, context, pathFilter, filter, true);
|
|
672
|
+
if (contains instanceof Promise) {
|
|
673
|
+
return contains.then((c) => {
|
|
674
|
+
if (c) {
|
|
675
|
+
return ele;
|
|
676
|
+
}
|
|
677
|
+
return;
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (contains) {
|
|
681
|
+
return ele;
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
if (result.find(ele => ele instanceof Promise)) {
|
|
685
|
+
return Promise.all(result).then((r2) => r2.filter(ele => !!ele));
|
|
686
|
+
}
|
|
687
|
+
return result.filter(ele => !!ele);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 对于有些特殊的查询(带很多$or的查询,多发生在系统级别),单个actionAuth无法满足,需要共同加以判定
|
|
691
|
+
* @param entity
|
|
692
|
+
* @param filter
|
|
693
|
+
* @param actionAuths
|
|
694
|
+
* @param context
|
|
695
|
+
* @param actions
|
|
696
|
+
*/
|
|
697
|
+
checkActionAuthInGroup(entity, filter, actionAuths, context) {
|
|
698
|
+
const filters = actionAuths.filter(ele => ele.path.destEntity === entity).map((ele) => {
|
|
699
|
+
const { path, relation, relationId } = ele;
|
|
700
|
+
if (relationId) {
|
|
701
|
+
const pathFilter = this.makePathFilter(entity, path, this.schema, {
|
|
702
|
+
userRelation$entity: {
|
|
703
|
+
userId: context.getCurrentUserId(),
|
|
704
|
+
relationId,
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
return pathFilter;
|
|
708
|
+
}
|
|
709
|
+
// 说明是通过userId关联
|
|
710
|
+
return this.makePathFilter(entity, path, this.schema, {
|
|
711
|
+
id: context.getCurrentUserId(),
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
const groupFilter = (0, filter_1.combineFilters)(entity, this.schema, filters, true);
|
|
715
|
+
if (groupFilter) {
|
|
716
|
+
return (0, filter_1.checkFilterContains)(entity, context, groupFilter, filter, true);
|
|
717
|
+
}
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
checkSelection(entity, selection, context) {
|
|
721
|
+
const leafSelections = this.destructSelection(entity, selection);
|
|
722
|
+
const deducedLeafSelections = leafSelections.map(({ entity, filter }) => this.getDeducedEntityFilters(entity, filter, ['select'], context));
|
|
723
|
+
const checkDeducedLeafSelections = (dlSelections2) => {
|
|
724
|
+
const dlSelections = dlSelections2.filter((ele) => {
|
|
725
|
+
const entities = ele.map(ele2 => ele2.entity);
|
|
726
|
+
// 同一个leaf的deducedSelections中只要有一个能通过就足够了
|
|
727
|
+
if ((0, lodash_1.intersection)(this.selectFreeEntities, entities).length > 0) {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
if ((0, lodash_1.intersection)(RelationAuth.SPECIAL_ENTITIES, entities).length > 0) {
|
|
731
|
+
// todo
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
return true;
|
|
735
|
+
});
|
|
736
|
+
if (dlSelections.length === 0) {
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if (!context.getCurrentUserId()) {
|
|
740
|
+
throw new types_1.OakUnloggedInException();
|
|
741
|
+
}
|
|
742
|
+
const allEntities = [];
|
|
743
|
+
dlSelections.forEach((ele) => ele.forEach(({ entity }) => {
|
|
744
|
+
allEntities.push(entity);
|
|
745
|
+
}));
|
|
746
|
+
const actionAuths = context.select('actionAuth', {
|
|
747
|
+
data: {
|
|
748
|
+
id: 1,
|
|
749
|
+
path: {
|
|
750
|
+
id: 1,
|
|
751
|
+
value: 1,
|
|
752
|
+
sourceEntity: 1,
|
|
753
|
+
destEntity: 1,
|
|
754
|
+
recursive: 1,
|
|
755
|
+
},
|
|
756
|
+
deActions: 1,
|
|
757
|
+
relationId: 1,
|
|
758
|
+
},
|
|
759
|
+
filter: {
|
|
760
|
+
deActions: {
|
|
761
|
+
$contains: 'select',
|
|
762
|
+
},
|
|
763
|
+
path: {
|
|
764
|
+
destEntity: {
|
|
765
|
+
$in: allEntities,
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
}
|
|
769
|
+
}, { dontCollect: true, ignoreAttrMiss: true });
|
|
770
|
+
/**
|
|
771
|
+
* 返回的结果中,第一层为leafNode,必须全通过,第二层为单个leafNode上的deduce,通过一个就可以
|
|
772
|
+
* @param result
|
|
773
|
+
* @returns
|
|
774
|
+
*/
|
|
775
|
+
const checkResult = (result) => {
|
|
776
|
+
let idx = 0;
|
|
777
|
+
for (const r1 of result) {
|
|
778
|
+
const r2 = r1.find(ele => ele === true);
|
|
779
|
+
if (!r2) {
|
|
780
|
+
if (process.env.NODE_ENV === 'development') {
|
|
781
|
+
console.warn('对象的select权限被否决,请检查', dlSelections[idx]);
|
|
782
|
+
}
|
|
783
|
+
idx++;
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
idx++;
|
|
787
|
+
}
|
|
788
|
+
return true;
|
|
789
|
+
};
|
|
790
|
+
if (actionAuths instanceof Promise) {
|
|
791
|
+
(0, assert_1.default)(context instanceof AsyncRowStore_1.AsyncContext);
|
|
792
|
+
return actionAuths.then((aas) => Promise.all(dlSelections.map((ele) => Promise.all(ele.map((ele2) => this.checkActionAuthInGroup(ele2.entity, ele2.filter, aas, context))))).then((result) => checkResult(result)));
|
|
793
|
+
}
|
|
794
|
+
return checkResult(dlSelections.map(ele => ele.map(ele2 => this.checkActionAuthInGroup(ele2.entity, ele2.filter, actionAuths, context))));
|
|
795
|
+
};
|
|
796
|
+
if (deducedLeafSelections[0] instanceof Promise) {
|
|
797
|
+
return Promise.all(deducedLeafSelections)
|
|
798
|
+
.then((dls) => checkDeducedLeafSelections(dls));
|
|
799
|
+
}
|
|
800
|
+
return checkDeducedLeafSelections(deducedLeafSelections);
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* 此函数判定一个结点是否能通过权限检测,同时寻找该结点本身对象上成立的actionAuth,用于本结点子孙结点的快速检测
|
|
804
|
+
* 如果结点因其deduce的对象通过了检测,其被推断对象的actionAuth无法用于更低对象的权限检测
|
|
805
|
+
* @param node
|
|
806
|
+
* @param context
|
|
807
|
+
* @returns
|
|
808
|
+
*/
|
|
809
|
+
findActionAuthsOnNode(node, context) {
|
|
810
|
+
const { entity, filter, action, userRelations } = node;
|
|
811
|
+
const deducedEntityFilters2 = this.getDeducedEntityFilters(entity, filter, [action], context);
|
|
812
|
+
/**
|
|
813
|
+
* 搜索判定是否允许自建对象,自建的条件是 path = '',destEntity === entity
|
|
814
|
+
* @param actionAuths
|
|
815
|
+
* @returns
|
|
816
|
+
*/
|
|
817
|
+
const findOwnCreateUserRelation = (actionAuths) => {
|
|
818
|
+
if (userRelations && userRelations.length > 0) {
|
|
819
|
+
const ars = actionAuths.filter((ar) => !!userRelations.find((ur) => ur.relationId === ar.relationId) && ar.path.value === '' && ar.path.destEntity === entity);
|
|
820
|
+
if (ars.length > 0) {
|
|
821
|
+
return ars;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
const actionAuthOnEntities = [];
|
|
826
|
+
const dealWithDeducedEntityFilters = (deducedEntityFilters) => {
|
|
827
|
+
const specialEntities = deducedEntityFilters.filter(ele => RelationAuth.SPECIAL_ENTITIES.includes(ele.entity));
|
|
828
|
+
const unspecicalEntities = deducedEntityFilters.filter(ele => !RelationAuth.SPECIAL_ENTITIES.includes(ele.entity));
|
|
829
|
+
const result = [];
|
|
830
|
+
if (specialEntities.length > 0) {
|
|
831
|
+
// 对于deduce出来的special对象,直接判定create应该问题不大,否则写起来太烦琐(具体情况遇到了再调试)
|
|
832
|
+
result.push(...specialEntities.map(ele => this.checkOperateSpecialEntities2(ele.entity, ele.entity === entity ? node.action : 'create', ele.filter, context)));
|
|
833
|
+
}
|
|
834
|
+
if (unspecicalEntities.length > 0) {
|
|
835
|
+
const allEntities = unspecicalEntities.map(ele => ele.entity);
|
|
836
|
+
const allActions = (0, lodash_1.uniq)(unspecicalEntities.map(ele => ele.actions).flat());
|
|
837
|
+
const actionAuths2 = context.select('actionAuth', {
|
|
838
|
+
data: {
|
|
839
|
+
id: 1,
|
|
840
|
+
path: {
|
|
841
|
+
id: 1,
|
|
842
|
+
destEntity: 1,
|
|
843
|
+
sourceEntity: 1,
|
|
844
|
+
value: 1,
|
|
845
|
+
recursive: 1,
|
|
846
|
+
},
|
|
847
|
+
deActions: 1,
|
|
848
|
+
relation: {
|
|
849
|
+
id: 1,
|
|
850
|
+
userRelation$relation: {
|
|
851
|
+
$entity: 'userRelation',
|
|
852
|
+
data: {
|
|
853
|
+
id: 1,
|
|
854
|
+
entity: 1,
|
|
855
|
+
entityId: 1,
|
|
856
|
+
},
|
|
857
|
+
filter: {
|
|
858
|
+
userId: context.getCurrentUserId(),
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
filter: {
|
|
864
|
+
path: {
|
|
865
|
+
destEntity: {
|
|
866
|
+
$in: allEntities,
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
deActions: {
|
|
870
|
+
$overlaps: allActions,
|
|
871
|
+
},
|
|
872
|
+
}
|
|
873
|
+
}, { dontCollect: true, ignoreAttrMiss: true });
|
|
874
|
+
const checkActionAuths = (actionAuths) => {
|
|
875
|
+
const created = findOwnCreateUserRelation(actionAuths);
|
|
876
|
+
if (created) {
|
|
877
|
+
actionAuthOnEntities.push(...created);
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
const result = deducedEntityFilters.map((ele) => {
|
|
881
|
+
const ars2 = actionAuths.filter(ele2 => ele2.path.destEntity === ele.entity && (0, lodash_1.intersection)(ele2.deActions, ele.actions).length > 0 // 这里只要overlap就可以了
|
|
882
|
+
);
|
|
883
|
+
const ars3 = this.filterActionAuths(ele.entity, ele.filter, ars2, context);
|
|
884
|
+
const checkFilteredArs = (actionAuths2) => {
|
|
885
|
+
if (actionAuths2.length > 0) {
|
|
886
|
+
if (ele.entity === entity) {
|
|
887
|
+
actionAuthOnEntities.push(...actionAuths2);
|
|
888
|
+
}
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
return false;
|
|
892
|
+
};
|
|
893
|
+
if (ars3 instanceof Promise) {
|
|
894
|
+
return ars3.then((ars4) => checkFilteredArs(ars4));
|
|
895
|
+
}
|
|
896
|
+
return checkFilteredArs(ars3);
|
|
897
|
+
});
|
|
898
|
+
if (result.find(ele => ele instanceof Promise)) {
|
|
899
|
+
return Promise.all(result).then((r2) => r2.includes(true));
|
|
900
|
+
}
|
|
901
|
+
return result.includes(true);
|
|
902
|
+
};
|
|
903
|
+
if (actionAuths2 instanceof Promise) {
|
|
904
|
+
result.push(actionAuths2.then((ars2) => checkActionAuths(ars2)));
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
result.push(checkActionAuths(actionAuths2));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (result.find(ele => ele instanceof Promise)) {
|
|
911
|
+
return Promise.all(result).then((r2) => {
|
|
912
|
+
// r2中只有一个通过就能通过
|
|
913
|
+
if (r2.includes(true)) {
|
|
914
|
+
return actionAuthOnEntities;
|
|
915
|
+
}
|
|
916
|
+
return false;
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
if (result.includes(true)) {
|
|
920
|
+
return actionAuthOnEntities;
|
|
921
|
+
}
|
|
922
|
+
return false;
|
|
923
|
+
};
|
|
924
|
+
if (deducedEntityFilters2 instanceof Promise) {
|
|
925
|
+
return deducedEntityFilters2.then((def2) => dealWithDeducedEntityFilters(def2));
|
|
926
|
+
}
|
|
927
|
+
return dealWithDeducedEntityFilters(deducedEntityFilters2);
|
|
928
|
+
}
|
|
929
|
+
checkOperationTree2(tree, context) {
|
|
930
|
+
const checkNode = (node, actionAuths) => {
|
|
931
|
+
const checkChildren = (legalPaths) => {
|
|
932
|
+
const { children } = node;
|
|
933
|
+
const childPath = Object.keys(children);
|
|
934
|
+
if (childPath.length === 0) {
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
const childResult = childPath.map((childPath) => {
|
|
938
|
+
const child = children[childPath];
|
|
939
|
+
const childEntity = child instanceof Array ? child[0].entity : child.entity;
|
|
940
|
+
// 这里如果该子结点能deduce到父,则直接通过
|
|
941
|
+
if (this.authDeduceRelationMap[childEntity]) {
|
|
942
|
+
(0, assert_1.default)(this.authDeduceRelationMap[childEntity] === 'entity');
|
|
943
|
+
const rel = (0, relation_1.judgeRelation)(this.schema, childEntity, childPath);
|
|
944
|
+
if (rel === 2) {
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const pathToParent = childPath.endsWith('$entity') ? node.entity : childPath.split('$')[1];
|
|
949
|
+
if (child instanceof Array) {
|
|
950
|
+
const childActions = child.map(ele => ele.action);
|
|
951
|
+
const childLegalAuths = legalPaths.map((ele) => {
|
|
952
|
+
const { path: { value: pv }, relationId } = ele;
|
|
953
|
+
const pv2 = pv ? `${pathToParent}.${pv}` : pathToParent;
|
|
954
|
+
return context.select('actionAuth', {
|
|
955
|
+
data: {
|
|
956
|
+
id: 1,
|
|
957
|
+
},
|
|
958
|
+
filter: {
|
|
959
|
+
path: {
|
|
960
|
+
value: pv2,
|
|
961
|
+
destEntity: childEntity,
|
|
962
|
+
},
|
|
963
|
+
deActions: {
|
|
964
|
+
$overlaps: childActions,
|
|
965
|
+
},
|
|
966
|
+
relationId: relationId || {
|
|
967
|
+
$exists: false,
|
|
968
|
+
},
|
|
969
|
+
}
|
|
970
|
+
}, { dontCollect: true });
|
|
971
|
+
});
|
|
972
|
+
if (childLegalAuths[0] instanceof Promise) {
|
|
973
|
+
return Promise.all(childLegalAuths).then((clas) => child.map((c) => checkNode(c, clas.flat())));
|
|
974
|
+
}
|
|
975
|
+
return child.map((c) => checkNode(c, childLegalAuths.flat()));
|
|
976
|
+
}
|
|
977
|
+
const childLegalAuths = legalPaths.map((ele) => {
|
|
978
|
+
const { path: { value: pv }, relationId } = ele;
|
|
979
|
+
const pv2 = pv ? `${pathToParent}.${pv}` : pathToParent;
|
|
980
|
+
return context.select('actionAuth', {
|
|
981
|
+
data: {
|
|
982
|
+
id: 1,
|
|
983
|
+
},
|
|
984
|
+
filter: {
|
|
985
|
+
path: {
|
|
986
|
+
value: pv2,
|
|
987
|
+
destEntity: childEntity,
|
|
988
|
+
},
|
|
989
|
+
deActions: {
|
|
990
|
+
$overlaps: child.action,
|
|
991
|
+
},
|
|
992
|
+
relationId: relationId || {
|
|
993
|
+
$exists: false,
|
|
994
|
+
},
|
|
995
|
+
}
|
|
996
|
+
}, { dontCollect: true });
|
|
997
|
+
}).flat();
|
|
998
|
+
if (childLegalAuths[0] instanceof Promise) {
|
|
999
|
+
return Promise.all(childLegalAuths).then((clas) => checkNode(child, clas.flat()));
|
|
1000
|
+
}
|
|
1001
|
+
return checkNode(child, childLegalAuths);
|
|
1002
|
+
}).flat();
|
|
1003
|
+
if (childResult[0] instanceof Promise) {
|
|
1004
|
+
return Promise.all(childResult).then((r) => !r.includes(false));
|
|
1005
|
+
}
|
|
1006
|
+
return !childResult.includes(false);
|
|
1007
|
+
};
|
|
1008
|
+
// 先根据parent传下来的合法auths来搜寻,只需要查找actionAuth,降低开销
|
|
1009
|
+
// 这里有可能父结点根据actionAuths能通过,但子结点需要重新搜索父结点上的新actionAuths才能通过吗?应该不存在这种情况。 by Xc 20230824
|
|
1010
|
+
if (actionAuths && actionAuths.length > 0) {
|
|
1011
|
+
return checkChildren(actionAuths);
|
|
1012
|
+
}
|
|
1013
|
+
// 没有能根据父亲传下来的actionAuth判定,只能自己找
|
|
1014
|
+
const result = this.findActionAuthsOnNode(node, context);
|
|
1015
|
+
const checkResult = (result2) => {
|
|
1016
|
+
if (result2 === false) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
// 如果是对user对象操作通过,需要增添一条虚假的的actionAuth
|
|
1020
|
+
if (node.entity === 'user') {
|
|
1021
|
+
result2.push({
|
|
1022
|
+
id: 'temp',
|
|
1023
|
+
pathId: 'path_temp',
|
|
1024
|
+
path: {
|
|
1025
|
+
id: 'path_temp',
|
|
1026
|
+
destEntity: 'user',
|
|
1027
|
+
sourceEntity: 'any',
|
|
1028
|
+
value: '',
|
|
1029
|
+
$$createAt$$: 1,
|
|
1030
|
+
$$updateAt$$: 1,
|
|
1031
|
+
$$seq$$: 123,
|
|
1032
|
+
recursive: false,
|
|
1033
|
+
},
|
|
1034
|
+
$$createAt$$: 1,
|
|
1035
|
+
$$updateAt$$: 1,
|
|
1036
|
+
$$seq$$: 123,
|
|
1037
|
+
deActions: [node.action],
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
return checkChildren(result2);
|
|
1041
|
+
};
|
|
1042
|
+
if (result instanceof Promise) {
|
|
1043
|
+
return result.then((r2) => checkResult(r2));
|
|
1044
|
+
}
|
|
1045
|
+
return checkResult(result);
|
|
1046
|
+
};
|
|
1047
|
+
return checkNode(tree);
|
|
1048
|
+
}
|
|
1049
|
+
checkOperation(entity, operation, context) {
|
|
1050
|
+
const { action, filter, data } = operation;
|
|
1051
|
+
if (this.updateFreeDict[entity] && this.updateFreeDict[entity].includes(action)) {
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
const userId = context.getCurrentUserId();
|
|
1055
|
+
if (!userId) {
|
|
1056
|
+
throw new types_1.OakUnloggedInException();
|
|
1057
|
+
}
|
|
1058
|
+
if (!filter && (!data || action !== 'create')) {
|
|
1059
|
+
if (process.env.NODE_ENV === 'development') {
|
|
1060
|
+
console.warn('operation不能没有限制条件', operation);
|
|
1061
|
+
}
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
const updateTree = this.destructOperation(entity, operation, userId);
|
|
1065
|
+
return this.checkOperationTree2(updateTree, context);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* 检查一个operation是否能被通过权限测试
|
|
1069
|
+
* 一个cascadeOperation是一棵树形结构:
|
|
1070
|
+
* * 对于select,只要叶子通过其父结点必然通过;
|
|
1071
|
+
* * 对于update,自顶向下进行检查,若父亲被权限S通过,则只需要检查子对于S有没有相对路径上的actionAuth
|
|
1072
|
+
* 另外在update中,还需要考虑自建userRelation的case(例如在电子商务网站上购买商品,创建订单同时创建用户和订单的关系)
|
|
1073
|
+
* @param entity
|
|
1074
|
+
* @param operation
|
|
1075
|
+
* @param context
|
|
1076
|
+
* @param actions
|
|
1077
|
+
* @returns
|
|
1078
|
+
*/
|
|
1079
|
+
checkActions2(entity, operation, context, actions) {
|
|
1080
|
+
const { action } = operation;
|
|
1081
|
+
if (!action || action_1.readOnlyActions.includes(action)) {
|
|
1082
|
+
const result = this.checkSelection(entity, operation, context);
|
|
1083
|
+
if (result instanceof Promise) {
|
|
1084
|
+
return result.then((r) => {
|
|
1085
|
+
if (!r) {
|
|
1086
|
+
throw new types_1.OakUserInvisibleException(entity, operation);
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (!result) {
|
|
1091
|
+
throw new types_1.OakUserInvisibleException(entity, operation);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
const result = this.checkOperation(entity, operation, context);
|
|
1096
|
+
if (result instanceof Promise) {
|
|
1097
|
+
return result.then((r) => {
|
|
1098
|
+
if (!r) {
|
|
1099
|
+
throw new types_1.OakUserUnpermittedException(entity, operation);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
if (!result) {
|
|
1104
|
+
throw new types_1.OakUserUnpermittedException(entity, operation);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
exports.RelationAuth = RelationAuth;
|
|
1110
|
+
;
|
|
1111
|
+
/**
|
|
1112
|
+
* 获取有对entity进行actions操作权限的userRelation关系
|
|
1113
|
+
* @param params
|
|
1114
|
+
* @param context
|
|
1115
|
+
* todo paths改成复数以后这里还未充分测试过
|
|
1116
|
+
*/
|
|
1117
|
+
async function getUserRelationsByActions(params, context) {
|
|
1118
|
+
const { entity, filter, actions, overlap } = params;
|
|
1119
|
+
const actionAuthfilter = {
|
|
1120
|
+
path: {
|
|
1121
|
+
destEntity: entity,
|
|
1122
|
+
},
|
|
1123
|
+
};
|
|
1124
|
+
if (overlap) {
|
|
1125
|
+
Object.assign(actionAuthfilter, {
|
|
1126
|
+
deActions: {
|
|
1127
|
+
$overlaps: actions,
|
|
1128
|
+
},
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
Object.assign(actionAuthfilter, {
|
|
1133
|
+
deActions: {
|
|
1134
|
+
$contains: actions,
|
|
1135
|
+
},
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
const actionAuths = await context.select('actionAuth', {
|
|
1139
|
+
data: {
|
|
1140
|
+
id: 1,
|
|
1141
|
+
path: {
|
|
1142
|
+
id: 1,
|
|
1143
|
+
value: 1,
|
|
1144
|
+
destEntity: 1,
|
|
1145
|
+
recursive: 1,
|
|
1146
|
+
},
|
|
1147
|
+
relationId: 1,
|
|
1148
|
+
relation: {
|
|
1149
|
+
id: 1,
|
|
1150
|
+
entity: 1,
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
filter: actionAuthfilter,
|
|
1154
|
+
}, { dontCollect: true });
|
|
1155
|
+
const getUserRelations = async (urAuths) => {
|
|
1156
|
+
// 相同的path可以groupBy掉
|
|
1157
|
+
const urAuthDict2 = {};
|
|
1158
|
+
urAuths.forEach((auth) => {
|
|
1159
|
+
const { path, relationId } = auth;
|
|
1160
|
+
const { value, recursive } = path;
|
|
1161
|
+
if (!urAuthDict2[value]) {
|
|
1162
|
+
urAuthDict2[value] = [[relationId], recursive];
|
|
1163
|
+
}
|
|
1164
|
+
else if (!urAuthDict2[value][0].includes(relationId)) {
|
|
1165
|
+
(0, assert_1.default)(urAuthDict2[value][1] === recursive);
|
|
1166
|
+
urAuthDict2[value][0].push(relationId);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
const userRelations = await Promise.all(Object.keys(urAuthDict2).map(async (path) => {
|
|
1170
|
+
const [relationIds, recursive] = urAuthDict2[path];
|
|
1171
|
+
const { projection, getData } = (0, relationPath_1.destructRelationPath)(context.getSchema(), entity, path, {
|
|
1172
|
+
relationId: {
|
|
1173
|
+
$in: relationIds,
|
|
1174
|
+
},
|
|
1175
|
+
}, recursive);
|
|
1176
|
+
const rows = await context.select(entity, {
|
|
1177
|
+
data: projection,
|
|
1178
|
+
filter,
|
|
1179
|
+
}, { dontCollect: true });
|
|
1180
|
+
const urs = rows.map(ele => getData(ele)).flat().filter(ele => !!ele);
|
|
1181
|
+
return urs;
|
|
1182
|
+
}));
|
|
1183
|
+
return userRelations.flat();
|
|
1184
|
+
};
|
|
1185
|
+
const getDirectUserEntities = async (directAuths) => {
|
|
1186
|
+
const userEntities = await Promise.all(directAuths.map(async ({ path }) => {
|
|
1187
|
+
const { value, recursive } = path;
|
|
1188
|
+
(0, assert_1.default)(!recursive);
|
|
1189
|
+
const { getData, projection } = (0, relationPath_1.destructDirectPath)(context.getSchema(), entity, value, recursive);
|
|
1190
|
+
const rows = await context.select(entity, {
|
|
1191
|
+
data: projection,
|
|
1192
|
+
filter,
|
|
1193
|
+
}, { dontCollect: true });
|
|
1194
|
+
const userEntities = rows.map(ele => getData(ele)).flat().filter(ele => !!ele);
|
|
1195
|
+
return userEntities;
|
|
1196
|
+
}));
|
|
1197
|
+
return userEntities.flat();
|
|
1198
|
+
};
|
|
1199
|
+
const urAuths2 = actionAuths.filter(ele => !!ele.relationId // 有relation说明通过userRelation关联
|
|
1200
|
+
);
|
|
1201
|
+
const directAuths2 = actionAuths.filter(ele => !ele.relationId // 没relation说明通过user关联
|
|
1202
|
+
);
|
|
1203
|
+
const [userRelations, userEntities] = await Promise.all([getUserRelations(urAuths2), getDirectUserEntities(directAuths2)]);
|
|
1204
|
+
return {
|
|
1205
|
+
userRelations,
|
|
1206
|
+
userEntities,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
exports.getUserRelationsByActions = getUserRelationsByActions;
|