@via-profit/ability 3.1.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -127
- package/README.md +238 -39
- package/dist/core/AbilityPolicy.d.ts +9 -14
- package/dist/core/AbilityResolver.d.ts +1 -1
- package/dist/core/AbilityResult.d.ts +1 -1
- package/dist/core/AbilityRule.d.ts +7 -1
- package/dist/core/AbilityRuleSet.d.ts +7 -5
- package/dist/core/AbilityTypeGenerator.d.ts +55 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +661 -1049
- package/dist/parsers/dsl/AbilityDSLParser.d.ts +1 -1
- package/dist/parsers/json/AbilityJSONParser.d.ts +1 -1
- package/package.json +13 -16
package/dist/index.js
CHANGED
|
@@ -1,105 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
/******/ "use strict";
|
|
3
|
-
/******/ var __webpack_modules__ = ({
|
|
1
|
+
'use strict';
|
|
4
2
|
|
|
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:
|
|
98
|
-
/***/ ((__unused_webpack_module, exports) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
102
|
-
exports.AbilityCode = void 0;
|
|
103
3
|
class AbilityCode {
|
|
104
4
|
_code;
|
|
105
5
|
constructor(code) {
|
|
@@ -115,44 +15,24 @@ class AbilityCode {
|
|
|
115
15
|
return !this.isEqual(compareWith);
|
|
116
16
|
}
|
|
117
17
|
}
|
|
118
|
-
exports.AbilityCode = AbilityCode;
|
|
119
|
-
exports["default"] = AbilityCode;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/***/ }),
|
|
123
18
|
|
|
124
|
-
|
|
125
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
129
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
130
|
-
};
|
|
131
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
132
|
-
exports.AbilityCompare = void 0;
|
|
133
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
134
|
-
class AbilityCompare extends AbilityCode_1.default {
|
|
19
|
+
class AbilityCompare extends AbilityCode {
|
|
135
20
|
static and = new AbilityCompare('and');
|
|
136
21
|
static or = new AbilityCompare('or');
|
|
137
22
|
}
|
|
138
|
-
exports.AbilityCompare = AbilityCompare;
|
|
139
|
-
exports["default"] = AbilityCompare;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
/***/ }),
|
|
143
|
-
|
|
144
|
-
/***/ 167:
|
|
145
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
146
23
|
|
|
24
|
+
class AbilityError extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class AbilityParserError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
147
34
|
|
|
148
|
-
|
|
149
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
150
|
-
};
|
|
151
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
152
|
-
exports.AbilityCondition = void 0;
|
|
153
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
154
|
-
const AbilityError_1 = __webpack_require__(216);
|
|
155
|
-
class AbilityCondition extends AbilityCode_1.default {
|
|
35
|
+
class AbilityCondition extends AbilityCode {
|
|
156
36
|
static equals = new AbilityCondition('=');
|
|
157
37
|
static not_equals = new AbilityCondition('<>');
|
|
158
38
|
static greater_than = new AbilityCondition('>');
|
|
@@ -193,7 +73,7 @@ class AbilityCondition extends AbilityCode_1.default {
|
|
|
193
73
|
case 'length_equals':
|
|
194
74
|
return this.length_equals;
|
|
195
75
|
default:
|
|
196
|
-
throw new
|
|
76
|
+
throw new AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
|
|
197
77
|
}
|
|
198
78
|
}
|
|
199
79
|
get literal() {
|
|
@@ -207,184 +87,27 @@ class AbilityCondition extends AbilityCode_1.default {
|
|
|
207
87
|
return literal;
|
|
208
88
|
}
|
|
209
89
|
}
|
|
210
|
-
exports.AbilityCondition = AbilityCondition;
|
|
211
|
-
exports["default"] = AbilityCondition;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
/***/ }),
|
|
215
|
-
|
|
216
|
-
/***/ 216:
|
|
217
|
-
/***/ ((__unused_webpack_module, exports) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
221
|
-
exports.AbilityParserError = exports.AbilityError = void 0;
|
|
222
|
-
class AbilityError extends Error {
|
|
223
|
-
constructor(message) {
|
|
224
|
-
super(message);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
exports.AbilityError = AbilityError;
|
|
228
|
-
class AbilityParserError extends Error {
|
|
229
|
-
constructor(message) {
|
|
230
|
-
super(message);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
exports.AbilityParserError = AbilityParserError;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
/***/ }),
|
|
237
|
-
|
|
238
|
-
/***/ 221:
|
|
239
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
243
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
244
|
-
};
|
|
245
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
246
|
-
exports.AbilityExplainPolicy = exports.AbilityExplainRuleSet = exports.AbilityExplainRule = exports.AbilityExplain = void 0;
|
|
247
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
248
|
-
class AbilityExplain {
|
|
249
|
-
type;
|
|
250
|
-
children;
|
|
251
|
-
name;
|
|
252
|
-
match;
|
|
253
|
-
constructor(config, children = []) {
|
|
254
|
-
this.type = config.type;
|
|
255
|
-
this.children = children;
|
|
256
|
-
this.name = config.name;
|
|
257
|
-
this.match = config.match;
|
|
258
|
-
}
|
|
259
|
-
toString(indent = 0) {
|
|
260
|
-
const pad = ' '.repeat(indent);
|
|
261
|
-
const mark = this.match.code === AbilityMatch_1.default.match.code ? '✓' : '✗';
|
|
262
|
-
let out = `${pad}${mark} ${this.type} «${this.name}» is ${this.match.code}`;
|
|
263
|
-
this.children.forEach(child => {
|
|
264
|
-
out += '\n' + child.toString(indent + 1);
|
|
265
|
-
});
|
|
266
|
-
return out;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
exports.AbilityExplain = AbilityExplain;
|
|
270
|
-
class AbilityExplainRule extends AbilityExplain {
|
|
271
|
-
constructor(rule) {
|
|
272
|
-
super({
|
|
273
|
-
type: 'rule',
|
|
274
|
-
match: rule.state,
|
|
275
|
-
name: rule.name,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
exports.AbilityExplainRule = AbilityExplainRule;
|
|
280
|
-
class AbilityExplainRuleSet extends AbilityExplain {
|
|
281
|
-
constructor(ruleSet) {
|
|
282
|
-
const children = ruleSet.rules.map(rule => new AbilityExplainRule(rule));
|
|
283
|
-
super({
|
|
284
|
-
type: 'ruleSet',
|
|
285
|
-
match: ruleSet.state,
|
|
286
|
-
name: ruleSet.name,
|
|
287
|
-
}, children);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
|
|
291
|
-
class AbilityExplainPolicy extends AbilityExplain {
|
|
292
|
-
constructor(policy) {
|
|
293
|
-
const children = policy.ruleSet.map(ruleSet => new AbilityExplainRuleSet(ruleSet));
|
|
294
|
-
super({
|
|
295
|
-
type: 'policy',
|
|
296
|
-
name: policy.name,
|
|
297
|
-
match: policy.matchState,
|
|
298
|
-
}, children);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
/***/ }),
|
|
305
|
-
|
|
306
|
-
/***/ 247:
|
|
307
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
308
|
-
|
|
309
90
|
|
|
310
|
-
|
|
311
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
312
|
-
};
|
|
313
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
314
|
-
exports.AbilityMatch = void 0;
|
|
315
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
316
|
-
class AbilityMatch extends AbilityCode_1.default {
|
|
91
|
+
class AbilityMatch extends AbilityCode {
|
|
317
92
|
static pending = new AbilityMatch('pending');
|
|
318
93
|
static match = new AbilityMatch('match');
|
|
319
94
|
static mismatch = new AbilityMatch('mismatch');
|
|
320
95
|
}
|
|
321
|
-
exports.AbilityMatch = AbilityMatch;
|
|
322
|
-
exports["default"] = AbilityMatch;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
/***/ }),
|
|
326
|
-
|
|
327
|
-
/***/ 147:
|
|
328
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
329
96
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
335
|
-
exports.AbilityParser = void 0;
|
|
336
|
-
const AbilityError_1 = __webpack_require__(216);
|
|
337
|
-
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
338
|
-
class AbilityParser {
|
|
339
|
-
/**
|
|
340
|
-
* Sets a value in a nested object structure based on a dot/bracket notation path.
|
|
341
|
-
* @param object - The target object to modify.
|
|
342
|
-
* @param path - The path to the property in dot/bracket notation.
|
|
343
|
-
* @param value - The value to set at the specified path.
|
|
344
|
-
*/
|
|
345
|
-
static setValueDotValue(object, path, value) {
|
|
346
|
-
if (!path || path.trim().length === 0) {
|
|
347
|
-
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
348
|
-
}
|
|
349
|
-
const way = path.replace(/\[/g, '.').replace(/]/g, '').split('.');
|
|
350
|
-
const last = way.pop();
|
|
351
|
-
if (!last) {
|
|
352
|
-
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
353
|
-
}
|
|
354
|
-
const lastObj = way.reduce((acc, key, index, array) => {
|
|
355
|
-
const currentValue = acc[key];
|
|
356
|
-
const nextKey = array[index + 1];
|
|
357
|
-
const shouldBeArray = nextKey !== undefined && isFinite(Number(nextKey));
|
|
358
|
-
if (currentValue === undefined || currentValue === null) {
|
|
359
|
-
// Create missing property
|
|
360
|
-
const newValue = shouldBeArray ? [] : {};
|
|
361
|
-
acc[key] = newValue;
|
|
362
|
-
return newValue;
|
|
363
|
-
}
|
|
364
|
-
if (typeof currentValue !== 'object') {
|
|
365
|
-
throw new AbilityError_1.AbilityParserError(`Cannot set property '${key}' on non-object value at path: ${path}`);
|
|
366
|
-
}
|
|
367
|
-
return currentValue;
|
|
368
|
-
}, object);
|
|
369
|
-
const existingValue = lastObj[last];
|
|
370
|
-
if (existingValue !== undefined &&
|
|
371
|
-
typeof existingValue === 'object' &&
|
|
372
|
-
existingValue !== null &&
|
|
373
|
-
!Array.isArray(existingValue)) {
|
|
374
|
-
throw new AbilityError_1.AbilityParserError(`Cannot set primitive value on existing object at path: ${path}`);
|
|
375
|
-
}
|
|
376
|
-
lastObj[last] = value;
|
|
97
|
+
class AbilityTypeGenerator {
|
|
98
|
+
policies;
|
|
99
|
+
constructor(policies) {
|
|
100
|
+
this.policies = policies;
|
|
377
101
|
}
|
|
378
102
|
/**
|
|
379
103
|
* Generates TypeScript type definitions based on the provided policies.
|
|
380
|
-
* @param policies - An array of AbilityPolicy instances.
|
|
381
104
|
* @returns A generated type definitions.
|
|
382
105
|
*/
|
|
383
|
-
|
|
106
|
+
generateTypeDefs() {
|
|
384
107
|
// Structure to store types: { [action]: { [subjectPath]: type } }
|
|
385
108
|
const typeStructure = {};
|
|
386
109
|
// Iterate through all policies
|
|
387
|
-
policies.forEach(policy => {
|
|
110
|
+
this.policies.forEach(policy => {
|
|
388
111
|
const action = policy.permission;
|
|
389
112
|
// Initialize object for action if it doesn't exist
|
|
390
113
|
if (!typeStructure[action]) {
|
|
@@ -416,22 +139,22 @@ class AbilityParser {
|
|
|
416
139
|
* @param rule - The rule to analyze
|
|
417
140
|
* @returns TypeScript type as string
|
|
418
141
|
*/
|
|
419
|
-
|
|
142
|
+
determineTypeFromRule(rule) {
|
|
420
143
|
// Numeric comparisons - always number
|
|
421
|
-
if (rule.condition.isEqual(
|
|
422
|
-
rule.condition.isEqual(
|
|
423
|
-
rule.condition.isEqual(
|
|
424
|
-
rule.condition.isEqual(
|
|
144
|
+
if (rule.condition.isEqual(AbilityCondition.greater_than) ||
|
|
145
|
+
rule.condition.isEqual(AbilityCondition.less_than) ||
|
|
146
|
+
rule.condition.isEqual(AbilityCondition.greater_or_equal) ||
|
|
147
|
+
rule.condition.isEqual(AbilityCondition.less_or_equal)) {
|
|
425
148
|
return 'number';
|
|
426
149
|
}
|
|
427
150
|
// Array operations
|
|
428
|
-
if (rule.condition.isEqual(
|
|
429
|
-
rule.condition.isEqual(
|
|
151
|
+
if (rule.condition.isEqual(AbilityCondition.in) ||
|
|
152
|
+
rule.condition.isEqual(AbilityCondition.not_in)) {
|
|
430
153
|
return this.getArrayType(rule.resource);
|
|
431
154
|
}
|
|
432
155
|
// Equality/Inequality operations
|
|
433
|
-
if (rule.condition.isEqual(
|
|
434
|
-
rule.condition.isEqual(
|
|
156
|
+
if (rule.condition.isEqual(AbilityCondition.equals) ||
|
|
157
|
+
rule.condition.isEqual(AbilityCondition.not_equals)) {
|
|
435
158
|
return this.getPrimitiveType(rule.resource);
|
|
436
159
|
}
|
|
437
160
|
return 'any';
|
|
@@ -441,7 +164,7 @@ class AbilityParser {
|
|
|
441
164
|
* @param resource - The resource value to analyze
|
|
442
165
|
* @returns TypeScript array type as string
|
|
443
166
|
*/
|
|
444
|
-
|
|
167
|
+
getArrayType(resource) {
|
|
445
168
|
if (Array.isArray(resource)) {
|
|
446
169
|
if (resource.length === 0)
|
|
447
170
|
return 'any[]';
|
|
@@ -450,18 +173,18 @@ class AbilityParser {
|
|
|
450
173
|
const elementType = elementTypes.size === 1
|
|
451
174
|
? Array.from(elementTypes)[0]
|
|
452
175
|
: `(${Array.from(elementTypes).join(' | ')})`;
|
|
453
|
-
return
|
|
176
|
+
return `readonly ${elementType}[]`;
|
|
454
177
|
}
|
|
455
178
|
// If resource is not an array but condition is in/not_in,
|
|
456
179
|
// it expects an array of such elements
|
|
457
|
-
return
|
|
180
|
+
return `readonly ${this.getPrimitiveType(resource)}[]`;
|
|
458
181
|
}
|
|
459
182
|
/**
|
|
460
183
|
* Gets primitive TypeScript type for a value
|
|
461
184
|
* @param value - The value to analyze
|
|
462
185
|
* @returns TypeScript primitive type as string
|
|
463
186
|
*/
|
|
464
|
-
|
|
187
|
+
getPrimitiveType(value) {
|
|
465
188
|
if (value === null)
|
|
466
189
|
return 'null';
|
|
467
190
|
if (value === undefined)
|
|
@@ -488,7 +211,7 @@ class AbilityParser {
|
|
|
488
211
|
* @param flatStructure - Flat structure with dot notation paths
|
|
489
212
|
* @returns Nested object structure
|
|
490
213
|
*/
|
|
491
|
-
|
|
214
|
+
buildNestedStructure(flatStructure) {
|
|
492
215
|
const result = {};
|
|
493
216
|
Object.entries(flatStructure).forEach(([action, paths]) => {
|
|
494
217
|
result[action] = {};
|
|
@@ -520,10 +243,9 @@ class AbilityParser {
|
|
|
520
243
|
* @param structure - Nested type structure
|
|
521
244
|
* @returns Formatted TypeScript type definition string
|
|
522
245
|
*/
|
|
523
|
-
|
|
246
|
+
formatTypeDefinitions(structure) {
|
|
524
247
|
let output = '// Automatically generated by via-profit/ability\n';
|
|
525
248
|
output += '// Do not edit manually\n';
|
|
526
|
-
output += '\n/* eslint-disable */\n\n';
|
|
527
249
|
output += 'export type Resources = {\n';
|
|
528
250
|
// Sort actions for stable output
|
|
529
251
|
const sortedActions = Object.keys(structure).sort();
|
|
@@ -541,7 +263,7 @@ class AbilityParser {
|
|
|
541
263
|
* @param indent - Current indentation level
|
|
542
264
|
* @returns Formatted string
|
|
543
265
|
*/
|
|
544
|
-
|
|
266
|
+
formatNestedObject(obj, indent) {
|
|
545
267
|
const spaces = ' '.repeat(indent);
|
|
546
268
|
let output = '';
|
|
547
269
|
// Sort keys for stable output
|
|
@@ -562,29 +284,60 @@ class AbilityParser {
|
|
|
562
284
|
return output;
|
|
563
285
|
}
|
|
564
286
|
}
|
|
565
|
-
exports.AbilityParser = AbilityParser;
|
|
566
|
-
exports["default"] = AbilityParser;
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
/***/ }),
|
|
570
|
-
|
|
571
|
-
/***/ 278:
|
|
572
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
573
287
|
|
|
288
|
+
class AbilityExplain {
|
|
289
|
+
type;
|
|
290
|
+
children;
|
|
291
|
+
name;
|
|
292
|
+
match;
|
|
293
|
+
constructor(config, children = []) {
|
|
294
|
+
this.type = config.type;
|
|
295
|
+
this.children = children;
|
|
296
|
+
this.name = config.name;
|
|
297
|
+
this.match = config.match;
|
|
298
|
+
}
|
|
299
|
+
toString(indent = 0) {
|
|
300
|
+
const pad = ' '.repeat(indent);
|
|
301
|
+
const mark = this.match.code === AbilityMatch.match.code ? '✓' : '✗';
|
|
302
|
+
let out = `${pad}${mark} ${this.type} «${this.name}» is ${this.match.code}`;
|
|
303
|
+
this.children.forEach(child => {
|
|
304
|
+
out += '\n' + child.toString(indent + 1);
|
|
305
|
+
});
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
class AbilityExplainRule extends AbilityExplain {
|
|
310
|
+
constructor(rule) {
|
|
311
|
+
super({
|
|
312
|
+
type: 'rule',
|
|
313
|
+
match: rule.state,
|
|
314
|
+
name: rule.name,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
class AbilityExplainRuleSet extends AbilityExplain {
|
|
319
|
+
constructor(ruleSet) {
|
|
320
|
+
const children = ruleSet.rules.map(rule => new AbilityExplainRule(rule));
|
|
321
|
+
super({
|
|
322
|
+
type: 'ruleSet',
|
|
323
|
+
match: ruleSet.state,
|
|
324
|
+
name: ruleSet.name,
|
|
325
|
+
}, children);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
class AbilityExplainPolicy extends AbilityExplain {
|
|
329
|
+
constructor(policy) {
|
|
330
|
+
const children = policy.ruleSet.map(ruleSet => new AbilityExplainRuleSet(ruleSet));
|
|
331
|
+
super({
|
|
332
|
+
type: 'policy',
|
|
333
|
+
name: policy.name,
|
|
334
|
+
match: policy.matchState,
|
|
335
|
+
}, children);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
574
338
|
|
|
575
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
576
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
577
|
-
};
|
|
578
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
579
|
-
exports.AbilityPolicy = void 0;
|
|
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);
|
|
586
339
|
class AbilityPolicy {
|
|
587
|
-
matchState =
|
|
340
|
+
matchState = AbilityMatch.pending;
|
|
588
341
|
/**
|
|
589
342
|
* List of rules
|
|
590
343
|
*/
|
|
@@ -599,7 +352,7 @@ class AbilityPolicy {
|
|
|
599
352
|
* rules will be returns «permit» status and for the «or» - if\
|
|
600
353
|
* one of the rules returns as «permit»
|
|
601
354
|
*/
|
|
602
|
-
compareMethod =
|
|
355
|
+
compareMethod = AbilityCompare.and;
|
|
603
356
|
/**
|
|
604
357
|
* Policy name
|
|
605
358
|
*/
|
|
@@ -614,7 +367,7 @@ class AbilityPolicy {
|
|
|
614
367
|
*/
|
|
615
368
|
permission;
|
|
616
369
|
constructor(params) {
|
|
617
|
-
const { name, id, permission, effect, compareMethod =
|
|
370
|
+
const { name, id, permission, effect, compareMethod = AbilityCompare.and } = params;
|
|
618
371
|
this.name = name;
|
|
619
372
|
this.id = id;
|
|
620
373
|
this.permission = permission;
|
|
@@ -645,7 +398,7 @@ class AbilityPolicy {
|
|
|
645
398
|
* @param environment - The user environment object
|
|
646
399
|
*/
|
|
647
400
|
async check(resource, environment) {
|
|
648
|
-
this.matchState =
|
|
401
|
+
this.matchState = AbilityMatch.mismatch;
|
|
649
402
|
if (!this.ruleSet.length) {
|
|
650
403
|
return this.matchState;
|
|
651
404
|
}
|
|
@@ -653,94 +406,104 @@ class AbilityPolicy {
|
|
|
653
406
|
for (const ruleSet of this.ruleSet) {
|
|
654
407
|
const state = await ruleSet.check(resource, environment);
|
|
655
408
|
rulesetCheckStates.push(state);
|
|
656
|
-
if (
|
|
409
|
+
if (AbilityCompare.and.isEqual(this.compareMethod) && AbilityMatch.mismatch.isEqual(state)) {
|
|
657
410
|
return this.matchState; // mismatch
|
|
658
411
|
}
|
|
659
|
-
if (
|
|
660
|
-
this.matchState =
|
|
412
|
+
if (AbilityCompare.or.isEqual(this.compareMethod) && AbilityMatch.match.isEqual(state)) {
|
|
413
|
+
this.matchState = AbilityMatch.match;
|
|
661
414
|
return this.matchState;
|
|
662
415
|
}
|
|
663
416
|
}
|
|
664
|
-
if (
|
|
665
|
-
if (rulesetCheckStates.every(s =>
|
|
666
|
-
this.matchState =
|
|
417
|
+
if (AbilityCompare.and.isEqual(this.compareMethod)) {
|
|
418
|
+
if (rulesetCheckStates.every(s => AbilityMatch.match.isEqual(s))) {
|
|
419
|
+
this.matchState = AbilityMatch.match;
|
|
667
420
|
}
|
|
668
421
|
}
|
|
669
|
-
if (
|
|
670
|
-
if (rulesetCheckStates.some(s =>
|
|
671
|
-
this.matchState =
|
|
422
|
+
if (AbilityCompare.or.isEqual(this.compareMethod)) {
|
|
423
|
+
if (rulesetCheckStates.some(s => AbilityMatch.match.isEqual(s))) {
|
|
424
|
+
this.matchState = AbilityMatch.match;
|
|
672
425
|
}
|
|
673
426
|
}
|
|
674
427
|
return this.matchState;
|
|
675
428
|
}
|
|
676
429
|
explain() {
|
|
677
|
-
if (this.matchState ===
|
|
678
|
-
throw new
|
|
430
|
+
if (this.matchState === AbilityMatch.pending) {
|
|
431
|
+
throw new AbilityError('First, run the check method, then explain');
|
|
432
|
+
}
|
|
433
|
+
return new AbilityExplainPolicy(this);
|
|
434
|
+
}
|
|
435
|
+
copyWith(props) {
|
|
436
|
+
const policy = new AbilityPolicy({
|
|
437
|
+
id: props.id ?? this.id,
|
|
438
|
+
name: props.name ?? this.name,
|
|
439
|
+
permission: props.permission ?? this.permission,
|
|
440
|
+
effect: props.effect ?? this.effect,
|
|
441
|
+
compareMethod: props.compareMethod ?? this.compareMethod,
|
|
442
|
+
});
|
|
443
|
+
const nextRuleSet = props.ruleSet ?? this.ruleSet;
|
|
444
|
+
for (const rule of nextRuleSet) {
|
|
445
|
+
policy.addRuleSet(rule);
|
|
679
446
|
}
|
|
680
|
-
return
|
|
447
|
+
return policy;
|
|
681
448
|
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
class AbilityPolicyEffect extends AbilityCode {
|
|
452
|
+
static deny = new AbilityPolicyEffect('deny');
|
|
453
|
+
static permit = new AbilityPolicyEffect('permit');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
class AbilityResult {
|
|
682
457
|
/**
|
|
683
|
-
*
|
|
684
|
-
* @param configs - Array of policy configurations
|
|
685
|
-
* @returns Array of AbilityPolicy instances
|
|
458
|
+
* Already checked policies (after call the policy.check())
|
|
686
459
|
*/
|
|
687
|
-
|
|
688
|
-
|
|
460
|
+
policies;
|
|
461
|
+
constructor(policies) {
|
|
462
|
+
this.policies = policies;
|
|
689
463
|
}
|
|
690
464
|
/**
|
|
691
|
-
*
|
|
465
|
+
* Returns a list of explanations for each policy involved in the ability evaluation.
|
|
466
|
+
* Each item describes how a specific policy contributed to the final permission result.
|
|
467
|
+
*
|
|
468
|
+
* Useful for debugging, logging, or building UI tools that visualize permission logic.
|
|
692
469
|
*/
|
|
693
|
-
|
|
694
|
-
return
|
|
470
|
+
explain() {
|
|
471
|
+
return this.policies.map(policy => {
|
|
472
|
+
return new AbilityExplainPolicy(policy);
|
|
473
|
+
});
|
|
695
474
|
}
|
|
696
|
-
|
|
697
|
-
|
|
475
|
+
getLastMatchedPolicy() {
|
|
476
|
+
for (let i = this.policies.length - 1; i >= 0; i--) {
|
|
477
|
+
if (this.policies[i].matchState.isEqual(AbilityMatch.match)) {
|
|
478
|
+
return this.policies[i];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
698
482
|
}
|
|
699
|
-
|
|
700
|
-
|
|
483
|
+
isAllowed() {
|
|
484
|
+
const effect = this.getLastEffectOfMatchedPolicy();
|
|
485
|
+
return effect?.isEqual(AbilityPolicyEffect.permit) ?? false;
|
|
701
486
|
}
|
|
702
|
-
|
|
703
|
-
|
|
487
|
+
isDenied() {
|
|
488
|
+
const effect = this.getLastEffectOfMatchedPolicy();
|
|
489
|
+
return effect?.isEqual(AbilityPolicyEffect.deny) ?? true;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Get the last effect of the policy
|
|
493
|
+
*
|
|
494
|
+
* @returns {AbilityPolicyEffect | null}
|
|
495
|
+
*/
|
|
496
|
+
getLastEffectOfMatchedPolicy() {
|
|
497
|
+
for (let i = this.policies.length - 1; i >= 0; i--) {
|
|
498
|
+
const p = this.policies[i];
|
|
499
|
+
if (p.matchState.isEqual(AbilityMatch.match)) {
|
|
500
|
+
return p.effect;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
704
504
|
}
|
|
705
505
|
}
|
|
706
|
-
exports.AbilityPolicy = AbilityPolicy;
|
|
707
|
-
exports["default"] = AbilityPolicy;
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
/***/ }),
|
|
711
|
-
|
|
712
|
-
/***/ 179:
|
|
713
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
717
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
718
|
-
};
|
|
719
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
720
|
-
exports.AbilityPolicyEffect = void 0;
|
|
721
|
-
const AbilityCode_1 = __importDefault(__webpack_require__(301));
|
|
722
|
-
class AbilityPolicyEffect extends AbilityCode_1.default {
|
|
723
|
-
static deny = new AbilityPolicyEffect('deny');
|
|
724
|
-
static permit = new AbilityPolicyEffect('permit');
|
|
725
|
-
}
|
|
726
|
-
exports.AbilityPolicyEffect = AbilityPolicyEffect;
|
|
727
|
-
exports["default"] = AbilityPolicyEffect;
|
|
728
|
-
|
|
729
506
|
|
|
730
|
-
/***/ }),
|
|
731
|
-
|
|
732
|
-
/***/ 634:
|
|
733
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
737
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
738
|
-
};
|
|
739
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
740
|
-
exports.AbilityResolver = void 0;
|
|
741
|
-
const AbilityError_1 = __webpack_require__(216);
|
|
742
|
-
const AbilityResult_1 = __webpack_require__(941);
|
|
743
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
744
507
|
class AbilityResolver {
|
|
745
508
|
policies;
|
|
746
509
|
cache;
|
|
@@ -775,20 +538,24 @@ class AbilityResolver {
|
|
|
775
538
|
}
|
|
776
539
|
}
|
|
777
540
|
const policyMatchState = await policy.check(resource, environment);
|
|
778
|
-
if (policyMatchState ===
|
|
779
|
-
throw new
|
|
541
|
+
if (policyMatchState === AbilityMatch.pending) {
|
|
542
|
+
throw new AbilityError(`The policy "${policy.name}" is still in a pending state. Make sure to call "check" to evaluate the policy before resolving permissions.`);
|
|
780
543
|
}
|
|
781
544
|
if (this.cache) {
|
|
782
545
|
await this.cache.set(cacheKey, policyMatchState);
|
|
783
546
|
}
|
|
784
547
|
}
|
|
785
|
-
return new
|
|
548
|
+
return new AbilityResult(filteredPolicies);
|
|
786
549
|
}
|
|
787
550
|
async enforce(permission, resource, environment) {
|
|
788
551
|
const result = await this.resolve(permission, resource, environment);
|
|
789
552
|
if (result.isDenied()) {
|
|
790
|
-
const
|
|
791
|
-
|
|
553
|
+
const lastPolicy = result.getLastMatchedPolicy();
|
|
554
|
+
if (lastPolicy) {
|
|
555
|
+
throw new AbilityError(`Permission denied by policy "${lastPolicy.name.toString()}"`);
|
|
556
|
+
}
|
|
557
|
+
// No policy matched → implicit deny
|
|
558
|
+
throw new AbilityError(`Permission denied: no matching policy found (implicit deny)`);
|
|
792
559
|
}
|
|
793
560
|
}
|
|
794
561
|
/**
|
|
@@ -817,91 +584,7 @@ class AbilityResolver {
|
|
|
817
584
|
await this.cache?.clear();
|
|
818
585
|
}
|
|
819
586
|
}
|
|
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 {
|
|
839
|
-
/**
|
|
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.
|
|
849
|
-
*
|
|
850
|
-
* Useful for debugging, logging, or building UI tools that visualize permission logic.
|
|
851
|
-
*/
|
|
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];
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
isAllowed() {
|
|
866
|
-
const effect = this.getLastEffectOfMatchedPolicy();
|
|
867
|
-
return effect?.isEqual(AbilityPolicyEffect_1.default.permit) ?? false;
|
|
868
|
-
}
|
|
869
|
-
isDenied() {
|
|
870
|
-
const effect = this.getLastEffectOfMatchedPolicy();
|
|
871
|
-
return effect?.isEqual(AbilityPolicyEffect_1.default.deny) ?? true;
|
|
872
|
-
}
|
|
873
|
-
/**
|
|
874
|
-
* Get the last effect of the policy
|
|
875
|
-
*
|
|
876
|
-
* @returns {AbilityPolicyEffect | null}
|
|
877
|
-
*/
|
|
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;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
exports.AbilityResult = AbilityResult;
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
/***/ }),
|
|
892
|
-
|
|
893
|
-
/***/ 306:
|
|
894
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
895
587
|
|
|
896
|
-
|
|
897
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
898
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
899
|
-
};
|
|
900
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
901
|
-
exports.AbilityRule = void 0;
|
|
902
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
903
|
-
const AbilityCondition_1 = __importDefault(__webpack_require__(167));
|
|
904
|
-
const AbilityJSONParser_1 = __webpack_require__(909);
|
|
905
588
|
/**
|
|
906
589
|
* Represents a rule that defines a condition to be checked against a subject and resource.
|
|
907
590
|
*/
|
|
@@ -917,7 +600,7 @@ class AbilityRule {
|
|
|
917
600
|
condition;
|
|
918
601
|
name;
|
|
919
602
|
id;
|
|
920
|
-
state =
|
|
603
|
+
state = AbilityMatch.pending;
|
|
921
604
|
/**
|
|
922
605
|
* Creates an instance of AbilityRule.
|
|
923
606
|
* @param {string} params.id - The unique identifier of the rule.
|
|
@@ -945,39 +628,39 @@ class AbilityRule {
|
|
|
945
628
|
const [subjectValue, resourceValue] = this.extractValues(resource, environment);
|
|
946
629
|
const isValue = (v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
|
|
947
630
|
// equals
|
|
948
|
-
if (
|
|
631
|
+
if (AbilityCondition.equals.isEqual(this.condition)) {
|
|
949
632
|
is = subjectValue === resourceValue;
|
|
950
633
|
}
|
|
951
634
|
// not equals
|
|
952
|
-
if (
|
|
635
|
+
if (AbilityCondition.not_equals.isEqual(this.condition)) {
|
|
953
636
|
is = subjectValue !== resourceValue;
|
|
954
637
|
}
|
|
955
638
|
// less than
|
|
956
|
-
if (
|
|
639
|
+
if (AbilityCondition.less_than.isEqual(this.condition)) {
|
|
957
640
|
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
958
641
|
is = subjectValue < resourceValue;
|
|
959
642
|
}
|
|
960
643
|
}
|
|
961
644
|
// less or equal
|
|
962
|
-
if (
|
|
645
|
+
if (AbilityCondition.less_or_equal.isEqual(this.condition)) {
|
|
963
646
|
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
964
647
|
is = subjectValue <= resourceValue;
|
|
965
648
|
}
|
|
966
649
|
}
|
|
967
650
|
// more than
|
|
968
|
-
if (
|
|
651
|
+
if (AbilityCondition.greater_than.isEqual(this.condition)) {
|
|
969
652
|
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
970
653
|
is = subjectValue > resourceValue;
|
|
971
654
|
}
|
|
972
655
|
}
|
|
973
656
|
// more or equal
|
|
974
|
-
if (
|
|
657
|
+
if (AbilityCondition.greater_or_equal.isEqual(this.condition)) {
|
|
975
658
|
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
976
659
|
is = subjectValue >= resourceValue;
|
|
977
660
|
}
|
|
978
661
|
}
|
|
979
662
|
// in
|
|
980
|
-
if (
|
|
663
|
+
if (AbilityCondition.in.isEqual(this.condition)) {
|
|
981
664
|
// value in array
|
|
982
665
|
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
983
666
|
is = resourceValue.includes(subjectValue);
|
|
@@ -988,7 +671,7 @@ class AbilityRule {
|
|
|
988
671
|
}
|
|
989
672
|
}
|
|
990
673
|
// not in
|
|
991
|
-
if (
|
|
674
|
+
if (AbilityCondition.not_in.isEqual(this.condition)) {
|
|
992
675
|
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
993
676
|
is = !resourceValue.includes(subjectValue);
|
|
994
677
|
}
|
|
@@ -997,7 +680,7 @@ class AbilityRule {
|
|
|
997
680
|
}
|
|
998
681
|
}
|
|
999
682
|
// contains
|
|
1000
|
-
if (
|
|
683
|
+
if (AbilityCondition.contains.isEqual(this.condition)) {
|
|
1001
684
|
// array contains value
|
|
1002
685
|
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
1003
686
|
is = subjectValue.includes(resourceValue);
|
|
@@ -1008,7 +691,7 @@ class AbilityRule {
|
|
|
1008
691
|
}
|
|
1009
692
|
}
|
|
1010
693
|
// not contains
|
|
1011
|
-
if (
|
|
694
|
+
if (AbilityCondition.not_contains.isEqual(this.condition)) {
|
|
1012
695
|
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
1013
696
|
is = !subjectValue.includes(resourceValue);
|
|
1014
697
|
}
|
|
@@ -1017,7 +700,7 @@ class AbilityRule {
|
|
|
1017
700
|
}
|
|
1018
701
|
}
|
|
1019
702
|
// length equals
|
|
1020
|
-
if (
|
|
703
|
+
if (AbilityCondition.length_equals.isEqual(this.condition)) {
|
|
1021
704
|
// foo.bar == n
|
|
1022
705
|
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
1023
706
|
is = String(subjectValue).length === resourceValue;
|
|
@@ -1036,7 +719,7 @@ class AbilityRule {
|
|
|
1036
719
|
}
|
|
1037
720
|
}
|
|
1038
721
|
// length greater than
|
|
1039
|
-
if (
|
|
722
|
+
if (AbilityCondition.length_greater_than.isEqual(this.condition)) {
|
|
1040
723
|
// foo.bar > n
|
|
1041
724
|
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
1042
725
|
is = String(subjectValue).length > resourceValue;
|
|
@@ -1055,7 +738,7 @@ class AbilityRule {
|
|
|
1055
738
|
}
|
|
1056
739
|
}
|
|
1057
740
|
// length greater than
|
|
1058
|
-
if (
|
|
741
|
+
if (AbilityCondition.length_less_than.isEqual(this.condition)) {
|
|
1059
742
|
// foo.bar < n
|
|
1060
743
|
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
1061
744
|
is = String(subjectValue).length < resourceValue;
|
|
@@ -1073,7 +756,7 @@ class AbilityRule {
|
|
|
1073
756
|
is = subjectValue.length < resourceValue.length;
|
|
1074
757
|
}
|
|
1075
758
|
}
|
|
1076
|
-
this.state = is ?
|
|
759
|
+
this.state = is ? AbilityMatch.match : AbilityMatch.mismatch;
|
|
1077
760
|
return this.state;
|
|
1078
761
|
}
|
|
1079
762
|
/**
|
|
@@ -1149,107 +832,96 @@ class AbilityRule {
|
|
|
1149
832
|
toString() {
|
|
1150
833
|
return `AbilityRule: ${this.name} condition: ${this.condition.code} subject: "${this.subject?.toString()}" resource: "${this.resource?.toString()}"`;
|
|
1151
834
|
}
|
|
1152
|
-
|
|
1153
|
-
return
|
|
835
|
+
copyWith(props) {
|
|
836
|
+
return new AbilityRule({
|
|
837
|
+
id: props.id ?? this.id,
|
|
838
|
+
name: props.name ?? this.name,
|
|
839
|
+
subject: props.subject ?? this.subject,
|
|
840
|
+
resource: props.resource ?? this.resource,
|
|
841
|
+
condition: props.condition ?? this.condition,
|
|
842
|
+
});
|
|
1154
843
|
}
|
|
1155
844
|
static equals(subject, resource) {
|
|
1156
845
|
return new AbilityRule({
|
|
1157
|
-
condition:
|
|
846
|
+
condition: AbilityCondition.equals,
|
|
1158
847
|
subject,
|
|
1159
848
|
resource,
|
|
1160
849
|
});
|
|
1161
850
|
}
|
|
1162
851
|
static notEquals(subject, resource) {
|
|
1163
852
|
return new AbilityRule({
|
|
1164
|
-
condition:
|
|
853
|
+
condition: AbilityCondition.not_equals,
|
|
1165
854
|
subject,
|
|
1166
855
|
resource,
|
|
1167
856
|
});
|
|
1168
857
|
}
|
|
1169
858
|
static contains(subject, resource) {
|
|
1170
859
|
return new AbilityRule({
|
|
1171
|
-
condition:
|
|
860
|
+
condition: AbilityCondition.contains,
|
|
1172
861
|
subject,
|
|
1173
862
|
resource,
|
|
1174
863
|
});
|
|
1175
864
|
}
|
|
1176
865
|
static notContains(subject, resource) {
|
|
1177
866
|
return new AbilityRule({
|
|
1178
|
-
condition:
|
|
867
|
+
condition: AbilityCondition.not_contains,
|
|
1179
868
|
subject,
|
|
1180
869
|
resource,
|
|
1181
870
|
});
|
|
1182
871
|
}
|
|
1183
872
|
static notIn(subject, resource) {
|
|
1184
873
|
return new AbilityRule({
|
|
1185
|
-
condition:
|
|
874
|
+
condition: AbilityCondition.not_in,
|
|
1186
875
|
subject,
|
|
1187
876
|
resource,
|
|
1188
877
|
});
|
|
1189
878
|
}
|
|
1190
879
|
static in(subject, resource) {
|
|
1191
880
|
return new AbilityRule({
|
|
1192
|
-
condition:
|
|
881
|
+
condition: AbilityCondition.in,
|
|
1193
882
|
subject,
|
|
1194
883
|
resource,
|
|
1195
884
|
});
|
|
1196
885
|
}
|
|
1197
886
|
static notEqual(subject, resource) {
|
|
1198
887
|
return new AbilityRule({
|
|
1199
|
-
condition:
|
|
888
|
+
condition: AbilityCondition.not_equals,
|
|
1200
889
|
subject,
|
|
1201
890
|
resource,
|
|
1202
891
|
});
|
|
1203
892
|
}
|
|
1204
893
|
static lessThan(subject, resource) {
|
|
1205
894
|
return new AbilityRule({
|
|
1206
|
-
condition:
|
|
895
|
+
condition: AbilityCondition.less_than,
|
|
1207
896
|
subject,
|
|
1208
897
|
resource,
|
|
1209
898
|
});
|
|
1210
899
|
}
|
|
1211
900
|
static lessOrEqual(subject, resource) {
|
|
1212
901
|
return new AbilityRule({
|
|
1213
|
-
condition:
|
|
902
|
+
condition: AbilityCondition.less_or_equal,
|
|
1214
903
|
subject,
|
|
1215
904
|
resource,
|
|
1216
905
|
});
|
|
1217
906
|
}
|
|
1218
907
|
static moreThan(subject, resource) {
|
|
1219
908
|
return new AbilityRule({
|
|
1220
|
-
condition:
|
|
909
|
+
condition: AbilityCondition.greater_than,
|
|
1221
910
|
subject,
|
|
1222
911
|
resource,
|
|
1223
912
|
});
|
|
1224
913
|
}
|
|
1225
914
|
static moreOrEqual(subject, resource) {
|
|
1226
915
|
return new AbilityRule({
|
|
1227
|
-
condition:
|
|
916
|
+
condition: AbilityCondition.greater_or_equal,
|
|
1228
917
|
subject,
|
|
1229
918
|
resource,
|
|
1230
919
|
});
|
|
1231
920
|
}
|
|
1232
921
|
}
|
|
1233
|
-
exports.AbilityRule = AbilityRule;
|
|
1234
|
-
exports["default"] = AbilityRule;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
/***/ }),
|
|
1238
922
|
|
|
1239
|
-
/***/ 56:
|
|
1240
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1244
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1245
|
-
};
|
|
1246
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1247
|
-
exports.AbilityRuleSet = void 0;
|
|
1248
|
-
const AbilityCompare_1 = __importDefault(__webpack_require__(413));
|
|
1249
|
-
const AbilityMatch_1 = __importDefault(__webpack_require__(247));
|
|
1250
|
-
const AbilityJSONParser_1 = __webpack_require__(909);
|
|
1251
923
|
class AbilityRuleSet {
|
|
1252
|
-
state =
|
|
924
|
+
state = AbilityMatch.pending;
|
|
1253
925
|
/**
|
|
1254
926
|
* List of rules
|
|
1255
927
|
*/
|
|
@@ -1260,7 +932,7 @@ class AbilityRuleSet {
|
|
|
1260
932
|
* rules will be returns «permit» status and for the «or» - if\
|
|
1261
933
|
* one of the rules returns as «permit»
|
|
1262
934
|
*/
|
|
1263
|
-
compareMethod =
|
|
935
|
+
compareMethod = AbilityCompare.and;
|
|
1264
936
|
/**
|
|
1265
937
|
* Group name
|
|
1266
938
|
*/
|
|
@@ -1284,7 +956,7 @@ class AbilityRuleSet {
|
|
|
1284
956
|
return this;
|
|
1285
957
|
}
|
|
1286
958
|
async check(resources, environment) {
|
|
1287
|
-
this.state =
|
|
959
|
+
this.state = AbilityMatch.mismatch;
|
|
1288
960
|
if (!this.rules.length) {
|
|
1289
961
|
return this.state;
|
|
1290
962
|
}
|
|
@@ -1292,22 +964,22 @@ class AbilityRuleSet {
|
|
|
1292
964
|
for (const rule of this.rules) {
|
|
1293
965
|
const state = await rule.check(resources, environment);
|
|
1294
966
|
ruleCheckStates.push(state);
|
|
1295
|
-
if (
|
|
967
|
+
if (AbilityCompare.and.isEqual(this.compareMethod) && AbilityMatch.mismatch.isEqual(state)) {
|
|
1296
968
|
return this.state; // mismatch
|
|
1297
969
|
}
|
|
1298
|
-
if (
|
|
1299
|
-
this.state =
|
|
970
|
+
if (AbilityCompare.or.isEqual(this.compareMethod) && AbilityMatch.match.isEqual(state)) {
|
|
971
|
+
this.state = AbilityMatch.match;
|
|
1300
972
|
return this.state;
|
|
1301
973
|
}
|
|
1302
974
|
}
|
|
1303
|
-
if (
|
|
1304
|
-
if (ruleCheckStates.every(s =>
|
|
1305
|
-
this.state =
|
|
975
|
+
if (AbilityCompare.and.isEqual(this.compareMethod)) {
|
|
976
|
+
if (ruleCheckStates.every(s => AbilityMatch.match.isEqual(s))) {
|
|
977
|
+
this.state = AbilityMatch.match;
|
|
1306
978
|
}
|
|
1307
979
|
}
|
|
1308
|
-
if (
|
|
1309
|
-
if (ruleCheckStates.some(s =>
|
|
1310
|
-
this.state =
|
|
980
|
+
if (AbilityCompare.or.isEqual(this.compareMethod)) {
|
|
981
|
+
if (ruleCheckStates.some(s => AbilityMatch.match.isEqual(s))) {
|
|
982
|
+
this.state = AbilityMatch.match;
|
|
1311
983
|
}
|
|
1312
984
|
}
|
|
1313
985
|
return this.state;
|
|
@@ -1315,78 +987,247 @@ class AbilityRuleSet {
|
|
|
1315
987
|
toString() {
|
|
1316
988
|
return `AbilityRuleSet: ${this.name} compareMethod: ${this.compareMethod.code}, rules: ${this.rules.map(rule => rule.toString()).join('\n')}`;
|
|
1317
989
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
990
|
+
copyWith(props) {
|
|
991
|
+
const next = new AbilityRuleSet({
|
|
992
|
+
id: props.id ?? this.id,
|
|
993
|
+
name: props.name ?? this.name,
|
|
994
|
+
compareMethod: props.compareMethod ?? this.compareMethod,
|
|
995
|
+
});
|
|
996
|
+
const nextRules = props.rules ?? this.rules;
|
|
997
|
+
for (const rule of nextRules) {
|
|
998
|
+
next.addRule(rule);
|
|
999
|
+
}
|
|
1000
|
+
return next;
|
|
1323
1001
|
}
|
|
1324
1002
|
static and(rules) {
|
|
1325
1003
|
return new AbilityRuleSet({
|
|
1326
|
-
compareMethod:
|
|
1004
|
+
compareMethod: AbilityCompare.and,
|
|
1327
1005
|
}).addRules(rules);
|
|
1328
1006
|
}
|
|
1329
1007
|
static or(rules) {
|
|
1330
1008
|
return new AbilityRuleSet({
|
|
1331
|
-
compareMethod:
|
|
1009
|
+
compareMethod: AbilityCompare.or,
|
|
1332
1010
|
}).addRules(rules);
|
|
1333
1011
|
}
|
|
1334
1012
|
}
|
|
1335
|
-
exports.AbilityRuleSet = AbilityRuleSet;
|
|
1336
|
-
exports["default"] = AbilityRuleSet;
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
/***/ }),
|
|
1340
|
-
|
|
1341
|
-
/***/ 156:
|
|
1342
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
1343
|
-
|
|
1344
1013
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1014
|
+
class AbilityInMemoryCache {
|
|
1015
|
+
store = new Map();
|
|
1016
|
+
async get(key) {
|
|
1017
|
+
const entry = this.store.get(key);
|
|
1018
|
+
if (!entry)
|
|
1019
|
+
return undefined;
|
|
1020
|
+
if (Date.now() > entry.expires) {
|
|
1021
|
+
this.store.delete(key);
|
|
1022
|
+
return undefined;
|
|
1023
|
+
}
|
|
1024
|
+
return entry.value;
|
|
1025
|
+
}
|
|
1026
|
+
async set(key, value, ttlSeconds = 60) {
|
|
1027
|
+
this.store.set(key, {
|
|
1028
|
+
value,
|
|
1029
|
+
expires: Date.now() + ttlSeconds * 1000,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
serialize(input) {
|
|
1033
|
+
return this.fastHash(this.stableStringify(input));
|
|
1034
|
+
}
|
|
1035
|
+
async delete(key) {
|
|
1036
|
+
this.store.delete(key);
|
|
1037
|
+
}
|
|
1038
|
+
async clear() {
|
|
1039
|
+
this.store.clear();
|
|
1040
|
+
}
|
|
1041
|
+
async deleteByPrefix(prefix) {
|
|
1042
|
+
for (const key of this.store.keys()) {
|
|
1043
|
+
if (key.startsWith(prefix)) {
|
|
1044
|
+
this.store.delete(key);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
fastHash(str) {
|
|
1049
|
+
let hash = 5381;
|
|
1050
|
+
for (let i = 0; i < str.length; i++) {
|
|
1051
|
+
hash = (hash * 33) ^ str.charCodeAt(i);
|
|
1052
|
+
}
|
|
1053
|
+
return (hash >>> 0).toString(36);
|
|
1054
|
+
}
|
|
1055
|
+
stableStringify(obj) {
|
|
1056
|
+
if (obj === null)
|
|
1057
|
+
return 'null';
|
|
1058
|
+
const type = typeof obj;
|
|
1059
|
+
if (type === 'string')
|
|
1060
|
+
return JSON.stringify(obj);
|
|
1061
|
+
if (type === 'number' || type === 'boolean')
|
|
1062
|
+
return String(obj);
|
|
1063
|
+
if (type === 'undefined')
|
|
1064
|
+
return 'undefined';
|
|
1065
|
+
if (Array.isArray(obj)) {
|
|
1066
|
+
let out = '[';
|
|
1067
|
+
for (let i = 0; i < obj.length; i++) {
|
|
1068
|
+
if (i > 0)
|
|
1069
|
+
out += ',';
|
|
1070
|
+
out += this.stableStringify(obj[i]);
|
|
1071
|
+
}
|
|
1072
|
+
return out + ']';
|
|
1073
|
+
}
|
|
1074
|
+
const keys = Object.keys(obj);
|
|
1075
|
+
keys.sort();
|
|
1076
|
+
let out = '{';
|
|
1077
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1078
|
+
const k = keys[i];
|
|
1079
|
+
if (i > 0)
|
|
1080
|
+
out += ',';
|
|
1081
|
+
out += k + ':' + this.stableStringify(obj[k]);
|
|
1082
|
+
}
|
|
1083
|
+
return out + '}';
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1382
1086
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1087
|
+
class AbilityJSONParser {
|
|
1088
|
+
/**
|
|
1089
|
+
* Parses an array of policy configurations into an array of AbilityPolicy instances.
|
|
1090
|
+
* @param configs - Array of policy configurations
|
|
1091
|
+
* @returns Array of AbilityPolicy instances
|
|
1092
|
+
*/
|
|
1093
|
+
static parse(configs) {
|
|
1094
|
+
return configs.map(config => AbilityJSONParser.parsePolicy(config));
|
|
1095
|
+
}
|
|
1096
|
+
static parsePolicy(config) {
|
|
1097
|
+
const { id, name, ruleSet, compareMethod, permission, effect } = config;
|
|
1098
|
+
// Create the empty policy
|
|
1099
|
+
const policy = new AbilityPolicy({
|
|
1100
|
+
name,
|
|
1101
|
+
id,
|
|
1102
|
+
permission: permission,
|
|
1103
|
+
effect: new AbilityPolicyEffect(effect),
|
|
1104
|
+
});
|
|
1105
|
+
policy.compareMethod = new AbilityCompare(compareMethod);
|
|
1106
|
+
ruleSet.forEach(ruleSetConfig => {
|
|
1107
|
+
policy.addRuleSet(AbilityJSONParser.parseRuleSet(ruleSetConfig));
|
|
1108
|
+
});
|
|
1109
|
+
return policy;
|
|
1110
|
+
}
|
|
1111
|
+
static parseRule(config) {
|
|
1112
|
+
const { id, name, subject, resource, condition } = config;
|
|
1113
|
+
return new AbilityRule({
|
|
1114
|
+
id,
|
|
1115
|
+
name,
|
|
1116
|
+
subject,
|
|
1117
|
+
resource,
|
|
1118
|
+
condition: new AbilityCondition(condition),
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Parse the config JSON format to Group class instance
|
|
1123
|
+
*/
|
|
1124
|
+
static parseRuleSet(config) {
|
|
1125
|
+
const { id, name, rules, compareMethod } = config;
|
|
1126
|
+
const ruleSet = new AbilityRuleSet({
|
|
1127
|
+
compareMethod: new AbilityCompare(compareMethod),
|
|
1128
|
+
name,
|
|
1129
|
+
id,
|
|
1130
|
+
});
|
|
1131
|
+
// Adding rules if exists
|
|
1132
|
+
if (rules && rules.length > 0) {
|
|
1133
|
+
const abilityRules = rules.map(ruleConfig => AbilityJSONParser.parseRule(ruleConfig));
|
|
1134
|
+
ruleSet.addRules(abilityRules);
|
|
1135
|
+
}
|
|
1136
|
+
return ruleSet;
|
|
1137
|
+
}
|
|
1138
|
+
static ruleToJSON(rule) {
|
|
1139
|
+
return {
|
|
1140
|
+
id: rule.id,
|
|
1141
|
+
name: rule.name,
|
|
1142
|
+
subject: rule.subject,
|
|
1143
|
+
resource: rule.resource,
|
|
1144
|
+
condition: rule.condition.code,
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
static ruleSetToJSON(ruleSet) {
|
|
1148
|
+
return {
|
|
1149
|
+
id: ruleSet.id.toString(),
|
|
1150
|
+
name: ruleSet.name.toString(),
|
|
1151
|
+
compareMethod: ruleSet.compareMethod.code.toString(),
|
|
1152
|
+
rules: ruleSet.rules.map(rule => AbilityJSONParser.ruleToJSON(rule)),
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
static policyToJSON(policy) {
|
|
1156
|
+
return {
|
|
1157
|
+
id: policy.id.toString(),
|
|
1158
|
+
name: policy.name.toString(),
|
|
1159
|
+
compareMethod: policy.compareMethod.code.toString(),
|
|
1160
|
+
ruleSet: policy.ruleSet.map(ruleSet => AbilityJSONParser.ruleSetToJSON(ruleSet)),
|
|
1161
|
+
permission: policy.permission,
|
|
1162
|
+
effect: policy.effect.code,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
static toJSON(policies) {
|
|
1166
|
+
return policies.map(policy => AbilityJSONParser.policyToJSON(policy));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1385
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* Represents a single token produced by the Ability DSL lexer.
|
|
1172
|
+
* Each token carries a type (e.g., EFFECT, IDENTIFIER, STRING) and its raw string value.
|
|
1173
|
+
*/
|
|
1174
|
+
class AbilityDSLToken extends AbilityCode {
|
|
1175
|
+
/** The literal text of the token as it appeared in the input (e.g., "permit", "user.roles", "admin"). */
|
|
1176
|
+
value = '';
|
|
1177
|
+
/** The line number in DSL */
|
|
1178
|
+
line = 1;
|
|
1179
|
+
/** The column in dsl */
|
|
1180
|
+
column = 1;
|
|
1181
|
+
constructor(type, value, line, column) {
|
|
1182
|
+
super(type);
|
|
1183
|
+
this.value = value;
|
|
1184
|
+
this.line = line;
|
|
1185
|
+
this.column = column;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Returns a human-readable representation of the token, useful for debugging.
|
|
1189
|
+
* Example output: "AbilityDSLToken([EFFECT] permit"
|
|
1190
|
+
*/
|
|
1191
|
+
toString() {
|
|
1192
|
+
return `AbilityDSLToken([${this.code}] "${this.value}" at ${this.line}:${this.column})`;
|
|
1193
|
+
}
|
|
1194
|
+
static EFFECT = 'EFFECT';
|
|
1195
|
+
static IF = 'IF';
|
|
1196
|
+
static PERMISSION = 'PERMISSION';
|
|
1197
|
+
static IDENTIFIER = 'IDENTIFIER';
|
|
1198
|
+
static COLON = 'COLON';
|
|
1199
|
+
static COMMA = 'COMMA';
|
|
1200
|
+
static DOT = 'DOT';
|
|
1201
|
+
static LBRACKET = 'LBRACKET';
|
|
1202
|
+
static RBRACKET = 'RBRACKET';
|
|
1203
|
+
static ALL = 'ALL';
|
|
1204
|
+
static ANY = 'ANY';
|
|
1205
|
+
static OF = 'OF';
|
|
1206
|
+
static EOF = 'EOF';
|
|
1207
|
+
static COMMENT = 'COMMENT';
|
|
1208
|
+
static EQ = 'EQ';
|
|
1209
|
+
static CONTAINS = 'CONTAINS';
|
|
1210
|
+
static IN = 'IN';
|
|
1211
|
+
static NOT_IN = 'NOT_IN';
|
|
1212
|
+
static NOT_CONTAINS = 'NOT_CONTAINS';
|
|
1213
|
+
static GT = 'GT';
|
|
1214
|
+
static GTE = 'GTE';
|
|
1215
|
+
static LT = 'LT';
|
|
1216
|
+
static LTE = 'LTE';
|
|
1217
|
+
static NULL = 'NULL';
|
|
1218
|
+
static EQ_NULL = 'EQ_NULL';
|
|
1219
|
+
static NOT_EQ_NULL = 'NOT_EQ_NULL';
|
|
1220
|
+
static LEN_GT = 'LEN_GT';
|
|
1221
|
+
static LEN_LT = 'LEN_LT';
|
|
1222
|
+
static LEN_EQ = 'LEN_EQ';
|
|
1223
|
+
static NOT_EQ = 'NOT_EQ';
|
|
1224
|
+
static STRING = 'STRING';
|
|
1225
|
+
static NUMBER = 'NUMBER';
|
|
1226
|
+
static BOOLEAN = 'BOOLEAN';
|
|
1227
|
+
static SYMBOL = 'SYMBOL';
|
|
1228
|
+
static KEYWORD = 'KEYWORD';
|
|
1229
|
+
}
|
|
1386
1230
|
|
|
1387
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1388
|
-
exports.AbilityDSLLexer = void 0;
|
|
1389
|
-
const AbilityDSLToken_1 = __webpack_require__(325);
|
|
1390
1231
|
class AbilityDSLLexer {
|
|
1391
1232
|
input;
|
|
1392
1233
|
pos = 0;
|
|
@@ -1454,7 +1295,7 @@ class AbilityDSLLexer {
|
|
|
1454
1295
|
}
|
|
1455
1296
|
throw new Error(`Unexpected character '${char}' at ${this.line}:${this.column}`);
|
|
1456
1297
|
}
|
|
1457
|
-
this.tokens.push(new
|
|
1298
|
+
this.tokens.push(new AbilityDSLToken(AbilityDSLToken.EOF, '', this.line, this.column));
|
|
1458
1299
|
return this.tokens;
|
|
1459
1300
|
}
|
|
1460
1301
|
readComment() {
|
|
@@ -1465,7 +1306,7 @@ class AbilityDSLLexer {
|
|
|
1465
1306
|
while (!this.isAtEnd() && !this.isNewline()) {
|
|
1466
1307
|
value += this.advance();
|
|
1467
1308
|
}
|
|
1468
|
-
return new
|
|
1309
|
+
return new AbilityDSLToken(AbilityDSLToken.COMMENT, value.trim(), startLine, startColumn);
|
|
1469
1310
|
}
|
|
1470
1311
|
readString() {
|
|
1471
1312
|
const startLine = this.line;
|
|
@@ -1485,7 +1326,7 @@ class AbilityDSLLexer {
|
|
|
1485
1326
|
continue;
|
|
1486
1327
|
}
|
|
1487
1328
|
if (char === quote) {
|
|
1488
|
-
return new
|
|
1329
|
+
return new AbilityDSLToken(AbilityDSLToken.STRING, value, startLine, startColumn);
|
|
1489
1330
|
}
|
|
1490
1331
|
value += char;
|
|
1491
1332
|
}
|
|
@@ -1499,7 +1340,7 @@ class AbilityDSLLexer {
|
|
|
1499
1340
|
this.advance();
|
|
1500
1341
|
}
|
|
1501
1342
|
const value = this.input.slice(start, this.pos);
|
|
1502
|
-
return new
|
|
1343
|
+
return new AbilityDSLToken(AbilityDSLToken.NUMBER, value, startLine, startColumn);
|
|
1503
1344
|
}
|
|
1504
1345
|
readSymbol() {
|
|
1505
1346
|
const startLine = this.line;
|
|
@@ -1507,41 +1348,41 @@ class AbilityDSLLexer {
|
|
|
1507
1348
|
const char = this.advance();
|
|
1508
1349
|
switch (char) {
|
|
1509
1350
|
case '.':
|
|
1510
|
-
return new
|
|
1351
|
+
return new AbilityDSLToken(AbilityDSLToken.DOT, char, startLine, startColumn);
|
|
1511
1352
|
case ':':
|
|
1512
|
-
return new
|
|
1353
|
+
return new AbilityDSLToken(AbilityDSLToken.COLON, char, startLine, startColumn);
|
|
1513
1354
|
case ',':
|
|
1514
|
-
return new
|
|
1355
|
+
return new AbilityDSLToken(AbilityDSLToken.COMMA, char, startLine, startColumn);
|
|
1515
1356
|
case '[':
|
|
1516
|
-
return new
|
|
1357
|
+
return new AbilityDSLToken(AbilityDSLToken.LBRACKET, char, startLine, startColumn);
|
|
1517
1358
|
case ']':
|
|
1518
|
-
return new
|
|
1359
|
+
return new AbilityDSLToken(AbilityDSLToken.RBRACKET, char, startLine, startColumn);
|
|
1519
1360
|
case '>':
|
|
1520
1361
|
if (this.peek() === '=') {
|
|
1521
1362
|
this.advance();
|
|
1522
|
-
return new
|
|
1363
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '>=', startLine, startColumn);
|
|
1523
1364
|
}
|
|
1524
|
-
return new
|
|
1365
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '>', startLine, startColumn);
|
|
1525
1366
|
case '<':
|
|
1526
1367
|
if (this.peek() === '=') {
|
|
1527
1368
|
this.advance();
|
|
1528
|
-
return new
|
|
1369
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '<=', startLine, startColumn);
|
|
1529
1370
|
}
|
|
1530
1371
|
if (this.peek() === '>') {
|
|
1531
1372
|
this.advance();
|
|
1532
|
-
return new
|
|
1373
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '<>', startLine, startColumn);
|
|
1533
1374
|
}
|
|
1534
|
-
return new
|
|
1375
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '<', startLine, startColumn);
|
|
1535
1376
|
case '=':
|
|
1536
1377
|
if (this.peek() === '=') {
|
|
1537
1378
|
this.advance();
|
|
1538
|
-
return new
|
|
1379
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '==', startLine, startColumn);
|
|
1539
1380
|
}
|
|
1540
|
-
return new
|
|
1381
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '=', startLine, startColumn);
|
|
1541
1382
|
case '!':
|
|
1542
1383
|
if (this.peek() === '=') {
|
|
1543
1384
|
this.advance();
|
|
1544
|
-
return new
|
|
1385
|
+
return new AbilityDSLToken(AbilityDSLToken.SYMBOL, '!=', startLine, startColumn);
|
|
1545
1386
|
}
|
|
1546
1387
|
throw new Error(`Unexpected symbol '!' at ${this.line}:${this.column}`);
|
|
1547
1388
|
default:
|
|
@@ -1570,52 +1411,52 @@ class AbilityDSLLexer {
|
|
|
1570
1411
|
// Если есть точка — это путь (identifier или permission)
|
|
1571
1412
|
if (word.includes('.')) {
|
|
1572
1413
|
const last = this.tokens[this.tokens.length - 1];
|
|
1573
|
-
if (last?.code ===
|
|
1414
|
+
if (last?.code === AbilityDSLToken.EFFECT) {
|
|
1574
1415
|
if (word.startsWith('permission.')) {
|
|
1575
|
-
return new
|
|
1416
|
+
return new AbilityDSLToken(AbilityDSLToken.PERMISSION, word, startLine, startColumn);
|
|
1576
1417
|
}
|
|
1577
1418
|
}
|
|
1578
|
-
return new
|
|
1419
|
+
return new AbilityDSLToken(AbilityDSLToken.IDENTIFIER, word, startLine, startColumn);
|
|
1579
1420
|
}
|
|
1580
1421
|
// Ключевые слова
|
|
1581
1422
|
if (this.keywords.has(word)) {
|
|
1582
1423
|
// Эффекты
|
|
1583
1424
|
if (word === 'permit' || word === 'allow') {
|
|
1584
|
-
return new
|
|
1425
|
+
return new AbilityDSLToken(AbilityDSLToken.EFFECT, 'permit', startLine, startColumn);
|
|
1585
1426
|
}
|
|
1586
1427
|
if (word === 'deny' || word === 'forbidden') {
|
|
1587
|
-
return new
|
|
1428
|
+
return new AbilityDSLToken(AbilityDSLToken.EFFECT, 'deny', startLine, startColumn);
|
|
1588
1429
|
}
|
|
1589
1430
|
// Групповые ключевые слова
|
|
1590
1431
|
if (word === 'all') {
|
|
1591
|
-
return new
|
|
1432
|
+
return new AbilityDSLToken(AbilityDSLToken.ALL, word, startLine, startColumn);
|
|
1592
1433
|
}
|
|
1593
1434
|
if (word === 'any') {
|
|
1594
|
-
return new
|
|
1435
|
+
return new AbilityDSLToken(AbilityDSLToken.ANY, word, startLine, startColumn);
|
|
1595
1436
|
}
|
|
1596
1437
|
if (word === 'of') {
|
|
1597
|
-
return new
|
|
1438
|
+
return new AbilityDSLToken(AbilityDSLToken.OF, word, startLine, startColumn);
|
|
1598
1439
|
}
|
|
1599
1440
|
if (word === 'if') {
|
|
1600
|
-
return new
|
|
1441
|
+
return new AbilityDSLToken(AbilityDSLToken.IF, word, startLine, startColumn);
|
|
1601
1442
|
}
|
|
1602
1443
|
// Булевы и null
|
|
1603
1444
|
if (word === 'true' || word === 'false') {
|
|
1604
|
-
return new
|
|
1445
|
+
return new AbilityDSLToken(AbilityDSLToken.BOOLEAN, word, startLine, startColumn);
|
|
1605
1446
|
}
|
|
1606
1447
|
if (word === 'null') {
|
|
1607
|
-
return new
|
|
1448
|
+
return new AbilityDSLToken(AbilityDSLToken.NULL, word, startLine, startColumn);
|
|
1608
1449
|
}
|
|
1609
1450
|
// Остальные ключевые слова (contains, in, equals, greater, less, not, is, or, than, equal)
|
|
1610
|
-
return new
|
|
1451
|
+
return new AbilityDSLToken(AbilityDSLToken.KEYWORD, word, startLine, startColumn);
|
|
1611
1452
|
}
|
|
1612
1453
|
// Если после EFFECT и нет точки — действие (например, "create")
|
|
1613
1454
|
const lastToken = this.tokens[this.tokens.length - 1];
|
|
1614
|
-
if (lastToken?.code ===
|
|
1615
|
-
return new
|
|
1455
|
+
if (lastToken?.code === AbilityDSLToken.EFFECT) {
|
|
1456
|
+
return new AbilityDSLToken(AbilityDSLToken.PERMISSION, word, startLine, startColumn);
|
|
1616
1457
|
}
|
|
1617
1458
|
// Обычный идентификатор
|
|
1618
|
-
return new
|
|
1459
|
+
return new AbilityDSLToken(AbilityDSLToken.IDENTIFIER, word, startLine, startColumn);
|
|
1619
1460
|
}
|
|
1620
1461
|
skipWhitespace() {
|
|
1621
1462
|
while (!this.isAtEnd() && /\s/.test(this.peek())) {
|
|
@@ -1648,33 +1489,73 @@ class AbilityDSLLexer {
|
|
|
1648
1489
|
}
|
|
1649
1490
|
return ch;
|
|
1650
1491
|
}
|
|
1651
|
-
isAtEnd() {
|
|
1652
|
-
return this.pos >= this.input.length;
|
|
1492
|
+
isAtEnd() {
|
|
1493
|
+
return this.pos >= this.input.length;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
class AbilityDSLSyntaxError extends Error {
|
|
1498
|
+
line;
|
|
1499
|
+
column;
|
|
1500
|
+
context;
|
|
1501
|
+
details;
|
|
1502
|
+
_formattedMessage;
|
|
1503
|
+
_originalStack;
|
|
1504
|
+
constructor(line, column, context, // строка DSL + ^ + соседние строки
|
|
1505
|
+
details) {
|
|
1506
|
+
super(details.split('\n')[0]); // message = только первая строка
|
|
1507
|
+
this.line = line;
|
|
1508
|
+
this.column = column;
|
|
1509
|
+
this.context = context;
|
|
1510
|
+
this.details = details;
|
|
1511
|
+
this.name = 'AbilityDSLSyntaxError';
|
|
1512
|
+
if (Error.captureStackTrace) {
|
|
1513
|
+
Error.captureStackTrace(this, AbilityDSLSyntaxError);
|
|
1514
|
+
}
|
|
1515
|
+
this._originalStack = this.stack;
|
|
1516
|
+
this._formattedMessage = this.formatMessage();
|
|
1517
|
+
Object.defineProperty(this, 'stack', {
|
|
1518
|
+
get: () => this._formattedMessage,
|
|
1519
|
+
configurable: true,
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
static supportsColor() {
|
|
1523
|
+
return typeof process !== 'undefined' && process.stdout?.isTTY;
|
|
1524
|
+
}
|
|
1525
|
+
formatMessage() {
|
|
1526
|
+
const useColor = AbilityDSLSyntaxError.supportsColor();
|
|
1527
|
+
const BOLD = useColor ? '\x1b[1m' : '';
|
|
1528
|
+
const RED = useColor ? '\x1b[31m' : '';
|
|
1529
|
+
const ORANGE = useColor ? '\x1b[33;1m' : '';
|
|
1530
|
+
const GRAY = useColor ? '\x1b[90m' : '';
|
|
1531
|
+
const RESET = useColor ? '\x1b[0m' : '';
|
|
1532
|
+
const lines = this.context.split('\n');
|
|
1533
|
+
// Find line with ^
|
|
1534
|
+
const pointerIndex = lines.findIndex(l => l.includes('^') || l.includes('~'));
|
|
1535
|
+
const commentIndex = lines.findIndex(l => l.trim().includes('#'));
|
|
1536
|
+
const formattedLines = lines.map((line, idx) => {
|
|
1537
|
+
if (idx === pointerIndex - 1) {
|
|
1538
|
+
// Error line
|
|
1539
|
+
return `${BOLD}${ORANGE}${line}${RESET}`;
|
|
1540
|
+
}
|
|
1541
|
+
if (idx === pointerIndex) {
|
|
1542
|
+
// Error with ~~~~~
|
|
1543
|
+
return `${RED}${line}${RESET}`;
|
|
1544
|
+
}
|
|
1545
|
+
// Comments # ...
|
|
1546
|
+
if (idx === commentIndex) {
|
|
1547
|
+
return `${GRAY}${line}${RESET}`;
|
|
1548
|
+
}
|
|
1549
|
+
return line;
|
|
1550
|
+
});
|
|
1551
|
+
const contextBlock = formattedLines.join('\n');
|
|
1552
|
+
return `${BOLD}${RED}${this.name}: ${this.details}${RESET}\n\n` + contextBlock;
|
|
1553
|
+
}
|
|
1554
|
+
toString() {
|
|
1555
|
+
return this._formattedMessage;
|
|
1653
1556
|
}
|
|
1654
1557
|
}
|
|
1655
|
-
exports.AbilityDSLLexer = AbilityDSLLexer;
|
|
1656
|
-
|
|
1657
1558
|
|
|
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
1559
|
/**
|
|
1679
1560
|
* Parser for the Ability DSL.
|
|
1680
1561
|
*
|
|
@@ -1708,7 +1589,7 @@ class AbilityDSLParser {
|
|
|
1708
1589
|
*/
|
|
1709
1590
|
parse() {
|
|
1710
1591
|
// Tokenize the entire DSL string.
|
|
1711
|
-
this.tokens = new
|
|
1592
|
+
this.tokens = new AbilityDSLLexer(this.dsl).tokenize();
|
|
1712
1593
|
this.pos = 0;
|
|
1713
1594
|
const policies = [];
|
|
1714
1595
|
// Keep parsing until we've consumed all tokens.
|
|
@@ -1736,29 +1617,29 @@ class AbilityDSLParser {
|
|
|
1736
1617
|
this.consumeLeadingComments();
|
|
1737
1618
|
const meta = this.takeAnnotations();
|
|
1738
1619
|
// Effect: "permit" or "deny"
|
|
1739
|
-
const effectToken = this.consume(
|
|
1620
|
+
const effectToken = this.consume(AbilityDSLToken.EFFECT, 'Expected effect');
|
|
1740
1621
|
const effect = effectToken.value;
|
|
1741
1622
|
// Permission: e.g. "order.update"
|
|
1742
|
-
const permissionToken = this.consume(
|
|
1623
|
+
const permissionToken = this.consume(AbilityDSLToken.PERMISSION, 'Expected permission');
|
|
1743
1624
|
const permission = permissionToken.value;
|
|
1744
1625
|
if (!permission.startsWith('permission.')) {
|
|
1745
1626
|
return this.syntaxError(`Unexpected token. The permission key, must be starts with prefix \`permission.\`, but got \`${permission}\`.\nDid you mean \`permission.${permission}\`?`, permissionToken);
|
|
1746
1627
|
}
|
|
1747
1628
|
// "if" keyword
|
|
1748
|
-
this.consume(
|
|
1629
|
+
this.consume(AbilityDSLToken.IF, 'Expected "if"');
|
|
1749
1630
|
// Group selector: "all" or "any" – determines how the top‑level rule sets are combined.
|
|
1750
|
-
const compareToken = this.consumeOneOf([
|
|
1751
|
-
const compareMethod = compareToken.code ===
|
|
1631
|
+
const compareToken = this.consumeOneOf([AbilityDSLToken.ALL, AbilityDSLToken.ANY], 'Expected "all" or "any"');
|
|
1632
|
+
const compareMethod = compareToken.code === AbilityDSLToken.ALL ? AbilityCompare.and : AbilityCompare.or;
|
|
1752
1633
|
// Colon after the group keyword
|
|
1753
|
-
this.consume(
|
|
1634
|
+
this.consume(AbilityDSLToken.COLON, 'Expected ":"');
|
|
1754
1635
|
// Parse the list of rule sets (each "all of:" or "any of:" block)
|
|
1755
1636
|
const ruleSets = this.parseRuleSets(compareMethod);
|
|
1756
1637
|
// Construct the policy instance.
|
|
1757
|
-
return new
|
|
1638
|
+
return new AbilityPolicy({
|
|
1758
1639
|
id: `${effect}:${permission}:${Math.random()}`,
|
|
1759
1640
|
name: meta.name ?? `${effect} ${permission}`,
|
|
1760
1641
|
permission: permission.replace(/^permission\./, ''),
|
|
1761
|
-
effect: effect === 'permit' ?
|
|
1642
|
+
effect: effect === 'permit' ? AbilityPolicyEffect.permit : AbilityPolicyEffect.deny,
|
|
1762
1643
|
compareMethod,
|
|
1763
1644
|
}).addRuleSets(ruleSets);
|
|
1764
1645
|
}
|
|
@@ -1779,7 +1660,7 @@ class AbilityDSLParser {
|
|
|
1779
1660
|
}
|
|
1780
1661
|
// Иначе — implicit group (all-of по умолчанию)
|
|
1781
1662
|
const meta = this.takeAnnotations();
|
|
1782
|
-
const group = new
|
|
1663
|
+
const group = new AbilityRuleSet({
|
|
1783
1664
|
compareMethod: policyCompareMethod,
|
|
1784
1665
|
name: meta.name,
|
|
1785
1666
|
});
|
|
@@ -1789,7 +1670,7 @@ class AbilityDSLParser {
|
|
|
1789
1670
|
if (this.isStartOfGroup() || this.isStartOfPolicy()) {
|
|
1790
1671
|
break;
|
|
1791
1672
|
}
|
|
1792
|
-
if (this.check(
|
|
1673
|
+
if (this.check(AbilityDSLToken.IDENTIFIER)) {
|
|
1793
1674
|
group.addRule(this.parseRule());
|
|
1794
1675
|
}
|
|
1795
1676
|
else {
|
|
@@ -1806,19 +1687,19 @@ class AbilityDSLParser {
|
|
|
1806
1687
|
parseGroup() {
|
|
1807
1688
|
this.consumeLeadingComments();
|
|
1808
1689
|
const meta = this.takeAnnotations();
|
|
1809
|
-
const compareToken = this.consumeOneOf([
|
|
1810
|
-
const compareMethod = compareToken.code ===
|
|
1811
|
-
if (this.check(
|
|
1690
|
+
const compareToken = this.consumeOneOf([AbilityDSLToken.ALL, AbilityDSLToken.ANY], 'Expected "all" or "any"');
|
|
1691
|
+
const compareMethod = compareToken.code === AbilityDSLToken.ALL ? AbilityCompare.and : AbilityCompare.or;
|
|
1692
|
+
if (this.check(AbilityDSLToken.OF)) {
|
|
1812
1693
|
this.advance();
|
|
1813
1694
|
}
|
|
1814
|
-
this.consume(
|
|
1815
|
-
const group = new
|
|
1695
|
+
this.consume(AbilityDSLToken.COLON, 'Expected ":"');
|
|
1696
|
+
const group = new AbilityRuleSet({ compareMethod, name: meta.name });
|
|
1816
1697
|
while (!this.isAtEnd()) {
|
|
1817
1698
|
this.consumeLeadingComments();
|
|
1818
1699
|
if (this.isStartOfGroup() || this.isStartOfPolicy()) {
|
|
1819
1700
|
break;
|
|
1820
1701
|
}
|
|
1821
|
-
if (this.check(
|
|
1702
|
+
if (this.check(AbilityDSLToken.IDENTIFIER)) {
|
|
1822
1703
|
group.addRule(this.parseRule());
|
|
1823
1704
|
}
|
|
1824
1705
|
else {
|
|
@@ -1836,19 +1717,19 @@ class AbilityDSLParser {
|
|
|
1836
1717
|
parseRule() {
|
|
1837
1718
|
this.consumeLeadingComments();
|
|
1838
1719
|
const meta = this.takeAnnotations();
|
|
1839
|
-
if (!this.check(
|
|
1720
|
+
if (!this.check(AbilityDSLToken.IDENTIFIER)) {
|
|
1840
1721
|
this.syntaxError(`Expected identifier, got ${this.peek().code}`, this.peek());
|
|
1841
1722
|
}
|
|
1842
1723
|
// Subject (e.g., "user.roles")
|
|
1843
|
-
const subject = this.consume(
|
|
1724
|
+
const subject = this.consume(AbilityDSLToken.IDENTIFIER, 'Expected field').value;
|
|
1844
1725
|
// Operator (e.g., "contains", "equals", "is not null")
|
|
1845
1726
|
const { condition, operator } = this.parseConditionOperator();
|
|
1846
1727
|
let resource;
|
|
1847
1728
|
let beforePos = this.pos;
|
|
1848
1729
|
// Special operators that don't consume a value token.
|
|
1849
|
-
if (operator ===
|
|
1850
|
-
operator ===
|
|
1851
|
-
operator ===
|
|
1730
|
+
if (operator === AbilityDSLToken.EQ_NULL ||
|
|
1731
|
+
operator === AbilityDSLToken.NOT_EQ_NULL ||
|
|
1732
|
+
operator === AbilityDSLToken.NULL) {
|
|
1852
1733
|
resource = null;
|
|
1853
1734
|
}
|
|
1854
1735
|
else {
|
|
@@ -1860,11 +1741,11 @@ class AbilityDSLParser {
|
|
|
1860
1741
|
this.consumeLeadingComments();
|
|
1861
1742
|
const resourceToken = this.tokens[beforePos];
|
|
1862
1743
|
if (typeof resource === 'string' &&
|
|
1863
|
-
resourceToken.code ===
|
|
1744
|
+
resourceToken.code === AbilityDSLToken.IDENTIFIER &&
|
|
1864
1745
|
!resourceToken.value.includes('.')) {
|
|
1865
|
-
this.syntaxError(`Expected comparison operator or value, got \`${resource}\``, this.tokens[beforePos], [
|
|
1746
|
+
this.syntaxError(`Expected comparison operator or value, got \`${resource}\``, this.tokens[beforePos], [AbilityDSLToken.KEYWORD]);
|
|
1866
1747
|
}
|
|
1867
|
-
return new
|
|
1748
|
+
return new AbilityRule({
|
|
1868
1749
|
subject,
|
|
1869
1750
|
resource,
|
|
1870
1751
|
condition,
|
|
@@ -1882,32 +1763,32 @@ class AbilityDSLParser {
|
|
|
1882
1763
|
const savedPos = this.pos;
|
|
1883
1764
|
// "length equals"
|
|
1884
1765
|
if (this.matchWord('length') && this.matchWord('equals')) {
|
|
1885
|
-
return { condition:
|
|
1766
|
+
return { condition: AbilityCondition.length_equals, operator: AbilityDSLToken.LEN_EQ };
|
|
1886
1767
|
}
|
|
1887
1768
|
this.pos = savedPos;
|
|
1888
1769
|
// "length ="
|
|
1889
1770
|
if (this.matchWord('length') && this.matchSymbol('=')) {
|
|
1890
|
-
return { condition:
|
|
1771
|
+
return { condition: AbilityCondition.length_equals, operator: AbilityDSLToken.LEN_EQ };
|
|
1891
1772
|
}
|
|
1892
1773
|
this.pos = savedPos;
|
|
1893
1774
|
// "length greater than"
|
|
1894
1775
|
if (this.matchWord('length') && this.matchWord('greater') && this.matchWord('than')) {
|
|
1895
|
-
return { condition:
|
|
1776
|
+
return { condition: AbilityCondition.length_greater_than, operator: AbilityDSLToken.LEN_GT };
|
|
1896
1777
|
}
|
|
1897
1778
|
this.pos = savedPos;
|
|
1898
1779
|
// "length >"
|
|
1899
1780
|
if (this.matchWord('length') && this.matchSymbol('>')) {
|
|
1900
|
-
return { condition:
|
|
1781
|
+
return { condition: AbilityCondition.length_greater_than, operator: AbilityDSLToken.LEN_GT };
|
|
1901
1782
|
}
|
|
1902
1783
|
this.pos = savedPos;
|
|
1903
1784
|
// "length less than"
|
|
1904
1785
|
if (this.matchWord('length') && this.matchWord('less') && this.matchWord('than')) {
|
|
1905
|
-
return { condition:
|
|
1786
|
+
return { condition: AbilityCondition.length_less_than, operator: AbilityDSLToken.LEN_LT };
|
|
1906
1787
|
}
|
|
1907
1788
|
this.pos = savedPos;
|
|
1908
1789
|
// "length <"
|
|
1909
1790
|
if (this.matchWord('length') && this.matchSymbol('<')) {
|
|
1910
|
-
return { condition:
|
|
1791
|
+
return { condition: AbilityCondition.length_less_than, operator: AbilityDSLToken.LEN_LT };
|
|
1911
1792
|
}
|
|
1912
1793
|
this.pos = savedPos;
|
|
1913
1794
|
// "greater than or equal"
|
|
@@ -1915,12 +1796,12 @@ class AbilityDSLParser {
|
|
|
1915
1796
|
this.matchWord('than') &&
|
|
1916
1797
|
this.matchWord('or') &&
|
|
1917
1798
|
this.matchWord('equal')) {
|
|
1918
|
-
return { condition:
|
|
1799
|
+
return { condition: AbilityCondition.greater_or_equal, operator: AbilityDSLToken.GTE };
|
|
1919
1800
|
}
|
|
1920
1801
|
this.pos = savedPos;
|
|
1921
1802
|
// greater than
|
|
1922
1803
|
if (this.matchWord('greater') && this.matchWord('than')) {
|
|
1923
|
-
return { condition:
|
|
1804
|
+
return { condition: AbilityCondition.greater_than, operator: AbilityDSLToken.GT };
|
|
1924
1805
|
}
|
|
1925
1806
|
this.pos = savedPos;
|
|
1926
1807
|
// less than or equal
|
|
@@ -1928,143 +1809,141 @@ class AbilityDSLParser {
|
|
|
1928
1809
|
this.matchWord('than') &&
|
|
1929
1810
|
this.matchWord('or') &&
|
|
1930
1811
|
this.matchWord('equal')) {
|
|
1931
|
-
return { condition:
|
|
1812
|
+
return { condition: AbilityCondition.less_or_equal, operator: AbilityDSLToken.LTE };
|
|
1932
1813
|
}
|
|
1933
1814
|
this.pos = savedPos;
|
|
1934
1815
|
// less than
|
|
1935
1816
|
if (this.matchWord('less') && this.matchWord('than')) {
|
|
1936
|
-
return { condition:
|
|
1817
|
+
return { condition: AbilityCondition.less_than, operator: AbilityDSLToken.LT };
|
|
1937
1818
|
}
|
|
1938
1819
|
this.pos = savedPos;
|
|
1939
1820
|
// "not contains"
|
|
1940
1821
|
if (this.matchWord('not') && this.matchWord('contains')) {
|
|
1941
1822
|
return {
|
|
1942
|
-
condition:
|
|
1943
|
-
operator:
|
|
1823
|
+
condition: AbilityCondition.not_contains,
|
|
1824
|
+
operator: AbilityDSLToken.NOT_CONTAINS,
|
|
1944
1825
|
};
|
|
1945
1826
|
}
|
|
1946
1827
|
this.pos = savedPos;
|
|
1947
1828
|
// "not includes"
|
|
1948
1829
|
if (this.matchWord('not') && this.matchWord('includes')) {
|
|
1949
1830
|
return {
|
|
1950
|
-
condition:
|
|
1951
|
-
operator:
|
|
1831
|
+
condition: AbilityCondition.not_contains,
|
|
1832
|
+
operator: AbilityDSLToken.NOT_CONTAINS,
|
|
1952
1833
|
};
|
|
1953
1834
|
}
|
|
1954
1835
|
this.pos = savedPos;
|
|
1955
1836
|
// "not includes"
|
|
1956
1837
|
if (this.matchWord('not') && this.matchWord('has')) {
|
|
1957
1838
|
return {
|
|
1958
|
-
condition:
|
|
1959
|
-
operator:
|
|
1839
|
+
condition: AbilityCondition.not_contains,
|
|
1840
|
+
operator: AbilityDSLToken.NOT_CONTAINS,
|
|
1960
1841
|
};
|
|
1961
1842
|
}
|
|
1962
1843
|
this.pos = savedPos;
|
|
1963
1844
|
// "is equals"
|
|
1964
1845
|
if (this.matchWord('is') && this.matchWord('equals')) {
|
|
1965
|
-
return { condition:
|
|
1846
|
+
return { condition: AbilityCondition.equals, operator: AbilityDSLToken.EQ };
|
|
1966
1847
|
}
|
|
1967
1848
|
this.pos = savedPos;
|
|
1968
1849
|
// not equal
|
|
1969
1850
|
if (this.matchWord('not') && this.matchWord('equals')) {
|
|
1970
|
-
return { condition:
|
|
1851
|
+
return { condition: AbilityCondition.not_equals, operator: AbilityDSLToken.NOT_EQ };
|
|
1971
1852
|
}
|
|
1972
1853
|
this.pos = savedPos;
|
|
1973
1854
|
// is not equals
|
|
1974
1855
|
if (this.matchWord('is') && this.matchWord('not') && this.matchWord('equals')) {
|
|
1975
|
-
return { condition:
|
|
1856
|
+
return { condition: AbilityCondition.not_equals, operator: AbilityDSLToken.NOT_EQ };
|
|
1976
1857
|
}
|
|
1977
1858
|
this.pos = savedPos;
|
|
1978
1859
|
// is in
|
|
1979
1860
|
if (this.matchWord('is') && this.matchWord('in')) {
|
|
1980
|
-
return { condition:
|
|
1861
|
+
return { condition: AbilityCondition.in, operator: AbilityDSLToken.IN };
|
|
1981
1862
|
}
|
|
1982
1863
|
this.pos = savedPos;
|
|
1983
1864
|
// not in
|
|
1984
1865
|
if (this.matchWord('not') && this.matchWord('in')) {
|
|
1985
|
-
return { condition:
|
|
1866
|
+
return { condition: AbilityCondition.not_in, operator: AbilityDSLToken.NOT_IN };
|
|
1986
1867
|
}
|
|
1987
1868
|
this.pos = savedPos;
|
|
1988
1869
|
// is not null
|
|
1989
1870
|
if (this.matchWord('is') && this.matchWord('not')) {
|
|
1990
|
-
if (this.check(
|
|
1871
|
+
if (this.check(AbilityDSLToken.NULL)) {
|
|
1991
1872
|
this.advance();
|
|
1992
1873
|
return {
|
|
1993
|
-
condition:
|
|
1994
|
-
operator:
|
|
1874
|
+
condition: AbilityCondition.not_equals,
|
|
1875
|
+
operator: AbilityDSLToken.NOT_EQ_NULL,
|
|
1995
1876
|
};
|
|
1996
1877
|
}
|
|
1997
1878
|
}
|
|
1998
1879
|
this.pos = savedPos;
|
|
1999
1880
|
// is null
|
|
2000
1881
|
if (this.matchWord('is') && this.matchWord('null')) {
|
|
2001
|
-
if (this.check(
|
|
1882
|
+
if (this.check(AbilityDSLToken.NULL)) {
|
|
2002
1883
|
this.advance();
|
|
2003
1884
|
return {
|
|
2004
|
-
condition:
|
|
2005
|
-
operator:
|
|
1885
|
+
condition: AbilityCondition.equals,
|
|
1886
|
+
operator: AbilityDSLToken.EQ_NULL,
|
|
2006
1887
|
};
|
|
2007
1888
|
}
|
|
2008
1889
|
}
|
|
2009
1890
|
this.pos = savedPos;
|
|
2010
1891
|
// Single token (symbol or keyword)
|
|
2011
1892
|
const token = this.peek();
|
|
2012
|
-
if (token.code !==
|
|
2013
|
-
token.code !==
|
|
2014
|
-
token.code !==
|
|
1893
|
+
if (token.code !== AbilityDSLToken.SYMBOL &&
|
|
1894
|
+
token.code !== AbilityDSLToken.KEYWORD &&
|
|
1895
|
+
token.code !== AbilityDSLToken.NULL) {
|
|
2015
1896
|
this.syntaxError(`Expected comparison operator, got \`${token.value}\``, token, [
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
1897
|
+
AbilityDSLToken.SYMBOL,
|
|
1898
|
+
AbilityDSLToken.KEYWORD,
|
|
1899
|
+
AbilityDSLToken.NULL,
|
|
2019
1900
|
]);
|
|
2020
1901
|
}
|
|
2021
1902
|
this.advance();
|
|
2022
1903
|
switch (token.code) {
|
|
2023
|
-
case
|
|
1904
|
+
case AbilityDSLToken.SYMBOL:
|
|
2024
1905
|
if (token.value === '=' || token.value === '==')
|
|
2025
|
-
return { condition:
|
|
1906
|
+
return { condition: AbilityCondition.equals, operator: AbilityDSLToken.EQ };
|
|
2026
1907
|
if (token.value === '!=' || token.value === '<>')
|
|
2027
|
-
return { condition:
|
|
1908
|
+
return { condition: AbilityCondition.not_equals, operator: AbilityDSLToken.NOT_EQ };
|
|
2028
1909
|
if (token.value === '>')
|
|
2029
|
-
return { condition:
|
|
1910
|
+
return { condition: AbilityCondition.greater_than, operator: AbilityDSLToken.GT };
|
|
2030
1911
|
if (token.value === '<')
|
|
2031
|
-
return { condition:
|
|
1912
|
+
return { condition: AbilityCondition.less_than, operator: AbilityDSLToken.LT };
|
|
2032
1913
|
if (token.value === '>=')
|
|
2033
|
-
return { condition:
|
|
1914
|
+
return { condition: AbilityCondition.greater_or_equal, operator: AbilityDSLToken.GTE };
|
|
2034
1915
|
if (token.value === '<=')
|
|
2035
|
-
return { condition:
|
|
1916
|
+
return { condition: AbilityCondition.less_or_equal, operator: AbilityDSLToken.LTE };
|
|
2036
1917
|
break;
|
|
2037
|
-
case
|
|
1918
|
+
case AbilityDSLToken.KEYWORD:
|
|
2038
1919
|
if (token.value === 'contains' || token.value === 'includes' || token.value === 'has')
|
|
2039
|
-
return { condition:
|
|
1920
|
+
return { condition: AbilityCondition.contains, operator: AbilityDSLToken.CONTAINS };
|
|
2040
1921
|
if (token.value === 'in')
|
|
2041
|
-
return { condition:
|
|
1922
|
+
return { condition: AbilityCondition.in, operator: AbilityDSLToken.IN };
|
|
2042
1923
|
if (token.value === 'equals')
|
|
2043
|
-
return { condition:
|
|
1924
|
+
return { condition: AbilityCondition.equals, operator: AbilityDSLToken.EQ };
|
|
2044
1925
|
if (token.value === 'gte') {
|
|
2045
|
-
return { condition:
|
|
1926
|
+
return { condition: AbilityCondition.greater_or_equal, operator: AbilityDSLToken.GTE };
|
|
2046
1927
|
}
|
|
2047
1928
|
if (token.value === 'greater' || token.value === 'gt') {
|
|
2048
1929
|
// If we come here, it means "greater" without "than" – treat as '>'
|
|
2049
|
-
return { condition:
|
|
1930
|
+
return { condition: AbilityCondition.greater_than, operator: AbilityDSLToken.GT };
|
|
2050
1931
|
}
|
|
2051
1932
|
if (token.value === 'less' || token.value === 'lt') {
|
|
2052
|
-
return { condition:
|
|
1933
|
+
return { condition: AbilityCondition.less_than, operator: AbilityDSLToken.LT };
|
|
2053
1934
|
}
|
|
2054
1935
|
if (token.value === 'lte') {
|
|
2055
|
-
return { condition:
|
|
1936
|
+
return { condition: AbilityCondition.less_or_equal, operator: AbilityDSLToken.LTE };
|
|
2056
1937
|
}
|
|
2057
1938
|
if (token.value === 'is') {
|
|
2058
1939
|
// "is" alone -> equals
|
|
2059
|
-
return { condition:
|
|
1940
|
+
return { condition: AbilityCondition.equals, operator: AbilityDSLToken.EQ };
|
|
2060
1941
|
}
|
|
2061
1942
|
break;
|
|
2062
|
-
default:
|
|
2063
|
-
break;
|
|
2064
1943
|
}
|
|
2065
1944
|
return this.syntaxError(`Unexpected operator token \`${token.value}\``, token, [
|
|
2066
|
-
|
|
2067
|
-
|
|
1945
|
+
AbilityDSLToken.SYMBOL,
|
|
1946
|
+
AbilityDSLToken.KEYWORD,
|
|
2068
1947
|
]);
|
|
2069
1948
|
}
|
|
2070
1949
|
/**
|
|
@@ -2077,7 +1956,7 @@ class AbilityDSLParser {
|
|
|
2077
1956
|
return false;
|
|
2078
1957
|
}
|
|
2079
1958
|
const token = this.peek();
|
|
2080
|
-
if ((token.code ===
|
|
1959
|
+
if ((token.code === AbilityDSLToken.KEYWORD || token.code === AbilityDSLToken.IDENTIFIER) &&
|
|
2081
1960
|
token.value === word) {
|
|
2082
1961
|
this.advance();
|
|
2083
1962
|
return true;
|
|
@@ -2088,7 +1967,7 @@ class AbilityDSLParser {
|
|
|
2088
1967
|
if (this.isAtEnd())
|
|
2089
1968
|
return false;
|
|
2090
1969
|
const token = this.peek();
|
|
2091
|
-
if (token.code ===
|
|
1970
|
+
if (token.code === AbilityDSLToken.SYMBOL && token.value === symbol) {
|
|
2092
1971
|
this.advance();
|
|
2093
1972
|
return true;
|
|
2094
1973
|
}
|
|
@@ -2103,32 +1982,32 @@ class AbilityDSLParser {
|
|
|
2103
1982
|
*/
|
|
2104
1983
|
parseValue() {
|
|
2105
1984
|
// Arrays start with a left bracket
|
|
2106
|
-
if (this.check(
|
|
1985
|
+
if (this.check(AbilityDSLToken.LBRACKET)) {
|
|
2107
1986
|
this.advance();
|
|
2108
1987
|
return this.parseArray();
|
|
2109
1988
|
}
|
|
2110
1989
|
// Ensure we are not about to read a structural token as a value.
|
|
2111
1990
|
const token = this.peek();
|
|
2112
|
-
if (token.code ===
|
|
2113
|
-
token.code ===
|
|
2114
|
-
token.code ===
|
|
1991
|
+
if (token.code === AbilityDSLToken.ALL ||
|
|
1992
|
+
token.code === AbilityDSLToken.ANY ||
|
|
1993
|
+
token.code === AbilityDSLToken.EFFECT) {
|
|
2115
1994
|
this.syntaxError(`Unexpected ${token.code} in value position`, token);
|
|
2116
1995
|
}
|
|
2117
1996
|
this.advance();
|
|
2118
1997
|
// CHECK THIS SWITCH COMPARE
|
|
2119
1998
|
switch (token.code) {
|
|
2120
|
-
case
|
|
1999
|
+
case AbilityDSLToken.STRING:
|
|
2121
2000
|
return token.value;
|
|
2122
|
-
case
|
|
2001
|
+
case AbilityDSLToken.NUMBER:
|
|
2123
2002
|
return Number(token.value);
|
|
2124
|
-
case
|
|
2003
|
+
case AbilityDSLToken.BOOLEAN:
|
|
2125
2004
|
return token.value === 'true';
|
|
2126
|
-
case
|
|
2005
|
+
case AbilityDSLToken.NULL:
|
|
2127
2006
|
return null;
|
|
2128
|
-
case
|
|
2007
|
+
case AbilityDSLToken.IDENTIFIER:
|
|
2129
2008
|
return token.value;
|
|
2130
2009
|
default: {
|
|
2131
|
-
this.syntaxError(`Unexpected value token "${token.value}"`, token ?? this.tokens[this.tokens.length - 1], [
|
|
2010
|
+
this.syntaxError(`Unexpected value token "${token.value}"`, token ?? this.tokens[this.tokens.length - 1], [AbilityDSLToken.KEYWORD]);
|
|
2132
2011
|
}
|
|
2133
2012
|
}
|
|
2134
2013
|
}
|
|
@@ -2139,11 +2018,11 @@ class AbilityDSLParser {
|
|
|
2139
2018
|
parseArray() {
|
|
2140
2019
|
const arr = [];
|
|
2141
2020
|
// Handle empty array
|
|
2142
|
-
if (this.check(
|
|
2021
|
+
if (this.check(AbilityDSLToken.RBRACKET)) {
|
|
2143
2022
|
this.advance();
|
|
2144
2023
|
return arr;
|
|
2145
2024
|
}
|
|
2146
|
-
while (!this.isAtEnd() && !this.check(
|
|
2025
|
+
while (!this.isAtEnd() && !this.check(AbilityDSLToken.RBRACKET)) {
|
|
2147
2026
|
const value = this.parseValue();
|
|
2148
2027
|
// Flatten nested arrays if they appear (though grammar doesn't currently allow nesting).
|
|
2149
2028
|
if (Array.isArray(value)) {
|
|
@@ -2159,18 +2038,18 @@ class AbilityDSLParser {
|
|
|
2159
2038
|
this.syntaxError('Unexpected null in array', this.peek());
|
|
2160
2039
|
}
|
|
2161
2040
|
// Optional comma between elements
|
|
2162
|
-
if (this.check(
|
|
2041
|
+
if (this.check(AbilityDSLToken.COMMA)) {
|
|
2163
2042
|
this.advance();
|
|
2164
2043
|
}
|
|
2165
2044
|
}
|
|
2166
|
-
this.consume(
|
|
2045
|
+
this.consume(AbilityDSLToken.RBRACKET, 'Expected "]"');
|
|
2167
2046
|
return arr;
|
|
2168
2047
|
}
|
|
2169
2048
|
// -------------------------------------------------------------------------
|
|
2170
2049
|
// #region Annotations and comments
|
|
2171
2050
|
// -------------------------------------------------------------------------
|
|
2172
2051
|
consumeLeadingComments() {
|
|
2173
|
-
while (this.check(
|
|
2052
|
+
while (this.check(AbilityDSLToken.COMMENT)) {
|
|
2174
2053
|
const token = this.advance();
|
|
2175
2054
|
this.processCommentToken(token);
|
|
2176
2055
|
}
|
|
@@ -2220,7 +2099,7 @@ class AbilityDSLParser {
|
|
|
2220
2099
|
const detailsMsg = `${details}\nDetails: Unexpected value token \`${actual}\``;
|
|
2221
2100
|
finalDetails = suggestion ? `${detailsMsg} Did you mean \`${suggestion}\`?` : detailsMsg;
|
|
2222
2101
|
}
|
|
2223
|
-
throw new
|
|
2102
|
+
throw new AbilityDSLSyntaxError(token.line, token.column, context + '\n', finalDetails);
|
|
2224
2103
|
}
|
|
2225
2104
|
getLine(lineNumber) {
|
|
2226
2105
|
return this.dsl.split(/\r?\n/)[lineNumber - 1] ?? '';
|
|
@@ -2269,7 +2148,7 @@ class AbilityDSLParser {
|
|
|
2269
2148
|
}
|
|
2270
2149
|
}
|
|
2271
2150
|
const expected = types.map(t => t).join(', ');
|
|
2272
|
-
const actual = token ? token.value :
|
|
2151
|
+
const actual = token ? token.value : AbilityDSLToken.EOF;
|
|
2273
2152
|
const suggestion = this.suggest(actual, types);
|
|
2274
2153
|
const details = `${message}\nDetails: Unexpected token \`${actual}\`, expected one of: ${expected}.`;
|
|
2275
2154
|
const finalMsg = suggestion ? `${details} Did you mean \`${suggestion}\`?` : details;
|
|
@@ -2281,7 +2160,7 @@ class AbilityDSLParser {
|
|
|
2281
2160
|
return this.advance();
|
|
2282
2161
|
}
|
|
2283
2162
|
const expected = type;
|
|
2284
|
-
const actual = token ? token.value :
|
|
2163
|
+
const actual = token ? token.value : AbilityDSLToken.EOF;
|
|
2285
2164
|
const suggestion = this.suggest(actual, [type]);
|
|
2286
2165
|
const details = `${message}\nDetails: Unexpected token \`${actual}\`, expected "${expected}".`;
|
|
2287
2166
|
const finalMsg = suggestion ? `${details} Did you mean \`${suggestion}\`?` : details;
|
|
@@ -2293,10 +2172,10 @@ class AbilityDSLParser {
|
|
|
2293
2172
|
return this.peek().code === type;
|
|
2294
2173
|
}
|
|
2295
2174
|
isStartOfPolicy() {
|
|
2296
|
-
return this.check(
|
|
2175
|
+
return this.check(AbilityDSLToken.EFFECT);
|
|
2297
2176
|
}
|
|
2298
2177
|
isStartOfGroup() {
|
|
2299
|
-
return this.check(
|
|
2178
|
+
return this.check(AbilityDSLToken.ALL) || this.check(AbilityDSLToken.ANY);
|
|
2300
2179
|
}
|
|
2301
2180
|
advance() {
|
|
2302
2181
|
return this.tokens[this.pos++];
|
|
@@ -2305,296 +2184,29 @@ class AbilityDSLParser {
|
|
|
2305
2184
|
return this.tokens[this.pos];
|
|
2306
2185
|
}
|
|
2307
2186
|
isAtEnd() {
|
|
2308
|
-
return this.peek().code ===
|
|
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;
|
|
2187
|
+
return this.peek().code === AbilityDSLToken.EOF;
|
|
2381
2188
|
}
|
|
2382
2189
|
}
|
|
2383
|
-
exports.AbilityDSLSyntaxError = AbilityDSLSyntaxError;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
/***/ }),
|
|
2387
|
-
|
|
2388
|
-
/***/ 325:
|
|
2389
|
-
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
2390
2190
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
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
|
-
}
|
|
2191
|
+
exports.AbilityCode = AbilityCode;
|
|
2192
|
+
exports.AbilityCompare = AbilityCompare;
|
|
2193
|
+
exports.AbilityCondition = AbilityCondition;
|
|
2194
|
+
exports.AbilityDSLLexer = AbilityDSLLexer;
|
|
2195
|
+
exports.AbilityDSLParser = AbilityDSLParser;
|
|
2458
2196
|
exports.AbilityDSLToken = AbilityDSLToken;
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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
|
-
}
|
|
2197
|
+
exports.AbilityError = AbilityError;
|
|
2198
|
+
exports.AbilityExplain = AbilityExplain;
|
|
2199
|
+
exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
2200
|
+
exports.AbilityExplainRule = AbilityExplainRule;
|
|
2201
|
+
exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
|
|
2202
|
+
exports.AbilityInMemoryCache = AbilityInMemoryCache;
|
|
2560
2203
|
exports.AbilityJSONParser = AbilityJSONParser;
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
/******/ // The require function
|
|
2571
|
-
/******/ function __webpack_require__(moduleId) {
|
|
2572
|
-
/******/ // Check if module is in cache
|
|
2573
|
-
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
2574
|
-
/******/ if (cachedModule !== undefined) {
|
|
2575
|
-
/******/ return cachedModule.exports;
|
|
2576
|
-
/******/ }
|
|
2577
|
-
/******/ // Create a new module (and put it into the cache)
|
|
2578
|
-
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
2579
|
-
/******/ // no module.id needed
|
|
2580
|
-
/******/ // no module.loaded needed
|
|
2581
|
-
/******/ exports: {}
|
|
2582
|
-
/******/ };
|
|
2583
|
-
/******/
|
|
2584
|
-
/******/ // Execute the module function
|
|
2585
|
-
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
2586
|
-
/******/
|
|
2587
|
-
/******/ // Return the exports of the module
|
|
2588
|
-
/******/ return module.exports;
|
|
2589
|
-
/******/ }
|
|
2590
|
-
/******/
|
|
2591
|
-
/************************************************************************/
|
|
2592
|
-
/******/
|
|
2593
|
-
/******/ // startup
|
|
2594
|
-
/******/ // Load entry module and return exports
|
|
2595
|
-
/******/ // This entry module is referenced by other modules so it can't be inlined
|
|
2596
|
-
/******/ var __webpack_exports__ = __webpack_require__(156);
|
|
2597
|
-
/******/ module.exports = __webpack_exports__;
|
|
2598
|
-
/******/
|
|
2599
|
-
/******/ })()
|
|
2600
|
-
;
|
|
2204
|
+
exports.AbilityMatch = AbilityMatch;
|
|
2205
|
+
exports.AbilityParserError = AbilityParserError;
|
|
2206
|
+
exports.AbilityPolicy = AbilityPolicy;
|
|
2207
|
+
exports.AbilityPolicyEffect = AbilityPolicyEffect;
|
|
2208
|
+
exports.AbilityResolver = AbilityResolver;
|
|
2209
|
+
exports.AbilityResult = AbilityResult;
|
|
2210
|
+
exports.AbilityRule = AbilityRule;
|
|
2211
|
+
exports.AbilityRuleSet = AbilityRuleSet;
|
|
2212
|
+
exports.AbilityTypeGenerator = AbilityTypeGenerator;
|