Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert unescape removal in DaxLexer, bump obfuscator 0.5-beta #21

Merged
merged 10 commits into from
Mar 10, 2024
Merged
4 changes: 3 additions & 1 deletion src/Dax.Vpax.Obfuscator.Common/ObfuscationDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ public sealed class ObfuscationDictionary
private readonly Dictionary<string, ObfuscationText> _obfuscated;

[JsonConstructor]
public ObfuscationDictionary(string id, ObfuscationText[] texts)
public ObfuscationDictionary(string id, string version, ObfuscationText[] texts)
{
if (id == null || !Guid.TryParseExact(id, "D", out var guid) || guid == Guid.Empty)
throw new ArgumentException("The dictionary identifier is not valid.", nameof(id));

Id = id;
Version = version ?? throw new ArgumentNullException(nameof(version));
Texts = texts.OrderBy((t) => t.Value).ToArray();

// Create dictionaries to allow for fast lookups. This also ensures uniqueness of the keys by throwing if there are duplicates.
Expand All @@ -23,6 +24,7 @@ public ObfuscationDictionary(string id, ObfuscationText[] texts)
}

public string Id { get; }
public string Version { get; }
public IReadOnlyList<ObfuscationText> Texts { get; }

public string GetValue(string obfuscated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal string DeobfuscateExpression(string expression)
case DaxToken.DELIMITED_COMMENT:
tokenText = _dictionary.GetValue(tokenText);
break;
case DaxToken.COLUMN_OR_MEASURE when token.IsReservedTokenName():
case DaxToken.COLUMN_OR_MEASURE when token.IsReservedTokenOrKeyword():
tokenText = token.Replace(expression, tokenText);
break;
case DaxToken.TABLE_OR_VARIABLE when token.IsVariable():
Expand All @@ -46,15 +46,17 @@ internal string DeobfuscateExpression(string expression)
case DaxToken.STRING_LITERAL:
case DaxToken.UNTERMINATED_STRING:
{
if (token.Text.TryGetTableAndColumnNames(out var table, out var column))
if (token.IsStringLiteral() && token.Text.TryGetTableAndColumnNames(out var table, out var column))
{
var value = DeobfuscateTableAndColumnNames(table, column, token).EscapeDax(token.Type);
var value = DeobfuscateTableAndColumnNames(table, column);
tokenText = token.Replace(expression, value);
}
else
{
var value = _dictionary.GetValue(tokenText).EscapeDax(token.Type);
if (token.IsStringOrTableOrColumnOrMeasure()) value = value.UnescapeDax(DaxToken.COLUMN_OR_MEASURE);
var value = _dictionary.GetValue(tokenText);
if (token.IsColumnOrMeasure()) value = value.EscapeDax(DaxToken.COLUMN_OR_MEASURE);
if (token.IsStringLiteral()) value = value.EscapeDax(DaxToken.STRING_LITERAL);
if (token.IsTable()) value = value.EscapeDax(DaxToken.TABLE);
tokenText = token.Replace(expression, value);
}
}
Expand All @@ -67,20 +69,18 @@ internal string DeobfuscateExpression(string expression)
return builder.ToString();
}

internal string DeobfuscateTableAndColumnNames(string table, string column, DaxToken? token = null)
internal string DeobfuscateTableAndColumnNames(string table, string column)
{
if (token.IsString() && table.TryGetTableAndColumnNames(out var tableTable, out var tableColumn))
{
var tableTableName = _dictionary.GetValue(tableTable);
var tableColumnName = _dictionary.GetValue(tableColumn);
var columnName = _dictionary.GetValue(column);
return $"'{tableTableName}[{tableColumnName}]'[{columnName}]";
}
else
{
var tableName = _dictionary.GetValue(table);
var columnName = _dictionary.GetValue(column);
return $"{tableName}[{columnName}]";
}
var quoted = table.IsQuoted();
if (quoted) table = table.Unquote();

var tableName = _dictionary.GetValue(table);
var columnName = _dictionary.GetValue(column);

tableName = tableName.EscapeDax(DaxToken.TABLE);
columnName = columnName.EscapeDax(DaxToken.COLUMN_OR_MEASURE);

if (quoted) tableName = $"'{tableName}'";
return $"{tableName}[{columnName}]".EscapeDax(DaxToken.STRING_LITERAL);
}
}
13 changes: 2 additions & 11 deletions src/Dax.Vpax.Obfuscator/DaxModelDeobfuscator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Dax.Metadata;
using Dax.Tokenizer;
using Dax.Vpax.Obfuscator.Common;
using Dax.Vpax.Obfuscator.Extensions;

