@via-profit/ability 3.0.1 → 3.1.1
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 +65 -0
- package/README.md +900 -934
- 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/{AbilityExplain.d.ts → core/AbilityExplain.d.ts} +3 -3
- package/dist/{AbilityParser.d.ts → core/AbilityParser.d.ts} +2 -0
- package/dist/{AbilityPolicy.d.ts → core/AbilityPolicy.d.ts} +22 -13
- 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} +10 -9
- package/dist/index.d.ts +19 -12
- package/dist/index.js +1718 -254
- 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 +5 -3
- package/dist/AbilityCondition.d.ts +0 -16
- package/dist/AbilityResolver.d.ts +0 -32
- package/dist/AbilityRule.d.ts +0 -78
- /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,35 +150,48 @@ 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
170
|
switch (literal) {
|
|
74
|
-
case '
|
|
75
|
-
return this.
|
|
76
|
-
case '
|
|
77
|
-
return this.
|
|
78
|
-
case '
|
|
79
|
-
return this.
|
|
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;
|
|
80
177
|
case 'less_than':
|
|
81
178
|
return this.less_than;
|
|
82
179
|
case 'less_or_equal':
|
|
83
180
|
return this.less_or_equal;
|
|
84
|
-
case '
|
|
85
|
-
return this.
|
|
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;
|
|
86
187
|
case 'in':
|
|
87
188
|
return this.in;
|
|
88
189
|
case 'not_in':
|
|
89
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;
|
|
90
195
|
default:
|
|
91
196
|
throw new AbilityError_1.AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
|
|
92
197
|
}
|
|
@@ -108,7 +213,7 @@ exports["default"] = AbilityCondition;
|
|
|
108
213
|
|
|
109
214
|
/***/ }),
|
|
110
215
|
|
|
111
|
-
/***/
|
|
216
|
+
/***/ 216:
|
|
112
217
|
/***/ ((__unused_webpack_module, exports) => {
|
|
113
218
|
|
|
114
219
|
|
|
@@ -130,7 +235,7 @@ exports.AbilityParserError = AbilityParserError;
|
|
|
130
235
|
|
|
131
236
|
/***/ }),
|
|
132
237
|
|
|
133
|
-
/***/
|
|
238
|
+
/***/ 221:
|
|
134
239
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
135
240
|
|
|
136
241
|
|
|
@@ -139,7 +244,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
139
244
|
};
|
|
140
245
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
141
246
|
exports.AbilityExplainPolicy = exports.AbilityExplainRuleSet = exports.AbilityExplainRule = exports.AbilityExplain = void 0;
|
|
142
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(
|
|
247
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
143
248
|
class AbilityExplain {
|
|
144
249
|
type;
|
|
145
250
|
children;
|
|
@@ -198,7 +303,7 @@ exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
|
198
303
|
|
|
199
304
|
/***/ }),
|
|
200
305
|
|
|
201
|
-
/***/
|
|
306
|
+
/***/ 247:
|
|
202
307
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
203
308
|
|
|
204
309
|
|
|
@@ -207,7 +312,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
207
312
|
};
|
|
208
313
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
209
314
|
exports.AbilityMatch = void 0;
|
|
210
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(
|
|
315
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
211
316
|
class AbilityMatch extends AbilityCode_1.default {
|
|
212
317
|
static pending = new AbilityMatch('pending');
|
|
213
318
|
static match = new AbilityMatch('match');
|
|
@@ -219,7 +324,7 @@ exports["default"] = AbilityMatch;
|
|
|
219
324
|
|
|
220
325
|
/***/ }),
|
|
221
326
|
|
|
222
|
-
/***/
|
|
327
|
+
/***/ 147:
|
|
223
328
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
224
329
|
|
|
225
330
|
|
|
@@ -228,8 +333,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
228
333
|
};
|
|
229
334
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
230
335
|
exports.AbilityParser = void 0;
|
|
231
|
-
const AbilityError_1 = __webpack_require__(
|
|
232
|
-
const AbilityCondition_1 = __importDefault(__webpack_require__(
|
|
336
|
+
const AbilityError_1 = __webpack_require__(216);
|
|
337
|
+
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
233
338
|
class AbilityParser {
|
|
234
339
|
/**
|
|
235
340
|
* Sets a value in a nested object structure based on a dot/bracket notation path.
|
|
@@ -280,7 +385,7 @@ class AbilityParser {
|
|
|
280
385
|
const typeStructure = {};
|
|
281
386
|
// Iterate through all policies
|
|
282
387
|
policies.forEach(policy => {
|
|
283
|
-
const action = policy.
|
|
388
|
+
const action = policy.permission;
|
|
284
389
|
// Initialize object for action if it doesn't exist
|
|
285
390
|
if (!typeStructure[action]) {
|
|
286
391
|
typeStructure[action] = {};
|
|
@@ -313,9 +418,9 @@ class AbilityParser {
|
|
|
313
418
|
*/
|
|
314
419
|
static determineTypeFromRule(rule) {
|
|
315
420
|
// Numeric comparisons - always number
|
|
316
|
-
if (rule.condition.isEqual(AbilityCondition_1.default.
|
|
421
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.greater_than) ||
|
|
317
422
|
rule.condition.isEqual(AbilityCondition_1.default.less_than) ||
|
|
318
|
-
rule.condition.isEqual(AbilityCondition_1.default.
|
|
423
|
+
rule.condition.isEqual(AbilityCondition_1.default.greater_or_equal) ||
|
|
319
424
|
rule.condition.isEqual(AbilityCondition_1.default.less_or_equal)) {
|
|
320
425
|
return 'number';
|
|
321
426
|
}
|
|
@@ -325,8 +430,8 @@ class AbilityParser {
|
|
|
325
430
|
return this.getArrayType(rule.resource);
|
|
326
431
|
}
|
|
327
432
|
// Equality/Inequality operations
|
|
328
|
-
if (rule.condition.isEqual(AbilityCondition_1.default.
|
|
329
|
-
rule.condition.isEqual(AbilityCondition_1.default.
|
|
433
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.equals) ||
|
|
434
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_equals)) {
|
|
330
435
|
return this.getPrimitiveType(rule.resource);
|
|
331
436
|
}
|
|
332
437
|
return 'any';
|
|
@@ -417,7 +522,8 @@ class AbilityParser {
|
|
|
417
522
|
*/
|
|
418
523
|
static formatTypeDefinitions(structure) {
|
|
419
524
|
let output = '// Automatically generated by via-profit/ability\n';
|
|
420
|
-
output += '// Do not edit manually\n
|
|
525
|
+
output += '// Do not edit manually\n';
|
|
526
|
+
output += '\n/* eslint-disable */\n\n';
|
|
421
527
|
output += 'export type Resources = {\n';
|
|
422
528
|
// Sort actions for stable output
|
|
423
529
|
const sortedActions = Object.keys(structure).sort();
|
|
@@ -462,7 +568,7 @@ exports["default"] = AbilityParser;
|
|
|
462
568
|
|
|
463
569
|
/***/ }),
|
|
464
570
|
|
|
465
|
-
/***/
|
|
571
|
+
/***/ 278:
|
|
466
572
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
467
573
|
|
|
468
574
|
|
|
@@ -471,12 +577,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
471
577
|
};
|
|
472
578
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
473
579
|
exports.AbilityPolicy = void 0;
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
const
|
|
479
|
-
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);
|
|
480
586
|
class AbilityPolicy {
|
|
481
587
|
matchState = AbilityMatch_1.default.pending;
|
|
482
588
|
/**
|
|
@@ -504,14 +610,14 @@ class AbilityPolicy {
|
|
|
504
610
|
id;
|
|
505
611
|
/**
|
|
506
612
|
* Running the `enforce` or `resolve` method
|
|
507
|
-
* will select only those from all passed policies that fall under the specified
|
|
613
|
+
* will select only those from all passed policies that fall under the specified permission key.
|
|
508
614
|
*/
|
|
509
|
-
|
|
615
|
+
permission;
|
|
510
616
|
constructor(params) {
|
|
511
|
-
const { name, id,
|
|
617
|
+
const { name, id, permission, effect, compareMethod = AbilityCompare_1.default.and } = params;
|
|
512
618
|
this.name = name;
|
|
513
619
|
this.id = id;
|
|
514
|
-
this.
|
|
620
|
+
this.permission = permission;
|
|
515
621
|
this.effect = effect;
|
|
516
622
|
this.compareMethod = compareMethod;
|
|
517
623
|
}
|
|
@@ -523,25 +629,45 @@ class AbilityPolicy {
|
|
|
523
629
|
this.ruleSet.push(ruleSet);
|
|
524
630
|
return this;
|
|
525
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
|
+
}
|
|
526
642
|
/**
|
|
527
643
|
* Check if the policy is matched
|
|
528
|
-
* @param
|
|
644
|
+
* @param resource - The resource to check
|
|
645
|
+
* @param environment - The user environment object
|
|
529
646
|
*/
|
|
530
|
-
check(
|
|
647
|
+
async check(resource, environment) {
|
|
531
648
|
this.matchState = AbilityMatch_1.default.mismatch;
|
|
532
649
|
if (!this.ruleSet.length) {
|
|
533
650
|
return this.matchState;
|
|
534
651
|
}
|
|
535
|
-
const rulesetCheckStates =
|
|
536
|
-
|
|
537
|
-
|
|
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
|
+
}
|
|
538
664
|
if (AbilityCompare_1.default.and.isEqual(this.compareMethod)) {
|
|
539
|
-
if (rulesetCheckStates.every(
|
|
665
|
+
if (rulesetCheckStates.every(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
540
666
|
this.matchState = AbilityMatch_1.default.match;
|
|
541
667
|
}
|
|
542
668
|
}
|
|
543
669
|
if (AbilityCompare_1.default.or.isEqual(this.compareMethod)) {
|
|
544
|
-
if (rulesetCheckStates.some(
|
|
670
|
+
if (rulesetCheckStates.some(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
545
671
|
this.matchState = AbilityMatch_1.default.match;
|
|
546
672
|
}
|
|
547
673
|
}
|
|
@@ -558,36 +684,23 @@ class AbilityPolicy {
|
|
|
558
684
|
* @param configs - Array of policy configurations
|
|
559
685
|
* @returns Array of AbilityPolicy instances
|
|
560
686
|
*/
|
|
561
|
-
static
|
|
562
|
-
return
|
|
687
|
+
static fromJSONAll(configs) {
|
|
688
|
+
return AbilityJSONParser_1.AbilityJSONParser.parse(configs);
|
|
563
689
|
}
|
|
564
690
|
/**
|
|
565
691
|
* Parse the config JSON format to Policy class instance
|
|
566
692
|
*/
|
|
567
|
-
static
|
|
568
|
-
|
|
569
|
-
// Create the empty policy
|
|
570
|
-
const policy = new AbilityPolicy({
|
|
571
|
-
name,
|
|
572
|
-
id,
|
|
573
|
-
action,
|
|
574
|
-
effect: new AbilityPolicyEffect_1.default(effect),
|
|
575
|
-
});
|
|
576
|
-
policy.compareMethod = new AbilityCompare_1.default(compareMethod);
|
|
577
|
-
ruleSet.forEach(ruleSetConfig => {
|
|
578
|
-
policy.addRuleSet(AbilityRuleSet_1.default.parse(ruleSetConfig));
|
|
579
|
-
});
|
|
580
|
-
return policy;
|
|
693
|
+
static fromJSON(config) {
|
|
694
|
+
return AbilityJSONParser_1.AbilityJSONParser.parsePolicy(config);
|
|
581
695
|
}
|
|
582
|
-
|
|
583
|
-
return
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
};
|
|
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());
|
|
591
704
|
}
|
|
592
705
|
}
|
|
593
706
|
exports.AbilityPolicy = AbilityPolicy;
|
|
@@ -596,7 +709,7 @@ exports["default"] = AbilityPolicy;
|
|
|
596
709
|
|
|
597
710
|
/***/ }),
|
|
598
711
|
|
|
599
|
-
/***/
|
|
712
|
+
/***/ 179:
|
|
600
713
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
601
714
|
|
|
602
715
|
|
|
@@ -605,7 +718,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
605
718
|
};
|
|
606
719
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
607
720
|
exports.AbilityPolicyEffect = void 0;
|
|
608
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(
|
|
721
|
+
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
609
722
|
class AbilityPolicyEffect extends AbilityCode_1.default {
|
|
610
723
|
static deny = new AbilityPolicyEffect('deny');
|
|
611
724
|
static permit = new AbilityPolicyEffect('permit');
|
|
@@ -616,7 +729,7 @@ exports["default"] = AbilityPolicyEffect;
|
|
|
616
729
|
|
|
617
730
|
/***/ }),
|
|
618
731
|
|
|
619
|
-
/***/
|
|
732
|
+
/***/ 634:
|
|
620
733
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
621
734
|
|
|
622
735
|
|
|
@@ -625,96 +738,159 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
625
738
|
};
|
|
626
739
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
627
740
|
exports.AbilityResolver = void 0;
|
|
628
|
-
const
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
const AbilityExplain_1 = __webpack_require__(363);
|
|
741
|
+
const AbilityError_1 = __webpack_require__(216);
|
|
742
|
+
const AbilityResult_1 = __webpack_require__(941);
|
|
743
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
632
744
|
class AbilityResolver {
|
|
633
745
|
policies;
|
|
634
|
-
|
|
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;
|
|
635
754
|
this.policies = Array.isArray(policyOrListOfPolicies)
|
|
636
755
|
? policyOrListOfPolicies
|
|
637
756
|
: [policyOrListOfPolicies];
|
|
638
757
|
}
|
|
639
758
|
/**
|
|
640
|
-
* Resolve policy for the resource and
|
|
759
|
+
* Resolve policy for the resource and permission key
|
|
641
760
|
*
|
|
642
|
-
@param
|
|
761
|
+
* @param permission - Permission key
|
|
643
762
|
* @param resource - Resource
|
|
763
|
+
* @param environment
|
|
644
764
|
*/
|
|
645
|
-
resolve(
|
|
646
|
-
const filteredPolicies = this.policies.filter(policy =>
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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);
|
|
652
786
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
|
798
|
+
*/
|
|
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];
|
|
656
805
|
});
|
|
657
806
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
throw new AbilityError_1.AbilityError(resolver.getMatchedPolicy()?.name?.toString() || 'Unknown permission error');
|
|
807
|
+
makeCacheKey(policyId, resource, environment) {
|
|
808
|
+
if (!this.cache) {
|
|
809
|
+
return '';
|
|
662
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);
|
|
663
815
|
}
|
|
816
|
+
async invalidateCache() {
|
|
817
|
+
await this.cache?.clear();
|
|
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 {
|
|
664
839
|
/**
|
|
665
|
-
*
|
|
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.
|
|
666
849
|
*
|
|
667
|
-
*
|
|
850
|
+
* Useful for debugging, logging, or building UI tools that visualize permission logic.
|
|
668
851
|
*/
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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];
|
|
673
861
|
}
|
|
674
|
-
return collect;
|
|
675
|
-
}, []);
|
|
676
|
-
if (effects.length) {
|
|
677
|
-
return effects[effects.length - 1];
|
|
678
862
|
}
|
|
679
863
|
return null;
|
|
680
864
|
}
|
|
681
|
-
|
|
682
|
-
const effect = this.
|
|
683
|
-
return effect
|
|
865
|
+
isAllowed() {
|
|
866
|
+
const effect = this.getLastEffectOfMatchedPolicy();
|
|
867
|
+
return effect?.isEqual(AbilityPolicyEffect_1.default.permit) ?? false;
|
|
684
868
|
}
|
|
685
|
-
|
|
686
|
-
const effect = this.
|
|
687
|
-
return effect
|
|
688
|
-
}
|
|
689
|
-
getMatchedPolicy() {
|
|
690
|
-
const matchedPolicies = this.policies.filter(policy => policy.matchState.isEqual(AbilityMatch_1.default.match));
|
|
691
|
-
const lastPolicy = matchedPolicies.length ? matchedPolicies[matchedPolicies.length - 1] : null;
|
|
692
|
-
return lastPolicy || null;
|
|
869
|
+
isDenied() {
|
|
870
|
+
const effect = this.getLastEffectOfMatchedPolicy();
|
|
871
|
+
return effect?.isEqual(AbilityPolicyEffect_1.default.deny) ?? true;
|
|
693
872
|
}
|
|
694
873
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
* @
|
|
874
|
+
* Get the last effect of the policy
|
|
875
|
+
*
|
|
876
|
+
* @returns {AbilityPolicyEffect | null}
|
|
698
877
|
*/
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
}, []);
|
|
708
|
-
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;
|
|
709
886
|
}
|
|
710
887
|
}
|
|
711
|
-
exports.
|
|
712
|
-
exports["default"] = AbilityResolver;
|
|
888
|
+
exports.AbilityResult = AbilityResult;
|
|
713
889
|
|
|
714
890
|
|
|
715
891
|
/***/ }),
|
|
716
892
|
|
|
717
|
-
/***/
|
|
893
|
+
/***/ 306:
|
|
718
894
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
719
895
|
|
|
720
896
|
|
|
@@ -723,8 +899,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
723
899
|
};
|
|
724
900
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
725
901
|
exports.AbilityRule = void 0;
|
|
726
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(
|
|
727
|
-
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);
|
|
728
905
|
/**
|
|
729
906
|
* Represents a rule that defines a condition to be checked against a subject and resource.
|
|
730
907
|
*/
|
|
@@ -743,11 +920,11 @@ class AbilityRule {
|
|
|
743
920
|
state = AbilityMatch_1.default.pending;
|
|
744
921
|
/**
|
|
745
922
|
* Creates an instance of AbilityRule.
|
|
746
|
-
* @param {string}
|
|
747
|
-
* @param {string}
|
|
748
|
-
* @param {AbilityCondition}
|
|
749
|
-
* @param {string}
|
|
750
|
-
* @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.
|
|
751
928
|
* @param params
|
|
752
929
|
*/
|
|
753
930
|
constructor(params) {
|
|
@@ -761,54 +938,139 @@ class AbilityRule {
|
|
|
761
938
|
/**
|
|
762
939
|
* Check if the rule is matched
|
|
763
940
|
* @param resource - The resource to check
|
|
941
|
+
* @param environment
|
|
764
942
|
*/
|
|
765
|
-
check(resource) {
|
|
943
|
+
async check(resource, environment) {
|
|
766
944
|
let is = false;
|
|
767
|
-
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
|
|
768
956
|
if (AbilityCondition_1.default.less_than.isEqual(this.condition)) {
|
|
769
|
-
|
|
957
|
+
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
958
|
+
is = subjectValue < resourceValue;
|
|
959
|
+
}
|
|
770
960
|
}
|
|
961
|
+
// less or equal
|
|
771
962
|
if (AbilityCondition_1.default.less_or_equal.isEqual(this.condition)) {
|
|
772
|
-
|
|
963
|
+
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
964
|
+
is = subjectValue <= resourceValue;
|
|
965
|
+
}
|
|
773
966
|
}
|
|
774
|
-
|
|
775
|
-
|
|
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
|
+
}
|
|
776
972
|
}
|
|
777
|
-
|
|
778
|
-
|
|
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
|
+
}
|
|
779
978
|
}
|
|
780
|
-
|
|
781
|
-
|
|
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
|
+
}
|
|
782
989
|
}
|
|
783
|
-
|
|
784
|
-
|
|
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
|
+
}
|
|
785
998
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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);
|
|
790
1004
|
}
|
|
791
|
-
//
|
|
792
|
-
if ((
|
|
793
|
-
is =
|
|
1005
|
+
// array intersects array
|
|
1006
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1007
|
+
is = subjectValue.some(v => resourceValue.includes(v));
|
|
794
1008
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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));
|
|
798
1017
|
}
|
|
799
1018
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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;
|
|
1024
|
+
}
|
|
1025
|
+
// ['foo', 'bar'] = n
|
|
1026
|
+
else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
|
|
1027
|
+
is = subjectValue.length === resourceValue;
|
|
1028
|
+
}
|
|
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;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
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;
|
|
804
1043
|
}
|
|
805
|
-
//
|
|
806
|
-
if ((
|
|
807
|
-
is =
|
|
1044
|
+
// ['foo', 'bar'] > n
|
|
1045
|
+
else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
|
|
1046
|
+
is = subjectValue.length > resourceValue;
|
|
808
1047
|
}
|
|
809
|
-
// [
|
|
810
|
-
if ((
|
|
811
|
-
is =
|
|
1048
|
+
// ['foo', 'bar'] > ['baz', 'taz']
|
|
1049
|
+
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
1050
|
+
is = subjectValue.length > resourceValue.length;
|
|
1051
|
+
}
|
|
1052
|
+
// 'foo' > 'bar'
|
|
1053
|
+
else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
|
|
1054
|
+
is = subjectValue.length > resourceValue.length;
|
|
1055
|
+
}
|
|
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;
|
|
812
1074
|
}
|
|
813
1075
|
}
|
|
814
1076
|
this.state = is ? AbilityMatch_1.default.match : AbilityMatch_1.default.mismatch;
|
|
@@ -817,29 +1079,44 @@ class AbilityRule {
|
|
|
817
1079
|
/**
|
|
818
1080
|
* Extract values from the resourceData
|
|
819
1081
|
* @param resourceData - The resourceData to extract values from
|
|
1082
|
+
* @param environment - Environment data
|
|
820
1083
|
*/
|
|
821
|
-
extractValues(resourceData) {
|
|
822
|
-
let
|
|
823
|
-
let
|
|
824
|
-
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')) {
|
|
825
1089
|
return [NaN, NaN];
|
|
826
1090
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
+
}
|
|
832
1101
|
}
|
|
833
1102
|
else {
|
|
834
|
-
|
|
1103
|
+
subjectValue = this.subject;
|
|
835
1104
|
}
|
|
836
|
-
|
|
837
|
-
|
|
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
|
+
}
|
|
838
1115
|
}
|
|
839
1116
|
else {
|
|
840
|
-
|
|
1117
|
+
resourceValue = this.resource;
|
|
841
1118
|
}
|
|
842
|
-
return [
|
|
1119
|
+
return [subjectValue, resourceValue];
|
|
843
1120
|
}
|
|
844
1121
|
/**
|
|
845
1122
|
* Get the value of the object by dot notation
|
|
@@ -850,7 +1127,7 @@ class AbilityRule {
|
|
|
850
1127
|
const arr = desc.split('.');
|
|
851
1128
|
while (arr.length && resource) {
|
|
852
1129
|
const comp = arr.shift() || '';
|
|
853
|
-
const match = new RegExp('(.+)\\[([0-9]*)
|
|
1130
|
+
const match = new RegExp('(.+)\\[([0-9]*)]').exec(comp);
|
|
854
1131
|
if (match !== null && match.length == 3) {
|
|
855
1132
|
const arrayData = {
|
|
856
1133
|
arrName: match[1],
|
|
@@ -869,31 +1146,36 @@ class AbilityRule {
|
|
|
869
1146
|
}
|
|
870
1147
|
return resource;
|
|
871
1148
|
}
|
|
872
|
-
|
|
873
|
-
|
|
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) {
|
|
874
1156
|
return new AbilityRule({
|
|
875
|
-
|
|
876
|
-
name,
|
|
1157
|
+
condition: AbilityCondition_1.default.equals,
|
|
877
1158
|
subject,
|
|
878
1159
|
resource,
|
|
879
|
-
condition: new AbilityCondition_1.default(condition),
|
|
880
1160
|
});
|
|
881
1161
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
name: this.name,
|
|
889
|
-
subject: this.subject,
|
|
890
|
-
resource: this.resource,
|
|
891
|
-
condition: this.condition.code,
|
|
892
|
-
};
|
|
1162
|
+
static notEquals(subject, resource) {
|
|
1163
|
+
return new AbilityRule({
|
|
1164
|
+
condition: AbilityCondition_1.default.not_equals,
|
|
1165
|
+
subject,
|
|
1166
|
+
resource,
|
|
1167
|
+
});
|
|
893
1168
|
}
|
|
894
|
-
static
|
|
1169
|
+
static contains(subject, resource) {
|
|
895
1170
|
return new AbilityRule({
|
|
896
|
-
condition: AbilityCondition_1.default.
|
|
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,
|
|
897
1179
|
subject,
|
|
898
1180
|
resource,
|
|
899
1181
|
});
|
|
@@ -914,7 +1196,7 @@ class AbilityRule {
|
|
|
914
1196
|
}
|
|
915
1197
|
static notEqual(subject, resource) {
|
|
916
1198
|
return new AbilityRule({
|
|
917
|
-
condition: AbilityCondition_1.default.
|
|
1199
|
+
condition: AbilityCondition_1.default.not_equals,
|
|
918
1200
|
subject,
|
|
919
1201
|
resource,
|
|
920
1202
|
});
|
|
@@ -935,14 +1217,14 @@ class AbilityRule {
|
|
|
935
1217
|
}
|
|
936
1218
|
static moreThan(subject, resource) {
|
|
937
1219
|
return new AbilityRule({
|
|
938
|
-
condition: AbilityCondition_1.default.
|
|
1220
|
+
condition: AbilityCondition_1.default.greater_than,
|
|
939
1221
|
subject,
|
|
940
1222
|
resource,
|
|
941
1223
|
});
|
|
942
1224
|
}
|
|
943
1225
|
static moreOrEqual(subject, resource) {
|
|
944
1226
|
return new AbilityRule({
|
|
945
|
-
condition: AbilityCondition_1.default.
|
|
1227
|
+
condition: AbilityCondition_1.default.greater_or_equal,
|
|
946
1228
|
subject,
|
|
947
1229
|
resource,
|
|
948
1230
|
});
|
|
@@ -954,7 +1236,7 @@ exports["default"] = AbilityRule;
|
|
|
954
1236
|
|
|
955
1237
|
/***/ }),
|
|
956
1238
|
|
|
957
|
-
/***/
|
|
1239
|
+
/***/ 56:
|
|
958
1240
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
959
1241
|
|
|
960
1242
|
|
|
@@ -963,9 +1245,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
963
1245
|
};
|
|
964
1246
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
965
1247
|
exports.AbilityRuleSet = void 0;
|
|
966
|
-
const
|
|
967
|
-
const
|
|
968
|
-
const
|
|
1248
|
+
const AbilityCompare_1 = __importDefault(__webpack_require__(413));
|
|
1249
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
1250
|
+
const AbilityJSONParser_1 = __webpack_require__(909);
|
|
969
1251
|
class AbilityRuleSet {
|
|
970
1252
|
state = AbilityMatch_1.default.pending;
|
|
971
1253
|
/**
|
|
@@ -989,7 +1271,7 @@ class AbilityRuleSet {
|
|
|
989
1271
|
id;
|
|
990
1272
|
constructor(params) {
|
|
991
1273
|
const { name, id, compareMethod } = params;
|
|
992
|
-
this.name = name || '
|
|
1274
|
+
this.name = name || '';
|
|
993
1275
|
this.id = id || this.name;
|
|
994
1276
|
this.compareMethod = compareMethod;
|
|
995
1277
|
}
|
|
@@ -1001,50 +1283,43 @@ class AbilityRuleSet {
|
|
|
1001
1283
|
rules.forEach(rule => this.addRule(rule));
|
|
1002
1284
|
return this;
|
|
1003
1285
|
}
|
|
1004
|
-
check(resources) {
|
|
1286
|
+
async check(resources, environment) {
|
|
1005
1287
|
this.state = AbilityMatch_1.default.mismatch;
|
|
1006
1288
|
if (!this.rules.length) {
|
|
1007
1289
|
return this.state;
|
|
1008
1290
|
}
|
|
1009
|
-
const ruleCheckStates =
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
+
}
|
|
1012
1303
|
if (AbilityCompare_1.default.and.isEqual(this.compareMethod)) {
|
|
1013
|
-
if (ruleCheckStates.every(
|
|
1304
|
+
if (ruleCheckStates.every(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
1014
1305
|
this.state = AbilityMatch_1.default.match;
|
|
1015
1306
|
}
|
|
1016
1307
|
}
|
|
1017
1308
|
if (AbilityCompare_1.default.or.isEqual(this.compareMethod)) {
|
|
1018
|
-
if (ruleCheckStates.some(
|
|
1309
|
+
if (ruleCheckStates.some(s => AbilityMatch_1.default.match.isEqual(s))) {
|
|
1019
1310
|
this.state = AbilityMatch_1.default.match;
|
|
1020
1311
|
}
|
|
1021
1312
|
}
|
|
1022
1313
|
return this.state;
|
|
1023
1314
|
}
|
|
1315
|
+
toString() {
|
|
1316
|
+
return `AbilityRuleSet: ${this.name} compareMethod: ${this.compareMethod.code}, rules: ${this.rules.map(rule => rule.toString()).join('\n')}`;
|
|
1317
|
+
}
|
|
1024
1318
|
/**
|
|
1025
1319
|
* Parse the config JSON format to Group class instance
|
|
1026
1320
|
*/
|
|
1027
|
-
static
|
|
1028
|
-
|
|
1029
|
-
const ruleSet = new AbilityRuleSet({
|
|
1030
|
-
compareMethod: new AbilityCompare_1.default(compareMethod),
|
|
1031
|
-
name,
|
|
1032
|
-
id,
|
|
1033
|
-
});
|
|
1034
|
-
// Adding rules if exists
|
|
1035
|
-
if (rules && rules.length > 0) {
|
|
1036
|
-
const abilityRules = rules.map(ruleConfig => AbilityRule_1.default.parse(ruleConfig));
|
|
1037
|
-
ruleSet.addRules(abilityRules);
|
|
1038
|
-
}
|
|
1039
|
-
return ruleSet;
|
|
1040
|
-
}
|
|
1041
|
-
export() {
|
|
1042
|
-
return {
|
|
1043
|
-
id: this.id.toString(),
|
|
1044
|
-
name: this.name.toString(),
|
|
1045
|
-
compareMethod: this.compareMethod.code.toString(),
|
|
1046
|
-
rules: this.rules.map(rule => rule.export()),
|
|
1047
|
-
};
|
|
1321
|
+
static fromJSON(config) {
|
|
1322
|
+
return AbilityJSONParser_1.AbilityJSONParser.parseRuleSet(config);
|
|
1048
1323
|
}
|
|
1049
1324
|
static and(rules) {
|
|
1050
1325
|
return new AbilityRuleSet({
|
|
@@ -1082,18 +1357,1207 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
1082
1357
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
1083
1358
|
};
|
|
1084
1359
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1085
|
-
__exportStar(__webpack_require__(
|
|
1086
|
-
__exportStar(__webpack_require__(
|
|
1087
|
-
__exportStar(__webpack_require__(
|
|
1088
|
-
__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);
|
|
1089
1375
|
__exportStar(__webpack_require__(909), exports);
|
|
1090
|
-
__exportStar(__webpack_require__(
|
|
1091
|
-
__exportStar(__webpack_require__(
|
|
1092
|
-
__exportStar(__webpack_require__(
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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;
|
|
1097
2561
|
|
|
1098
2562
|
|
|
1099
2563
|
/***/ })
|