Skip to content

Commit deeb79e

Browse files
Obfuscator 1.2 - support for UDFs, Calendars, VPAX 1.11.0 (#98)
* Update DaxLexer.generated.cs * Update DaxToken.generated.cs * Bump Dax.Tokenizer version to 1.1 * Update Dax.Vpax package version to 1.11.0-preview5 Updated the version of the `Dax.Vpax` package in the `Directory.Packages.props` file from `1.9.0` to `1.11.0-preview5`. * Update unobfuscable DaxKeywords including Visual Shape keywords * Update test DDLProperty_IsNotObfuscated * Support obfuscation of new CalculationGroup properties New properties added in VPAX version 1.11.0-preview2 and 1.11.0-preview4 * FIX missing obfusction for CalculationItem.FormatStringDefinition * Add support for UDF obfuscation * Update Dax.Vpax package version to 1.11.0-preview6 * Add support for Calendar obfuscation * Update Dax.Vpax package version to 1.11.0-preview7 * Support vpax extract in TestApp * Update Dax.Vpax package version to 1.11.0 * Bump version 1.2
1 parent 392ecf0 commit deeb79e

19 files changed

Lines changed: 1256 additions & 678 deletions

src/Dax.Tokenizer/DaxLexer.generated.cs

Lines changed: 558 additions & 480 deletions
Large diffs are not rendered by default.

src/Dax.Tokenizer/DaxToken.generated.cs

Lines changed: 153 additions & 115 deletions
Large diffs are not rendered by default.

src/Dax.Tokenizer/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "1.0",
3+
"version": "1.1",
44
"nugetPackageVersion": {
55
"semVer": 2
66
},

src/Dax.Vpax.Obfuscator/Constants.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,49 @@ internal static class Constants
3838
// From DAX lexer grammar. Interval (Z__FIRSTKEYWORD__ .. Z__LASTKEYWORD__) excluding DDL keywords.
3939
"ABS",
4040
"ALPHABETICAL",
41+
"ANCHORED",
42+
"ANYREF",
43+
"ANYVAL",
4144
"BLANKS",
4245
"BOOLEAN",
4346
"BOTH",
47+
"CALENDARREF",
48+
"COLUMNREF",
49+
"COLUMNS",
4450
"CURRENCY",
4551
"DATATYPE",
4652
"DATETIME",
4753
"DAY",
54+
"DECIMAL",
55+
"DENSIFY",
4856
"DEFAULT",
4957
"DOUBLE",
58+
"EXPR",
59+
"EXTENDING",
5060
"FIRST",
5161
"HOUR",
62+
"INT64",
5263
"INTEGER",
5364
"KEEP",
5465
"LAST",
66+
"MEASUREREF",
5567
"MINUTE",
5668
"MONTH",
5769
"NONE",
70+
"NUMERIC",
5871
"ONEWAY_LEFTFILTERSRIGHT",
5972
"ONEWAY_RIGHTFILTERSLEFT",
6073
"ONEWAY",
74+
"PRECISE",
6175
"QUARTER",
6276
"REL",
77+
"ROWS",
78+
"SCALAR",
6379
"SECOND",
6480
"STRING",
81+
"TABLEREF",
6582
"TEXT",
83+
"VAL",
6684
"VARIANT",
6785
"WEEK",
6886
"YEAR",

src/Dax.Vpax.Obfuscator/DaxModelDeobfuscator.DeobfuscateExpression.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ internal string DeobfuscateExpression(string expression)
3535
{
3636
case DaxToken.SINGLE_LINE_COMMENT:
3737
case DaxToken.DELIMITED_COMMENT:
38-
tokenText = DeobfuscateText(tokenText);
38+
case DaxToken.TABLE_OR_VARIABLE when token.IsFunction() && _identifiers.IsKnownUserDefinedFunction(tokenText):
39+
case DaxToken.OTHER_IDENTIFIER when token.IsFunction() && _identifiers.IsKnownUserDefinedFunction(tokenText):
40+
{
41+
tokenText = DeobfuscateText(tokenText, ObfuscationRule.None);
42+
}
3943
break;
4044
case DaxToken.TABLE:
4145
case DaxToken.TABLE_OR_VARIABLE when token.IsVariable():

src/Dax.Vpax.Obfuscator/DaxModelDeobfuscator.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ internal partial class DaxModelDeobfuscator
88
{
99
private readonly Model _model;
1010
private readonly ObfuscationDictionary _dictionary;
11+
private readonly NamedObjectIdentifierCollection _identifiers = new();
1112

1213
public DaxModelDeobfuscator(Model model, ObfuscationDictionary dictionary)
1314
{
@@ -20,11 +21,15 @@ public DaxModelDeobfuscator(Model model, ObfuscationDictionary dictionary)
2021

2122
public void Deobfuscate()
2223
{
24+
// Deobfuscate names first to ensure that all identifiers are mapped; only applies to UDFs here
25+
_model.Functions.ForEach(DeobfuscateIdentifiers);
26+
2327
Deobfuscate(_model.ModelName);
2428
Deobfuscate(_model.ServerName);
2529
_model.Tables.ForEach(Deobfuscate);
2630
_model.Relationships.ForEach(Deobfuscate);
2731
_model.Roles.ForEach(Deobfuscate);
32+
_model.Functions.ForEach(Deobfuscate);
2833
}
2934

3035
private void Deobfuscate(Table table)
@@ -34,12 +39,41 @@ private void Deobfuscate(Table table)
3439
Deobfuscate(table.DefaultDetailRowsExpression);
3540
Deobfuscate(table.CalculationGroup);
3641
Deobfuscate(table.Description);
42+
table.Calendars.ForEach(Deobfuscate);
3743
table.Columns.ForEach(Deobfuscate);
3844
table.Measures.ForEach(Deobfuscate);
3945
table.UserHierarchies.ForEach(Deobfuscate);
4046
table.Partitions.ForEach(Deobfuscate);
4147
}
4248

49+
private void DeobfuscateIdentifiers(Function function)
50+
{
51+
_identifiers.Map(function);
52+
DeobfuscateFunctionName(function.FunctionName);
53+
}
54+
55+
private void Deobfuscate(Calendar calendar)
56+
{
57+
DeobfuscateCalendarName(calendar.CalendarName);
58+
Deobfuscate(calendar.Description);
59+
60+
foreach (var columnGroup in calendar.CalendarColumnGroups)
61+
{
62+
switch (columnGroup)
63+
{
64+
case TimeRelatedColumnGroup:
65+
// Columns reference table columns - no obfuscation needed
66+
break;
67+
case TimeUnitColumnAssociation:
68+
// PrimaryColumn and AssociatedColumns reference table columns - no obfuscation needed
69+
break;
70+
default:
71+
// Ensure an exception is thrown when new CalendarColumnGroup types are added and not handled here
72+
throw new NotSupportedException($"Unknown calendar column group type '{columnGroup.GetType().FullName}'.");
73+
}
74+
}
75+
}
76+
4377
private void Deobfuscate(Column column)
4478
{
4579
DeobfuscateColumnName(column.ColumnName);
@@ -91,10 +125,19 @@ private void Deobfuscate(CalculationGroup calculationGroup)
91125
if (calculationGroup == null)
92126
return;
93127

128+
Deobfuscate(calculationGroup.Description);
129+
Deobfuscate(calculationGroup.MultipleOrEmptySelectionExpression);
130+
Deobfuscate(calculationGroup.MultipleOrEmptySelectionExpressionDescription);
131+
Deobfuscate(calculationGroup.MultipleOrEmptySelectionFormatStringExpression);
132+
Deobfuscate(calculationGroup.NoSelectionExpression);
133+
Deobfuscate(calculationGroup.NoSelectionExpressionDescription);
134+
Deobfuscate(calculationGroup.NoSelectionFormatStringExpression);
135+
94136
foreach (var calculationItem in calculationGroup.CalculationItems)
95137
{
96138
Deobfuscate(calculationItem.ItemName);
97139
Deobfuscate(calculationItem.ItemExpression);
140+
Deobfuscate(calculationItem.FormatStringDefinition);
98141
Deobfuscate(calculationItem.Description);
99142
}
100143
}
@@ -110,9 +153,17 @@ private void Deobfuscate(TablePermission tablePermission)
110153
Deobfuscate(tablePermission.FilterExpression);
111154
}
112155

156+
private void Deobfuscate(Function function)
157+
{
158+
Deobfuscate(function.Description);
159+
Deobfuscate(function.FunctionExpression);
160+
}
161+
162+
private void DeobfuscateCalendarName(DaxName name) => Deobfuscate(name, ObfuscationRule.PreserveDaxKeywords);
113163
private void DeobfuscateTableName(DaxName name) => Deobfuscate(name, ObfuscationRule.PreserveDaxKeywords);
114164
private void DeobfuscateColumnName(DaxName name) => Deobfuscate(name, ObfuscationRule.PreserveDaxReservedNames);
115165
private void DeobfuscateMeasureName(DaxName name) => Deobfuscate(name, ObfuscationRule.PreserveDaxReservedNames);
166+
private void DeobfuscateFunctionName(DaxName name) => Deobfuscate(name, ObfuscationRule.None);
116167

117168
private void Deobfuscate(DaxName name, ObfuscationRule rule = ObfuscationRule.None)
118169
{

src/Dax.Vpax.Obfuscator/DaxModelObfuscator.ObfuscateExpression.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ internal string ObfuscateExpression(string expression)
3535
{
3636
case DaxToken.SINGLE_LINE_COMMENT:
3737
case DaxToken.DELIMITED_COMMENT:
38-
tokenText = ObfuscateText(new DaxText(tokenText));
38+
case DaxToken.TABLE_OR_VARIABLE when token.IsFunction() && _identifiers.IsKnownUserDefinedFunction(tokenText):
39+
case DaxToken.OTHER_IDENTIFIER when token.IsFunction() && _identifiers.IsKnownUserDefinedFunction(tokenText):
40+
{
41+
tokenText = ObfuscateText(new DaxText(tokenText), ObfuscationRule.None);
42+
}
3943
break;
4044
case DaxToken.TABLE:
4145
case DaxToken.TABLE_OR_VARIABLE when token.IsVariable():

src/Dax.Vpax.Obfuscator/DaxModelObfuscator.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Dax.Vpax.Obfuscator;
88
internal sealed partial class DaxModelObfuscator
99
{
1010
private readonly DaxTextObfuscator _obfuscator = new();
11+
private readonly NamedObjectIdentifierCollection _identifiers = new();
1112

1213
public DaxModelObfuscator(ObfuscationOptions options, Model model, ObfuscationDictionary dictionary)
1314
: this(options, model)
@@ -28,22 +29,24 @@ public DaxModelObfuscator(ObfuscationOptions options, Model model)
2829
Model.ObfuscatorLibVersion = VpaxObfuscator.Version;
2930
}
3031

31-
public Model Model { get; } // test only
32+
public Model Model { get; }
3233
public ObfuscationMode Mode { get; }
3334
public ObfuscationOptions Options { get; }
3435
public DaxTextCollection Texts { get; } = new();
3536
public List<string> UnobfuscatedValues { get; } = new();
3637

3738
public ObfuscationDictionary Obfuscate()
3839
{
39-
// Obfuscate and map identifiers first
40+
// Obfuscate names first to ensure that all identifiers are mapped before obfuscating expressions
4041
Model.Tables.ForEach(ObfuscateIdentifiers);
42+
Model.Functions.ForEach(ObfuscateIdentifiers);
4143

4244
Obfuscate(Model.ModelName);
4345
Obfuscate(Model.ServerName);
4446
Model.Tables.ForEach(Obfuscate);
4547
Model.Relationships.ForEach(Obfuscate);
4648
Model.Roles.ForEach(Obfuscate);
49+
Model.Functions.ForEach(Obfuscate);
4750

4851
var id = Model.ObfuscatorDictionaryId;
4952
var version = VpaxObfuscator.Version;
@@ -56,10 +59,16 @@ public ObfuscationDictionary Obfuscate()
5659
private void ObfuscateIdentifiers(Table table)
5760
{
5861
ObfuscateTableName(table.TableName);
62+
table.Calendars.ForEach(ObfuscateIdentifiers);
5963
table.Columns.ForEach(ObfuscateIdentifiers);
6064
table.Measures.ForEach(ObfuscateIdentifiers);
6165
}
6266

67+
private void ObfuscateIdentifiers(Calendar calendar)
68+
{
69+
ObfuscateCalendarName(calendar.CalendarName);
70+
}
71+
6372
private void ObfuscateIdentifiers(Column column)
6473
{
6574
ObfuscateColumnName(column.ColumnName);
@@ -93,18 +102,46 @@ void CreateKpiMeasureText(DaxExpression kpi, string type)
93102
}
94103
}
95104

105+
private void ObfuscateIdentifiers(Function function)
106+
{
107+
_identifiers.Map(function);
108+
ObfuscateFunctionName(function.FunctionName);
109+
}
110+
96111
private void Obfuscate(Table table)
97112
{
98113
Obfuscate(table.TableExpression);
99114
Obfuscate(table.DefaultDetailRowsExpression);
100115
Obfuscate(table.CalculationGroup);
101116
Obfuscate(table.Description);
117+
table.Calendars.ForEach(Obfuscate);
102118
table.Columns.ForEach(Obfuscate);
103119
table.Measures.ForEach(Obfuscate);
104120
table.UserHierarchies.ForEach(Obfuscate);
105121
table.Partitions.ForEach(Obfuscate);
106122
}
107123

124+
private void Obfuscate(Calendar calendar)
125+
{
126+
Obfuscate(calendar.Description);
127+
128+
foreach (var columnGroup in calendar.CalendarColumnGroups)
129+
{
130+
switch (columnGroup)
131+
{
132+
case TimeRelatedColumnGroup:
133+
// Columns reference table columns - no obfuscation needed
134+
break;
135+
case TimeUnitColumnAssociation:
136+
// PrimaryColumn and AssociatedColumns reference table columns - no obfuscation needed
137+
break;
138+
default:
139+
// Ensure an exception is thrown when new CalendarColumnGroup types are added and not handled here
140+
throw new NotSupportedException($"Unknown calendar column group type '{columnGroup.GetType().FullName}'.");
141+
}
142+
}
143+
}
144+
108145
private void Obfuscate(Column column)
109146
{
110147
Obfuscate(column.ColumnExpression);
@@ -151,10 +188,19 @@ private void Obfuscate(CalculationGroup calculationGroup)
151188
if (calculationGroup == null)
152189
return;
153190

191+
Obfuscate(calculationGroup.Description);
192+
Obfuscate(calculationGroup.MultipleOrEmptySelectionExpression);
193+
Obfuscate(calculationGroup.MultipleOrEmptySelectionExpressionDescription);
194+
Obfuscate(calculationGroup.MultipleOrEmptySelectionFormatStringExpression);
195+
Obfuscate(calculationGroup.NoSelectionExpression);
196+
Obfuscate(calculationGroup.NoSelectionExpressionDescription);
197+
Obfuscate(calculationGroup.NoSelectionFormatStringExpression);
198+
154199
foreach (var calculationItem in calculationGroup.CalculationItems)
155200
{
156201
Obfuscate(calculationItem.ItemName);
157202
Obfuscate(calculationItem.ItemExpression);
203+
Obfuscate(calculationItem.FormatStringDefinition);
158204
Obfuscate(calculationItem.Description);
159205
}
160206
}
@@ -170,9 +216,17 @@ private void Obfuscate(TablePermission tablePermission)
170216
Obfuscate(tablePermission.FilterExpression);
171217
}
172218

219+
private void Obfuscate(Function function)
220+
{
221+
Obfuscate(function.Description);
222+
Obfuscate(function.FunctionExpression);
223+
}
224+
225+
private void ObfuscateCalendarName(DaxName name) => Obfuscate(name, ObfuscationRule.PreserveDaxKeywords);
173226
private void ObfuscateTableName(DaxName name) => Obfuscate(name, ObfuscationRule.PreserveDaxKeywords);
174227
private void ObfuscateColumnName(DaxName name) => Obfuscate(name, ObfuscationRule.PreserveDaxReservedNames);
175228
private string? ObfuscateMeasureName(DaxName name) => Obfuscate(name, ObfuscationRule.PreserveDaxReservedNames);
229+
private void ObfuscateFunctionName(DaxName name) => Obfuscate(name, ObfuscationRule.None);
176230

177231
private string? Obfuscate(DaxName name, ObfuscationRule rule = ObfuscationRule.None)
178232
{

src/Dax.Vpax.Obfuscator/Extensions/DaxTokenExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ internal static class DaxTokenExtensions
66
{
77
public static bool IsVariable(this DaxToken token) => !IsFunction(token);
88

9+
/// <summary>
10+
/// Determines whether the specified token represents a DAX function call.
11+
/// </summary>
12+
/// <remarks>
13+
/// This applies to both built-in and user-defined functions.
14+
/// </remarks>
915
public static bool IsFunction(this DaxToken token)
1016
{
11-
if (token.Type != DaxToken.TABLE_OR_VARIABLE) throw new ArgumentException($"Token must be of type {nameof(DaxToken.TABLE_OR_VARIABLE)}", nameof(token));
17+
if (token.Type is not (DaxToken.TABLE_OR_VARIABLE or DaxToken.OTHER_IDENTIFIER))
18+
throw new InvalidOperationException($"Invalid token for function detection. Token value: '{token.Text}', type: {token.Type}.");
1219

1320
var current = token.Next;
1421
while (current != null && current.CommentOrWhitespace)

0 commit comments

Comments
 (0)