Expand Down Expand Up @@ -114,16 +113,8 @@ private void Deobfuscate(DaxName name)
{
if (string.IsNullOrWhiteSpace(name?.Name)) return;

if (name!.Name.TryGetTableAndColumnNames(out var table, out var column))
{
var value = DeobfuscateTableAndColumnNames(table, column);
name.Name = value;
}
else
{
var value = _dictionary.GetValue(name!.Name);
name.Name = value.UnescapeDax(DaxToken.COLUMN_OR_MEASURE);
}
var value = _dictionary.GetValue(name!.Name);
name.Name = value;
}

private void Deobfuscate(DaxNote note)
Expand Down
34 changes: 12 additions & 22 deletions src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal string ObfuscateExpression(string expression)
case DaxToken.DELIMITED_COMMENT:
tokenText = ObfuscateText(new DaxText(tokenText)).ObfuscatedValue;
break;
case DaxToken.COLUMN_OR_MEASURE when token.IsReservedTokenName():
case DaxToken.COLUMN_OR_MEASURE when token.IsReservedTokenOrKeyword():
tokenText = token.Replace(expression, tokenText);
break;
case DaxToken.TABLE_OR_VARIABLE when token.IsVariable():
Expand All @@ -46,14 +46,13 @@ internal string ObfuscateExpression(string expression)
case DaxToken.STRING_LITERAL:
case DaxToken.UNTERMINATED_STRING:
{
if (token.Text.TryGetTableAndColumnNames(out var table, out var column))
if (token.IsStringLiteral() && token.Text.TryGetTableAndColumnNames(out var table, out var column))
{
var value = ObfuscateTableAndColumnNames(table, column, token);
var value = ObfuscateTableAndColumnNames(table, column);
tokenText = token.Replace(expression, value);
}
else
{
if (token.IsStringOrTableOrColumnOrMeasure()) tokenText = tokenText.EscapeDax(DaxToken.COLUMN_OR_MEASURE);
var value = ObfuscateText(new DaxText(tokenText)).ObfuscatedValue;
tokenText = token.Replace(expression, value);
}
Expand All @@ -67,27 +66,18 @@ internal string ObfuscateExpression(string expression)
return builder.ToString();
}

internal string ObfuscateTableAndColumnNames(string table, string column, DaxToken? token = null)
internal string ObfuscateTableAndColumnNames(string table, string column)
{
var isStringToken = token.IsString();
if (isStringToken && table.TryGetTableAndColumnNames(out var tableTable, out var tableColumn))
{
var tableTableName = ObfuscateText(new DaxText(tableTable)).ObfuscatedValue;
var tableColumnName = ObfuscateText(new DaxText(tableColumn)).ObfuscatedValue;
var columnName = ObfuscateText(new DaxText(column)).ObfuscatedValue;
table = table.UnescapeDax(DaxToken.TABLE).Trim();
column = column.UnescapeDax(DaxToken.COLUMN_OR_MEASURE);

return $"'{tableTableName}[{tableColumnName}]'[{columnName}]";
}
else
{
if (isStringToken) table = table.Trim();
var quoted = table.IsQuoted();
if (quoted) table = table.Unquote();

var tableName = ObfuscateText(new DaxText(table)).ObfuscatedValue;
var columnName = ObfuscateText(new DaxText(column)).ObfuscatedValue;
var tableName = ObfuscateText(new DaxText(table)).ObfuscatedValue;
var columnName = ObfuscateText(new DaxText(column)).ObfuscatedValue;

return token.IsColumnOrMeasure()
? $"{tableName}[{columnName}]]" // escape the closing bracket
: $"{tableName}[{columnName}]";
}
if (quoted) tableName = $"'{tableName}'";
return $"{tableName}[{columnName}]";
}
}
25 changes: 7 additions & 18 deletions src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Reflection;
using Dax.Metadata;
using Dax.Tokenizer;
using Dax.Metadata;
using Dax.Vpax.Obfuscator.Common;
using Dax.Vpax.Obfuscator.Extensions;

Expand All @@ -20,7 +18,7 @@ public DaxModelObfuscator(Model model, ObfuscationDictionary? dictionary = null)
_model = model;
_model.ObfuscatorDictionaryId = dictionary != null ? dictionary.Id : Guid.NewGuid().ToString("D");
_model.ObfuscatorLib = "Dax.Vpax.Obfuscator"; // hard-coded
_model.ObfuscatorLibVersion = GetType().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? throw new InvalidOperationException("The assembly informational version is not available.");
_model.ObfuscatorLibVersion = VpaxObfuscator.Version;
_obfuscator = new DaxTextObfuscator();
_texts = new DaxTextCollection(dictionary);
}
Expand All @@ -39,8 +37,10 @@ public ObfuscationDictionary Obfuscate()
_model.Relationships.ForEach(Obfuscate);
_model.Roles.ForEach(Obfuscate);

