From ecac8bfb6c62c0441b1f25e764ade2aca3a3ba32 Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Tue, 5 Mar 2024 18:02:20 +0100 Subject: [PATCH] Refactor DaxText to immutable (#18) --- .../DaxModelObfuscator.ObfuscateText.cs | 12 ++++----- src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs | 7 +++--- src/Dax.Vpax.Obfuscator/DaxText.cs | 2 +- src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs | 25 +++++++++---------- .../DaxTextCollectionTests.cs | 13 ++++------ 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateText.cs b/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateText.cs index ecc18e2..f03f37f 100644 --- a/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateText.cs +++ b/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateText.cs @@ -7,19 +7,19 @@ internal DaxText ObfuscateText(DaxText text) if (Texts.TryGet(text, out var obfuscatedText)) return obfuscatedText ?? throw new InvalidOperationException($"Obfuscated text is null. {text.Value}"); - _ = _obfuscator.Obfuscate(text); + obfuscatedText = _obfuscator.Obfuscate(text); var retryLimit = DaxTextObfuscator.RetryLimitBeforeExtension + 100; // retry with the same length otherwise extend the obfuscated string length up to 100 characters var retryCount = 0; while (retryCount < retryLimit && IsRetryNeeded()) - _ = _obfuscator.Obfuscate(text, ++retryCount); + obfuscatedText = _obfuscator.Obfuscate(obfuscatedText, ++retryCount); if (retryCount >= retryLimit) - throw new InvalidOperationException($"Failed to obfuscate text. {text.Value} | {text.ObfuscatedValue}"); + throw new InvalidOperationException($"Failed to obfuscate text. {obfuscatedText.Value} | {obfuscatedText.ObfuscatedValue}"); - Texts.Add(text); // << throws in case of unresolved collision (duplicate value/obfuscated value) - return text; + Texts.Add(obfuscatedText); // << throws in case of unresolved collision (duplicate value/obfuscated value) + return obfuscatedText; - bool IsRetryNeeded() => text.IsObfuscatedAsDaxKeyword || Texts.Contains(text); + bool IsRetryNeeded() => obfuscatedText.IsObfuscatedAsDaxKeyword || Texts.Contains(obfuscatedText); } } diff --git a/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs b/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs index 4291b53..442adbd 100644 --- a/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs +++ b/src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs @@ -65,10 +65,11 @@ void CreateKpiMeasure(DaxExpression kpi, string type) { if (string.IsNullOrWhiteSpace(kpi?.Expression)) return; - var text = new DaxText($"_{name} {type}"); - text.ObfuscatedValue = $"_{obfuscatedName} {type}"; + var value = $"_{name} {type}"; + var obfuscatedValue = $"_{obfuscatedName} {type}"; + var text = new DaxText(value, obfuscatedValue); - // It may already exist in case of incremental obfuscation + // Only add the KPI measure if it does not exist. Can happen in case of incremental obfuscation if (Texts.IsIncrementalObfuscation && Texts.Contains(text)) return; diff --git a/src/Dax.Vpax.Obfuscator/DaxText.cs b/src/Dax.Vpax.Obfuscator/DaxText.cs index 4019f96..874ac29 100644 --- a/src/Dax.Vpax.Obfuscator/DaxText.cs +++ b/src/Dax.Vpax.Obfuscator/DaxText.cs @@ -25,7 +25,7 @@ public DaxText(string value) } public string Value { get; } - public string ObfuscatedValue { get; internal set; } + public string ObfuscatedValue { get; } public bool IsObfuscatedAsDaxKeyword => DaxTextObfuscator.DaxKeywords.Contains(ObfuscatedValue ?? throw new InvalidOperationException("ObfuscatedValue is null")); public override string ToString() => string.Format(CultureInfo.InvariantCulture, "{0} | {1}", Value, ObfuscatedValue); diff --git a/src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs b/src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs index 857d245..4a61274 100644 --- a/src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs +++ b/src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs @@ -27,10 +27,8 @@ public DaxText Obfuscate(DaxText text, int retryCount = 0) if (text.ObfuscatedValue == null && retryCount != 0) throw new InvalidOperationException("Text has not been obfuscated yet."); // Skip obfuscation for reserved tokens and empty or whitespace strings - if (ReservedTokens.Contains(text.Value) || string.IsNullOrWhiteSpace(text.Value)) { - text.ObfuscatedValue = text.Value; - return text; - } + if (ReservedTokens.Contains(text.Value) || string.IsNullOrWhiteSpace(text.Value)) + return new DaxText(text.Value, text.Value); // ObfuscatedValue is the same as Value var plaintext = text.Value; var salt = retryCount != 0 ? _random.Next() : 0; // An additional salt used during retries to avoid generating the same obfuscated 'base' string when extending the string length @@ -39,14 +37,16 @@ public DaxText Obfuscate(DaxText text, int retryCount = 0) var retryLenght = Math.Max(0, retryCount - RetryLimitBeforeExtension); var obfuscated = new char[plaintext.Length + retryLenght]; - for (var i = 0; i < obfuscated.Length; i++) { - // Skip reserved chars - if (i < plaintext.Length && IsReservedChar(plaintext[i])) { - obfuscated[i] = plaintext[i]; + for (var i = 0; i < obfuscated.Length; i++) + { + if (i < plaintext.Length && IsReservedChar(plaintext[i])) + { + obfuscated[i] = plaintext[i]; // Do not obfuscate reserved characters continue; } - // Always use an alphabetic char for the first char to avoid generating invalid DAX identifiers - if (i == 0) { + + if (i == 0) // Always use an alphabetic char for the first char to avoid generating invalid DAX identifiers + { obfuscated[i] = AlphaCharSet[random.Next(AlphaCharSet.Length)]; continue; } @@ -55,10 +55,9 @@ public DaxText Obfuscate(DaxText text, int retryCount = 0) } var obfuscatedValue = new string(obfuscated); - Debug.WriteLineIf(condition: retryCount > 0, $"\t>> Retry {retryCount} for: {text.Value} | {text.ObfuscatedValue} > {obfuscatedValue}"); + Debug.WriteLineIf(retryCount > 0, $"\t>> Retry {retryCount} for: {text.Value} | {text.ObfuscatedValue} > {obfuscatedValue}"); - text.ObfuscatedValue = obfuscatedValue; - return text; + return new DaxText(text.Value, obfuscatedValue); } private static bool IsReservedChar(char @char) diff --git a/tests/Dax.Vpax.Obfuscator.Tests/DaxTextCollectionTests.cs b/tests/Dax.Vpax.Obfuscator.Tests/DaxTextCollectionTests.cs index 02f3671..fb005b2 100644 --- a/tests/Dax.Vpax.Obfuscator.Tests/DaxTextCollectionTests.cs +++ b/tests/Dax.Vpax.Obfuscator.Tests/DaxTextCollectionTests.cs @@ -10,8 +10,8 @@ public class DaxTextCollectionTests [InlineData("a", "a")] public void Add_DuplicateValue_Throws(string value1, string value2) { - var text1 = new DaxText(value1); text1.ObfuscatedValue = value1; - var text2 = new DaxText(value2); text2.ObfuscatedValue = value2; + var text1 = new DaxText(value1, value1); + var text2 = new DaxText(value2, value2); var texts = new DaxTextCollection(); texts.Add(text1); @@ -32,7 +32,7 @@ public void Add_DuplicateValue_Throws__DifferentLengthsTest(int count) var dictionary = new DaxTextCollection(); foreach (var value in values) { - var text = new DaxText(value); text.ObfuscatedValue = value; + var text = new DaxText(value, value); dictionary.Add(text); // Seed the result dictionary Assert.Throws(() => dictionary.Add(text)); } @@ -44,11 +44,8 @@ public void Add_DuplicateValue_Throws__DifferentLengthsTest(int count) [InlineData("a", "a")] public void Add_DuplicateObfuscatedValue_Throws(string obfuscatedValue1, string obfuscatedValue2) { - var text1 = new DaxText("X"); - var text2 = new DaxText("Y"); - - text1.ObfuscatedValue = obfuscatedValue1; - text2.ObfuscatedValue = obfuscatedValue2; + var text1 = new DaxText("X", obfuscatedValue1); + var text2 = new DaxText("Y", obfuscatedValue2); var texts = new DaxTextCollection(); texts.Add(text1);