@via-profit/ability 2.1.0 → 3.1.0
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/CHANGELOG.md +129 -0
- package/CONTRIBUTING.md +14 -0
- package/LICENSE +21 -0
- package/README.md +1058 -319
- package/SECURITY.md +33 -0
- package/dist/cache/AbilityCacheAdapter.d.ts +8 -0
- package/dist/cache/AbilityInMemoryCache.d.ts +12 -0
- package/dist/core/AbilityCondition.d.ts +21 -0
- package/dist/core/AbilityExplain.d.ts +27 -0
- package/dist/core/AbilityParser.d.ts +61 -0
- package/dist/core/AbilityPolicy.d.ts +84 -0
- package/dist/core/AbilityResolver.d.ts +35 -0
- package/dist/core/AbilityResult.d.ts +27 -0
- package/dist/core/AbilityRule.d.ts +77 -0
- package/dist/{AbilityRuleSet.d.ts → core/AbilityRuleSet.d.ts} +14 -11
- package/dist/index.d.ts +19 -11
- package/dist/index.js +2097 -303
- package/dist/parsers/dsl/AbilityDSLLexer.d.ts +24 -0
- package/dist/parsers/dsl/AbilityDSLParser.d.ts +86 -0
- package/dist/parsers/dsl/AbilityDSLSyntaxError.d.ts +13 -0
- package/dist/parsers/dsl/AbilityDSLToken.d.ts +55 -0
- package/dist/parsers/json/AbilityJSONParser.d.ts +22 -0
- package/package.json +15 -4
- package/assets/ability-01.drawio.png +0 -0
- package/build/playground.js +0 -456
- package/build/playground.js.map +0 -1
- package/dist/AbilityCondition.d.ts +0 -16
- package/dist/AbilityParser.d.ts +0 -18
- package/dist/AbilityPolicy.d.ts +0 -65
- package/dist/AbilityResolver.d.ts +0 -30
- package/dist/AbilityRule.d.ts +0 -70
- /package/dist/{AbilityCode.d.ts → core/AbilityCode.d.ts} +0 -0
- /package/dist/{AbilityCompare.d.ts → core/AbilityCompare.d.ts} +0 -0
- /package/dist/{AbilityError.d.ts → core/AbilityError.d.ts} +0 -0
- /package/dist/{AbilityMatch.d.ts → core/AbilityMatch.d.ts} +0 -0
- /package/dist/{AbilityPolicyEffect.d.ts → core/AbilityPolicyEffect.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,7 +2,99 @@
|
|
|
2
2
|
/******/ "use strict";
|
|
3
3
|
/******/ var __webpack_modules__ = ({
|
|
4
4
|
|
|
5
|
-
/***/
|
|
5
|
+
/***/ 6:
|
|
6
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/***/ }),
|
|
13
|
+
|
|
14
|
+
/***/ 829:
|
|
15
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
19
|
+
exports.AbilityInMemoryCache = void 0;
|
|
20
|
+
class AbilityInMemoryCache {
|
|
21
|
+
store = new Map();
|
|
22
|
+
async get(key) {
|
|
23
|
+
const entry = this.store.get(key);
|
|
24
|
+
if (!entry)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (Date.now() > entry.expires) {
|
|
27
|
+
this.store.delete(key);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return entry.value;
|
|
31
|
+
}
|
|
32
|
+
async set(key, value, ttlSeconds = 60) {
|
|
33
|
+
this.store.set(key, {
|
|
34
|
+
value,
|
|
35
|
+
expires: Date.now() + ttlSeconds * 1000,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
serialize(input) {
|
|
39
|
+
return this.fastHash(this.stableStringify(input));
|
|
40
|
+
}
|
|
41
|
+
async delete(key) {
|
|
42
|
+
this.store.delete(key);
|
|
43
|
+
}
|
|
44
|
+
async clear() {
|
|
45
|
+
this.store.clear();
|
|
46
|
+
}
|
|
47
|
+
async deleteByPrefix(prefix) {
|
|
48
|
+
for (const key of this.store.keys()) {
|
|
49
|
+
if (key.startsWith(prefix)) {
|
|
50
|
+
this.store.delete(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
fastHash(str) {
|
|
55
|
+
let hash = 5381;
|
|
56
|
+
for (let i = 0; i < str.length; i++) {
|
|
57
|
+
hash = (hash * 33) ^ str.charCodeAt(i);
|
|
58
|
+
}
|
|
59
|
+
return (hash >>> 0).toString(36);
|
|
60
|
+
}
|
|
61
|
+
stableStringify(obj) {
|
|
62
|
+
if (obj === null)
|
|
63
|
+
return 'null';
|
|
64
|
+
const type = typeof obj;
|
|
65
|
+
if (type === 'string')
|
|
66
|
+
return JSON.stringify(obj);
|
|
67
|
+
if (type === 'number' || type === 'boolean')
|
|
68
|
+
return String(obj);
|
|
69
|
+
if (type === 'undefined')
|
|
70
|
+
return 'undefined';
|
|
71
|
+
if (Array.isArray(obj)) {
|
|
72
|
+
let out = '[';
|
|
73
|
+
for (let i = 0; i < obj.length; i++) {
|
|
74
|
+
if (i > 0)
|
|
75
|
+
out += ',';
|
|
76
|
+
out += this.stableStringify(obj[i]);
|
|
77
|
+
}
|
|
78
|
+
return out + ']';
|
|
79
|
+
}
|
|
80
|
+
const keys = Object.keys(obj);
|
|
81
|
+
keys.sort();
|
|
82
|
+
let out = '{';
|
|
83
|
+
for (let i = 0; i < keys.length; i++) {
|
|
84
|
+
const k = keys[i];
|
|
85
|
+
if (i > 0)
|
|
86
|
+
out += ',';
|
|
87
|
+
out += k + ':' + this.stableStringify(obj[k]);
|
|
88
|
+
}
|
|
89
|
+
return out + '}';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.AbilityInMemoryCache = AbilityInMemoryCache;
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/***/ }),
|
|
96
|
+
|
|
97
|
+
/***/ 301:
|
|
6
98
|
/***/ ((__unused_webpack_module, exports) => {
|
|
7
99
|
|
|
8
100
|
|
|
@@ -29,7 +121,7 @@ exports["default"] = AbilityCode;
|
|
|
29
121
|
|
|
30
122
|
/***/ }),
|
|
31
123
|
|
|
32
|
-
/***/
|
|
124
|
+
/***/ 413:
|
|
33
125
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
34
126
|
|
|
35
127
|
|
|
@@ -38,7 +130,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
130
|
};
|
|
39
131
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
40
132
|
exports.AbilityCompare = void 0;
|
|
41
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(
|
|
133
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
42
134
|
class AbilityCompare extends AbilityCode_1.default {
|
|
43
135
|
static and = new AbilityCompare('and');
|
|
44
136
|
static or = new AbilityCompare('or');
|
|
@@ -49,7 +141,7 @@ exports["default"] = AbilityCompare;
|
|
|
49
141
|
|
|
50
142
|
/***/ }),
|
|
51
143
|
|
|
52
|
-
/***/
|
|
144
|
+
/***/ 167:
|
|
53
145
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
54
146
|
|
|
55
147
|
|
|
@@ -58,23 +150,51 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
58
150
|
};
|
|
59
151
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
60
152
|
exports.AbilityCondition = void 0;
|
|
61
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(
|
|
62
|
-
const AbilityError_1 = __webpack_require__(
|
|
153
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
154
|
+
const AbilityError_1 = __webpack_require__(216);
|
|
63
155
|
class AbilityCondition extends AbilityCode_1.default {
|
|
64
|
-
static
|
|
65
|
-
static
|
|
66
|
-
static
|
|
156
|
+
static equals = new AbilityCondition('=');
|
|
157
|
+
static not_equals = new AbilityCondition('<>');
|
|
158
|
+
static greater_than = new AbilityCondition('>');
|
|
67
159
|
static less_than = new AbilityCondition('<');
|
|
68
160
|
static less_or_equal = new AbilityCondition('<=');
|
|
69
|
-
static
|
|
161
|
+
static greater_or_equal = new AbilityCondition('>=');
|
|
70
162
|
static in = new AbilityCondition('in');
|
|
71
163
|
static not_in = new AbilityCondition('not in');
|
|
164
|
+
static contains = new AbilityCondition('contains');
|
|
165
|
+
static not_contains = new AbilityCondition('not contains');
|
|
166
|
+
static length_greater_than = new AbilityCondition('length greater than');
|
|
167
|
+
static length_less_than = new AbilityCondition('length less than');
|
|
168
|
+
static length_equals = new AbilityCondition('length equals');
|
|
72
169
|
static fromLiteral(literal) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
170
|
+
switch (literal) {
|
|
171
|
+
case 'equals':
|
|
172
|
+
return this.equals;
|
|
173
|
+
case 'not_equals':
|
|
174
|
+
return this.not_equals;
|
|
175
|
+
case 'greater_than':
|
|
176
|
+
return this.greater_than;
|
|
177
|
+
case 'less_than':
|
|
178
|
+
return this.less_than;
|
|
179
|
+
case 'less_or_equal':
|
|
180
|
+
return this.less_or_equal;
|
|
181
|
+
case 'greater_or_equal':
|
|
182
|
+
return this.greater_or_equal;
|
|
183
|
+
case 'contains':
|
|
184
|
+
return this.contains;
|
|
185
|
+
case 'no_contains':
|
|
186
|
+
return this.not_contains;
|
|
187
|
+
case 'in':
|
|
188
|
+
return this.in;
|
|
189
|
+
case 'not_in':
|
|
190
|
+
return this.not_in;
|
|
191
|
+
case 'length_greater_than':
|
|
192
|
+
return this.length_greater_than;
|
|
193
|
+
case 'length_equals':
|
|
194
|
+
return this.length_equals;
|
|
195
|
+
default:
|
|
196
|
+
throw new AbilityError_1.AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
|
|
76
197
|
}
|
|
77
|
-
return new AbilityCondition(code);
|
|
78
198
|
}
|
|
79
199
|
get literal() {
|
|
80
200
|
const literal = Object.keys(AbilityCondition).find(member => {
|
|
@@ -93,7 +213,7 @@ exports["default"] = AbilityCondition;
|
|
|
93
213
|
|
|
94
214
|
/***/ }),
|
|
95
215
|
|
|
96
|
-
/***/
|
|
216
|
+
/***/ 216:
|
|
97
217
|
/***/ ((__unused_webpack_module, exports) => {
|
|
98
218
|
|
|
99
219
|
|
|
@@ -115,7 +235,75 @@ exports.AbilityParserError = AbilityParserError;
|
|
|
115
235
|
|
|
116
236
|
/***/ }),
|
|
117
237
|
|
|
118
|
-
/***/
|
|
238
|
+
/***/ 221:
|
|
239
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
243
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
244
|
+
};
|
|
245
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
246
|
+
exports.AbilityExplainPolicy = exports.AbilityExplainRuleSet = exports.AbilityExplainRule = exports.AbilityExplain = void 0;
|
|
247
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
248
|
+
class AbilityExplain {
|
|
249
|
+
type;
|
|
250
|
+
children;
|
|
251
|
+
name;
|
|
252
|
+
match;
|
|
253
|
+
constructor(config, children = []) {
|
|
254
|
+
this.type = config.type;
|
|
255
|
+
this.children = children;
|
|
256
|
+
this.name = config.name;
|
|
257
|
+
this.match = config.match;
|
|
258
|
+
}
|
|
259
|
+
toString(indent = 0) {
|
|
260
|
+
const pad = ' '.repeat(indent);
|
|
261
|
+
const mark = this.match.code === AbilityMatch_1.default.match.code ? '✓' : '✗';
|
|
262
|
+
let out = `${pad}${mark} ${this.type} «${this.name}» is ${this.match.code}`;
|
|
263
|
+
this.children.forEach(child => {
|
|
264
|
+
out += '\n' + child.toString(indent + 1);
|
|
265
|
+
});
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
exports.AbilityExplain = AbilityExplain;
|
|
270
|
+
class AbilityExplainRule extends AbilityExplain {
|
|
271
|
+
constructor(rule) {
|
|
272
|
+
super({
|
|
273
|
+
type: 'rule',
|
|
274
|
+
match: rule.state,
|
|
275
|
+
name: rule.name,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
exports.AbilityExplainRule = AbilityExplainRule;
|
|
280
|
+
class AbilityExplainRuleSet extends AbilityExplain {
|
|
281
|
+
constructor(ruleSet) {
|
|
282
|
+
const children = ruleSet.rules.map(rule => new AbilityExplainRule(rule));
|
|
283
|
+
super({
|
|
284
|
+
type: 'ruleSet',
|
|
285
|
+
match: ruleSet.state,
|
|
286
|
+
name: ruleSet.name,
|
|
287
|
+
}, children);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
|
|
291
|
+
class AbilityExplainPolicy extends AbilityExplain {
|
|
292
|
+
constructor(policy) {
|
|
293
|
+
const children = policy.ruleSet.map(ruleSet => new AbilityExplainRuleSet(ruleSet));
|
|
294
|
+
super({
|
|
295
|
+
type: 'policy',
|
|
296
|
+
name: policy.name,
|
|
297
|
+
match: policy.matchState,
|
|
298
|
+
}, children);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
/***/ }),
|
|
305
|
+
|
|
306
|
+
/***/ 247:
|
|
119
307
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
120
308
|
|
|
121
309
|
|
|
@@ -124,7 +312,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
124
312
|
};
|
|
125
313
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
126
314
|
exports.AbilityMatch = void 0;
|
|
127
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(
|
|
315
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
128
316
|
class AbilityMatch extends AbilityCode_1.default {
|
|
129
317
|
static pending = new AbilityMatch('pending');
|
|
130
318
|
static match = new AbilityMatch('match');
|
|
@@ -136,7 +324,7 @@ exports["default"] = AbilityMatch;
|
|
|
136
324
|
|
|
137
325
|
/***/ }),
|
|
138
326
|
|
|
139
|
-
/***/
|
|
327
|
+
/***/ 147:
|
|
140
328
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
141
329
|
|
|
142
330
|
|
|
@@ -145,23 +333,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
145
333
|
};
|
|
146
334
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
147
335
|
exports.AbilityParser = void 0;
|
|
148
|
-
const AbilityError_1 = __webpack_require__(
|
|
149
|
-
const AbilityCondition_1 = __importDefault(__webpack_require__(
|
|
336
|
+
const AbilityError_1 = __webpack_require__(216);
|
|
337
|
+
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
150
338
|
class AbilityParser {
|
|
151
|
-
/*
|
|
152
|
-
*
|
|
153
|
-
* readonly ['order.update']: {
|
|
154
|
-
* readonly user: {
|
|
155
|
-
* readonly roles: readonly string[];
|
|
156
|
-
* readonly department: string;
|
|
157
|
-
* };
|
|
158
|
-
* readonly order: {
|
|
159
|
-
* readonly estimatedArrivalAt: number;
|
|
160
|
-
* readonly status: string;
|
|
161
|
-
* }
|
|
162
|
-
* }
|
|
163
|
-
*
|
|
164
|
-
* */
|
|
165
339
|
/**
|
|
166
340
|
* Sets a value in a nested object structure based on a dot/bracket notation path.
|
|
167
341
|
* @param object - The target object to modify.
|
|
@@ -169,52 +343,223 @@ class AbilityParser {
|
|
|
169
343
|
* @param value - The value to set at the specified path.
|
|
170
344
|
*/
|
|
171
345
|
static setValueDotValue(object, path, value) {
|
|
172
|
-
|
|
346
|
+
if (!path || path.trim().length === 0) {
|
|
347
|
+
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
348
|
+
}
|
|
349
|
+
const way = path.replace(/\[/g, '.').replace(/]/g, '').split('.');
|
|
173
350
|
const last = way.pop();
|
|
174
351
|
if (!last) {
|
|
175
352
|
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
176
353
|
}
|
|
177
|
-
way.reduce((
|
|
178
|
-
|
|
179
|
-
|
|
354
|
+
const lastObj = way.reduce((acc, key, index, array) => {
|
|
355
|
+
const currentValue = acc[key];
|
|
356
|
+
const nextKey = array[index + 1];
|
|
357
|
+
const shouldBeArray = nextKey !== undefined && isFinite(Number(nextKey));
|
|
358
|
+
if (currentValue === undefined || currentValue === null) {
|
|
359
|
+
// Create missing property
|
|
360
|
+
const newValue = shouldBeArray ? [] : {};
|
|
361
|
+
acc[key] = newValue;
|
|
362
|
+
return newValue;
|
|
363
|
+
}
|
|
364
|
+
if (typeof currentValue !== 'object') {
|
|
365
|
+
throw new AbilityError_1.AbilityParserError(`Cannot set property '${key}' on non-object value at path: ${path}`);
|
|
180
366
|
}
|
|
181
|
-
return
|
|
182
|
-
}, object)
|
|
367
|
+
return currentValue;
|
|
368
|
+
}, object);
|
|
369
|
+
const existingValue = lastObj[last];
|
|
370
|
+
if (existingValue !== undefined &&
|
|
371
|
+
typeof existingValue === 'object' &&
|
|
372
|
+
existingValue !== null &&
|
|
373
|
+
!Array.isArray(existingValue)) {
|
|
374
|
+
throw new AbilityError_1.AbilityParserError(`Cannot set primitive value on existing object at path: ${path}`);
|
|
375
|
+
}
|
|
376
|
+
lastObj[last] = value;
|
|
183
377
|
}
|
|
184
378
|
/**
|
|
185
379
|
* Generates TypeScript type definitions based on the provided policies.
|
|
186
380
|
* @param policies - An array of AbilityPolicy instances.
|
|
187
|
-
* @
|
|
188
|
-
* @returns A record containing the generated type definitions.
|
|
381
|
+
* @returns A generated type definitions.
|
|
189
382
|
*/
|
|
190
|
-
static generateTypeDefs(policies
|
|
191
|
-
|
|
383
|
+
static generateTypeDefs(policies) {
|
|
384
|
+
// Structure to store types: { [action]: { [subjectPath]: type } }
|
|
385
|
+
const typeStructure = {};
|
|
386
|
+
// Iterate through all policies
|
|
192
387
|
policies.forEach(policy => {
|
|
388
|
+
const action = policy.permission;
|
|
389
|
+
// Initialize object for action if it doesn't exist
|
|
390
|
+
if (!typeStructure[action]) {
|
|
391
|
+
typeStructure[action] = {};
|
|
392
|
+
}
|
|
393
|
+
// Iterate through all ruleSets in the policy
|
|
193
394
|
policy.ruleSet.forEach(ruleSet => {
|
|
395
|
+
// Iterate through all rules in the ruleSet
|
|
194
396
|
ruleSet.rules.forEach(rule => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
break;
|
|
205
|
-
case rule.condition.isEqual(AbilityCondition_1.default.more_or_equal):
|
|
206
|
-
case rule.condition.isEqual(AbilityCondition_1.default.more_than):
|
|
207
|
-
case rule.condition.isEqual(AbilityCondition_1.default.less_or_equal):
|
|
208
|
-
case rule.condition.isEqual(AbilityCondition_1.default.less_than):
|
|
209
|
-
value = 'number';
|
|
210
|
-
break;
|
|
397
|
+
const subjectPath = rule.subject;
|
|
398
|
+
const existingType = typeStructure[action][subjectPath];
|
|
399
|
+
const ruleType = this.determineTypeFromRule(rule);
|
|
400
|
+
// If a type already exists for this path, create a union
|
|
401
|
+
if (existingType && existingType !== ruleType) {
|
|
402
|
+
typeStructure[action][subjectPath] = `${existingType} | ${ruleType}`;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
typeStructure[action][subjectPath] = ruleType;
|
|
211
406
|
}
|
|
212
|
-
AbilityParser.setValueDotValue(record, rule.subject, value);
|
|
213
407
|
});
|
|
214
408
|
});
|
|
215
409
|
});
|
|
216
|
-
|
|
217
|
-
|
|
410
|
+
// Transform flat structure into nested structure for easier use
|
|
411
|
+
const nestedStructure = this.buildNestedStructure(typeStructure);
|
|
412
|
+
return this.formatTypeDefinitions(nestedStructure);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Determines TypeScript type based on the rule
|
|
416
|
+
* @param rule - The rule to analyze
|
|
417
|
+
* @returns TypeScript type as string
|
|
418
|
+
*/
|
|
419
|
+
static determineTypeFromRule(rule) {
|
|
420
|
+
// Numeric comparisons - always number
|
|
421
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.greater_than) ||
|
|
422
|
+
rule.condition.isEqual(AbilityCondition_1.default.less_than) ||
|
|
423
|
+
rule.condition.isEqual(AbilityCondition_1.default.greater_or_equal) ||
|
|
424
|
+
rule.condition.isEqual(AbilityCondition_1.default.less_or_equal)) {
|
|
425
|
+
return 'number';
|
|
426
|
+
}
|
|
427
|
+
// Array operations
|
|
428
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.in) ||
|
|
429
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_in)) {
|
|
430
|
+
return this.getArrayType(rule.resource);
|
|
431
|
+
}
|
|
432
|
+
// Equality/Inequality operations
|
|
433
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.equals) ||
|
|
434
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_equals)) {
|
|
435
|
+
return this.getPrimitiveType(rule.resource);
|
|
436
|
+
}
|
|
437
|
+
return 'any';
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Gets TypeScript type for array values
|
|
441
|
+
* @param resource - The resource value to analyze
|
|
442
|
+
* @returns TypeScript array type as string
|
|
443
|
+
*/
|
|
444
|
+
static getArrayType(resource) {
|
|
445
|
+
if (Array.isArray(resource)) {
|
|
446
|
+
if (resource.length === 0)
|
|
447
|
+
return 'any[]';
|
|
448
|
+
// Determine types of array elements
|
|
449
|
+
const elementTypes = new Set(resource.map(item => this.getPrimitiveType(item)));
|
|
450
|
+
const elementType = elementTypes.size === 1
|
|
451
|
+
? Array.from(elementTypes)[0]
|
|
452
|
+
: `(${Array.from(elementTypes).join(' | ')})`;
|
|
453
|
+
return `${elementType}[]`;
|
|
454
|
+
}
|
|
455
|
+
// If resource is not an array but condition is in/not_in,
|
|
456
|
+
// it expects an array of such elements
|
|
457
|
+
return `${this.getPrimitiveType(resource)}[]`;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Gets primitive TypeScript type for a value
|
|
461
|
+
* @param value - The value to analyze
|
|
462
|
+
* @returns TypeScript primitive type as string
|
|
463
|
+
*/
|
|
464
|
+
static getPrimitiveType(value) {
|
|
465
|
+
if (value === null)
|
|
466
|
+
return 'null';
|
|
467
|
+
if (value === undefined)
|
|
468
|
+
return 'undefined';
|
|
469
|
+
switch (typeof value) {
|
|
470
|
+
case 'string':
|
|
471
|
+
return 'string';
|
|
472
|
+
case 'number':
|
|
473
|
+
return 'number';
|
|
474
|
+
case 'boolean':
|
|
475
|
+
return 'boolean';
|
|
476
|
+
case 'object':
|
|
477
|
+
if (Array.isArray(value)) {
|
|
478
|
+
return 'array'; // special marker, handled separately
|
|
479
|
+
}
|
|
480
|
+
return 'object';
|
|
481
|
+
default:
|
|
482
|
+
return 'any';
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Builds nested structure from flat paths
|
|
487
|
+
* Example: 'user.profile.name' -> { user: { profile: { name: 'string' } } }
|
|
488
|
+
* @param flatStructure - Flat structure with dot notation paths
|
|
489
|
+
* @returns Nested object structure
|
|
490
|
+
*/
|
|
491
|
+
static buildNestedStructure(flatStructure) {
|
|
492
|
+
const result = {};
|
|
493
|
+
Object.entries(flatStructure).forEach(([action, paths]) => {
|
|
494
|
+
result[action] = {};
|
|
495
|
+
Object.entries(paths).forEach(([path, type]) => {
|
|
496
|
+
const parts = path.split('.');
|
|
497
|
+
let current = result[action];
|
|
498
|
+
// Iterate through all parts except the last one
|
|
499
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
500
|
+
const part = parts[i];
|
|
501
|
+
const currentValue = current[part];
|
|
502
|
+
if (!currentValue || typeof currentValue !== 'object') {
|
|
503
|
+
const newObj = {};
|
|
504
|
+
current[part] = newObj;
|
|
505
|
+
current = newObj;
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
current = currentValue;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// Set type for the last part
|
|
512
|
+
const lastPart = parts[parts.length - 1];
|
|
513
|
+
current[lastPart] = type;
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Formats type structure into a string
|
|
520
|
+
* @param structure - Nested type structure
|
|
521
|
+
* @returns Formatted TypeScript type definition string
|
|
522
|
+
*/
|
|
523
|
+
static formatTypeDefinitions(structure) {
|
|
524
|
+
let output = '// Automatically generated by via-profit/ability\n';
|
|
525
|
+
output += '// Do not edit manually\n';
|
|
526
|
+
output += '\n/* eslint-disable */\n\n';
|
|
527
|
+
output += 'export type Resources = {\n';
|
|
528
|
+
// Sort actions for stable output
|
|
529
|
+
const sortedActions = Object.keys(structure).sort();
|
|
530
|
+
sortedActions.forEach(action => {
|
|
531
|
+
output += ` ['${action}']: {\n`;
|
|
532
|
+
output += this.formatNestedObject(structure[action], 4);
|
|
533
|
+
output += ' };\n';
|
|
534
|
+
});
|
|
535
|
+
output += '}\n';
|
|
536
|
+
return output;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Recursively formats nested object
|
|
540
|
+
* @param obj - Object to format
|
|
541
|
+
* @param indent - Current indentation level
|
|
542
|
+
* @returns Formatted string
|
|
543
|
+
*/
|
|
544
|
+
static formatNestedObject(obj, indent) {
|
|
545
|
+
const spaces = ' '.repeat(indent);
|
|
546
|
+
let output = '';
|
|
547
|
+
// Sort keys for stable output
|
|
548
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
549
|
+
sortedKeys.forEach(key => {
|
|
550
|
+
const value = obj[key];
|
|
551
|
+
if (typeof value === 'object' && value !== null) {
|
|
552
|
+
// Nested object
|
|
553
|
+
output += `${spaces}readonly ${key}: {\n`;
|
|
554
|
+
output += this.formatNestedObject(value, indent + 2);
|
|
555
|
+
output += `${spaces}};\n`;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
// Primitive type
|
|
559
|
+
output += `${spaces}readonly ${key}: ${value};\n`;
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
return output;
|
|
218
563
|
}
|
|
219
564
|
}
|
|
220
565
|
exports.AbilityParser = AbilityParser;
|
|
@@ -223,7 +568,7 @@ exports["default"] = AbilityParser;
|
|
|
223
568
|
|
|
224
569
|
/***/ }),
|
|
225
570
|
|
|
226
|
-
/***/
|
|
571
|
+
/***/ 278:
|
|
227
572
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
228
573
|
|
|
229
574
|
|
|
@@ -232,10 +577,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
232
577
|
};
|
|
233
578
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
234
579
|
exports.AbilityPolicy = void 0;
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
const
|
|
580
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
581
|
+
const AbilityCompare_1 = __importDefault(__webpack_require__(413));
|
|
582
|
+
const AbilityExplain_1 = __webpack_require__(221);
|
|
583
|
+
const AbilityError_1 = __webpack_require__(216);
|
|
584
|
+
const AbilityJSONParser_1 = __webpack_require__(909);
|
|
585
|
+
const AbilityDSLParser_1 = __webpack_require__(577);
|
|
239
586
|
class AbilityPolicy {
|
|
240
587
|
matchState = AbilityMatch_1.default.pending;
|
|
241
588
|
/**
|
|
@@ -262,15 +609,17 @@ class AbilityPolicy {
|
|
|
262
609
|
*/
|
|
263
610
|
id;
|
|
264
611
|
/**
|
|
265
|
-
*
|
|
612
|
+
* Running the `enforce` or `resolve` method
|
|
613
|
+
* will select only those from all passed policies that fall under the specified permission key.
|
|
266
614
|
*/
|
|
267
|
-
|
|
615
|
+
permission;
|
|
268
616
|
constructor(params) {
|
|
269
|
-
const { name, id,
|
|
617
|
+
const { name, id, permission, effect, compareMethod = AbilityCompare_1.default.and } = params;
|
|
270
618
|
this.name = name;
|
|
271
619
|
this.id = id;
|
|
272
|
-
this.
|
|
620
|
+
this.permission = permission;
|
|
273
621
|
this.effect = effect;
|
|
622
|
+
this.compareMethod = compareMethod;
|
|
274
623
|
}
|
|
275
624
|
/**
|
|
276
625
|
* Add rule set to the policy
|
|
@@ -280,57 +629,78 @@ class AbilityPolicy {
|
|
|
280
629
|
this.ruleSet.push(ruleSet);
|
|
281
630
|
return this;
|
|
282
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Add rule set to the policy
|
|
634
|
+
* @param ruleSets - The array of rule set to add
|
|
635
|
+
*/
|
|
636
|
+
addRuleSets(ruleSets) {
|
|
637
|
+
for (const ruleSet of ruleSets) {
|
|
638
|
+
this.ruleSet.push(ruleSet);
|
|
639
|
+
}
|
|
640
|
+
return this;
|
|
641
|
+
}
|
|
283
642
|
/**
|
|
284
643
|
* Check if the policy is matched
|
|
285
|
-
* @param
|
|
644
|
+
* @param resource - The resource to check
|
|
645
|
+
* @param environment - The user environment object
|
|
286
646
|
*/
|
|
287
|
-
check(
|
|
647
|
+
async check(resource, environment) {
|
|
288
648
|
this.matchState = AbilityMatch_1.default.mismatch;
|
|
289
649
|
if (!this.ruleSet.length) {
|
|
290
650
|
return this.matchState;
|
|
291
651
|
}
|
|
292
|
-
const rulesetCheckStates =
|
|
293
|
-
|
|
294
|
-
|
|
652
|
+
const rulesetCheckStates = [];
|
|
653
|
+
for (const ruleSet of this.ruleSet) {
|
|
654
|
+
const state = await ruleSet.check(resource, environment);
|
|
655
|
+
rulesetCheckStates.push(state);
|
|
656
|
+
if (AbilityCompare_1.default.and.isEqual(this.compareMethod) && AbilityMatch_1.default.mismatch.isEqual(state)) {
|
|
657
|
+
return this.matchState; // mismatch
|
|
658
|
+
}
|
|
659
|
+
if (AbilityCompare_1.default.or.isEqual(this.compareMethod) && AbilityMatch_1.default.match.isEqual(state)) {
|
|
660
|
+
this.matchState = AbilityMatch_1.default.match;
|
|
661
|
+
return this.matchState;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
295
664
|
if (AbilityCompare_1.default.and.isEqual(this.compareMethod)) {
|
|
296
|
-
if (rulesetCheckStates.every(
|
|
665
|
+
if (rulesetCheckStates.every(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
297
666
|
this.matchState = AbilityMatch_1.default.match;
|
|
298
667
|
}
|
|
299
668
|
}
|
|
300
669
|
if (AbilityCompare_1.default.or.isEqual(this.compareMethod)) {
|
|
301
|
-
if (rulesetCheckStates.some(
|
|
670
|
+
if (rulesetCheckStates.some(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
302
671
|
this.matchState = AbilityMatch_1.default.match;
|
|
303
672
|
}
|
|
304
673
|
}
|
|
305
674
|
return this.matchState;
|
|
306
675
|
}
|
|
676
|
+
explain() {
|
|
677
|
+
if (this.matchState === AbilityMatch_1.default.pending) {
|
|
678
|
+
throw new AbilityError_1.AbilityError('First, run the check method, then explain');
|
|
679
|
+
}
|
|
680
|
+
return new AbilityExplain_1.AbilityExplainPolicy(this);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Parses an array of policy configurations into an array of AbilityPolicy instances.
|
|
684
|
+
* @param configs - Array of policy configurations
|
|
685
|
+
* @returns Array of AbilityPolicy instances
|
|
686
|
+
*/
|
|
687
|
+
static fromJSONAll(configs) {
|
|
688
|
+
return AbilityJSONParser_1.AbilityJSONParser.parse(configs);
|
|
689
|
+
}
|
|
307
690
|
/**
|
|
308
691
|
* Parse the config JSON format to Policy class instance
|
|
309
692
|
*/
|
|
310
|
-
static
|
|
311
|
-
|
|
312
|
-
// Create the empty policy
|
|
313
|
-
const policy = new AbilityPolicy({
|
|
314
|
-
name,
|
|
315
|
-
id,
|
|
316
|
-
action,
|
|
317
|
-
effect: new AbilityPolicyEffect_1.default(effect),
|
|
318
|
-
});
|
|
319
|
-
policy.compareMethod = new AbilityCompare_1.default(compareMethod);
|
|
320
|
-
ruleSet.forEach(ruleSetConfig => {
|
|
321
|
-
policy.addRuleSet(AbilityRuleSet_1.default.parse(ruleSetConfig));
|
|
322
|
-
});
|
|
323
|
-
return policy;
|
|
693
|
+
static fromJSON(config) {
|
|
694
|
+
return AbilityJSONParser_1.AbilityJSONParser.parsePolicy(config);
|
|
324
695
|
}
|
|
325
|
-
|
|
326
|
-
return
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
};
|
|
696
|
+
static fromDSL(dsl) {
|
|
697
|
+
return new AbilityDSLParser_1.AbilityDSLParser(dsl).parse()[0];
|
|
698
|
+
}
|
|
699
|
+
toJSON() {
|
|
700
|
+
return AbilityJSONParser_1.AbilityJSONParser.toJSON([this])[0];
|
|
701
|
+
}
|
|
702
|
+
toString() {
|
|
703
|
+
return JSON.stringify(this.toJSON());
|
|
334
704
|
}
|
|
335
705
|
}
|
|
336
706
|
exports.AbilityPolicy = AbilityPolicy;
|
|
@@ -339,7 +709,7 @@ exports["default"] = AbilityPolicy;
|
|
|
339
709
|
|
|
340
710
|
/***/ }),
|
|
341
711
|
|
|
342
|
-
/***/
|
|
712
|
+
/***/ 179:
|
|
343
713
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
344
714
|
|
|
345
715
|
|
|
@@ -348,7 +718,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
348
718
|
};
|
|
349
719
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
350
720
|
exports.AbilityPolicyEffect = void 0;
|
|
351
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(
|
|
721
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
352
722
|
class AbilityPolicyEffect extends AbilityCode_1.default {
|
|
353
723
|
static deny = new AbilityPolicyEffect('deny');
|
|
354
724
|
static permit = new AbilityPolicyEffect('permit');
|
|
@@ -359,7 +729,7 @@ exports["default"] = AbilityPolicyEffect;
|
|
|
359
729
|
|
|
360
730
|
/***/ }),
|
|
361
731
|
|
|
362
|
-
/***/
|
|
732
|
+
/***/ 634:
|
|
363
733
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
364
734
|
|
|
365
735
|
|
|
@@ -368,90 +738,159 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
368
738
|
};
|
|
369
739
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
370
740
|
exports.AbilityResolver = void 0;
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
const
|
|
741
|
+
const AbilityError_1 = __webpack_require__(216);
|
|
742
|
+
const AbilityResult_1 = __webpack_require__(941);
|
|
743
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
374
744
|
class AbilityResolver {
|
|
375
745
|
policies;
|
|
376
|
-
|
|
746
|
+
cache;
|
|
747
|
+
constructor(
|
|
748
|
+
/**
|
|
749
|
+
* `Important!` The incorrect Resources type was intentionally passed to AbilityPolicy so that TypeScript could suggest the name of the permission and the structure of its resource in the parse method.
|
|
750
|
+
*/
|
|
751
|
+
policyOrListOfPolicies, options) {
|
|
752
|
+
const { cache } = options || {};
|
|
753
|
+
this.cache = cache;
|
|
377
754
|
this.policies = Array.isArray(policyOrListOfPolicies)
|
|
378
755
|
? policyOrListOfPolicies
|
|
379
756
|
: [policyOrListOfPolicies];
|
|
380
757
|
}
|
|
381
758
|
/**
|
|
382
|
-
* Resolve policy for the resource and
|
|
759
|
+
* Resolve policy for the resource and permission key
|
|
383
760
|
*
|
|
384
|
-
@param
|
|
761
|
+
* @param permission - Permission key
|
|
385
762
|
* @param resource - Resource
|
|
763
|
+
* @param environment
|
|
764
|
+
*/
|
|
765
|
+
async resolve(permission, resource, environment) {
|
|
766
|
+
const filteredPolicies = this.policies.filter(policy => AbilityResolver.isInPermissionContain(policy.permission, String(permission).replace(/^permission\./, '')));
|
|
767
|
+
for (const policy of filteredPolicies) {
|
|
768
|
+
const cacheKey = this.cache ? this.makeCacheKey(policy.id, resource, environment) : '';
|
|
769
|
+
// cache
|
|
770
|
+
if (this.cache) {
|
|
771
|
+
const cached = await this.cache.get(cacheKey);
|
|
772
|
+
if (cached !== undefined) {
|
|
773
|
+
policy.matchState = cached;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const policyMatchState = await policy.check(resource, environment);
|
|
778
|
+
if (policyMatchState === AbilityMatch_1.default.pending) {
|
|
779
|
+
throw new AbilityError_1.AbilityError(`The policy "${policy.name}" is still in a pending state. Make sure to call "check" to evaluate the policy before resolving permissions.`);
|
|
780
|
+
}
|
|
781
|
+
if (this.cache) {
|
|
782
|
+
await this.cache.set(cacheKey, policyMatchState);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return new AbilityResult_1.AbilityResult(filteredPolicies);
|
|
786
|
+
}
|
|
787
|
+
async enforce(permission, resource, environment) {
|
|
788
|
+
const result = await this.resolve(permission, resource, environment);
|
|
789
|
+
if (result.isDenied()) {
|
|
790
|
+
const policyName = result.getLastMatchedPolicy()?.name?.toString() || 'unknown';
|
|
791
|
+
throw new AbilityError_1.AbilityError(`Permission denied by policy "${policyName}"`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Check if the permission key is contained in another permission key
|
|
796
|
+
* @param permissionA - The first permission to check
|
|
797
|
+
* @param permissionB - The second permission to check
|
|
386
798
|
*/
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
799
|
+
static isInPermissionContain(permissionA, permissionB) {
|
|
800
|
+
const A = permissionA.split('.');
|
|
801
|
+
const B = permissionB.split('.');
|
|
802
|
+
const [longer, shorter] = A.length >= B.length ? [A, B] : [B, A];
|
|
803
|
+
return shorter.every((chunk, i) => {
|
|
804
|
+
return chunk === '*' || longer[i] === '*' || chunk === longer[i];
|
|
390
805
|
});
|
|
391
|
-
filteredPolicies.map(policy => policy.check(resource));
|
|
392
|
-
this.policies = filteredPolicies;
|
|
393
|
-
return this;
|
|
394
806
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
throw new AbilityError_1.AbilityError(resolver.getMatchedPolicy()?.name?.toString() || 'Unknown permission error');
|
|
807
|
+
makeCacheKey(policyId, resource, environment) {
|
|
808
|
+
if (!this.cache) {
|
|
809
|
+
return '';
|
|
399
810
|
}
|
|
811
|
+
return `policy:${policyId}:res:${this.cache.serialize(resource)}:env:${this.cache.serialize(environment)}`;
|
|
812
|
+
}
|
|
813
|
+
async invalidatePolicy(policyId) {
|
|
814
|
+
await this.cache?.deleteByPrefix(policyId);
|
|
815
|
+
}
|
|
816
|
+
async invalidateCache() {
|
|
817
|
+
await this.cache?.clear();
|
|
400
818
|
}
|
|
819
|
+
}
|
|
820
|
+
exports.AbilityResolver = AbilityResolver;
|
|
821
|
+
exports["default"] = AbilityResolver;
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
/***/ }),
|
|
825
|
+
|
|
826
|
+
/***/ 941:
|
|
827
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
831
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
832
|
+
};
|
|
833
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
834
|
+
exports.AbilityResult = void 0;
|
|
835
|
+
const AbilityExplain_1 = __webpack_require__(221);
|
|
836
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
837
|
+
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(179));
|
|
838
|
+
class AbilityResult {
|
|
401
839
|
/**
|
|
402
|
-
*
|
|
840
|
+
* Already checked policies (after call the policy.check())
|
|
841
|
+
*/
|
|
842
|
+
policies;
|
|
843
|
+
constructor(policies) {
|
|
844
|
+
this.policies = policies;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Returns a list of explanations for each policy involved in the ability evaluation.
|
|
848
|
+
* Each item describes how a specific policy contributed to the final permission result.
|
|
403
849
|
*
|
|
404
|
-
*
|
|
850
|
+
* Useful for debugging, logging, or building UI tools that visualize permission logic.
|
|
405
851
|
*/
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
852
|
+
explain() {
|
|
853
|
+
return this.policies.map(policy => {
|
|
854
|
+
return new AbilityExplain_1.AbilityExplainPolicy(policy);
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
getLastMatchedPolicy() {
|
|
858
|
+
for (let i = this.policies.length - 1; i >= 0; i--) {
|
|
859
|
+
if (this.policies[i].matchState.isEqual(AbilityMatch_1.default.match)) {
|
|
860
|
+
return this.policies[i];
|
|
410
861
|
}
|
|
411
|
-
return collect;
|
|
412
|
-
}, []);
|
|
413
|
-
if (effects.length) {
|
|
414
|
-
return effects[effects.length - 1];
|
|
415
862
|
}
|
|
416
863
|
return null;
|
|
417
864
|
}
|
|
418
|
-
|
|
419
|
-
const effect = this.
|
|
420
|
-
return effect
|
|
865
|
+
isAllowed() {
|
|
866
|
+
const effect = this.getLastEffectOfMatchedPolicy();
|
|
867
|
+
return effect?.isEqual(AbilityPolicyEffect_1.default.permit) ?? false;
|
|
421
868
|
}
|
|
422
|
-
|
|
423
|
-
const effect = this.
|
|
424
|
-
return effect
|
|
425
|
-
}
|
|
426
|
-
getMatchedPolicy() {
|
|
427
|
-
const matchedPolicies = this.policies.filter(policy => policy.matchState.isEqual(AbilityMatch_1.default.match));
|
|
428
|
-
const lastPolicy = matchedPolicies.length ? matchedPolicies[matchedPolicies.length - 1] : null;
|
|
429
|
-
return lastPolicy || null;
|
|
869
|
+
isDenied() {
|
|
870
|
+
const effect = this.getLastEffectOfMatchedPolicy();
|
|
871
|
+
return effect?.isEqual(AbilityPolicyEffect_1.default.deny) ?? true;
|
|
430
872
|
}
|
|
431
873
|
/**
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* @
|
|
874
|
+
* Get the last effect of the policy
|
|
875
|
+
*
|
|
876
|
+
* @returns {AbilityPolicyEffect | null}
|
|
435
877
|
*/
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}, []);
|
|
445
|
-
return c.every(Boolean);
|
|
878
|
+
getLastEffectOfMatchedPolicy() {
|
|
879
|
+
for (let i = this.policies.length - 1; i >= 0; i--) {
|
|
880
|
+
const p = this.policies[i];
|
|
881
|
+
if (p.matchState.isEqual(AbilityMatch_1.default.match)) {
|
|
882
|
+
return p.effect;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return null;
|
|
446
886
|
}
|
|
447
887
|
}
|
|
448
|
-
exports.
|
|
449
|
-
exports["default"] = AbilityResolver;
|
|
888
|
+
exports.AbilityResult = AbilityResult;
|
|
450
889
|
|
|
451
890
|
|
|
452
891
|
/***/ }),
|
|
453
892
|
|
|
454
|
-
/***/
|
|
893
|
+
/***/ 306:
|
|
455
894
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
456
895
|
|
|
457
896
|
|
|
@@ -460,8 +899,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
460
899
|
};
|
|
461
900
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
462
901
|
exports.AbilityRule = void 0;
|
|
463
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(
|
|
464
|
-
const AbilityCondition_1 = __importDefault(__webpack_require__(
|
|
902
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
903
|
+
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
904
|
+
const AbilityJSONParser_1 = __webpack_require__(909);
|
|
465
905
|
/**
|
|
466
906
|
* Represents a rule that defines a condition to be checked against a subject and resource.
|
|
467
907
|
*/
|
|
@@ -480,17 +920,17 @@ class AbilityRule {
|
|
|
480
920
|
state = AbilityMatch_1.default.pending;
|
|
481
921
|
/**
|
|
482
922
|
* Creates an instance of AbilityRule.
|
|
483
|
-
* @param {string}
|
|
484
|
-
* @param {string}
|
|
485
|
-
* @param {AbilityCondition}
|
|
486
|
-
* @param {string}
|
|
487
|
-
* @param {string}
|
|
923
|
+
* @param {string} params.id - The unique identifier of the rule.
|
|
924
|
+
* @param {string} params.name - The name of the rule.
|
|
925
|
+
* @param {AbilityCondition} params.condition - The condition to evaluate.
|
|
926
|
+
* @param {string} params.subject - The subject of the rule.
|
|
927
|
+
* @param {string} params.resource - The resource to compare against.
|
|
488
928
|
* @param params
|
|
489
929
|
*/
|
|
490
930
|
constructor(params) {
|
|
491
931
|
const { id, name, subject, resource, condition } = params;
|
|
492
|
-
this.
|
|
493
|
-
this.
|
|
932
|
+
this.name = name || `${JSON.stringify(subject)} ${condition.code} ${JSON.stringify(resource)}`;
|
|
933
|
+
this.id = id || this.name;
|
|
494
934
|
this.subject = subject;
|
|
495
935
|
this.resource = resource;
|
|
496
936
|
this.condition = condition;
|
|
@@ -498,54 +938,139 @@ class AbilityRule {
|
|
|
498
938
|
/**
|
|
499
939
|
* Check if the rule is matched
|
|
500
940
|
* @param resource - The resource to check
|
|
941
|
+
* @param environment
|
|
501
942
|
*/
|
|
502
|
-
check(resource) {
|
|
943
|
+
async check(resource, environment) {
|
|
503
944
|
let is = false;
|
|
504
|
-
const [
|
|
945
|
+
const [subjectValue, resourceValue] = this.extractValues(resource, environment);
|
|
946
|
+
const isValue = (v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
|
|
947
|
+
// equals
|
|
948
|
+
if (AbilityCondition_1.default.equals.isEqual(this.condition)) {
|
|
949
|
+
is = subjectValue === resourceValue;
|
|
950
|
+
}
|
|
951
|
+
// not equals
|
|
952
|
+
if (AbilityCondition_1.default.not_equals.isEqual(this.condition)) {
|
|
953
|
+
is = subjectValue !== resourceValue;
|
|
954
|
+
}
|
|
955
|
+
// less than
|
|
505
956
|
if (AbilityCondition_1.default.less_than.isEqual(this.condition)) {
|
|
506
|
-
|
|
957
|
+
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
958
|
+
is = subjectValue < resourceValue;
|
|
959
|
+
}
|
|
507
960
|
}
|
|
961
|
+
// less or equal
|
|
508
962
|
if (AbilityCondition_1.default.less_or_equal.isEqual(this.condition)) {
|
|
509
|
-
|
|
963
|
+
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
964
|
+
is = subjectValue <= resourceValue;
|
|
965
|
+
}
|
|
510
966
|
}
|
|
511
|
-
|
|
512
|
-
|
|
967
|
+
// more than
|
|
968
|
+
if (AbilityCondition_1.default.greater_than.isEqual(this.condition)) {
|
|
969
|
+
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
970
|
+
is = subjectValue > resourceValue;
|
|
971
|
+
}
|
|
513
972
|
}
|
|
514
|
-
|
|
515
|
-
|
|
973
|
+
// more or equal
|
|
974
|
+
if (AbilityCondition_1.default.greater_or_equal.isEqual(this.condition)) {
|
|
975
|
+
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
976
|
+
is = subjectValue >= resourceValue;
|
|
977
|
+
}
|
|
516
978
|
}
|
|
517
|
-
|
|
518
|
-
|
|
979
|
+
// in
|
|
980
|
+
if (AbilityCondition_1.default.in.isEqual(this.condition)) {
|
|
981
|
+
// value in array
|
|
982
|
+
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
983
|
+
is = resourceValue.includes(subjectValue);
|
|
984
|
+
}
|
|
985
|
+
// array intersects array
|
|
986
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
987
|
+
is = subjectValue.some(v => resourceValue.includes(v));
|
|
988
|
+
}
|
|
519
989
|
}
|
|
520
|
-
|
|
521
|
-
|
|
990
|
+
// not in
|
|
991
|
+
if (AbilityCondition_1.default.not_in.isEqual(this.condition)) {
|
|
992
|
+
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
993
|
+
is = !resourceValue.includes(subjectValue);
|
|
994
|
+
}
|
|
995
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
996
|
+
is = !subjectValue.some(v => resourceValue.includes(v));
|
|
997
|
+
}
|
|
522
998
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
999
|
+
// contains
|
|
1000
|
+
if (AbilityCondition_1.default.contains.isEqual(this.condition)) {
|
|
1001
|
+
// array contains value
|
|
1002
|
+
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
1003
|
+
is = subjectValue.includes(resourceValue);
|
|
1004
|
+
}
|
|
1005
|
+
// array intersects array
|
|
1006
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1007
|
+
is = subjectValue.some(v => resourceValue.includes(v));
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
// not contains
|
|
1011
|
+
if (AbilityCondition_1.default.not_contains.isEqual(this.condition)) {
|
|
1012
|
+
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
1013
|
+
is = !subjectValue.includes(resourceValue);
|
|
1014
|
+
}
|
|
1015
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1016
|
+
is = !subjectValue.some(v => resourceValue.includes(v));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
// length equals
|
|
1020
|
+
if (AbilityCondition_1.default.length_equals.isEqual(this.condition)) {
|
|
1021
|
+
// foo.bar == n
|
|
1022
|
+
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
1023
|
+
is = String(subjectValue).length === resourceValue;
|
|
527
1024
|
}
|
|
528
|
-
//
|
|
529
|
-
if ((
|
|
530
|
-
is =
|
|
1025
|
+
// ['foo', 'bar'] = n
|
|
1026
|
+
else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
|
|
1027
|
+
is = subjectValue.length === resourceValue;
|
|
531
1028
|
}
|
|
532
|
-
// [
|
|
533
|
-
if ((
|
|
534
|
-
is =
|
|
1029
|
+
// ['foo', 'bar'] = ['baz', 'taz']
|
|
1030
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1031
|
+
is = subjectValue.length === resourceValue.length;
|
|
1032
|
+
}
|
|
1033
|
+
// 'foo' = 'bar'
|
|
1034
|
+
else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
|
|
1035
|
+
is = subjectValue.length === resourceValue.length;
|
|
535
1036
|
}
|
|
536
1037
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1038
|
+
// length greater than
|
|
1039
|
+
if (AbilityCondition_1.default.length_greater_than.isEqual(this.condition)) {
|
|
1040
|
+
// foo.bar > n
|
|
1041
|
+
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
1042
|
+
is = String(subjectValue).length > resourceValue;
|
|
1043
|
+
}
|
|
1044
|
+
// ['foo', 'bar'] > n
|
|
1045
|
+
else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
|
|
1046
|
+
is = subjectValue.length > resourceValue;
|
|
1047
|
+
}
|
|
1048
|
+
// ['foo', 'bar'] > ['baz', 'taz']
|
|
1049
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1050
|
+
is = subjectValue.length > resourceValue.length;
|
|
541
1051
|
}
|
|
542
|
-
//
|
|
543
|
-
if (
|
|
544
|
-
is =
|
|
1052
|
+
// 'foo' > 'bar'
|
|
1053
|
+
else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
|
|
1054
|
+
is = subjectValue.length > resourceValue.length;
|
|
545
1055
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1056
|
+
}
|
|
1057
|
+
// length greater than
|
|
1058
|
+
if (AbilityCondition_1.default.length_less_than.isEqual(this.condition)) {
|
|
1059
|
+
// foo.bar < n
|
|
1060
|
+
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
1061
|
+
is = String(subjectValue).length < resourceValue;
|
|
1062
|
+
}
|
|
1063
|
+
// ['foo', 'bar'] < n
|
|
1064
|
+
else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
|
|
1065
|
+
is = subjectValue.length < resourceValue;
|
|
1066
|
+
}
|
|
1067
|
+
// ['foo', 'bar'] < ['baz', 'taz']
|
|
1068
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1069
|
+
is = subjectValue.length < resourceValue.length;
|
|
1070
|
+
}
|
|
1071
|
+
// 'foo' < 'bar'
|
|
1072
|
+
else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
|
|
1073
|
+
is = subjectValue.length < resourceValue.length;
|
|
549
1074
|
}
|
|
550
1075
|
}
|
|
551
1076
|
this.state = is ? AbilityMatch_1.default.match : AbilityMatch_1.default.mismatch;
|
|
@@ -554,29 +1079,44 @@ class AbilityRule {
|
|
|
554
1079
|
/**
|
|
555
1080
|
* Extract values from the resourceData
|
|
556
1081
|
* @param resourceData - The resourceData to extract values from
|
|
1082
|
+
* @param environment - Environment data
|
|
557
1083
|
*/
|
|
558
|
-
extractValues(resourceData) {
|
|
559
|
-
let
|
|
560
|
-
let
|
|
561
|
-
if (resourceData === null || typeof resourceData === 'undefined')
|
|
1084
|
+
extractValues(resourceData, environment) {
|
|
1085
|
+
let subjectValue;
|
|
1086
|
+
let resourceValue;
|
|
1087
|
+
if ((resourceData === null || typeof resourceData === 'undefined') &&
|
|
1088
|
+
(environment === null || typeof environment === 'undefined')) {
|
|
562
1089
|
return [NaN, NaN];
|
|
563
1090
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1091
|
+
// left side resolve
|
|
1092
|
+
if (this.subject.includes('.')) {
|
|
1093
|
+
// if is environment
|
|
1094
|
+
if (this.subject.startsWith('env.') && typeof environment !== 'undefined') {
|
|
1095
|
+
subjectValue = this.getDotNotationValue(environment, this.subject.replace(/^env\./, ''));
|
|
1096
|
+
// if is resource
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
subjectValue = this.getDotNotationValue(resourceData, this.subject);
|
|
1100
|
+
}
|
|
569
1101
|
}
|
|
570
1102
|
else {
|
|
571
|
-
|
|
1103
|
+
subjectValue = this.subject;
|
|
572
1104
|
}
|
|
573
|
-
|
|
574
|
-
|
|
1105
|
+
// right side resolve
|
|
1106
|
+
if (typeof this.resource === 'string' && this.resource.includes('.')) {
|
|
1107
|
+
// if is environment
|
|
1108
|
+
if (this.resource.startsWith('env.') && typeof environment !== 'undefined') {
|
|
1109
|
+
resourceValue = this.getDotNotationValue(environment, this.resource.replace(/^env\./, ''));
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
// if is resource
|
|
1113
|
+
resourceValue = this.getDotNotationValue(resourceData, this.resource);
|
|
1114
|
+
}
|
|
575
1115
|
}
|
|
576
1116
|
else {
|
|
577
|
-
|
|
1117
|
+
resourceValue = this.resource;
|
|
578
1118
|
}
|
|
579
|
-
return [
|
|
1119
|
+
return [subjectValue, resourceValue];
|
|
580
1120
|
}
|
|
581
1121
|
/**
|
|
582
1122
|
* Get the value of the object by dot notation
|
|
@@ -587,7 +1127,7 @@ class AbilityRule {
|
|
|
587
1127
|
const arr = desc.split('.');
|
|
588
1128
|
while (arr.length && resource) {
|
|
589
1129
|
const comp = arr.shift() || '';
|
|
590
|
-
const match = new RegExp('(.+)\\[([0-9]*)
|
|
1130
|
+
const match = new RegExp('(.+)\\[([0-9]*)]').exec(comp);
|
|
591
1131
|
if (match !== null && match.length == 3) {
|
|
592
1132
|
const arrayData = {
|
|
593
1133
|
arrName: match[1],
|
|
@@ -606,63 +1146,124 @@ class AbilityRule {
|
|
|
606
1146
|
}
|
|
607
1147
|
return resource;
|
|
608
1148
|
}
|
|
609
|
-
|
|
610
|
-
|
|
1149
|
+
toString() {
|
|
1150
|
+
return `AbilityRule: ${this.name} condition: ${this.condition.code} subject: "${this.subject?.toString()}" resource: "${this.resource?.toString()}"`;
|
|
1151
|
+
}
|
|
1152
|
+
static fromJSON(config) {
|
|
1153
|
+
return AbilityJSONParser_1.AbilityJSONParser.parseRule(config);
|
|
1154
|
+
}
|
|
1155
|
+
static equals(subject, resource) {
|
|
611
1156
|
return new AbilityRule({
|
|
612
|
-
|
|
613
|
-
name,
|
|
1157
|
+
condition: AbilityCondition_1.default.equals,
|
|
614
1158
|
subject,
|
|
615
1159
|
resource,
|
|
616
|
-
condition: new AbilityCondition_1.default(condition),
|
|
617
1160
|
});
|
|
618
1161
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
name: this.name,
|
|
626
|
-
subject: this.subject,
|
|
627
|
-
resource: this.resource,
|
|
628
|
-
condition: this.condition.code,
|
|
629
|
-
};
|
|
1162
|
+
static notEquals(subject, resource) {
|
|
1163
|
+
return new AbilityRule({
|
|
1164
|
+
condition: AbilityCondition_1.default.not_equals,
|
|
1165
|
+
subject,
|
|
1166
|
+
resource,
|
|
1167
|
+
});
|
|
630
1168
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1169
|
+
static contains(subject, resource) {
|
|
1170
|
+
return new AbilityRule({
|
|
1171
|
+
condition: AbilityCondition_1.default.contains,
|
|
1172
|
+
subject,
|
|
1173
|
+
resource,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
static notContains(subject, resource) {
|
|
1177
|
+
return new AbilityRule({
|
|
1178
|
+
condition: AbilityCondition_1.default.not_contains,
|
|
1179
|
+
subject,
|
|
1180
|
+
resource,
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
static notIn(subject, resource) {
|
|
1184
|
+
return new AbilityRule({
|
|
1185
|
+
condition: AbilityCondition_1.default.not_in,
|
|
1186
|
+
subject,
|
|
1187
|
+
resource,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
static in(subject, resource) {
|
|
1191
|
+
return new AbilityRule({
|
|
1192
|
+
condition: AbilityCondition_1.default.in,
|
|
1193
|
+
subject,
|
|
1194
|
+
resource,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
static notEqual(subject, resource) {
|
|
1198
|
+
return new AbilityRule({
|
|
1199
|
+
condition: AbilityCondition_1.default.not_equals,
|
|
1200
|
+
subject,
|
|
1201
|
+
resource,
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
static lessThan(subject, resource) {
|
|
1205
|
+
return new AbilityRule({
|
|
1206
|
+
condition: AbilityCondition_1.default.less_than,
|
|
1207
|
+
subject,
|
|
1208
|
+
resource,
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
static lessOrEqual(subject, resource) {
|
|
1212
|
+
return new AbilityRule({
|
|
1213
|
+
condition: AbilityCondition_1.default.less_or_equal,
|
|
1214
|
+
subject,
|
|
1215
|
+
resource,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
static moreThan(subject, resource) {
|
|
1219
|
+
return new AbilityRule({
|
|
1220
|
+
condition: AbilityCondition_1.default.greater_than,
|
|
1221
|
+
subject,
|
|
1222
|
+
resource,
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
static moreOrEqual(subject, resource) {
|
|
1226
|
+
return new AbilityRule({
|
|
1227
|
+
condition: AbilityCondition_1.default.greater_or_equal,
|
|
1228
|
+
subject,
|
|
1229
|
+
resource,
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
exports.AbilityRule = AbilityRule;
|
|
1234
|
+
exports["default"] = AbilityRule;
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
/***/ }),
|
|
1238
|
+
|
|
1239
|
+
/***/ 56:
|
|
1240
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1244
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1245
|
+
};
|
|
1246
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1247
|
+
exports.AbilityRuleSet = void 0;
|
|
1248
|
+
const AbilityCompare_1 = __importDefault(__webpack_require__(413));
|
|
1249
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
1250
|
+
const AbilityJSONParser_1 = __webpack_require__(909);
|
|
1251
|
+
class AbilityRuleSet {
|
|
1252
|
+
state = AbilityMatch_1.default.pending;
|
|
1253
|
+
/**
|
|
1254
|
+
* List of rules
|
|
1255
|
+
*/
|
|
1256
|
+
rules = [];
|
|
1257
|
+
/**
|
|
1258
|
+
* Rules compare method.\
|
|
1259
|
+
* For the «and» method the rule will be permitted if all\
|
|
1260
|
+
* rules will be returns «permit» status and for the «or» - if\
|
|
1261
|
+
* one of the rules returns as «permit»
|
|
1262
|
+
*/
|
|
1263
|
+
compareMethod = AbilityCompare_1.default.and;
|
|
1264
|
+
/**
|
|
1265
|
+
* Group name
|
|
1266
|
+
*/
|
|
666
1267
|
name;
|
|
667
1268
|
/**
|
|
668
1269
|
* Group ID
|
|
@@ -670,8 +1271,8 @@ class AbilityRuleSet {
|
|
|
670
1271
|
id;
|
|
671
1272
|
constructor(params) {
|
|
672
1273
|
const { name, id, compareMethod } = params;
|
|
673
|
-
this.name = name;
|
|
674
|
-
this.id = id;
|
|
1274
|
+
this.name = name || '';
|
|
1275
|
+
this.id = id || this.name;
|
|
675
1276
|
this.compareMethod = compareMethod;
|
|
676
1277
|
}
|
|
677
1278
|
addRule(rule) {
|
|
@@ -682,50 +1283,53 @@ class AbilityRuleSet {
|
|
|
682
1283
|
rules.forEach(rule => this.addRule(rule));
|
|
683
1284
|
return this;
|
|
684
1285
|
}
|
|
685
|
-
check(resources) {
|
|
1286
|
+
async check(resources, environment) {
|
|
686
1287
|
this.state = AbilityMatch_1.default.mismatch;
|
|
687
1288
|
if (!this.rules.length) {
|
|
688
1289
|
return this.state;
|
|
689
1290
|
}
|
|
690
|
-
const ruleCheckStates =
|
|
691
|
-
|
|
692
|
-
|
|
1291
|
+
const ruleCheckStates = [];
|
|
1292
|
+
for (const rule of this.rules) {
|
|
1293
|
+
const state = await rule.check(resources, environment);
|
|
1294
|
+
ruleCheckStates.push(state);
|
|
1295
|
+
if (AbilityCompare_1.default.and.isEqual(this.compareMethod) && AbilityMatch_1.default.mismatch.isEqual(state)) {
|
|
1296
|
+
return this.state; // mismatch
|
|
1297
|
+
}
|
|
1298
|
+
if (AbilityCompare_1.default.or.isEqual(this.compareMethod) && AbilityMatch_1.default.match.isEqual(state)) {
|
|
1299
|
+
this.state = AbilityMatch_1.default.match;
|
|
1300
|
+
return this.state;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
693
1303
|
if (AbilityCompare_1.default.and.isEqual(this.compareMethod)) {
|
|
694
|
-
if (ruleCheckStates.every(
|
|
1304
|
+
if (ruleCheckStates.every(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
695
1305
|
this.state = AbilityMatch_1.default.match;
|
|
696
1306
|
}
|
|
697
1307
|
}
|
|
698
1308
|
if (AbilityCompare_1.default.or.isEqual(this.compareMethod)) {
|
|
699
|
-
if (ruleCheckStates.some(
|
|
1309
|
+
if (ruleCheckStates.some(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
700
1310
|
this.state = AbilityMatch_1.default.match;
|
|
701
1311
|
}
|
|
702
1312
|
}
|
|
703
1313
|
return this.state;
|
|
704
1314
|
}
|
|
1315
|
+
toString() {
|
|
1316
|
+
return `AbilityRuleSet: ${this.name} compareMethod: ${this.compareMethod.code}, rules: ${this.rules.map(rule => rule.toString()).join('\n')}`;
|
|
1317
|
+
}
|
|
705
1318
|
/**
|
|
706
1319
|
* Parse the config JSON format to Group class instance
|
|
707
1320
|
*/
|
|
708
|
-
static
|
|
709
|
-
|
|
710
|
-
const ruleSet = new AbilityRuleSet({
|
|
711
|
-
compareMethod: new AbilityCompare_1.default(compareMethod),
|
|
712
|
-
name,
|
|
713
|
-
id,
|
|
714
|
-
});
|
|
715
|
-
// Adding rules if exists
|
|
716
|
-
if (rules && rules.length > 0) {
|
|
717
|
-
const abilityRules = rules.map(ruleConfig => AbilityRule_1.default.parse(ruleConfig));
|
|
718
|
-
ruleSet.addRules(abilityRules);
|
|
719
|
-
}
|
|
720
|
-
return ruleSet;
|
|
1321
|
+
static fromJSON(config) {
|
|
1322
|
+
return AbilityJSONParser_1.AbilityJSONParser.parseRuleSet(config);
|
|
721
1323
|
}
|
|
722
|
-
|
|
723
|
-
return {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1324
|
+
static and(rules) {
|
|
1325
|
+
return new AbilityRuleSet({
|
|
1326
|
+
compareMethod: AbilityCompare_1.default.and,
|
|
1327
|
+
}).addRules(rules);
|
|
1328
|
+
}
|
|
1329
|
+
static or(rules) {
|
|
1330
|
+
return new AbilityRuleSet({
|
|
1331
|
+
compareMethod: AbilityCompare_1.default.or,
|
|
1332
|
+
}).addRules(rules);
|
|
729
1333
|
}
|
|
730
1334
|
}
|
|
731
1335
|
exports.AbilityRuleSet = AbilityRuleSet;
|
|
@@ -753,17 +1357,1207 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
753
1357
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
754
1358
|
};
|
|
755
1359
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
756
|
-
__exportStar(__webpack_require__(
|
|
757
|
-
__exportStar(__webpack_require__(
|
|
758
|
-
__exportStar(__webpack_require__(
|
|
759
|
-
__exportStar(__webpack_require__(
|
|
1360
|
+
__exportStar(__webpack_require__(301), exports);
|
|
1361
|
+
__exportStar(__webpack_require__(413), exports);
|
|
1362
|
+
__exportStar(__webpack_require__(167), exports);
|
|
1363
|
+
__exportStar(__webpack_require__(216), exports);
|
|
1364
|
+
__exportStar(__webpack_require__(247), exports);
|
|
1365
|
+
__exportStar(__webpack_require__(147), exports);
|
|
1366
|
+
__exportStar(__webpack_require__(278), exports);
|
|
1367
|
+
__exportStar(__webpack_require__(179), exports);
|
|
1368
|
+
__exportStar(__webpack_require__(634), exports);
|
|
1369
|
+
__exportStar(__webpack_require__(306), exports);
|
|
1370
|
+
__exportStar(__webpack_require__(56), exports);
|
|
1371
|
+
__exportStar(__webpack_require__(221), exports);
|
|
1372
|
+
__exportStar(__webpack_require__(941), exports);
|
|
1373
|
+
__exportStar(__webpack_require__(6), exports);
|
|
1374
|
+
__exportStar(__webpack_require__(829), exports);
|
|
760
1375
|
__exportStar(__webpack_require__(909), exports);
|
|
761
|
-
__exportStar(__webpack_require__(
|
|
762
|
-
__exportStar(__webpack_require__(
|
|
763
|
-
__exportStar(__webpack_require__(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1376
|
+
__exportStar(__webpack_require__(577), exports);
|
|
1377
|
+
__exportStar(__webpack_require__(10), exports);
|
|
1378
|
+
__exportStar(__webpack_require__(325), exports);
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
/***/ }),
|
|
1382
|
+
|
|
1383
|
+
/***/ 10:
|
|
1384
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1388
|
+
exports.AbilityDSLLexer = void 0;
|
|
1389
|
+
const AbilityDSLToken_1 = __webpack_require__(325);
|
|
1390
|
+
class AbilityDSLLexer {
|
|
1391
|
+
input;
|
|
1392
|
+
pos = 0;
|
|
1393
|
+
tokens = [];
|
|
1394
|
+
line = 1;
|
|
1395
|
+
column = 1;
|
|
1396
|
+
// Список ключевых слов
|
|
1397
|
+
keywords = new Set([
|
|
1398
|
+
'if',
|
|
1399
|
+
'all',
|
|
1400
|
+
'any',
|
|
1401
|
+
'of',
|
|
1402
|
+
'permit',
|
|
1403
|
+
'allow',
|
|
1404
|
+
'deny',
|
|
1405
|
+
'forbidden',
|
|
1406
|
+
'true',
|
|
1407
|
+
'false',
|
|
1408
|
+
'null',
|
|
1409
|
+
'contains',
|
|
1410
|
+
'includes',
|
|
1411
|
+
'length',
|
|
1412
|
+
'has',
|
|
1413
|
+
'in',
|
|
1414
|
+
'gt',
|
|
1415
|
+
'lt',
|
|
1416
|
+
'gte',
|
|
1417
|
+
'lte',
|
|
1418
|
+
'equals',
|
|
1419
|
+
'greater',
|
|
1420
|
+
'less',
|
|
1421
|
+
'not',
|
|
1422
|
+
'is',
|
|
1423
|
+
'or',
|
|
1424
|
+
'than',
|
|
1425
|
+
]);
|
|
1426
|
+
constructor(input) {
|
|
1427
|
+
this.input = input;
|
|
1428
|
+
}
|
|
1429
|
+
tokenize() {
|
|
1430
|
+
while (!this.isAtEnd()) {
|
|
1431
|
+
this.skipWhitespace();
|
|
1432
|
+
if (this.isAtEnd())
|
|
1433
|
+
break;
|
|
1434
|
+
const char = this.peek();
|
|
1435
|
+
if (char === '#') {
|
|
1436
|
+
this.tokens.push(this.readComment());
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (char === '"' || char === "'") {
|
|
1440
|
+
this.tokens.push(this.readString());
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (this.isDigit(char)) {
|
|
1444
|
+
this.tokens.push(this.readNumber());
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (this.isSymbol(char)) {
|
|
1448
|
+
this.tokens.push(this.readSymbol());
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
if (this.isAlpha(char)) {
|
|
1452
|
+
this.tokens.push(this.readWord());
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
throw new Error(`Unexpected character '${char}' at ${this.line}:${this.column}`);
|
|
1456
|
+
}
|
|
1457
|
+
this.tokens.push(new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.EOF, '', this.line, this.column));
|
|
1458
|
+
return this.tokens;
|
|
1459
|
+
}
|
|
1460
|
+
readComment() {
|
|
1461
|
+
const startLine = this.line;
|
|
1462
|
+
const startColumn = this.column;
|
|
1463
|
+
this.advance(); // skip '#'
|
|
1464
|
+
let value = '';
|
|
1465
|
+
while (!this.isAtEnd() && !this.isNewline()) {
|
|
1466
|
+
value += this.advance();
|
|
1467
|
+
}
|
|
1468
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.COMMENT, value.trim(), startLine, startColumn);
|
|
1469
|
+
}
|
|
1470
|
+
readString() {
|
|
1471
|
+
const startLine = this.line;
|
|
1472
|
+
const startColumn = this.column;
|
|
1473
|
+
const quote = this.advance();
|
|
1474
|
+
let value = '';
|
|
1475
|
+
let escaped = false;
|
|
1476
|
+
while (!this.isAtEnd()) {
|
|
1477
|
+
const char = this.advance();
|
|
1478
|
+
if (escaped) {
|
|
1479
|
+
value += char;
|
|
1480
|
+
escaped = false;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
if (char === '\\') {
|
|
1484
|
+
escaped = true;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (char === quote) {
|
|
1488
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.STRING, value, startLine, startColumn);
|
|
1489
|
+
}
|
|
1490
|
+
value += char;
|
|
1491
|
+
}
|
|
1492
|
+
throw new Error(`Unterminated string at ${startLine}:${startColumn}`);
|
|
1493
|
+
}
|
|
1494
|
+
readNumber() {
|
|
1495
|
+
const startLine = this.line;
|
|
1496
|
+
const startColumn = this.column;
|
|
1497
|
+
const start = this.pos;
|
|
1498
|
+
while (!this.isAtEnd() && this.isDigit(this.peek())) {
|
|
1499
|
+
this.advance();
|
|
1500
|
+
}
|
|
1501
|
+
const value = this.input.slice(start, this.pos);
|
|
1502
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.NUMBER, value, startLine, startColumn);
|
|
1503
|
+
}
|
|
1504
|
+
readSymbol() {
|
|
1505
|
+
const startLine = this.line;
|
|
1506
|
+
const startColumn = this.column;
|
|
1507
|
+
const char = this.advance();
|
|
1508
|
+
switch (char) {
|
|
1509
|
+
case '.':
|
|
1510
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.DOT, char, startLine, startColumn);
|
|
1511
|
+
case ':':
|
|
1512
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.COLON, char, startLine, startColumn);
|
|
1513
|
+
case ',':
|
|
1514
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.COMMA, char, startLine, startColumn);
|
|
1515
|
+
case '[':
|
|
1516
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.LBRACKET, char, startLine, startColumn);
|
|
1517
|
+
case ']':
|
|
1518
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.RBRACKET, char, startLine, startColumn);
|
|
1519
|
+
case '>':
|
|
1520
|
+
if (this.peek() === '=') {
|
|
1521
|
+
this.advance();
|
|
1522
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '>=', startLine, startColumn);
|
|
1523
|
+
}
|
|
1524
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '>', startLine, startColumn);
|
|
1525
|
+
case '<':
|
|
1526
|
+
if (this.peek() === '=') {
|
|
1527
|
+
this.advance();
|
|
1528
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '<=', startLine, startColumn);
|
|
1529
|
+
}
|
|
1530
|
+
if (this.peek() === '>') {
|
|
1531
|
+
this.advance();
|
|
1532
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '<>', startLine, startColumn);
|
|
1533
|
+
}
|
|
1534
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '<', startLine, startColumn);
|
|
1535
|
+
case '=':
|
|
1536
|
+
if (this.peek() === '=') {
|
|
1537
|
+
this.advance();
|
|
1538
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '==', startLine, startColumn);
|
|
1539
|
+
}
|
|
1540
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '=', startLine, startColumn);
|
|
1541
|
+
case '!':
|
|
1542
|
+
if (this.peek() === '=') {
|
|
1543
|
+
this.advance();
|
|
1544
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '!=', startLine, startColumn);
|
|
1545
|
+
}
|
|
1546
|
+
throw new Error(`Unexpected symbol '!' at ${this.line}:${this.column}`);
|
|
1547
|
+
default:
|
|
1548
|
+
throw new Error(`Unknown symbol '${char}' at ${this.line}:${this.column}`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
readWord() {
|
|
1552
|
+
const startLine = this.line;
|
|
1553
|
+
const startColumn = this.column;
|
|
1554
|
+
const start = this.pos;
|
|
1555
|
+
// Первый сегмент
|
|
1556
|
+
while (!this.isAtEnd() && /[a-zA-Z0-9_*]/.test(this.peek())) {
|
|
1557
|
+
this.advance();
|
|
1558
|
+
}
|
|
1559
|
+
// Сегменты через точку
|
|
1560
|
+
while (!this.isAtEnd() && this.peek() === '.') {
|
|
1561
|
+
this.advance(); // dot
|
|
1562
|
+
if (!/[a-zA-Z_*]/.test(this.peek())) {
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1565
|
+
while (!this.isAtEnd() && /[a-zA-Z0-9_*]/.test(this.peek())) {
|
|
1566
|
+
this.advance();
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
const word = this.input.slice(start, this.pos);
|
|
1570
|
+
// Если есть точка — это путь (identifier или permission)
|
|
1571
|
+
if (word.includes('.')) {
|
|
1572
|
+
const last = this.tokens[this.tokens.length - 1];
|
|
1573
|
+
if (last?.code === AbilityDSLToken_1.AbilityDSLToken.EFFECT) {
|
|
1574
|
+
if (word.startsWith('permission.')) {
|
|
1575
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.PERMISSION, word, startLine, startColumn);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER, word, startLine, startColumn);
|
|
1579
|
+
}
|
|
1580
|
+
// Ключевые слова
|
|
1581
|
+
if (this.keywords.has(word)) {
|
|
1582
|
+
// Эффекты
|
|
1583
|
+
if (word === 'permit' || word === 'allow') {
|
|
1584
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.EFFECT, 'permit', startLine, startColumn);
|
|
1585
|
+
}
|
|
1586
|
+
if (word === 'deny' || word === 'forbidden') {
|
|
1587
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.EFFECT, 'deny', startLine, startColumn);
|
|
1588
|
+
}
|
|
1589
|
+
// Групповые ключевые слова
|
|
1590
|
+
if (word === 'all') {
|
|
1591
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.ALL, word, startLine, startColumn);
|
|
1592
|
+
}
|
|
1593
|
+
if (word === 'any') {
|
|
1594
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.ANY, word, startLine, startColumn);
|
|
1595
|
+
}
|
|
1596
|
+
if (word === 'of') {
|
|
1597
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.OF, word, startLine, startColumn);
|
|
1598
|
+
}
|
|
1599
|
+
if (word === 'if') {
|
|
1600
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.IF, word, startLine, startColumn);
|
|
1601
|
+
}
|
|
1602
|
+
// Булевы и null
|
|
1603
|
+
if (word === 'true' || word === 'false') {
|
|
1604
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.BOOLEAN, word, startLine, startColumn);
|
|
1605
|
+
}
|
|
1606
|
+
if (word === 'null') {
|
|
1607
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.NULL, word, startLine, startColumn);
|
|
1608
|
+
}
|
|
1609
|
+
// Остальные ключевые слова (contains, in, equals, greater, less, not, is, or, than, equal)
|
|
1610
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.KEYWORD, word, startLine, startColumn);
|
|
1611
|
+
}
|
|
1612
|
+
// Если после EFFECT и нет точки — действие (например, "create")
|
|
1613
|
+
const lastToken = this.tokens[this.tokens.length - 1];
|
|
1614
|
+
if (lastToken?.code === AbilityDSLToken_1.AbilityDSLToken.EFFECT) {
|
|
1615
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.PERMISSION, word, startLine, startColumn);
|
|
1616
|
+
}
|
|
1617
|
+
// Обычный идентификатор
|
|
1618
|
+
return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER, word, startLine, startColumn);
|
|
1619
|
+
}
|
|
1620
|
+
skipWhitespace() {
|
|
1621
|
+
while (!this.isAtEnd() && /\s/.test(this.peek())) {
|
|
1622
|
+
this.advance();
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
isDigit(char) {
|
|
1626
|
+
return char >= '0' && char <= '9';
|
|
1627
|
+
}
|
|
1628
|
+
isAlpha(char) {
|
|
1629
|
+
return /[a-zA-Z_]/.test(char);
|
|
1630
|
+
}
|
|
1631
|
+
isSymbol(char) {
|
|
1632
|
+
return ['.', ':', ',', '[', ']', '>', '<', '=', '!'].includes(char);
|
|
1633
|
+
}
|
|
1634
|
+
isNewline() {
|
|
1635
|
+
return this.peek() === '\n';
|
|
1636
|
+
}
|
|
1637
|
+
peek() {
|
|
1638
|
+
return this.input[this.pos];
|
|
1639
|
+
}
|
|
1640
|
+
advance() {
|
|
1641
|
+
const ch = this.input[this.pos++];
|
|
1642
|
+
if (ch === '\n') {
|
|
1643
|
+
this.line++;
|
|
1644
|
+
this.column = 1;
|
|
1645
|
+
}
|
|
1646
|
+
else {
|
|
1647
|
+
this.column++;
|
|
1648
|
+
}
|
|
1649
|
+
return ch;
|
|
1650
|
+
}
|
|
1651
|
+
isAtEnd() {
|
|
1652
|
+
return this.pos >= this.input.length;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
exports.AbilityDSLLexer = AbilityDSLLexer;
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
/***/ }),
|
|
1659
|
+
|
|
1660
|
+
/***/ 577:
|
|
1661
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1665
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1666
|
+
};
|
|
1667
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1668
|
+
exports.AbilityDSLParser = void 0;
|
|
1669
|
+
const AbilityCompare_1 = __importDefault(__webpack_require__(413));
|
|
1670
|
+
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
1671
|
+
const AbilityPolicy_1 = __importDefault(__webpack_require__(278));
|
|
1672
|
+
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(179));
|
|
1673
|
+
const AbilityRule_1 = __importDefault(__webpack_require__(306));
|
|
1674
|
+
const AbilityRuleSet_1 = __importDefault(__webpack_require__(56));
|
|
1675
|
+
const AbilityDSLLexer_1 = __webpack_require__(10);
|
|
1676
|
+
const AbilityDSLToken_1 = __webpack_require__(325);
|
|
1677
|
+
const AbilityDSLSyntaxError_1 = __webpack_require__(883);
|
|
1678
|
+
/**
|
|
1679
|
+
* Parser for the Ability DSL.
|
|
1680
|
+
*
|
|
1681
|
+
* Converts a DSL string into one or more AbilityPolicy instances.
|
|
1682
|
+
* The grammar follows the structure:
|
|
1683
|
+
*
|
|
1684
|
+
* <effect> <permission> if <group> [ <group> ... ]
|
|
1685
|
+
*
|
|
1686
|
+
* where <group> is either "all of:" or "any of:", followed by a colon,
|
|
1687
|
+
* and then a list of rules (one per line).
|
|
1688
|
+
*
|
|
1689
|
+
* Each rule is: <identifier> <operator> <value>
|
|
1690
|
+
*
|
|
1691
|
+
* Operators can be simple (equals, contains, in) or
|
|
1692
|
+
* composed (is null, is not null, greater than, less than or equal, etc.).
|
|
1693
|
+
*/
|
|
1694
|
+
class AbilityDSLParser {
|
|
1695
|
+
dsl;
|
|
1696
|
+
tokens = [];
|
|
1697
|
+
pos = 0;
|
|
1698
|
+
annotationBuffer = {
|
|
1699
|
+
name: null,
|
|
1700
|
+
description: null,
|
|
1701
|
+
};
|
|
1702
|
+
constructor(dsl) {
|
|
1703
|
+
this.dsl = dsl;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Main entry point: tokenize the input and parse all policies.
|
|
1707
|
+
* @returns Array of AbilityPolicy instances.
|
|
1708
|
+
*/
|
|
1709
|
+
parse() {
|
|
1710
|
+
// Tokenize the entire DSL string.
|
|
1711
|
+
this.tokens = new AbilityDSLLexer_1.AbilityDSLLexer(this.dsl).tokenize();
|
|
1712
|
+
this.pos = 0;
|
|
1713
|
+
const policies = [];
|
|
1714
|
+
// Keep parsing until we've consumed all tokens.
|
|
1715
|
+
while (!this.isAtEnd()) {
|
|
1716
|
+
this.consumeLeadingComments();
|
|
1717
|
+
// Every policy must start with an EFFECT token.
|
|
1718
|
+
if (!this.isStartOfPolicy()) {
|
|
1719
|
+
const token = this.peek();
|
|
1720
|
+
this.syntaxError(`Expected policy, got ${token.code}.`, token, ['EFFECT']);
|
|
1721
|
+
}
|
|
1722
|
+
policies.push(this.parsePolicy());
|
|
1723
|
+
}
|
|
1724
|
+
return policies;
|
|
1725
|
+
}
|
|
1726
|
+
// -------------------------------------------------------------------------
|
|
1727
|
+
// #region Policy parsing
|
|
1728
|
+
// -------------------------------------------------------------------------
|
|
1729
|
+
/**
|
|
1730
|
+
* Parses a single policy from the current token position.
|
|
1731
|
+
*
|
|
1732
|
+
* Grammar:
|
|
1733
|
+
* policy = EFFECT PERMISSION IF (ALL | ANY) COLON ruleSets
|
|
1734
|
+
*/
|
|
1735
|
+
parsePolicy() {
|
|
1736
|
+
this.consumeLeadingComments();
|
|
1737
|
+
const meta = this.takeAnnotations();
|
|
1738
|
+
// Effect: "permit" or "deny"
|
|
1739
|
+
const effectToken = this.consume(AbilityDSLToken_1.AbilityDSLToken.EFFECT, 'Expected effect');
|
|
1740
|
+
const effect = effectToken.value;
|
|
1741
|
+
// Permission: e.g. "order.update"
|
|
1742
|
+
const permissionToken = this.consume(AbilityDSLToken_1.AbilityDSLToken.PERMISSION, 'Expected permission');
|
|
1743
|
+
const permission = permissionToken.value;
|
|
1744
|
+
if (!permission.startsWith('permission.')) {
|
|
1745
|
+
return this.syntaxError(`Unexpected token. The permission key, must be starts with prefix \`permission.\`, but got \`${permission}\`.\nDid you mean \`permission.${permission}\`?`, permissionToken);
|
|
1746
|
+
}
|
|
1747
|
+
// "if" keyword
|
|
1748
|
+
this.consume(AbilityDSLToken_1.AbilityDSLToken.IF, 'Expected "if"');
|
|
1749
|
+
// Group selector: "all" or "any" – determines how the top‑level rule sets are combined.
|
|
1750
|
+
const compareToken = this.consumeOneOf([AbilityDSLToken_1.AbilityDSLToken.ALL, AbilityDSLToken_1.AbilityDSLToken.ANY], 'Expected "all" or "any"');
|
|
1751
|
+
const compareMethod = compareToken.code === AbilityDSLToken_1.AbilityDSLToken.ALL ? AbilityCompare_1.default.and : AbilityCompare_1.default.or;
|
|
1752
|
+
// Colon after the group keyword
|
|
1753
|
+
this.consume(AbilityDSLToken_1.AbilityDSLToken.COLON, 'Expected ":"');
|
|
1754
|
+
// Parse the list of rule sets (each "all of:" or "any of:" block)
|
|
1755
|
+
const ruleSets = this.parseRuleSets(compareMethod);
|
|
1756
|
+
// Construct the policy instance.
|
|
1757
|
+
return new AbilityPolicy_1.default({
|
|
1758
|
+
id: `${effect}:${permission}:${Math.random()}`,
|
|
1759
|
+
name: meta.name ?? `${effect} ${permission}`,
|
|
1760
|
+
permission: permission.replace(/^permission\./, ''),
|
|
1761
|
+
effect: effect === 'permit' ? AbilityPolicyEffect_1.default.permit : AbilityPolicyEffect_1.default.deny,
|
|
1762
|
+
compareMethod,
|
|
1763
|
+
}).addRuleSets(ruleSets);
|
|
1764
|
+
}
|
|
1765
|
+
// -------------------------------------------------------------------------
|
|
1766
|
+
// #region Rule set parsing (groups of rules)
|
|
1767
|
+
// -------------------------------------------------------------------------
|
|
1768
|
+
/**
|
|
1769
|
+
* Parses a sequence of rule sets (groups) until a new policy starts or EOF.
|
|
1770
|
+
*/
|
|
1771
|
+
parseRuleSets(policyCompareMethod) {
|
|
1772
|
+
const sets = [];
|
|
1773
|
+
while (!this.isAtEnd() && !this.isStartOfPolicy()) {
|
|
1774
|
+
this.consumeLeadingComments();
|
|
1775
|
+
// Если начинается новая группа — парсим её
|
|
1776
|
+
if (this.isStartOfGroup()) {
|
|
1777
|
+
sets.push(this.parseGroup());
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
1780
|
+
// Иначе — implicit group (all-of по умолчанию)
|
|
1781
|
+
const meta = this.takeAnnotations();
|
|
1782
|
+
const group = new AbilityRuleSet_1.default({
|
|
1783
|
+
compareMethod: policyCompareMethod,
|
|
1784
|
+
name: meta.name,
|
|
1785
|
+
});
|
|
1786
|
+
// Читаем правила implicit-группы
|
|
1787
|
+
while (!this.isAtEnd()) {
|
|
1788
|
+
this.consumeLeadingComments();
|
|
1789
|
+
if (this.isStartOfGroup() || this.isStartOfPolicy()) {
|
|
1790
|
+
break;
|
|
1791
|
+
}
|
|
1792
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER)) {
|
|
1793
|
+
group.addRule(this.parseRule());
|
|
1794
|
+
}
|
|
1795
|
+
else {
|
|
1796
|
+
this.syntaxError(`Unexpected token in implicit group: ${this.peek().code}`, this.peek());
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
sets.push(group);
|
|
1800
|
+
}
|
|
1801
|
+
return sets;
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Parses a single group, e.g. "all of:" or "any of:", and returns a RuleSet.
|
|
1805
|
+
*/
|
|
1806
|
+
parseGroup() {
|
|
1807
|
+
this.consumeLeadingComments();
|
|
1808
|
+
const meta = this.takeAnnotations();
|
|
1809
|
+
const compareToken = this.consumeOneOf([AbilityDSLToken_1.AbilityDSLToken.ALL, AbilityDSLToken_1.AbilityDSLToken.ANY], 'Expected "all" or "any"');
|
|
1810
|
+
const compareMethod = compareToken.code === AbilityDSLToken_1.AbilityDSLToken.ALL ? AbilityCompare_1.default.and : AbilityCompare_1.default.or;
|
|
1811
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.OF)) {
|
|
1812
|
+
this.advance();
|
|
1813
|
+
}
|
|
1814
|
+
this.consume(AbilityDSLToken_1.AbilityDSLToken.COLON, 'Expected ":"');
|
|
1815
|
+
const group = new AbilityRuleSet_1.default({ compareMethod, name: meta.name });
|
|
1816
|
+
while (!this.isAtEnd()) {
|
|
1817
|
+
this.consumeLeadingComments();
|
|
1818
|
+
if (this.isStartOfGroup() || this.isStartOfPolicy()) {
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER)) {
|
|
1822
|
+
group.addRule(this.parseRule());
|
|
1823
|
+
}
|
|
1824
|
+
else {
|
|
1825
|
+
this.syntaxError(`Unexpected token in group: ${this.peek().code}`, this.peek());
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
return group;
|
|
1829
|
+
}
|
|
1830
|
+
// -------------------------------------------------------------------------
|
|
1831
|
+
// #region Rule parsing
|
|
1832
|
+
// -------------------------------------------------------------------------
|
|
1833
|
+
/**
|
|
1834
|
+
* Parses a single rule: subject operator value
|
|
1835
|
+
*/
|
|
1836
|
+
parseRule() {
|
|
1837
|
+
this.consumeLeadingComments();
|
|
1838
|
+
const meta = this.takeAnnotations();
|
|
1839
|
+
if (!this.check(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER)) {
|
|
1840
|
+
this.syntaxError(`Expected identifier, got ${this.peek().code}`, this.peek());
|
|
1841
|
+
}
|
|
1842
|
+
// Subject (e.g., "user.roles")
|
|
1843
|
+
const subject = this.consume(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER, 'Expected field').value;
|
|
1844
|
+
// Operator (e.g., "contains", "equals", "is not null")
|
|
1845
|
+
const { condition, operator } = this.parseConditionOperator();
|
|
1846
|
+
let resource;
|
|
1847
|
+
let beforePos = this.pos;
|
|
1848
|
+
// Special operators that don't consume a value token.
|
|
1849
|
+
if (operator === AbilityDSLToken_1.AbilityDSLToken.EQ_NULL ||
|
|
1850
|
+
operator === AbilityDSLToken_1.AbilityDSLToken.NOT_EQ_NULL ||
|
|
1851
|
+
operator === AbilityDSLToken_1.AbilityDSLToken.NULL) {
|
|
1852
|
+
resource = null;
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
beforePos = this.pos;
|
|
1856
|
+
resource = this.parseValue();
|
|
1857
|
+
}
|
|
1858
|
+
// Checking that there are no extra tokens after the value
|
|
1859
|
+
// (skip comments)
|
|
1860
|
+
this.consumeLeadingComments();
|
|
1861
|
+
const resourceToken = this.tokens[beforePos];
|
|
1862
|
+
if (typeof resource === 'string' &&
|
|
1863
|
+
resourceToken.code === AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER &&
|
|
1864
|
+
!resourceToken.value.includes('.')) {
|
|
1865
|
+
this.syntaxError(`Expected comparison operator or value, got \`${resource}\``, this.tokens[beforePos], [AbilityDSLToken_1.AbilityDSLToken.KEYWORD]);
|
|
1866
|
+
}
|
|
1867
|
+
return new AbilityRule_1.default({
|
|
1868
|
+
subject,
|
|
1869
|
+
resource,
|
|
1870
|
+
condition,
|
|
1871
|
+
name: meta.name,
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
// -------------------------------------------------------------------------
|
|
1875
|
+
// #region Operator parsing
|
|
1876
|
+
// -------------------------------------------------------------------------
|
|
1877
|
+
/**
|
|
1878
|
+
* Parses the comparison operator part of a rule.
|
|
1879
|
+
* Returns both the resulting AbilityCondition and the token type that was consumed.
|
|
1880
|
+
*/
|
|
1881
|
+
parseConditionOperator() {
|
|
1882
|
+
const savedPos = this.pos;
|
|
1883
|
+
// "length equals"
|
|
1884
|
+
if (this.matchWord('length') && this.matchWord('equals')) {
|
|
1885
|
+
return { condition: AbilityCondition_1.default.length_equals, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_EQ };
|
|
1886
|
+
}
|
|
1887
|
+
this.pos = savedPos;
|
|
1888
|
+
// "length ="
|
|
1889
|
+
if (this.matchWord('length') && this.matchSymbol('=')) {
|
|
1890
|
+
return { condition: AbilityCondition_1.default.length_equals, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_EQ };
|
|
1891
|
+
}
|
|
1892
|
+
this.pos = savedPos;
|
|
1893
|
+
// "length greater than"
|
|
1894
|
+
if (this.matchWord('length') && this.matchWord('greater') && this.matchWord('than')) {
|
|
1895
|
+
return { condition: AbilityCondition_1.default.length_greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_GT };
|
|
1896
|
+
}
|
|
1897
|
+
this.pos = savedPos;
|
|
1898
|
+
// "length >"
|
|
1899
|
+
if (this.matchWord('length') && this.matchSymbol('>')) {
|
|
1900
|
+
return { condition: AbilityCondition_1.default.length_greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_GT };
|
|
1901
|
+
}
|
|
1902
|
+
this.pos = savedPos;
|
|
1903
|
+
// "length less than"
|
|
1904
|
+
if (this.matchWord('length') && this.matchWord('less') && this.matchWord('than')) {
|
|
1905
|
+
return { condition: AbilityCondition_1.default.length_less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_LT };
|
|
1906
|
+
}
|
|
1907
|
+
this.pos = savedPos;
|
|
1908
|
+
// "length <"
|
|
1909
|
+
if (this.matchWord('length') && this.matchSymbol('<')) {
|
|
1910
|
+
return { condition: AbilityCondition_1.default.length_less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_LT };
|
|
1911
|
+
}
|
|
1912
|
+
this.pos = savedPos;
|
|
1913
|
+
// "greater than or equal"
|
|
1914
|
+
if (this.matchWord('greater') &&
|
|
1915
|
+
this.matchWord('than') &&
|
|
1916
|
+
this.matchWord('or') &&
|
|
1917
|
+
this.matchWord('equal')) {
|
|
1918
|
+
return { condition: AbilityCondition_1.default.greater_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.GTE };
|
|
1919
|
+
}
|
|
1920
|
+
this.pos = savedPos;
|
|
1921
|
+
// greater than
|
|
1922
|
+
if (this.matchWord('greater') && this.matchWord('than')) {
|
|
1923
|
+
return { condition: AbilityCondition_1.default.greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.GT };
|
|
1924
|
+
}
|
|
1925
|
+
this.pos = savedPos;
|
|
1926
|
+
// less than or equal
|
|
1927
|
+
if (this.matchWord('less') &&
|
|
1928
|
+
this.matchWord('than') &&
|
|
1929
|
+
this.matchWord('or') &&
|
|
1930
|
+
this.matchWord('equal')) {
|
|
1931
|
+
return { condition: AbilityCondition_1.default.less_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.LTE };
|
|
1932
|
+
}
|
|
1933
|
+
this.pos = savedPos;
|
|
1934
|
+
// less than
|
|
1935
|
+
if (this.matchWord('less') && this.matchWord('than')) {
|
|
1936
|
+
return { condition: AbilityCondition_1.default.less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LT };
|
|
1937
|
+
}
|
|
1938
|
+
this.pos = savedPos;
|
|
1939
|
+
// "not contains"
|
|
1940
|
+
if (this.matchWord('not') && this.matchWord('contains')) {
|
|
1941
|
+
return {
|
|
1942
|
+
condition: AbilityCondition_1.default.not_contains,
|
|
1943
|
+
operator: AbilityDSLToken_1.AbilityDSLToken.NOT_CONTAINS,
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
this.pos = savedPos;
|
|
1947
|
+
// "not includes"
|
|
1948
|
+
if (this.matchWord('not') && this.matchWord('includes')) {
|
|
1949
|
+
return {
|
|
1950
|
+
condition: AbilityCondition_1.default.not_contains,
|
|
1951
|
+
operator: AbilityDSLToken_1.AbilityDSLToken.NOT_CONTAINS,
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
this.pos = savedPos;
|
|
1955
|
+
// "not includes"
|
|
1956
|
+
if (this.matchWord('not') && this.matchWord('has')) {
|
|
1957
|
+
return {
|
|
1958
|
+
condition: AbilityCondition_1.default.not_contains,
|
|
1959
|
+
operator: AbilityDSLToken_1.AbilityDSLToken.NOT_CONTAINS,
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
this.pos = savedPos;
|
|
1963
|
+
// "is equals"
|
|
1964
|
+
if (this.matchWord('is') && this.matchWord('equals')) {
|
|
1965
|
+
return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
|
|
1966
|
+
}
|
|
1967
|
+
this.pos = savedPos;
|
|
1968
|
+
// not equal
|
|
1969
|
+
if (this.matchWord('not') && this.matchWord('equals')) {
|
|
1970
|
+
return { condition: AbilityCondition_1.default.not_equals, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ };
|
|
1971
|
+
}
|
|
1972
|
+
this.pos = savedPos;
|
|
1973
|
+
// is not equals
|
|
1974
|
+
if (this.matchWord('is') && this.matchWord('not') && this.matchWord('equals')) {
|
|
1975
|
+
return { condition: AbilityCondition_1.default.not_equals, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ };
|
|
1976
|
+
}
|
|
1977
|
+
this.pos = savedPos;
|
|
1978
|
+
// is in
|
|
1979
|
+
if (this.matchWord('is') && this.matchWord('in')) {
|
|
1980
|
+
return { condition: AbilityCondition_1.default.in, operator: AbilityDSLToken_1.AbilityDSLToken.IN };
|
|
1981
|
+
}
|
|
1982
|
+
this.pos = savedPos;
|
|
1983
|
+
// not in
|
|
1984
|
+
if (this.matchWord('not') && this.matchWord('in')) {
|
|
1985
|
+
return { condition: AbilityCondition_1.default.not_in, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_IN };
|
|
1986
|
+
}
|
|
1987
|
+
this.pos = savedPos;
|
|
1988
|
+
// is not null
|
|
1989
|
+
if (this.matchWord('is') && this.matchWord('not')) {
|
|
1990
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.NULL)) {
|
|
1991
|
+
this.advance();
|
|
1992
|
+
return {
|
|
1993
|
+
condition: AbilityCondition_1.default.not_equals,
|
|
1994
|
+
operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ_NULL,
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
this.pos = savedPos;
|
|
1999
|
+
// is null
|
|
2000
|
+
if (this.matchWord('is') && this.matchWord('null')) {
|
|
2001
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.NULL)) {
|
|
2002
|
+
this.advance();
|
|
2003
|
+
return {
|
|
2004
|
+
condition: AbilityCondition_1.default.equals,
|
|
2005
|
+
operator: AbilityDSLToken_1.AbilityDSLToken.EQ_NULL,
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
this.pos = savedPos;
|
|
2010
|
+
// Single token (symbol or keyword)
|
|
2011
|
+
const token = this.peek();
|
|
2012
|
+
if (token.code !== AbilityDSLToken_1.AbilityDSLToken.SYMBOL &&
|
|
2013
|
+
token.code !== AbilityDSLToken_1.AbilityDSLToken.KEYWORD &&
|
|
2014
|
+
token.code !== AbilityDSLToken_1.AbilityDSLToken.NULL) {
|
|
2015
|
+
this.syntaxError(`Expected comparison operator, got \`${token.value}\``, token, [
|
|
2016
|
+
AbilityDSLToken_1.AbilityDSLToken.SYMBOL,
|
|
2017
|
+
AbilityDSLToken_1.AbilityDSLToken.KEYWORD,
|
|
2018
|
+
AbilityDSLToken_1.AbilityDSLToken.NULL,
|
|
2019
|
+
]);
|
|
2020
|
+
}
|
|
2021
|
+
this.advance();
|
|
2022
|
+
switch (token.code) {
|
|
2023
|
+
case AbilityDSLToken_1.AbilityDSLToken.SYMBOL:
|
|
2024
|
+
if (token.value === '=' || token.value === '==')
|
|
2025
|
+
return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
|
|
2026
|
+
if (token.value === '!=' || token.value === '<>')
|
|
2027
|
+
return { condition: AbilityCondition_1.default.not_equals, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ };
|
|
2028
|
+
if (token.value === '>')
|
|
2029
|
+
return { condition: AbilityCondition_1.default.greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.GT };
|
|
2030
|
+
if (token.value === '<')
|
|
2031
|
+
return { condition: AbilityCondition_1.default.less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LT };
|
|
2032
|
+
if (token.value === '>=')
|
|
2033
|
+
return { condition: AbilityCondition_1.default.greater_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.GTE };
|
|
2034
|
+
if (token.value === '<=')
|
|
2035
|
+
return { condition: AbilityCondition_1.default.less_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.LTE };
|
|
2036
|
+
break;
|
|
2037
|
+
case AbilityDSLToken_1.AbilityDSLToken.KEYWORD:
|
|
2038
|
+
if (token.value === 'contains' || token.value === 'includes' || token.value === 'has')
|
|
2039
|
+
return { condition: AbilityCondition_1.default.contains, operator: AbilityDSLToken_1.AbilityDSLToken.CONTAINS };
|
|
2040
|
+
if (token.value === 'in')
|
|
2041
|
+
return { condition: AbilityCondition_1.default.in, operator: AbilityDSLToken_1.AbilityDSLToken.IN };
|
|
2042
|
+
if (token.value === 'equals')
|
|
2043
|
+
return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
|
|
2044
|
+
if (token.value === 'gte') {
|
|
2045
|
+
return { condition: AbilityCondition_1.default.greater_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.GTE };
|
|
2046
|
+
}
|
|
2047
|
+
if (token.value === 'greater' || token.value === 'gt') {
|
|
2048
|
+
// If we come here, it means "greater" without "than" – treat as '>'
|
|
2049
|
+
return { condition: AbilityCondition_1.default.greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.GT };
|
|
2050
|
+
}
|
|
2051
|
+
if (token.value === 'less' || token.value === 'lt') {
|
|
2052
|
+
return { condition: AbilityCondition_1.default.less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LT };
|
|
2053
|
+
}
|
|
2054
|
+
if (token.value === 'lte') {
|
|
2055
|
+
return { condition: AbilityCondition_1.default.less_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.LTE };
|
|
2056
|
+
}
|
|
2057
|
+
if (token.value === 'is') {
|
|
2058
|
+
// "is" alone -> equals
|
|
2059
|
+
return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
|
|
2060
|
+
}
|
|
2061
|
+
break;
|
|
2062
|
+
default:
|
|
2063
|
+
break;
|
|
2064
|
+
}
|
|
2065
|
+
return this.syntaxError(`Unexpected operator token \`${token.value}\``, token, [
|
|
2066
|
+
AbilityDSLToken_1.AbilityDSLToken.SYMBOL,
|
|
2067
|
+
AbilityDSLToken_1.AbilityDSLToken.KEYWORD,
|
|
2068
|
+
]);
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Helper to match and consume a specific word token (KEYWORD or IDENTIFIER).
|
|
2072
|
+
* @param word The exact string to look for.
|
|
2073
|
+
* @returns True if the next token has that value.
|
|
2074
|
+
*/
|
|
2075
|
+
matchWord(word) {
|
|
2076
|
+
if (this.isAtEnd()) {
|
|
2077
|
+
return false;
|
|
2078
|
+
}
|
|
2079
|
+
const token = this.peek();
|
|
2080
|
+
if ((token.code === AbilityDSLToken_1.AbilityDSLToken.KEYWORD || token.code === AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER) &&
|
|
2081
|
+
token.value === word) {
|
|
2082
|
+
this.advance();
|
|
2083
|
+
return true;
|
|
2084
|
+
}
|
|
2085
|
+
return false;
|
|
2086
|
+
}
|
|
2087
|
+
matchSymbol(symbol) {
|
|
2088
|
+
if (this.isAtEnd())
|
|
2089
|
+
return false;
|
|
2090
|
+
const token = this.peek();
|
|
2091
|
+
if (token.code === AbilityDSLToken_1.AbilityDSLToken.SYMBOL && token.value === symbol) {
|
|
2092
|
+
this.advance();
|
|
2093
|
+
return true;
|
|
2094
|
+
}
|
|
2095
|
+
return false;
|
|
2096
|
+
}
|
|
2097
|
+
// -------------------------------------------------------------------------
|
|
2098
|
+
// #region Value parsing (literals, arrays, identifiers)
|
|
2099
|
+
// -------------------------------------------------------------------------
|
|
2100
|
+
/**
|
|
2101
|
+
* Parses a resource value. Can be a string literal, number, boolean,
|
|
2102
|
+
* null, a path (identifier), or an array.
|
|
2103
|
+
*/
|
|
2104
|
+
parseValue() {
|
|
2105
|
+
// Arrays start with a left bracket
|
|
2106
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.LBRACKET)) {
|
|
2107
|
+
this.advance();
|
|
2108
|
+
return this.parseArray();
|
|
2109
|
+
}
|
|
2110
|
+
// Ensure we are not about to read a structural token as a value.
|
|
2111
|
+
const token = this.peek();
|
|
2112
|
+
if (token.code === AbilityDSLToken_1.AbilityDSLToken.ALL ||
|
|
2113
|
+
token.code === AbilityDSLToken_1.AbilityDSLToken.ANY ||
|
|
2114
|
+
token.code === AbilityDSLToken_1.AbilityDSLToken.EFFECT) {
|
|
2115
|
+
this.syntaxError(`Unexpected ${token.code} in value position`, token);
|
|
2116
|
+
}
|
|
2117
|
+
this.advance();
|
|
2118
|
+
// CHECK THIS SWITCH COMPARE
|
|
2119
|
+
switch (token.code) {
|
|
2120
|
+
case AbilityDSLToken_1.AbilityDSLToken.STRING:
|
|
2121
|
+
return token.value;
|
|
2122
|
+
case AbilityDSLToken_1.AbilityDSLToken.NUMBER:
|
|
2123
|
+
return Number(token.value);
|
|
2124
|
+
case AbilityDSLToken_1.AbilityDSLToken.BOOLEAN:
|
|
2125
|
+
return token.value === 'true';
|
|
2126
|
+
case AbilityDSLToken_1.AbilityDSLToken.NULL:
|
|
2127
|
+
return null;
|
|
2128
|
+
case AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER:
|
|
2129
|
+
return token.value;
|
|
2130
|
+
default: {
|
|
2131
|
+
this.syntaxError(`Unexpected value token "${token.value}"`, token ?? this.tokens[this.tokens.length - 1], [AbilityDSLToken_1.AbilityDSLToken.KEYWORD]);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Parses an array literal: [ <value>, <value>, ... ]
|
|
2137
|
+
* The opening bracket has already been consumed.
|
|
2138
|
+
*/
|
|
2139
|
+
parseArray() {
|
|
2140
|
+
const arr = [];
|
|
2141
|
+
// Handle empty array
|
|
2142
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.RBRACKET)) {
|
|
2143
|
+
this.advance();
|
|
2144
|
+
return arr;
|
|
2145
|
+
}
|
|
2146
|
+
while (!this.isAtEnd() && !this.check(AbilityDSLToken_1.AbilityDSLToken.RBRACKET)) {
|
|
2147
|
+
const value = this.parseValue();
|
|
2148
|
+
// Flatten nested arrays if they appear (though grammar doesn't currently allow nesting).
|
|
2149
|
+
if (Array.isArray(value)) {
|
|
2150
|
+
arr.push(...value);
|
|
2151
|
+
}
|
|
2152
|
+
else if (typeof value === 'string' ||
|
|
2153
|
+
typeof value === 'number' ||
|
|
2154
|
+
typeof value === 'boolean') {
|
|
2155
|
+
arr.push(value);
|
|
2156
|
+
}
|
|
2157
|
+
else if (value === null) {
|
|
2158
|
+
// Null is allowed in arrays? Currently, we throw.
|
|
2159
|
+
this.syntaxError('Unexpected null in array', this.peek());
|
|
2160
|
+
}
|
|
2161
|
+
// Optional comma between elements
|
|
2162
|
+
if (this.check(AbilityDSLToken_1.AbilityDSLToken.COMMA)) {
|
|
2163
|
+
this.advance();
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
this.consume(AbilityDSLToken_1.AbilityDSLToken.RBRACKET, 'Expected "]"');
|
|
2167
|
+
return arr;
|
|
2168
|
+
}
|
|
2169
|
+
// -------------------------------------------------------------------------
|
|
2170
|
+
// #region Annotations and comments
|
|
2171
|
+
// -------------------------------------------------------------------------
|
|
2172
|
+
consumeLeadingComments() {
|
|
2173
|
+
while (this.check(AbilityDSLToken_1.AbilityDSLToken.COMMENT)) {
|
|
2174
|
+
const token = this.advance();
|
|
2175
|
+
this.processCommentToken(token);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
processCommentToken(token) {
|
|
2179
|
+
const text = token.value.trim();
|
|
2180
|
+
if (text.startsWith('@name ')) {
|
|
2181
|
+
this.annotationBuffer.name = text.slice(6).trim();
|
|
2182
|
+
}
|
|
2183
|
+
if (text.startsWith('@description ')) {
|
|
2184
|
+
this.annotationBuffer.description = text.slice(13).trim();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
takeAnnotations() {
|
|
2188
|
+
const meta = { ...this.annotationBuffer };
|
|
2189
|
+
this.annotationBuffer = {
|
|
2190
|
+
name: null,
|
|
2191
|
+
description: null,
|
|
2192
|
+
};
|
|
2193
|
+
return meta;
|
|
2194
|
+
}
|
|
2195
|
+
// -------------------------------------------------------------------------
|
|
2196
|
+
// #region Errors
|
|
2197
|
+
// -------------------------------------------------------------------------
|
|
2198
|
+
syntaxError(details, token, expected) {
|
|
2199
|
+
const lines = this.dsl.split(/\r?\n/);
|
|
2200
|
+
const lineIdx = token.line - 1;
|
|
2201
|
+
const lineBefore = lineIdx > 0 ? lines[lineIdx - 1] : '';
|
|
2202
|
+
const current = lines[lineIdx];
|
|
2203
|
+
const linesAfter = lineIdx + 1 < lines.length ? lines[lineIdx + 1] : '';
|
|
2204
|
+
const wave = ' '.repeat(Math.max(0, token.column - 1)) + '~'.repeat(token.value.length);
|
|
2205
|
+
const lineNumWidth = String(token.line + 1).length;
|
|
2206
|
+
const num = (n) => String(n).padStart(lineNumWidth, ' ');
|
|
2207
|
+
let context = '';
|
|
2208
|
+
if (lineBefore.trim() !== '') {
|
|
2209
|
+
context += `${num(token.line - 1)} | ${lineBefore}\n`;
|
|
2210
|
+
}
|
|
2211
|
+
context += `${num(token.line)} | ${current}\n`;
|
|
2212
|
+
context += `${' '.repeat(lineNumWidth)} | ${wave}\n`;
|
|
2213
|
+
if (linesAfter.trim() !== '') {
|
|
2214
|
+
context += `${num(token.line + 1)} | ${linesAfter}`;
|
|
2215
|
+
}
|
|
2216
|
+
let finalDetails = details;
|
|
2217
|
+
if (expected && expected?.length > 0) {
|
|
2218
|
+
const actual = token.value;
|
|
2219
|
+
const suggestion = this.suggest(actual, expected);
|
|
2220
|
+
const detailsMsg = `${details}\nDetails: Unexpected value token \`${actual}\``;
|
|
2221
|
+
finalDetails = suggestion ? `${detailsMsg} Did you mean \`${suggestion}\`?` : detailsMsg;
|
|
2222
|
+
}
|
|
2223
|
+
throw new AbilityDSLSyntaxError_1.AbilityDSLSyntaxError(token.line, token.column, context + '\n', finalDetails);
|
|
2224
|
+
}
|
|
2225
|
+
getLine(lineNumber) {
|
|
2226
|
+
return this.dsl.split(/\r?\n/)[lineNumber - 1] ?? '';
|
|
2227
|
+
}
|
|
2228
|
+
suggest(actual, expectedTypes) {
|
|
2229
|
+
const candidates = [];
|
|
2230
|
+
for (const type of expectedTypes) {
|
|
2231
|
+
candidates.push(type);
|
|
2232
|
+
}
|
|
2233
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
2234
|
+
let best = null;
|
|
2235
|
+
let bestDist = 3;
|
|
2236
|
+
for (const candidate of uniqueCandidates) {
|
|
2237
|
+
const d = this.levenshteinDistance(actual.toLowerCase(), candidate.toLowerCase());
|
|
2238
|
+
if (d < bestDist) {
|
|
2239
|
+
bestDist = d;
|
|
2240
|
+
best = candidate;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return best;
|
|
2244
|
+
}
|
|
2245
|
+
levenshteinDistance(a, b) {
|
|
2246
|
+
const matrix = Array(b.length + 1)
|
|
2247
|
+
.fill(null)
|
|
2248
|
+
.map(() => Array(a.length + 1).fill(null));
|
|
2249
|
+
for (let i = 0; i <= a.length; i++)
|
|
2250
|
+
matrix[0][i] = i;
|
|
2251
|
+
for (let j = 0; j <= b.length; j++)
|
|
2252
|
+
matrix[j][0] = j;
|
|
2253
|
+
for (let j = 1; j <= b.length; j++) {
|
|
2254
|
+
for (let i = 1; i <= a.length; i++) {
|
|
2255
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
2256
|
+
matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + cost);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
return matrix[b.length][a.length];
|
|
2260
|
+
}
|
|
2261
|
+
// -------------------------------------------------------------------------
|
|
2262
|
+
// #region Helper / lookahead methods
|
|
2263
|
+
// -------------------------------------------------------------------------
|
|
2264
|
+
consumeOneOf(types, message) {
|
|
2265
|
+
const token = this.peek();
|
|
2266
|
+
for (const t of types) {
|
|
2267
|
+
if (token && token.code === t) {
|
|
2268
|
+
return this.advance();
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
const expected = types.map(t => t).join(', ');
|
|
2272
|
+
const actual = token ? token.value : AbilityDSLToken_1.AbilityDSLToken.EOF;
|
|
2273
|
+
const suggestion = this.suggest(actual, types);
|
|
2274
|
+
const details = `${message}\nDetails: Unexpected token \`${actual}\`, expected one of: ${expected}.`;
|
|
2275
|
+
const finalMsg = suggestion ? `${details} Did you mean \`${suggestion}\`?` : details;
|
|
2276
|
+
this.syntaxError(finalMsg, token ?? this.tokens[this.tokens.length - 1]);
|
|
2277
|
+
}
|
|
2278
|
+
consume(type, message) {
|
|
2279
|
+
const token = this.peek();
|
|
2280
|
+
if (token && token.code === type) {
|
|
2281
|
+
return this.advance();
|
|
2282
|
+
}
|
|
2283
|
+
const expected = type;
|
|
2284
|
+
const actual = token ? token.value : AbilityDSLToken_1.AbilityDSLToken.EOF;
|
|
2285
|
+
const suggestion = this.suggest(actual, [type]);
|
|
2286
|
+
const details = `${message}\nDetails: Unexpected token \`${actual}\`, expected "${expected}".`;
|
|
2287
|
+
const finalMsg = suggestion ? `${details} Did you mean \`${suggestion}\`?` : details;
|
|
2288
|
+
this.syntaxError(finalMsg, token ?? this.tokens[this.tokens.length - 1]);
|
|
2289
|
+
}
|
|
2290
|
+
check(type) {
|
|
2291
|
+
if (this.isAtEnd())
|
|
2292
|
+
return false;
|
|
2293
|
+
return this.peek().code === type;
|
|
2294
|
+
}
|
|
2295
|
+
isStartOfPolicy() {
|
|
2296
|
+
return this.check(AbilityDSLToken_1.AbilityDSLToken.EFFECT);
|
|
2297
|
+
}
|
|
2298
|
+
isStartOfGroup() {
|
|
2299
|
+
return this.check(AbilityDSLToken_1.AbilityDSLToken.ALL) || this.check(AbilityDSLToken_1.AbilityDSLToken.ANY);
|
|
2300
|
+
}
|
|
2301
|
+
advance() {
|
|
2302
|
+
return this.tokens[this.pos++];
|
|
2303
|
+
}
|
|
2304
|
+
peek() {
|
|
2305
|
+
return this.tokens[this.pos];
|
|
2306
|
+
}
|
|
2307
|
+
isAtEnd() {
|
|
2308
|
+
return this.peek().code === AbilityDSLToken_1.AbilityDSLToken.EOF;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
exports.AbilityDSLParser = AbilityDSLParser;
|
|
2312
|
+
|
|
2313
|
+
|
|
2314
|
+
/***/ }),
|
|
2315
|
+
|
|
2316
|
+
/***/ 883:
|
|
2317
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
2318
|
+
|
|
2319
|
+
|
|
2320
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2321
|
+
exports.AbilityDSLSyntaxError = void 0;
|
|
2322
|
+
class AbilityDSLSyntaxError extends Error {
|
|
2323
|
+
line;
|
|
2324
|
+
column;
|
|
2325
|
+
context;
|
|
2326
|
+
details;
|
|
2327
|
+
_formattedMessage;
|
|
2328
|
+
_originalStack;
|
|
2329
|
+
constructor(line, column, context, // строка DSL + ^ + соседние строки
|
|
2330
|
+
details) {
|
|
2331
|
+
super(details.split('\n')[0]); // message = только первая строка
|
|
2332
|
+
this.line = line;
|
|
2333
|
+
this.column = column;
|
|
2334
|
+
this.context = context;
|
|
2335
|
+
this.details = details;
|
|
2336
|
+
this.name = 'AbilityDSLSyntaxError';
|
|
2337
|
+
if (Error.captureStackTrace) {
|
|
2338
|
+
Error.captureStackTrace(this, AbilityDSLSyntaxError);
|
|
2339
|
+
}
|
|
2340
|
+
this._originalStack = this.stack;
|
|
2341
|
+
this._formattedMessage = this.formatMessage();
|
|
2342
|
+
Object.defineProperty(this, 'stack', {
|
|
2343
|
+
get: () => this._formattedMessage,
|
|
2344
|
+
configurable: true,
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
static supportsColor() {
|
|
2348
|
+
return typeof process !== 'undefined' && process.stdout?.isTTY;
|
|
2349
|
+
}
|
|
2350
|
+
formatMessage() {
|
|
2351
|
+
const useColor = AbilityDSLSyntaxError.supportsColor();
|
|
2352
|
+
const BOLD = useColor ? '\x1b[1m' : '';
|
|
2353
|
+
const RED = useColor ? '\x1b[31m' : '';
|
|
2354
|
+
const ORANGE = useColor ? '\x1b[33;1m' : '';
|
|
2355
|
+
const GRAY = useColor ? '\x1b[90m' : '';
|
|
2356
|
+
const RESET = useColor ? '\x1b[0m' : '';
|
|
2357
|
+
const lines = this.context.split('\n');
|
|
2358
|
+
// Find line with ^
|
|
2359
|
+
const pointerIndex = lines.findIndex(l => l.includes('^') || l.includes('~'));
|
|
2360
|
+
const commentIndex = lines.findIndex(l => l.trim().includes('#'));
|
|
2361
|
+
const formattedLines = lines.map((line, idx) => {
|
|
2362
|
+
if (idx === pointerIndex - 1) {
|
|
2363
|
+
// Error line
|
|
2364
|
+
return `${BOLD}${ORANGE}${line}${RESET}`;
|
|
2365
|
+
}
|
|
2366
|
+
if (idx === pointerIndex) {
|
|
2367
|
+
// Error with ~~~~~
|
|
2368
|
+
return `${RED}${line}${RESET}`;
|
|
2369
|
+
}
|
|
2370
|
+
// Comments # ...
|
|
2371
|
+
if (idx === commentIndex) {
|
|
2372
|
+
return `${GRAY}${line}${RESET}`;
|
|
2373
|
+
}
|
|
2374
|
+
return line;
|
|
2375
|
+
});
|
|
2376
|
+
const contextBlock = formattedLines.join('\n');
|
|
2377
|
+
return `${BOLD}${RED}${this.name}: ${this.details}${RESET}\n\n` + contextBlock;
|
|
2378
|
+
}
|
|
2379
|
+
toString() {
|
|
2380
|
+
return this._formattedMessage;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
exports.AbilityDSLSyntaxError = AbilityDSLSyntaxError;
|
|
2384
|
+
|
|
2385
|
+
|
|
2386
|
+
/***/ }),
|
|
2387
|
+
|
|
2388
|
+
/***/ 325:
|
|
2389
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
2390
|
+
|
|
2391
|
+
|
|
2392
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2393
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2394
|
+
};
|
|
2395
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2396
|
+
exports.AbilityDSLToken = void 0;
|
|
2397
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
2398
|
+
/**
|
|
2399
|
+
* Represents a single token produced by the Ability DSL lexer.
|
|
2400
|
+
* Each token carries a type (e.g., EFFECT, IDENTIFIER, STRING) and its raw string value.
|
|
2401
|
+
*/
|
|
2402
|
+
class AbilityDSLToken extends AbilityCode_1.default {
|
|
2403
|
+
/** The literal text of the token as it appeared in the input (e.g., "permit", "user.roles", "admin"). */
|
|
2404
|
+
value = '';
|
|
2405
|
+
/** The line number in DSL */
|
|
2406
|
+
line = 1;
|
|
2407
|
+
/** The column in dsl */
|
|
2408
|
+
column = 1;
|
|
2409
|
+
constructor(type, value, line, column) {
|
|
2410
|
+
super(type);
|
|
2411
|
+
this.value = value;
|
|
2412
|
+
this.line = line;
|
|
2413
|
+
this.column = column;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Returns a human-readable representation of the token, useful for debugging.
|
|
2417
|
+
* Example output: "AbilityDSLToken([EFFECT] permit"
|
|
2418
|
+
*/
|
|
2419
|
+
toString() {
|
|
2420
|
+
return `AbilityDSLToken([${this.code}] "${this.value}" at ${this.line}:${this.column})`;
|
|
2421
|
+
}
|
|
2422
|
+
static EFFECT = 'EFFECT';
|
|
2423
|
+
static IF = 'IF';
|
|
2424
|
+
static PERMISSION = 'PERMISSION';
|
|
2425
|
+
static IDENTIFIER = 'IDENTIFIER';
|
|
2426
|
+
static COLON = 'COLON';
|
|
2427
|
+
static COMMA = 'COMMA';
|
|
2428
|
+
static DOT = 'DOT';
|
|
2429
|
+
static LBRACKET = 'LBRACKET';
|
|
2430
|
+
static RBRACKET = 'RBRACKET';
|
|
2431
|
+
static ALL = 'ALL';
|
|
2432
|
+
static ANY = 'ANY';
|
|
2433
|
+
static OF = 'OF';
|
|
2434
|
+
static EOF = 'EOF';
|
|
2435
|
+
static COMMENT = 'COMMENT';
|
|
2436
|
+
static EQ = 'EQ';
|
|
2437
|
+
static CONTAINS = 'CONTAINS';
|
|
2438
|
+
static IN = 'IN';
|
|
2439
|
+
static NOT_IN = 'NOT_IN';
|
|
2440
|
+
static NOT_CONTAINS = 'NOT_CONTAINS';
|
|
2441
|
+
static GT = 'GT';
|
|
2442
|
+
static GTE = 'GTE';
|
|
2443
|
+
static LT = 'LT';
|
|
2444
|
+
static LTE = 'LTE';
|
|
2445
|
+
static NULL = 'NULL';
|
|
2446
|
+
static EQ_NULL = 'EQ_NULL';
|
|
2447
|
+
static NOT_EQ_NULL = 'NOT_EQ_NULL';
|
|
2448
|
+
static LEN_GT = 'LEN_GT';
|
|
2449
|
+
static LEN_LT = 'LEN_LT';
|
|
2450
|
+
static LEN_EQ = 'LEN_EQ';
|
|
2451
|
+
static NOT_EQ = 'NOT_EQ';
|
|
2452
|
+
static STRING = 'STRING';
|
|
2453
|
+
static NUMBER = 'NUMBER';
|
|
2454
|
+
static BOOLEAN = 'BOOLEAN';
|
|
2455
|
+
static SYMBOL = 'SYMBOL';
|
|
2456
|
+
static KEYWORD = 'KEYWORD';
|
|
2457
|
+
}
|
|
2458
|
+
exports.AbilityDSLToken = AbilityDSLToken;
|
|
2459
|
+
|
|
2460
|
+
|
|
2461
|
+
/***/ }),
|
|
2462
|
+
|
|
2463
|
+
/***/ 909:
|
|
2464
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
2465
|
+
|
|
2466
|
+
|
|
2467
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2468
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2469
|
+
};
|
|
2470
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2471
|
+
exports.AbilityJSONParser = void 0;
|
|
2472
|
+
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
2473
|
+
const AbilityRule_1 = __webpack_require__(306);
|
|
2474
|
+
const AbilityRuleSet_1 = __webpack_require__(56);
|
|
2475
|
+
const AbilityCompare_1 = __importDefault(__webpack_require__(413));
|
|
2476
|
+
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(179));
|
|
2477
|
+
const AbilityPolicy_1 = __webpack_require__(278);
|
|
2478
|
+
class AbilityJSONParser {
|
|
2479
|
+
/**
|
|
2480
|
+
* Parses an array of policy configurations into an array of AbilityPolicy instances.
|
|
2481
|
+
* @param configs - Array of policy configurations
|
|
2482
|
+
* @returns Array of AbilityPolicy instances
|
|
2483
|
+
*/
|
|
2484
|
+
static parse(configs) {
|
|
2485
|
+
return configs.map(config => AbilityJSONParser.parsePolicy(config));
|
|
2486
|
+
}
|
|
2487
|
+
static parsePolicy(config) {
|
|
2488
|
+
const { id, name, ruleSet, compareMethod, permission, effect } = config;
|
|
2489
|
+
// Create the empty policy
|
|
2490
|
+
const policy = new AbilityPolicy_1.AbilityPolicy({
|
|
2491
|
+
name,
|
|
2492
|
+
id,
|
|
2493
|
+
permission: permission,
|
|
2494
|
+
effect: new AbilityPolicyEffect_1.default(effect),
|
|
2495
|
+
});
|
|
2496
|
+
policy.compareMethod = new AbilityCompare_1.default(compareMethod);
|
|
2497
|
+
ruleSet.forEach(ruleSetConfig => {
|
|
2498
|
+
policy.addRuleSet(AbilityRuleSet_1.AbilityRuleSet.fromJSON(ruleSetConfig));
|
|
2499
|
+
});
|
|
2500
|
+
return policy;
|
|
2501
|
+
}
|
|
2502
|
+
static parseRule(config) {
|
|
2503
|
+
const { id, name, subject, resource, condition } = config;
|
|
2504
|
+
return new AbilityRule_1.AbilityRule({
|
|
2505
|
+
id,
|
|
2506
|
+
name,
|
|
2507
|
+
subject,
|
|
2508
|
+
resource,
|
|
2509
|
+
condition: new AbilityCondition_1.default(condition),
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Parse the config JSON format to Group class instance
|
|
2514
|
+
*/
|
|
2515
|
+
static parseRuleSet(config) {
|
|
2516
|
+
const { id, name, rules, compareMethod } = config;
|
|
2517
|
+
const ruleSet = new AbilityRuleSet_1.AbilityRuleSet({
|
|
2518
|
+
compareMethod: new AbilityCompare_1.default(compareMethod),
|
|
2519
|
+
name,
|
|
2520
|
+
id,
|
|
2521
|
+
});
|
|
2522
|
+
// Adding rules if exists
|
|
2523
|
+
if (rules && rules.length > 0) {
|
|
2524
|
+
const abilityRules = rules.map(ruleConfig => AbilityRule_1.AbilityRule.fromJSON(ruleConfig));
|
|
2525
|
+
ruleSet.addRules(abilityRules);
|
|
2526
|
+
}
|
|
2527
|
+
return ruleSet;
|
|
2528
|
+
}
|
|
2529
|
+
static ruleToJSON(rule) {
|
|
2530
|
+
return {
|
|
2531
|
+
id: rule.id,
|
|
2532
|
+
name: rule.name,
|
|
2533
|
+
subject: rule.subject,
|
|
2534
|
+
resource: rule.resource,
|
|
2535
|
+
condition: rule.condition.code,
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
static ruleSetToJSON(ruleSet) {
|
|
2539
|
+
return {
|
|
2540
|
+
id: ruleSet.id.toString(),
|
|
2541
|
+
name: ruleSet.name.toString(),
|
|
2542
|
+
compareMethod: ruleSet.compareMethod.code.toString(),
|
|
2543
|
+
rules: ruleSet.rules.map(rule => AbilityJSONParser.ruleToJSON(rule)),
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
static policyToJSON(policy) {
|
|
2547
|
+
return {
|
|
2548
|
+
id: policy.id.toString(),
|
|
2549
|
+
name: policy.name.toString(),
|
|
2550
|
+
compareMethod: policy.compareMethod.code.toString(),
|
|
2551
|
+
ruleSet: policy.ruleSet.map(ruleSet => AbilityJSONParser.ruleSetToJSON(ruleSet)),
|
|
2552
|
+
permission: policy.permission,
|
|
2553
|
+
effect: policy.effect.code,
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
static toJSON(policies) {
|
|
2557
|
+
return policies.map(policy => AbilityJSONParser.policyToJSON(policy));
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
exports.AbilityJSONParser = AbilityJSONParser;
|
|
767
2561
|
|
|
768
2562
|
|
|
769
2563
|
/***/ })
|