var id = _model.ObfuscatorDictionaryId;
var version = VpaxObfuscator.Version;
var texts = Texts.Select((t) => t.ToObfuscationText()).ToArray();
return new ObfuscationDictionary(id: _model.ObfuscatorDictionaryId, texts);
return new ObfuscationDictionary(id, version, texts);
}

private void ObfuscateIdentifiers(Table table)
Expand Down Expand Up @@ -162,19 +162,8 @@ private void Obfuscate(TablePermission tablePermission)
{
if (string.IsNullOrWhiteSpace(name?.Name)) return null;

if (name!.Name.TryGetTableAndColumnNames(out var table, out var column))
{
var value = ObfuscateTableAndColumnNames(table, column);
name.Name = value;
}
else
{
var value = name!.Name.EscapeDax(DaxToken.COLUMN_OR_MEASURE);
var text = ObfuscateText(new DaxText(value));
name.Name = text.ObfuscatedValue;
}

return name.Name;
var text = ObfuscateText(new DaxText(name!.Name));
return name.Name = text.ObfuscatedValue;
}

private void Obfuscate(DaxNote note)
Expand Down
20 changes: 9 additions & 11 deletions src/Dax.Vpax.Obfuscator/DaxTextObfuscator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ internal sealed class DaxTextObfuscator
internal const string AlphaCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
internal const string CharSet = /**/ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // ANSI alphanumeric character range
internal const int RetryLimitBeforeExtension = 26; // A..Z
/// <summary>
/// CALENDAR() [Date] extension column.
/// </summary>
internal const string ReservedToken_Date = "Date";
/// <summary>
/// ''[Value] or table constructor { } extension columns
/// </summary>
internal const string ReservedToken_Value = "Value";

/// <summary>
/// A salt combined with the plaintext hashcode to ensure that a different obfuscated string is generated for each program execution
Expand Down Expand Up @@ -70,7 +78,7 @@ private static bool IsReservedChar(char @char)

switch (@char)
{
case ReservedChar_Minus: // single-line comment char
case '-': // single-line comment char
case '/': // multi-line comment char
case '*': // multi-line comment char
case '\n': // line feed char e.g. in multi-line comments
Expand All @@ -86,14 +94,4 @@ private static bool IsReservedChar(char @char)
ReservedToken_Date,
ReservedToken_Value
};

internal const char ReservedChar_Minus = '-';
/// <summary>
/// CALENDAR() [Date] extension column.
/// </summary>
internal const string ReservedToken_Date = "Date";
/// <summary>
/// ''[Value] or table constructor { } extension columns
/// </summary>
internal const string ReservedToken_Value = "Value";
}
5 changes: 2 additions & 3 deletions src/Dax.Vpax.Obfuscator/Extensions/DaxTokenExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ namespace Dax.Vpax.Obfuscator.Extensions;

