|
1 | 1 | /* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
| -import { Condition, OperatorType, IRule } from './dto/rule-dto'; |
| 2 | +import { |
| 3 | + valid as validSemver, |
| 4 | + gt as semverGt, |
| 5 | + lt as semverLt, |
| 6 | + gte as semverGte, |
| 7 | + lte as semverLte, |
| 8 | +} from 'semver'; |
| 9 | + |
| 10 | +import { Condition, OperatorType, IRule, OperatorValueType } from './dto/rule-dto'; |
3 | 11 | import { decodeBase64, getMD5Hash } from './obfuscation';
|
4 | 12 |
|
5 | 13 | export function findMatchingRule(
|
@@ -42,15 +50,30 @@ function evaluateRuleConditions(
|
42 | 50 |
|
43 | 51 | function evaluateCondition(subjectAttributes: Record<string, any>, condition: Condition): boolean {
|
44 | 52 | const value = subjectAttributes[condition.attribute];
|
| 53 | + |
| 54 | + const conditionValueType = targetingRuleConditionValuesTypesFromValues(condition.value); |
| 55 | + |
45 | 56 | if (value != null) {
|
46 | 57 | switch (condition.operator) {
|
47 | 58 | case OperatorType.GTE:
|
| 59 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 60 | + return compareSemVer(value, condition.value, semverGte); |
| 61 | + } |
48 | 62 | return compareNumber(value, condition.value, (a, b) => a >= b);
|
49 | 63 | case OperatorType.GT:
|
| 64 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 65 | + return compareSemVer(value, condition.value, semverGt); |
| 66 | + } |
50 | 67 | return compareNumber(value, condition.value, (a, b) => a > b);
|
51 | 68 | case OperatorType.LTE:
|
| 69 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 70 | + return compareSemVer(value, condition.value, semverLte); |
| 71 | + } |
52 | 72 | return compareNumber(value, condition.value, (a, b) => a <= b);
|
53 | 73 | case OperatorType.LT:
|
| 74 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 75 | + return compareSemVer(value, condition.value, semverLt); |
| 76 | + } |
54 | 77 | return compareNumber(value, condition.value, (a, b) => a < b);
|
55 | 78 | case OperatorType.MATCHES:
|
56 | 79 | return new RegExp(condition.value as string).test(value as string);
|
@@ -78,15 +101,29 @@ function evaluateObfuscatedCondition(
|
78 | 101 | {},
|
79 | 102 | );
|
80 | 103 | const value = hashedSubjectAttributes[condition.attribute];
|
| 104 | + const conditionValueType = targetingRuleConditionValuesTypesFromValues(value); |
| 105 | + |
81 | 106 | if (value != null) {
|
82 | 107 | switch (condition.operator) {
|
83 | 108 | case getMD5Hash(OperatorType.GTE):
|
| 109 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 110 | + return compareSemVer(value, decodeBase64(condition.value), semverGte); |
| 111 | + } |
84 | 112 | return compareNumber(value, Number(decodeBase64(condition.value)), (a, b) => a >= b);
|
85 | 113 | case getMD5Hash(OperatorType.GT):
|
| 114 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 115 | + return compareSemVer(value, decodeBase64(condition.value), semverGt); |
| 116 | + } |
86 | 117 | return compareNumber(value, Number(decodeBase64(condition.value)), (a, b) => a > b);
|
87 | 118 | case getMD5Hash(OperatorType.LTE):
|
| 119 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 120 | + return compareSemVer(value, decodeBase64(condition.value), semverLte); |
| 121 | + } |
88 | 122 | return compareNumber(value, Number(decodeBase64(condition.value)), (a, b) => a <= b);
|
89 | 123 | case getMD5Hash(OperatorType.LT):
|
| 124 | + if (conditionValueType === OperatorValueType.SEM_VER) { |
| 125 | + return compareSemVer(value, decodeBase64(condition.value), semverLt); |
| 126 | + } |
90 | 127 | return compareNumber(value, Number(decodeBase64(condition.value)), (a, b) => a < b);
|
91 | 128 | case getMD5Hash(OperatorType.MATCHES):
|
92 | 129 | return new RegExp(decodeBase64(condition.value)).test(value as string);
|
@@ -115,10 +152,48 @@ function compareNumber(
|
115 | 152 | attributeValue: any,
|
116 | 153 | conditionValue: any,
|
117 | 154 | compareFn: (a: number, b: number) => boolean,
|
118 |
| -) { |
| 155 | +): boolean { |
119 | 156 | return (
|
120 | 157 | typeof attributeValue === 'number' &&
|
121 | 158 | typeof conditionValue === 'number' &&
|
122 | 159 | compareFn(attributeValue, conditionValue)
|
123 | 160 | );
|
124 | 161 | }
|
| 162 | + |
| 163 | +function compareSemVer( |
| 164 | + attributeValue: any, |
| 165 | + conditionValue: any, |
| 166 | + compareFn: (a: string, b: string) => boolean, |
| 167 | +): boolean { |
| 168 | + return ( |
| 169 | + !!validSemver(attributeValue) && |
| 170 | + !!validSemver(conditionValue) && |
| 171 | + compareFn(attributeValue, conditionValue) |
| 172 | + ); |
| 173 | +} |
| 174 | + |
| 175 | +function targetingRuleConditionValuesTypesFromValues( |
| 176 | + value: number | string | string[], |
| 177 | +): OperatorValueType { |
| 178 | + // Check if input is a number |
| 179 | + if (typeof value === 'number') { |
| 180 | + return OperatorValueType.NUMERIC; |
| 181 | + } |
| 182 | + |
| 183 | + if (Array.isArray(value)) { |
| 184 | + return OperatorValueType.STRING_ARRAY; |
| 185 | + } |
| 186 | + |
| 187 | + // Check if input is a string that represents a SemVer |
| 188 | + if (validSemver(value)) { |
| 189 | + return OperatorValueType.SEM_VER; |
| 190 | + } |
| 191 | + |
| 192 | + // Check if input is a string that represents a number |
| 193 | + if (!isNaN(Number(value))) { |
| 194 | + return OperatorValueType.NUMERIC; |
| 195 | + } |
| 196 | + |
| 197 | + // If none of the above, it's a general string |
| 198 | + return OperatorValueType.PLAIN_STRING; |
| 199 | +} |
0 commit comments