@via-profit/ability 3.4.0 → 3.5.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 +2 -2
- package/CONTRIBUTING.md +14 -14
- package/LICENSE +21 -21
- package/README.md +1363 -1374
- package/SECURITY.md +33 -33
- package/dist/index.d.ts +574 -19
- package/dist/index.js +234 -305
- package/package.json +74 -73
- package/dist/cache/AbilityCacheAdapter.d.ts +0 -8
- package/dist/cache/AbilityInMemoryCache.d.ts +0 -12
- package/dist/core/AbilityCode.d.ts +0 -8
- package/dist/core/AbilityCompare.d.ts +0 -7
- package/dist/core/AbilityCondition.d.ts +0 -23
- package/dist/core/AbilityError.d.ts +0 -6
- package/dist/core/AbilityExplain.d.ts +0 -27
- package/dist/core/AbilityMatch.d.ts +0 -8
- package/dist/core/AbilityParser.d.ts +0 -61
- package/dist/core/AbilityPolicy.d.ts +0 -79
- package/dist/core/AbilityPolicyEffect.d.ts +0 -7
- package/dist/core/AbilityResolver.d.ts +0 -35
- package/dist/core/AbilityResult.d.ts +0 -27
- package/dist/core/AbilityRule.d.ts +0 -83
- package/dist/core/AbilityRuleSet.d.ts +0 -51
- package/dist/core/AbilityTypeGenerator.d.ts +0 -55
- package/dist/parsers/dsl/AbilityDSLLexer.d.ts +0 -24
- package/dist/parsers/dsl/AbilityDSLParser.d.ts +0 -85
- package/dist/parsers/dsl/AbilityDSLSyntaxError.d.ts +0 -13
- package/dist/parsers/dsl/AbilityDSLToken.d.ts +0 -57
- package/dist/parsers/json/AbilityJSONParser.d.ts +0 -22
package/dist/index.js
CHANGED
|
@@ -22,75 +22,116 @@ class AbilityCompare extends AbilityCode {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
class AbilityError extends Error {
|
|
25
|
-
constructor(message) {
|
|
26
|
-
super(message);
|
|
25
|
+
constructor(message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = 'AbilityError';
|
|
28
|
+
if (Error.captureStackTrace) {
|
|
29
|
+
Error.captureStackTrace(this, this.constructor);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
class AbilityParserError extends Error {
|
|
30
|
-
constructor(message) {
|
|
31
|
-
super(message);
|
|
34
|
+
constructor(message, options) {
|
|
35
|
+
super(message, options);
|
|
36
|
+
this.name = 'AbilityParserError';
|
|
37
|
+
if (Error.captureStackTrace) {
|
|
38
|
+
Error.captureStackTrace(this, this.constructor);
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
class AbilityCondition extends AbilityCode {
|
|
36
|
-
static equals
|
|
37
|
-
static not_equals
|
|
38
|
-
static greater_than
|
|
39
|
-
static less_than
|
|
40
|
-
static less_or_equal
|
|
41
|
-
static greater_or_equal
|
|
42
|
-
static in
|
|
43
|
-
static not_in
|
|
44
|
-
static contains
|
|
45
|
-
static not_contains
|
|
46
|
-
static length_greater_than
|
|
47
|
-
static length_less_than
|
|
48
|
-
static length_equals
|
|
49
|
-
static always
|
|
50
|
-
static never
|
|
44
|
+
static equals;
|
|
45
|
+
static not_equals;
|
|
46
|
+
static greater_than;
|
|
47
|
+
static less_than;
|
|
48
|
+
static less_or_equal;
|
|
49
|
+
static greater_or_equal;
|
|
50
|
+
static in;
|
|
51
|
+
static not_in;
|
|
52
|
+
static contains;
|
|
53
|
+
static not_contains;
|
|
54
|
+
static length_greater_than;
|
|
55
|
+
static length_less_than;
|
|
56
|
+
static length_equals;
|
|
57
|
+
static always;
|
|
58
|
+
static never;
|
|
59
|
+
static {
|
|
60
|
+
this.equals = new AbilityCondition('=');
|
|
61
|
+
this.not_equals = new AbilityCondition('<>');
|
|
62
|
+
this.greater_than = new AbilityCondition('>');
|
|
63
|
+
this.less_than = new AbilityCondition('<');
|
|
64
|
+
this.less_or_equal = new AbilityCondition('<=');
|
|
65
|
+
this.greater_or_equal = new AbilityCondition('>=');
|
|
66
|
+
this.in = new AbilityCondition('in');
|
|
67
|
+
this.not_in = new AbilityCondition('not in');
|
|
68
|
+
this.contains = new AbilityCondition('contains');
|
|
69
|
+
this.not_contains = new AbilityCondition('not contains');
|
|
70
|
+
this.length_greater_than = new AbilityCondition('length greater than');
|
|
71
|
+
this.length_less_than = new AbilityCondition('length less than');
|
|
72
|
+
this.length_equals = new AbilityCondition('length equals');
|
|
73
|
+
this.always = new AbilityCondition('always');
|
|
74
|
+
this.never = new AbilityCondition('never');
|
|
75
|
+
}
|
|
51
76
|
static fromLiteral(literal) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
const map = {
|
|
78
|
+
equals: this.equals,
|
|
79
|
+
not_equals: this.not_equals,
|
|
80
|
+
greater_than: this.greater_than,
|
|
81
|
+
less_than: this.less_than,
|
|
82
|
+
less_or_equal: this.less_or_equal,
|
|
83
|
+
greater_or_equal: this.greater_or_equal,
|
|
84
|
+
in: this.in,
|
|
85
|
+
not_in: this.not_in,
|
|
86
|
+
contains: this.contains,
|
|
87
|
+
not_contains: this.not_contains,
|
|
88
|
+
length_greater_than: this.length_greater_than,
|
|
89
|
+
length_equals: this.length_equals,
|
|
90
|
+
always: this.always,
|
|
91
|
+
never: this.never,
|
|
92
|
+
length_less_than: this.length_less_than,
|
|
93
|
+
};
|
|
94
|
+
const condition = map[literal];
|
|
95
|
+
if (!condition) {
|
|
96
|
+
throw new AbilityParserError(`Literal "${literal}" does not found in AbilityCondition class`);
|
|
97
|
+
}
|
|
98
|
+
return condition;
|
|
99
|
+
}
|
|
100
|
+
get literal() {
|
|
101
|
+
switch (this.code) {
|
|
102
|
+
case '=':
|
|
103
|
+
return 'equals';
|
|
104
|
+
case '<>':
|
|
105
|
+
return 'not_equals';
|
|
106
|
+
case '>':
|
|
107
|
+
return 'greater_than';
|
|
108
|
+
case '<':
|
|
109
|
+
return 'less_than';
|
|
110
|
+
case '>=':
|
|
111
|
+
return 'greater_or_equal';
|
|
112
|
+
case '<=':
|
|
113
|
+
return 'less_or_equal';
|
|
69
114
|
case 'in':
|
|
70
|
-
return
|
|
71
|
-
case '
|
|
72
|
-
return
|
|
73
|
-
case '
|
|
74
|
-
return
|
|
75
|
-
case '
|
|
76
|
-
return
|
|
115
|
+
return 'in';
|
|
116
|
+
case 'not in':
|
|
117
|
+
return 'not_in';
|
|
118
|
+
case 'contains':
|
|
119
|
+
return 'contains';
|
|
120
|
+
case 'not contains':
|
|
121
|
+
return 'not_contains';
|
|
122
|
+
case 'length greater than':
|
|
123
|
+
return 'length_greater_than';
|
|
124
|
+
case 'length less than':
|
|
125
|
+
return 'length_less_than';
|
|
126
|
+
case 'length equals':
|
|
127
|
+
return 'length_equals';
|
|
77
128
|
case 'always':
|
|
78
|
-
return
|
|
129
|
+
return 'always';
|
|
79
130
|
case 'never':
|
|
80
|
-
return
|
|
131
|
+
return 'never';
|
|
81
132
|
default:
|
|
82
|
-
throw new
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
get literal() {
|
|
86
|
-
const literal = Object.keys(AbilityCondition).find(member => {
|
|
87
|
-
const val = AbilityCondition[member];
|
|
88
|
-
return val.code === this.code;
|
|
89
|
-
});
|
|
90
|
-
if (typeof literal === 'undefined') {
|
|
91
|
-
throw new Error(`Literal value does not found in class AbilityCondition`);
|
|
133
|
+
throw new Error(`Unknown condition code: ${String(this.code)}`);
|
|
92
134
|
}
|
|
93
|
-
return literal;
|
|
94
135
|
}
|
|
95
136
|
}
|
|
96
137
|
|
|
@@ -126,8 +167,11 @@ class AbilityTypeGenerator {
|
|
|
126
167
|
const subjectPath = rule.subject;
|
|
127
168
|
const existingType = typeStructure[action][subjectPath];
|
|
128
169
|
const ruleType = this.determineTypeFromRule(rule);
|
|
129
|
-
|
|
170
|
+
if (!ruleType) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
130
173
|
if (existingType && existingType !== ruleType) {
|
|
174
|
+
// If a type already exists for this path, create a union
|
|
131
175
|
typeStructure[action][subjectPath] = `${existingType} | ${ruleType}`;
|
|
132
176
|
}
|
|
133
177
|
else {
|
|
@@ -146,6 +190,10 @@ class AbilityTypeGenerator {
|
|
|
146
190
|
* @returns TypeScript type as string
|
|
147
191
|
*/
|
|
148
192
|
determineTypeFromRule(rule) {
|
|
193
|
+
if (rule.condition.isEqual(AbilityCondition.never) ||
|
|
194
|
+
rule.condition.isEqual(AbilityCondition.always)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
149
197
|
// Numeric comparisons - always number
|
|
150
198
|
if (rule.condition.isEqual(AbilityCondition.greater_than) ||
|
|
151
199
|
rule.condition.isEqual(AbilityCondition.less_than) ||
|
|
@@ -172,8 +220,9 @@ class AbilityTypeGenerator {
|
|
|
172
220
|
*/
|
|
173
221
|
getArrayType(resource) {
|
|
174
222
|
if (Array.isArray(resource)) {
|
|
175
|
-
if (resource.length === 0)
|
|
223
|
+
if (resource.length === 0) {
|
|
176
224
|
return 'any[]';
|
|
225
|
+
}
|
|
177
226
|
// Determine types of array elements
|
|
178
227
|
const elementTypes = new Set(resource.map(item => this.getPrimitiveType(item)));
|
|
179
228
|
const elementType = elementTypes.size === 1
|
|
@@ -191,10 +240,12 @@ class AbilityTypeGenerator {
|
|
|
191
240
|
* @returns TypeScript primitive type as string
|
|
192
241
|
*/
|
|
193
242
|
getPrimitiveType(value) {
|
|
194
|
-
if (value === null)
|
|
243
|
+
if (value === null) {
|
|
195
244
|
return 'null';
|
|
196
|
-
|
|
245
|
+
}
|
|
246
|
+
if (value === undefined) {
|
|
197
247
|
return 'undefined';
|
|
248
|
+
}
|
|
198
249
|
switch (typeof value) {
|
|
199
250
|
case 'string':
|
|
200
251
|
return 'string';
|
|
@@ -253,12 +304,20 @@ class AbilityTypeGenerator {
|
|
|
253
304
|
let output = '// Automatically generated by via-profit/ability\n';
|
|
254
305
|
output += '// Do not edit manually\n';
|
|
255
306
|
output += 'export type Resources = {\n';
|
|
256
|
-
// Sort actions for stable output
|
|
257
307
|
const sortedActions = Object.keys(structure).sort();
|
|
258
308
|
sortedActions.forEach(action => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
309
|
+
const actionObj = structure[action];
|
|
310
|
+
const isEmpty = Object.keys(actionObj).length === 0;
|
|
311
|
+
if (isEmpty) {
|
|
312
|
+
// Пустой объект → undefined
|
|
313
|
+
output += ` ['${action}']: undefined;\n`;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// Непустой объект → как раньше
|
|
317
|
+
output += ` ['${action}']: {\n`;
|
|
318
|
+
output += this.formatNestedObject(actionObj, 4);
|
|
319
|
+
output += ' };\n';
|
|
320
|
+
}
|
|
262
321
|
});
|
|
263
322
|
output += '}\n';
|
|
264
323
|
return output;
|
|
@@ -403,14 +462,14 @@ class AbilityPolicy {
|
|
|
403
462
|
* @param resource - The resource to check
|
|
404
463
|
* @param environment - The user environment object
|
|
405
464
|
*/
|
|
406
|
-
|
|
465
|
+
check(resource, environment) {
|
|
407
466
|
this.matchState = AbilityMatch.mismatch;
|
|
408
467
|
if (!this.ruleSet.length) {
|
|
409
468
|
return this.matchState;
|
|
410
469
|
}
|
|
411
470
|
const rulesetCheckStates = [];
|
|
412
471
|
for (const ruleSet of this.ruleSet) {
|
|
413
|
-
const state =
|
|
472
|
+
const state = ruleSet.check(resource, environment);
|
|
414
473
|
rulesetCheckStates.push(state);
|
|
415
474
|
if (AbilityCompare.and.isEqual(this.compareMethod) && AbilityMatch.mismatch.isEqual(state)) {
|
|
416
475
|
return this.matchState; // mismatch
|
|
@@ -512,14 +571,11 @@ class AbilityResult {
|
|
|
512
571
|
|
|
513
572
|
class AbilityResolver {
|
|
514
573
|
policies;
|
|
515
|
-
cache;
|
|
516
574
|
constructor(
|
|
517
575
|
/**
|
|
518
576
|
* `Important!` The incorrect Resources type was intentionally passed to AbilityPolicy so that TypeScript could suggest the name of the permission and the structure of its resource in the parse method.
|
|
519
577
|
*/
|
|
520
|
-
policyOrListOfPolicies
|
|
521
|
-
const { cache } = options || {};
|
|
522
|
-
this.cache = cache;
|
|
578
|
+
policyOrListOfPolicies) {
|
|
523
579
|
this.policies = Array.isArray(policyOrListOfPolicies)
|
|
524
580
|
? policyOrListOfPolicies
|
|
525
581
|
: [policyOrListOfPolicies];
|
|
@@ -531,30 +587,18 @@ class AbilityResolver {
|
|
|
531
587
|
* @param resource - Resource
|
|
532
588
|
* @param environment
|
|
533
589
|
*/
|
|
534
|
-
|
|
590
|
+
resolve(permission, resource, environment) {
|
|
535
591
|
const filteredPolicies = this.policies.filter(policy => AbilityResolver.isInPermissionContain(policy.permission, String(permission).replace(/^permission\./, '')));
|
|
536
592
|
for (const policy of filteredPolicies) {
|
|
537
|
-
const
|
|
538
|
-
// cache
|
|
539
|
-
if (this.cache) {
|
|
540
|
-
const cached = await this.cache.get(cacheKey);
|
|
541
|
-
if (cached !== undefined) {
|
|
542
|
-
policy.matchState = cached;
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const policyMatchState = await policy.check(resource, environment);
|
|
593
|
+
const policyMatchState = policy.check(resource, environment);
|
|
547
594
|
if (policyMatchState === AbilityMatch.pending) {
|
|
548
595
|
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.`);
|
|
549
596
|
}
|
|
550
|
-
if (this.cache) {
|
|
551
|
-
await this.cache.set(cacheKey, policyMatchState);
|
|
552
|
-
}
|
|
553
597
|
}
|
|
554
598
|
return new AbilityResult(filteredPolicies);
|
|
555
599
|
}
|
|
556
|
-
|
|
557
|
-
const result =
|
|
600
|
+
enforce(permission, resource, environment) {
|
|
601
|
+
const result = this.resolve(permission, resource, environment);
|
|
558
602
|
if (result.isDenied()) {
|
|
559
603
|
const lastPolicy = result.getLastMatchedPolicy();
|
|
560
604
|
if (lastPolicy) {
|
|
@@ -577,18 +621,6 @@ class AbilityResolver {
|
|
|
577
621
|
return chunk === '*' || longer[i] === '*' || chunk === longer[i];
|
|
578
622
|
});
|
|
579
623
|
}
|
|
580
|
-
makeCacheKey(policyId, resource, environment) {
|
|
581
|
-
if (!this.cache) {
|
|
582
|
-
return '';
|
|
583
|
-
}
|
|
584
|
-
return `policy:${policyId}:res:${this.cache.serialize(resource)}:env:${this.cache.serialize(environment)}`;
|
|
585
|
-
}
|
|
586
|
-
async invalidatePolicy(policyId) {
|
|
587
|
-
await this.cache?.deleteByPrefix(policyId);
|
|
588
|
-
}
|
|
589
|
-
async invalidateCache() {
|
|
590
|
-
await this.cache?.clear();
|
|
591
|
-
}
|
|
592
624
|
}
|
|
593
625
|
|
|
594
626
|
/**
|
|
@@ -624,149 +656,122 @@ class AbilityRule {
|
|
|
624
656
|
this.resource = resource;
|
|
625
657
|
this.condition = condition;
|
|
626
658
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (AbilityCondition.not_equals.isEqual(this.condition)) {
|
|
646
|
-
is = subjectValue !== resourceValue;
|
|
647
|
-
}
|
|
648
|
-
// less than
|
|
649
|
-
if (AbilityCondition.less_than.isEqual(this.condition)) {
|
|
650
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
651
|
-
is = subjectValue < resourceValue;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
// less or equal
|
|
655
|
-
if (AbilityCondition.less_or_equal.isEqual(this.condition)) {
|
|
656
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
657
|
-
is = subjectValue <= resourceValue;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// more than
|
|
661
|
-
if (AbilityCondition.greater_than.isEqual(this.condition)) {
|
|
662
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
663
|
-
is = subjectValue > resourceValue;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
// more or equal
|
|
667
|
-
if (AbilityCondition.greater_or_equal.isEqual(this.condition)) {
|
|
668
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
669
|
-
is = subjectValue >= resourceValue;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
// in
|
|
673
|
-
if (AbilityCondition.in.isEqual(this.condition)) {
|
|
674
|
-
// value in array
|
|
675
|
-
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
676
|
-
is = resourceValue.includes(subjectValue);
|
|
677
|
-
}
|
|
678
|
-
// array intersects array
|
|
679
|
-
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
680
|
-
is = subjectValue.some(v => resourceValue.includes(v));
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
// not in
|
|
684
|
-
if (AbilityCondition.not_in.isEqual(this.condition)) {
|
|
685
|
-
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
686
|
-
is = !resourceValue.includes(subjectValue);
|
|
687
|
-
}
|
|
688
|
-
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
689
|
-
is = !subjectValue.some(v => resourceValue.includes(v));
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
// contains
|
|
693
|
-
if (AbilityCondition.contains.isEqual(this.condition)) {
|
|
694
|
-
// array contains value
|
|
695
|
-
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
696
|
-
is = subjectValue.includes(resourceValue);
|
|
697
|
-
}
|
|
698
|
-
// array intersects array
|
|
699
|
-
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
700
|
-
is = subjectValue.some(v => resourceValue.includes(v));
|
|
659
|
+
isPrimitive(v) {
|
|
660
|
+
return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
|
|
661
|
+
}
|
|
662
|
+
isNumber(v) {
|
|
663
|
+
return typeof v === 'number';
|
|
664
|
+
}
|
|
665
|
+
isString(v) {
|
|
666
|
+
return typeof v === 'string';
|
|
667
|
+
}
|
|
668
|
+
valueLen = (v) => this.isString(v) || Array.isArray(v) ? v.length : null;
|
|
669
|
+
operatorHandlers = {
|
|
670
|
+
[AbilityCondition.always.literal]: () => true,
|
|
671
|
+
[AbilityCondition.never.literal]: () => false,
|
|
672
|
+
[AbilityCondition.equals.literal]: (a, b) => a === b,
|
|
673
|
+
[AbilityCondition.not_equals.literal]: (a, b) => a !== b,
|
|
674
|
+
[AbilityCondition.contains.literal]: (a, b) => {
|
|
675
|
+
if (Array.isArray(a) && this.isPrimitive(b)) {
|
|
676
|
+
return a.includes(b);
|
|
701
677
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
if (AbilityCondition.not_contains.isEqual(this.condition)) {
|
|
705
|
-
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
706
|
-
is = !subjectValue.includes(resourceValue);
|
|
678
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
679
|
+
return a.some(v => b.includes(v));
|
|
707
680
|
}
|
|
708
|
-
|
|
709
|
-
|
|
681
|
+
return false;
|
|
682
|
+
},
|
|
683
|
+
[AbilityCondition.not_contains.literal]: (a, b) => {
|
|
684
|
+
if (Array.isArray(a) && this.isPrimitive(b)) {
|
|
685
|
+
return !a.includes(b);
|
|
710
686
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (AbilityCondition.length_equals.isEqual(this.condition)) {
|
|
714
|
-
// foo.bar == n
|
|
715
|
-
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
716
|
-
is = String(subjectValue).length === resourceValue;
|
|
687
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
688
|
+
return !a.some(v => b.includes(v));
|
|
717
689
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
690
|
+
return false;
|
|
691
|
+
},
|
|
692
|
+
[AbilityCondition.in.literal]: (a, b) => {
|
|
693
|
+
if (this.isPrimitive(a) && Array.isArray(b)) {
|
|
694
|
+
return b.includes(a);
|
|
721
695
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
is = subjectValue.length === resourceValue.length;
|
|
696
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
697
|
+
return a.some(v => b.includes(v));
|
|
725
698
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
699
|
+
return false;
|
|
700
|
+
},
|
|
701
|
+
[AbilityCondition.not_in.literal]: (a, b) => {
|
|
702
|
+
if (this.isPrimitive(a) && Array.isArray(b)) {
|
|
703
|
+
return !b.includes(a);
|
|
729
704
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (AbilityCondition.length_greater_than.isEqual(this.condition)) {
|
|
733
|
-
// foo.bar > n
|
|
734
|
-
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
735
|
-
is = String(subjectValue).length > resourceValue;
|
|
705
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
706
|
+
return !a.some(v => b.includes(v));
|
|
736
707
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
708
|
+
return false;
|
|
709
|
+
},
|
|
710
|
+
[AbilityCondition.greater_than.literal]: (a, b) => {
|
|
711
|
+
return this.isNumber(a) && this.isNumber(b) ? a > b : false;
|
|
712
|
+
},
|
|
713
|
+
[AbilityCondition.less_than.literal]: (a, b) => {
|
|
714
|
+
return this.isNumber(a) && this.isNumber(b) ? a < b : false;
|
|
715
|
+
},
|
|
716
|
+
[AbilityCondition.greater_or_equal.literal]: (a, b) => {
|
|
717
|
+
return this.isNumber(a) && this.isNumber(b) ? a >= b : false;
|
|
718
|
+
},
|
|
719
|
+
[AbilityCondition.less_or_equal.literal]: (a, b) => {
|
|
720
|
+
return this.isNumber(a) && this.isNumber(b) ? a <= b : false;
|
|
721
|
+
},
|
|
722
|
+
[AbilityCondition.length_greater_than.literal]: (a, b) => {
|
|
723
|
+
const alen = this.valueLen(a);
|
|
724
|
+
if (alen === null) {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
if (this.isNumber(b)) {
|
|
728
|
+
return alen > b;
|
|
729
|
+
}
|
|
730
|
+
const bLen = this.valueLen(b);
|
|
731
|
+
if (bLen !== null) {
|
|
732
|
+
return alen > bLen;
|
|
740
733
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
734
|
+
return false;
|
|
735
|
+
},
|
|
736
|
+
[AbilityCondition.length_less_than.literal]: (a, b) => {
|
|
737
|
+
const alen = this.valueLen(a);
|
|
738
|
+
if (alen === null) {
|
|
739
|
+
return false;
|
|
744
740
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
is = subjectValue.length > resourceValue.length;
|
|
741
|
+
if (this.isNumber(b)) {
|
|
742
|
+
return alen < b;
|
|
748
743
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
// foo.bar < n
|
|
753
|
-
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
754
|
-
is = String(subjectValue).length < resourceValue;
|
|
744
|
+
const bLen = this.valueLen(b);
|
|
745
|
+
if (bLen !== null) {
|
|
746
|
+
return alen < bLen;
|
|
755
747
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
748
|
+
return false;
|
|
749
|
+
},
|
|
750
|
+
[AbilityCondition.length_equals.literal]: (a, b) => {
|
|
751
|
+
const alen = this.valueLen(a);
|
|
752
|
+
if (alen === null) {
|
|
753
|
+
return false;
|
|
759
754
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
is = subjectValue.length < resourceValue.length;
|
|
755
|
+
if (this.isNumber(b)) {
|
|
756
|
+
return alen === b;
|
|
763
757
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
758
|
+
const bLen = this.valueLen(b);
|
|
759
|
+
if (bLen !== null) {
|
|
760
|
+
return alen === bLen;
|
|
767
761
|
}
|
|
768
|
-
|
|
769
|
-
|
|
762
|
+
return false;
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
/**
|
|
766
|
+
* Check if the rule is matched
|
|
767
|
+
* @param resource - The resource to check
|
|
768
|
+
* @param environment
|
|
769
|
+
*/
|
|
770
|
+
check(resource, environment) {
|
|
771
|
+
const [subjectValue, resourceValue] = this.extractValues(resource, environment);
|
|
772
|
+
const handler = this.operatorHandlers[this.condition.literal];
|
|
773
|
+
const result = handler(subjectValue, resourceValue);
|
|
774
|
+
this.state = result ? AbilityMatch.match : AbilityMatch.mismatch;
|
|
770
775
|
return this.state;
|
|
771
776
|
}
|
|
772
777
|
/**
|
|
@@ -965,14 +970,14 @@ class AbilityRuleSet {
|
|
|
965
970
|
rules.forEach(rule => this.addRule(rule));
|
|
966
971
|
return this;
|
|
967
972
|
}
|
|
968
|
-
|
|
973
|
+
check(resources, environment) {
|
|
969
974
|
this.state = AbilityMatch.mismatch;
|
|
970
975
|
if (!this.rules.length) {
|
|
971
976
|
return this.state;
|
|
972
977
|
}
|
|
973
978
|
const ruleCheckStates = [];
|
|
974
979
|
for (const rule of this.rules) {
|
|
975
|
-
const state =
|
|
980
|
+
const state = rule.check(resources, environment);
|
|
976
981
|
ruleCheckStates.push(state);
|
|
977
982
|
if (AbilityCompare.and.isEqual(this.compareMethod) && AbilityMatch.mismatch.isEqual(state)) {
|
|
978
983
|
return this.state; // mismatch
|
|
@@ -1021,79 +1026,6 @@ class AbilityRuleSet {
|
|
|
1021
1026
|
}
|
|
1022
1027
|
}
|
|
1023
1028
|
|
|
1024
|
-
class AbilityInMemoryCache {
|
|
1025
|
-
store = new Map();
|
|
1026
|
-
async get(key) {
|
|
1027
|
-
const entry = this.store.get(key);
|
|
1028
|
-
if (!entry)
|
|
1029
|
-
return undefined;
|
|
1030
|
-
if (Date.now() > entry.expires) {
|
|
1031
|
-
this.store.delete(key);
|
|
1032
|
-
return undefined;
|
|
1033
|
-
}
|
|
1034
|
-
return entry.value;
|
|
1035
|
-
}
|
|
1036
|
-
async set(key, value, ttlSeconds = 60) {
|
|
1037
|
-
this.store.set(key, {
|
|
1038
|
-
value,
|
|
1039
|
-
expires: Date.now() + ttlSeconds * 1000,
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
serialize(input) {
|
|
1043
|
-
return this.fastHash(this.stableStringify(input));
|
|
1044
|
-
}
|
|
1045
|
-
async delete(key) {
|
|
1046
|
-
this.store.delete(key);
|
|
1047
|
-
}
|
|
1048
|
-
async clear() {
|
|
1049
|
-
this.store.clear();
|
|
1050
|
-
}
|
|
1051
|
-
async deleteByPrefix(prefix) {
|
|
1052
|
-
for (const key of this.store.keys()) {
|
|
1053
|
-
if (key.startsWith(prefix)) {
|
|
1054
|
-
this.store.delete(key);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
fastHash(str) {
|
|
1059
|
-
let hash = 5381;
|
|
1060
|
-
for (let i = 0; i < str.length; i++) {
|
|
1061
|
-
hash = (hash * 33) ^ str.charCodeAt(i);
|
|
1062
|
-
}
|
|
1063
|
-
return (hash >>> 0).toString(36);
|
|
1064
|
-
}
|
|
1065
|
-
stableStringify(obj) {
|
|
1066
|
-
if (obj === null)
|
|
1067
|
-
return 'null';
|
|
1068
|
-
const type = typeof obj;
|
|
1069
|
-
if (type === 'string')
|
|
1070
|
-
return JSON.stringify(obj);
|
|
1071
|
-
if (type === 'number' || type === 'boolean')
|
|
1072
|
-
return String(obj);
|
|
1073
|
-
if (type === 'undefined')
|
|
1074
|
-
return 'undefined';
|
|
1075
|
-
if (Array.isArray(obj)) {
|
|
1076
|
-
let out = '[';
|
|
1077
|
-
for (let i = 0; i < obj.length; i++) {
|
|
1078
|
-
if (i > 0)
|
|
1079
|
-
out += ',';
|
|
1080
|
-
out += this.stableStringify(obj[i]);
|
|
1081
|
-
}
|
|
1082
|
-
return out + ']';
|
|
1083
|
-
}
|
|
1084
|
-
const keys = Object.keys(obj);
|
|
1085
|
-
keys.sort();
|
|
1086
|
-
let out = '{';
|
|
1087
|
-
for (let i = 0; i < keys.length; i++) {
|
|
1088
|
-
const k = keys[i];
|
|
1089
|
-
if (i > 0)
|
|
1090
|
-
out += ',';
|
|
1091
|
-
out += k + ':' + this.stableStringify(obj[k]);
|
|
1092
|
-
}
|
|
1093
|
-
return out + '}';
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
1029
|
class AbilityJSONParser {
|
|
1098
1030
|
/**
|
|
1099
1031
|
* Parses an array of policy configurations into an array of AbilityPolicy instances.
|
|
@@ -2171,9 +2103,7 @@ class AbilityDSLParser {
|
|
|
2171
2103
|
return best;
|
|
2172
2104
|
}
|
|
2173
2105
|
levenshteinDistance(a, b) {
|
|
2174
|
-
const matrix = Array(b.length + 1)
|
|
2175
|
-
.fill(null)
|
|
2176
|
-
.map(() => Array(a.length + 1).fill(null));
|
|
2106
|
+
const matrix = Array.from({ length: b.length + 1 }, () => Array.from({ length: a.length + 1 }, () => 0));
|
|
2177
2107
|
for (let i = 0; i <= a.length; i++)
|
|
2178
2108
|
matrix[0][i] = i;
|
|
2179
2109
|
for (let j = 0; j <= b.length; j++)
|
|
@@ -2248,7 +2178,6 @@ exports.AbilityExplain = AbilityExplain;
|
|
|
2248
2178
|
exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
2249
2179
|
exports.AbilityExplainRule = AbilityExplainRule;
|
|
2250
2180
|
exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
|
|
2251
|
-
exports.AbilityInMemoryCache = AbilityInMemoryCache;
|
|
2252
2181
|
exports.AbilityJSONParser = AbilityJSONParser;
|
|
2253
2182
|
exports.AbilityMatch = AbilityMatch;
|
|
2254
2183
|
exports.AbilityParserError = AbilityParserError;
|