internal static class DaxTokenExtensions
{
public static bool IsString(this DaxToken? token) => token?.Type == DaxToken.STRING_LITERAL || token?.Type == DaxToken.UNTERMINATED_STRING;
public static bool IsStringLiteral(this DaxToken? token) => token?.Type == DaxToken.STRING_LITERAL || token?.Type == DaxToken.UNTERMINATED_STRING;
public static bool IsTable(this DaxToken? token) => token?.Type == DaxToken.TABLE || (token?.Type == DaxToken.TABLE_OR_VARIABLE && !IsVariable(token));
public static bool IsColumnOrMeasure(this DaxToken? token) => token?.Type == DaxToken.COLUMN_OR_MEASURE || token?.Type == DaxToken.UNTERMINATED_COLREF;
public static bool IsStringOrTableOrColumnOrMeasure(this DaxToken? token) => IsString(token) || IsTable(token) || IsColumnOrMeasure(token);

public static bool IsVariable(this DaxToken token)
{
Expand All @@ -27,7 +26,7 @@ public static bool IsFunction(this DaxToken token)
return current != null && current.Type == DaxToken.OPEN_PARENS;
}

public static bool IsReservedTokenName(this DaxToken token)
public static bool IsReservedTokenOrKeyword(this DaxToken token)
{
Debug.Assert(token.Type == DaxToken.COLUMN_OR_MEASURE);

Expand Down
6 changes: 1 addition & 5 deletions src/Dax.Vpax.Obfuscator/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ public static string Unbracket(this string value)

public static bool TryGetTableAndColumnNames(this string value, out string table, out string column)
{
//value = value.UnescapeDax(DaxToken.STRING_LITERAL);

var endsWithBracket = value.TrimEnd().EndsWith("]");
if (!endsWithBracket) goto unqualified_column_name;

Expand All @@ -33,7 +31,7 @@ public static bool TryGetTableAndColumnNames(this string value, out string table
var quoteOpenIndex = value.IndexOf('\'');
var quoteCloseIndex = value.Replace("''", "__").IndexOf('\'', quoteOpenIndex + 1);
if (quoteCloseIndex == -1) goto unqualified_column_name;
table = value.Substring(0, quoteCloseIndex + 1).Trim().Unquote();
table = value.Substring(0, quoteCloseIndex + 1).Trim();
column = value.Substring(quoteCloseIndex + 1);

var columnTrimmed = column.Trim();
Expand All @@ -52,8 +50,6 @@ public static bool TryGetTableAndColumnNames(this string value, out string table
if (IsInvalidUnquotedTable(table)) goto unqualified_column_name;
}

//table = table.UnescapeDax(DaxToken.TABLE);
//column = column.UnescapeDax(DaxToken.COLUMN_OR_MEASURE);
return true;

unqualified_column_name:
Expand Down
2 changes: 2 additions & 0 deletions src/Dax.Vpax.Obfuscator/VpaxObfuscator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Dax.Vpax.Obfuscator;

public sealed class VpaxObfuscator : IVpaxObfuscator
{
public static string Version { get; } = ThisAssembly.AssemblyInformationalVersion;

public ObfuscationDictionary Obfuscate(Stream stream) => ObfuscateImpl(stream, dictionary: null);
public ObfuscationDictionary Obfuscate(Model model) => ObfuscateImpl(model, dictionary: null);
public ObfuscationDictionary Obfuscate(Stream stream, ObfuscationDictionary dictionary) => ObfuscateImpl(stream, dictionary ?? throw new ArgumentNullException(nameof(dictionary)));
Expand Down
2 changes: 1 addition & 1 deletion src/Dax.Vpax.Obfuscator/version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "0.4-beta",
"version": "0.5-beta",
"nugetPackageVersion": {
"semVer": 2.0
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ public class ObfuscationDictionaryTests
[Fact]
public void ctor_EmptyTexts_ReturnsEmptyDictionary()
{
var dictionary = new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), texts: []);
var dictionary = new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), "0.0.0-test", texts: []);
Assert.Empty(dictionary.Texts);
}

[Fact]
public void ctor_EmptyId_Throws()
{
var exception = Assert.Throws<ArgumentException>(() => new ObfuscationDictionary(id: Guid.Empty.ToString("D"), texts: []));
var exception = Assert.Throws<ArgumentException>(() => new ObfuscationDictionary(id: Guid.Empty.ToString("D"), "0.0.0-test", texts: []));
Assert.StartsWith("The dictionary identifier is not valid.", exception.Message);
}

Expand All @@ -27,7 +27,7 @@ public void ctor_DuplicateTexts_Throws()
new ObfuscationText("VALUE", "XXXXXX"),
};

var exception = Assert.Throws<ArgumentException>(() => new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), texts));
var exception = Assert.Throws<ArgumentException>(() => new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), "0.0.0-test", texts));
Assert.StartsWith("An item with the same key has already been added.", exception.Message);
}

Expand Down Expand Up @@ -128,6 +128,6 @@ private ObfuscationDictionary CreateTestDictionary()

private ObfuscationDictionary CreateDictionary(ObfuscationText[] texts)
{
return new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), texts);
return new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), "0.0.0-test", texts);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,13 @@ public void DeobfuscateExpression_TableNameMultipleReferencesWithDifferentCasing
[Fact]
public void DeobfuscateExpression_ColumnName_ReturnsDeobfuscatedValuePreservingSquareBracketEscapeChar()
{
var expression = "RELATED( XXXXX[YYYY[Z]]] )";
var expression = "RELATED( XXXXX[YYYYYYY] )";
var expected = "RELATED( Sales[Rate[%]]] )";

var (_, _, deobfuscator) = CreateTest(
[
new ObfuscationText("Sales", "XXXXX"),
new ObfuscationText("Rate", "YYYY"),
new ObfuscationText("%", "Z")
new ObfuscationText("Rate[%]", "YYYYYYY"),
]);
var actual = deobfuscator.DeobfuscateExpression(expression);

Expand Down Expand Up @@ -183,7 +182,7 @@ public void DeobfuscateExpression_StringLiteralWithEscapedQuotationMark_IsObfusc

private (Model model, ObfuscationDictionary dictionary, DaxModelDeobfuscator deobfuscator) CreateTest(ObfuscationText[] texts)
{
var dictionary = new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), texts);
var dictionary = new ObfuscationDictionary(id: Guid.NewGuid().ToString("D"), "0.0.0-test", texts);
var model = new Model
{
ObfuscatorDictionaryId = dictionary.Id,
Expand Down
Loading
Loading