From 17bddd7e3854852cd4a2346ed79464bcc27c166e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 22 Jan 2025 17:54:46 +0100 Subject: [PATCH 1/8] Fix CVE-2024-51417 - remove 'object' from PredefinedTypes - refactor DefaultDynamicLinqCustomTypeProvider so that only classes with DynamicLinqType annotation are resolved --- .../AbstractDynamicLinqCustomTypeProvider.cs | 58 +++++++-------- .../DefaultDynamicLinqCustomTypeProvider.cs | 13 ++-- .../Extensions/LinqExtensions.cs | 16 ----- .../Parser/PredefinedTypesHelper.cs | 1 - src/System.Linq.Dynamic.Core/ParsingConfig.cs | 19 +++++ ...faultDynamicLinqCustomTypeProviderTests.cs | 11 ++- .../DynamicClassTest.cs | 3 +- .../DynamicExpressionParserTests.cs | 2 +- .../Entities/Worker.cs | 5 +- .../ExpressionTests.cs | 14 +++- .../Helpers/Models/AppSettings.cs | 32 +++++++++ .../Parser/ExpressionParserTests.cs | 2 +- .../Parser/MethodFinderTest.cs | 4 +- .../QueryableTests.Is,OfType,As,Cast.cs | 12 +++- .../QueryableTests.Select.cs | 6 +- .../SecurityTests.cs | 70 +++++++++++++++++-- .../TestClasses/TestCustomTypeProvider.cs | 7 +- 17 files changed, 200 insertions(+), 75 deletions(-) delete mode 100644 src/System.Linq.Dynamic.Core/Extensions/LinqExtensions.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs index 3f54beed9..cccb56c48 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/AbstractDynamicLinqCustomTypeProvider.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq.Dynamic.Core.Extensions; using System.Linq.Dynamic.Core.Validation; using System.Reflection; @@ -13,11 +12,25 @@ namespace System.Linq.Dynamic.Core.CustomTypeProviders; public abstract class AbstractDynamicLinqCustomTypeProvider { /// - /// Finds the unique types marked with DynamicLinqTypeAttribute. + /// Additional types which should also be resolved. + /// + protected readonly IList AdditionalTypes; + + /// + /// Initializes a new instance of the class. + /// + /// A list of additional types (without the DynamicLinqTypeAttribute annotation) which should also be resolved. + protected AbstractDynamicLinqCustomTypeProvider(IList additionalTypes) + { + AdditionalTypes = Check.NotNull(additionalTypes); + } + + /// + /// Finds the unique types annotated with DynamicLinqTypeAttribute. /// /// The assemblies to process. /// - protected IEnumerable FindTypesMarkedWithDynamicLinqTypeAttribute(IEnumerable assemblies) + protected Type[] FindTypesMarkedWithDynamicLinqTypeAttribute(IEnumerable assemblies) { Check.NotNull(assemblies); #if !NET35 @@ -27,7 +40,7 @@ protected IEnumerable FindTypesMarkedWithDynamicLinqTypeAttribute(IEnumera } /// - /// Resolve any type which is registered in the current application domain. + /// Resolve a type which is annotated with DynamicLinqTypeAttribute or when the type is listed in AdditionalTypes. /// /// The assemblies to inspect. /// The typename to resolve. @@ -37,20 +50,13 @@ protected IEnumerable FindTypesMarkedWithDynamicLinqTypeAttribute(IEnumera Check.NotNull(assemblies); Check.NotEmpty(typeName); - foreach (var assembly in assemblies) - { - var resolvedType = assembly.GetType(typeName, false, true); - if (resolvedType != null) - { - return resolvedType; - } - } - - return null; + var types = FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies).Union(AdditionalTypes); + return types.FirstOrDefault(t => t.FullName == typeName); } /// - /// Resolve a type by the simple name which is registered in the current application domain. + /// Resolve a type which is annotated with DynamicLinqTypeAttribute by the simple (short) name. + /// Also when the type is listed in AdditionalTypes. /// /// The assemblies to inspect. /// The simple typename to resolve. @@ -60,22 +66,16 @@ protected IEnumerable FindTypesMarkedWithDynamicLinqTypeAttribute(IEnumera Check.NotNull(assemblies); Check.NotEmpty(simpleTypeName); - foreach (var assembly in assemblies) - { - var fullNames = assembly.GetTypes().Select(t => t.FullName!).Distinct(); - var firstMatchingFullname = fullNames.FirstOrDefault(fn => fn.EndsWith($".{simpleTypeName}")); + var types = FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies); + var fullNames = types.Select(t => t.FullName!).Distinct().ToArray(); + var firstMatchingFullname = fullNames.FirstOrDefault(fn => fn.EndsWith($".{simpleTypeName}")); - if (firstMatchingFullname != null) - { - var resolvedType = assembly.GetType(firstMatchingFullname, false, true); - if (resolvedType != null) - { - return resolvedType; - } - } + if (firstMatchingFullname == null) + { + return null; } - return null; + return types.FirstOrDefault(t => t.FullName == firstMatchingFullname); } #if (UAP10_0 || NETSTANDARD) @@ -147,7 +147,7 @@ protected Type[] GetAssemblyTypesWithDynamicLinqTypeAttribute(IEnumerable().ToArray(); } catch { diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs index f69c9c49f..ce4f184fa 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs @@ -10,8 +10,6 @@ namespace System.Linq.Dynamic.Core.CustomTypeProviders; /// /// Scans the current AppDomain for all types marked with , and adds them as custom Dynamic Link types. /// -/// Also provides functionality to resolve a Type in the current Application Domain. -/// /// This class is used as default for full .NET Framework and .NET Core App 2.x and higher. /// public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider @@ -24,11 +22,10 @@ public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTyp /// /// Initializes a new instance of the class. - /// Backwards compatibility for issue https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/830. /// + /// The parsing configuration. /// Defines whether to cache the CustomTypes (including extension methods) which are found in the Application Domain. Default set to 'true'. - [Obsolete("Please use the DefaultDynamicLinqCustomTypeProvider(ParsingConfig config, bool cacheCustomTypes = true) constructor.")] - public DefaultDynamicLinqCustomTypeProvider(bool cacheCustomTypes = true) : this(ParsingConfig.Default, cacheCustomTypes) + public DefaultDynamicLinqCustomTypeProvider(ParsingConfig config, bool cacheCustomTypes = true) : this(config, new List(), cacheCustomTypes) { } @@ -36,8 +33,9 @@ public DefaultDynamicLinqCustomTypeProvider(bool cacheCustomTypes = true) : this /// Initializes a new instance of the class. /// /// The parsing configuration. + /// A list of additional types (without the DynamicLinqTypeAttribute annotation) which should also be resolved. /// Defines whether to cache the CustomTypes (including extension methods) which are found in the Application Domain. Default set to 'true'. - public DefaultDynamicLinqCustomTypeProvider(ParsingConfig config, bool cacheCustomTypes = true) + public DefaultDynamicLinqCustomTypeProvider(ParsingConfig config, IList additionalTypes, bool cacheCustomTypes = true) : base(additionalTypes) { _assemblyHelper = new DefaultAssemblyHelper(Check.NotNull(config)); _cacheCustomTypes = cacheCustomTypes; @@ -96,7 +94,8 @@ public Dictionary> GetExtensionMethods() private HashSet GetCustomTypesInternal() { IEnumerable assemblies = _assemblyHelper.GetAssemblies(); - return new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies)); + var types = FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies).Union(AdditionalTypes); + return new HashSet(types); } private Dictionary> GetExtensionMethodsInternal() diff --git a/src/System.Linq.Dynamic.Core/Extensions/LinqExtensions.cs b/src/System.Linq.Dynamic.Core/Extensions/LinqExtensions.cs deleted file mode 100644 index dc563fa8c..000000000 --- a/src/System.Linq.Dynamic.Core/Extensions/LinqExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Dynamic.Core.Validation; - -namespace System.Linq.Dynamic.Core.Extensions; - -[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] -internal static class LinqExtensions -{ - public static IEnumerable WhereNotNull(this IEnumerable sequence) - { - Check.NotNull(sequence); - - return sequence.Where(e => e != null)!; - } -} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs index 58cb5f640..4ea30b16b 100644 --- a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs @@ -28,7 +28,6 @@ internal static class PredefinedTypesHelper public static readonly IDictionary PredefinedTypes = new ConcurrentDictionary(new Dictionary { - { typeof(object), 0 }, { typeof(bool), 0 }, { typeof(char), 0 }, { typeof(string), 0 }, diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 70acd35cc..4886577b3 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -71,6 +71,25 @@ public IDynamicLinkCustomTypeProvider? CustomTypeProvider } } + /// + /// Sets the CustomTypeProvider to . + /// + /// Defines whether to cache the CustomTypes (including extension methods) which are found in the Application Domain. Default set to true. + public void UseDefaultDynamicLinqCustomTypeProvider(bool cacheCustomTypes = true) + { + _customTypeProvider = new DefaultDynamicLinqCustomTypeProvider(this, cacheCustomTypes); + } + + /// + /// Sets the CustomTypeProvider to . + /// + /// Defines whether to cache the CustomTypes (including extension methods) which are found in the Application Domain. Default set to true. + /// A list of additional types (without the DynamicLinqTypeAttribute annotation) which should also be resolved. + public void UseDefaultDynamicLinqCustomTypeProvider(IList additionalTypes, bool cacheCustomTypes = true) + { + _customTypeProvider = new DefaultDynamicLinqCustomTypeProvider(this, additionalTypes, cacheCustomTypes); + } + /// /// Load additional assemblies from the current domain base directory. /// Note: only used when full .NET Framework and .NET Core App 2.x and higher. diff --git a/test/System.Linq.Dynamic.Core.Tests/CustomTypeProviders/DefaultDynamicLinqCustomTypeProviderTests.cs b/test/System.Linq.Dynamic.Core.Tests/CustomTypeProviders/DefaultDynamicLinqCustomTypeProviderTests.cs index 6fe19ba87..1b9867b0d 100644 --- a/test/System.Linq.Dynamic.Core.Tests/CustomTypeProviders/DefaultDynamicLinqCustomTypeProviderTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/CustomTypeProviders/DefaultDynamicLinqCustomTypeProviderTests.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Linq.Dynamic.Core.CustomTypeProviders; using FluentAssertions; using NFluent; @@ -8,11 +9,17 @@ namespace System.Linq.Dynamic.Core.Tests.CustomTypeProviders; public class DefaultDynamicLinqCustomTypeProviderTests { + private readonly IList _additionalTypes = new List + { + typeof(DirectoryInfo), + typeof(DefaultDynamicLinqCustomTypeProviderTests) + }; + private readonly DefaultDynamicLinqCustomTypeProvider _sut; public DefaultDynamicLinqCustomTypeProviderTests() { - _sut = new DefaultDynamicLinqCustomTypeProvider(ParsingConfig.Default); + _sut = new DefaultDynamicLinqCustomTypeProvider(ParsingConfig.Default, _additionalTypes); } [Fact] diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs index ab8bd2894..c020cb94a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs @@ -281,7 +281,8 @@ public void DynamicClassArray_Issue593_Fails() isValid.Should().BeFalse(); // This should actually be true, but fails. For solution see Issue593_Solution1 and Issue593_Solution2. } - [SkipIfGitHubActions] + // [SkipIfGitHubActions] + [Fact(Skip = "867")] public void DynamicClassArray_Issue593_Solution1() { // Arrange diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index a0895cc5b..9260cf8d7 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1058,7 +1058,7 @@ public void DynamicExpressionParser_ParseLambda_StringLiteral_QuotationMark() Assert.Equal(expectedRightValue, rightValue); } - [Fact] + [Fact(Skip = "867")] public void DynamicExpressionParser_ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression() { var expression = DynamicExpressionParser.ParseLambda( diff --git a/test/System.Linq.Dynamic.Core.Tests/Entities/Worker.cs b/test/System.Linq.Dynamic.Core.Tests/Entities/Worker.cs index 4d5475a35..77bfd548e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Entities/Worker.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Entities/Worker.cs @@ -1,5 +1,8 @@ -namespace System.Linq.Dynamic.Core.Tests.Entities +using System.Linq.Dynamic.Core.CustomTypeProviders; + +namespace System.Linq.Dynamic.Core.Tests.Entities { + [DynamicLinqType] public class Worker : BaseEmployee { public string Other { get; set; } diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 42b5a743d..74abc9cd7 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -13,6 +13,7 @@ namespace System.Linq.Dynamic.Core.Tests { + [DynamicLinqType] public enum TestEnumPublic : sbyte { Var1 = 0, @@ -25,6 +26,7 @@ public enum TestEnumPublic : sbyte public partial class ExpressionTests { + [DynamicLinqType] public enum TestEnum2 : sbyte { Var1 = 0, @@ -919,6 +921,9 @@ public void ExpressionTests_Enum_Property_Equality_Using_PublicEnum_And_FullName public void ExpressionTests_Enum_Property_Equality_Using_Enum_And_FullName_Inline() { // Arrange + var config = new ParsingConfig(); + config.UseDefaultDynamicLinqCustomTypeProvider([typeof(TestEnum2)]); + var qry = new List { new TestEnumClass { B = TestEnum2.Var2 } }.AsQueryable(); string enumType = typeof(TestEnum2).FullName!; @@ -948,7 +953,7 @@ public void ExpressionTests_Enum_Property_Equality_Using_PublicEnum_Name_Inline( } [Fact] - public void ExpressionTests_Enum_Property_Equality_Using_Enum_Name_Inline_Should_Throw_Exception() + public void ExpressionTests_Enum_Property_Equality_Using_Enum_Name_Inline_ShouldBeOk() { // Arrange var config = new ParsingConfig @@ -962,7 +967,7 @@ public void ExpressionTests_Enum_Property_Equality_Using_Enum_Name_Inline_Should Action a = () => qry.Where(config, $"{enumType}.Var2 == it.B").ToDynamicArray(); // Assert - a.Should().Throw(); + a.Should().NotThrow(); } [Fact] @@ -1031,7 +1036,10 @@ public void ExpressionTests_Enum_NullableProperty() [Fact] public void ExpressionTests_Enum_MoreTests() { - var config = new ParsingConfig(); + var config = new ParsingConfig + { + ResolveTypesBySimpleName = true + }; // Arrange var lst = new List { TestEnum.Var1, TestEnum.Var2, TestEnum.Var3, TestEnum.Var4, TestEnum.Var5, TestEnum.Var6 }; diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs new file mode 100644 index 000000000..5196bbcc9 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq.Dynamic.Core.CustomTypeProviders; + +namespace System.Linq.Dynamic.Core.Tests.Helpers.Models +{ + public static class AppSettings + { + public static Dictionary SettingsProp { get; } = new() + { + { "jwt", "test" } + }; + + public static Dictionary SettingsField = new() + { + { "jwt", "test" } + }; + } + + [DynamicLinqType] + public static class AppSettings2 + { + public static Dictionary SettingsProp { get; } = new() + { + { "jwt", "test" } + }; + + public static Dictionary SettingsField = new() + { + { "jwt", "test" } + }; + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs index 22283e6ba..644f6ef1e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs @@ -346,7 +346,7 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT [Theory] [InlineData("it.MainCompany.Name != null", "(company.MainCompany.Name != null)")] [InlineData("@MainCompany.Companies.Count() > 0", "(company.MainCompany.Companies.Count() > 0)")] - [InlineData("Company.Equals(null, null)", "Equals(null, null)")] + // [InlineData("Company.Equals(null, null)", "Equals(null, null)")] issue 867 [InlineData("MainCompany.Name", "company.MainCompany.Name")] [InlineData("Name", "company.Name")] [InlineData("company.Name", "company.Name")] diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs index a1afadd49..c4b318e1e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs @@ -7,14 +7,14 @@ namespace System.Linq.Dynamic.Core.Tests.Parser; public class MethodFinderTest { - [Fact] + [Fact(Skip = "867")] public void MethodsOfDynamicLinqAndSystemLinqShouldBeEqual() { Expression> expr = x => x.ToString(); var selector = "ToString()"; var prm = Parameter(typeof(int?)); - var parser = new ExpressionParser(new[] { prm }, selector, new object[] { }, ParsingConfig.Default); + var parser = new ExpressionParser([prm], selector, [], ParsingConfig.Default); var expr1 = parser.Parse(null); Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expr1).Method.DeclaringType); diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Is,OfType,As,Cast.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Is,OfType,As,Cast.cs index 93e7c1e92..1b41ad25f 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Is,OfType,As,Cast.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Is,OfType,As,Cast.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Entities; using FluentAssertions; @@ -98,6 +99,7 @@ public void OfType_Dynamic_WithFullName_UseParameterizedNamesInDynamicQuery(bool internal class Base { } + [DynamicLinqType] internal class DerivedA : Base { } internal class DerivedB : Base { } @@ -294,7 +296,8 @@ public void As_Dynamic_ActingOnProperty_NullableInt() countAsDynamic.Should().Be(count); } - public enum TestEnum + [DynamicLinqType] + public enum TestEnumForThisTest { None = 0, @@ -305,10 +308,10 @@ public enum TestEnum public void As_Dynamic_ActingOnProperty_NullableEnum() { // Assign - var nullableEnumType = $"{typeof(TestEnum).FullName}?"; + var nullableEnumType = $"{typeof(TestEnumForThisTest).FullName}?"; var qry = new[] { - new { Value = TestEnum.X } + new { Value = TestEnumForThisTest.X } }.AsQueryable(); // Act @@ -365,7 +368,10 @@ public void As_Dynamic_ActingOnProperty_WithType() countAsDynamic.Should().Be(1); } + [DynamicLinqType] public class AS_A { } + + [DynamicLinqType] public class AS_B : AS_A { public string MyProperty { get; set; } diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index f1d3a428f..c1b55430c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers.Models; using FluentAssertions; @@ -19,6 +20,7 @@ namespace System.Linq.Dynamic.Core.Tests { public partial class QueryableTests { + [DynamicLinqType] public class Example { public int Field; @@ -29,10 +31,12 @@ public class Example public int Sec { get; set; } public int? SecNull { get; set; } + [DynamicLinqType] public class NestedDto { public string Name { get; set; } + [DynamicLinqType] public class NestedDto2 { public string Name2 { get; set; } @@ -320,7 +324,7 @@ public void Select_Dynamic_IntoTypeWithNullableParameterInConstructor() Check.That(resultDynamic.Last()).Equals(result.Last()); } - [Fact] + [Fact(Skip = "867")] public void Select_Dynamic_SystemType1() { // Arrange diff --git a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs index 77fa0c762..3f5d31cc4 100644 --- a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs @@ -7,7 +7,7 @@ namespace System.Linq.Dynamic.Core.Tests; -public class SecurityTests +public partial class SecurityTests { class Message { @@ -26,13 +26,13 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test1() { // Arrange var baseQuery = new[] { 1, 2, 3 }.AsQueryable(); - string predicate = "\"\".GetType().Assembly.DefinedTypes.Where(it.name == \"Assembly\").First().DeclaredMethods.Where(it.Name == \"GetName\").First().Invoke(\"\".GetType().Assembly, new Object[] {} ).Name.ToString() != \"Test\""; + var predicate = "\"\".GetType().Assembly.DefinedTypes.Where(it.name == \"Assembly\").First().DeclaredMethods.Where(it.Name == \"GetName\").First().Invoke(\"\".GetType().Assembly, new Object[] {} ).Name.ToString() != \"Test\""; // Act Action action = () => baseQuery.OrderBy(predicate); // Assert - action.Should().Throw().WithMessage("Methods on type 'MethodBase' are not accessible"); + action.Should().Throw().WithMessage("Methods on type 'Object' are not accessible"); } [Fact] @@ -41,8 +41,7 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test2() // Arrange var messages = new[] { - new Message("Alice", "Bob"), - new Message("Bob", "Alice") + new Message("Alice", "Bob") }.AsQueryable(); Action action = () => messages.Where( @@ -50,7 +49,7 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test2() ); // Assert - action.Should().Throw().WithMessage($"Methods on type 'Assembly' are not accessible"); + action.Should().Throw().WithMessage($"Methods on type 'Object' are not accessible"); } [Theory] @@ -64,4 +63,63 @@ public void DynamicExpressionParser_ParseLambda_IllegalMethodCall_ThrowsExceptio // Assert action.Should().Throw().WithMessage($"Methods on type '{type}' are not accessible"); } + + [Theory] + [InlineData("c => string.Join(\"_\", c.GetType().Assembly.DefinedTypes.SelectMany(t => t.CustomAttributes).Select(a => a.AttributeType).Select(t => t.AssemblyQualifiedName))")] + [InlineData("c => string.Join(\"_\", c.GetType().Assembly.DefinedTypes.Select(t => t.BaseType).Select(t => t.AssemblyQualifiedName))")] + [InlineData("c => string.Join(\"_\", c.GetType().Assembly.FullName))")] + public void UsingSystemReflectionAssembly_ThrowsException(string selector) + { + // Arrange + var queryable = new[] + { + new Message("Alice", "Bob") + }.AsQueryable(); + + // Act + Action action = () => queryable.Select(selector); + + // Assert + action.Should().Throw().WithMessage("Methods on type 'Object' are not accessible"); + } + + [Theory] + [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] + [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] + [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] + [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] + public void UsingStaticClass_ThrowsException(string selector) + { + // Arrange + var queryable = new[] + { + new Message("Alice", "Bob") + }.AsQueryable(); + + // Act + Action action = () => queryable.Select(selector); + + // Assert + action.Should().Throw().WithMessage("Type 'System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings' not found"); + } + + [Theory] + [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsProp[\"jwt\"]")] + [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsField[\"jwt\"]")] + [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsProp[\"jwt\"]")] + [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsField[\"jwt\"]")] + public void UsingStaticClassWithDynamicTypeAttribute_ShouldBeOk(string selector) + { + // Arrange + var queryable = new[] + { + new Message("Alice", "Bob") + }.AsQueryable(); + + // Act + Action action = () => queryable.Select(selector); + + // Assert + action.Should().NotThrow(); + } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs index 5ae6565b2..ccb883c18 100644 --- a/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs @@ -9,6 +9,10 @@ public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDy { private HashSet? _customTypes; + public TestCustomTypeProvider() : base([]) + { + } + public virtual HashSet GetCustomTypes() { if (_customTypes != null) @@ -16,12 +20,13 @@ public virtual HashSet GetCustomTypes() return _customTypes; } - _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })) + _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute([GetType().GetTypeInfo().Assembly])) { typeof(CustomClassWithStaticMethod), typeof(StaticHelper), typeof(StaticHelper.Nested) }; + return _customTypes; } From 9e49208da49903f146ac022ec62bac54016f8bbd Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 22 Jan 2025 19:08:09 +0100 Subject: [PATCH 2/8] UsingStaticClass_WhenAddedDefaultDynamicLinqCustomTypeProvider_ShouldBeOk --- .../SecurityTests.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs index 3f5d31cc4..6ecda498c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs @@ -1,13 +1,12 @@ using System.IO; using System.Linq.Dynamic.Core.Exceptions; -using System.Net; using System.Reflection; using FluentAssertions; using Xunit; namespace System.Linq.Dynamic.Core.Tests; -public partial class SecurityTests +public class SecurityTests { class Message { @@ -103,6 +102,29 @@ public void UsingStaticClass_ThrowsException(string selector) action.Should().Throw().WithMessage("Type 'System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings' not found"); } + [Theory] + [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] + [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] + [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] + [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] + public void UsingStaticClass_WhenAddedDefaultDynamicLinqCustomTypeProvider_ShouldBeOk(string selector) + { + // Arrange + var config = new ParsingConfig(); + config.UseDefaultDynamicLinqCustomTypeProvider([typeof(Helpers.Models.AppSettings)]); + + var queryable = new[] + { + new Message("Alice", "Bob") + }.AsQueryable(); + + // Act + Action action = () => queryable.Select(config, selector); + + // Assert + action.Should().NotThrow(); + } + [Theory] [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsProp[\"jwt\"]")] [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsField[\"jwt\"]")] From d11c867f2a6f8b4bd7c2faf7ce6448289459e579 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 22 Jan 2025 22:20:38 +0100 Subject: [PATCH 3/8] Fix Select_Dynamic_SystemType1 unit test --- .../QueryableTests.Select.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index c1b55430c..0383b1c29 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -324,11 +324,15 @@ public void Select_Dynamic_IntoTypeWithNullableParameterInConstructor() Check.That(resultDynamic.Last()).Equals(result.Last()); } - [Fact(Skip = "867")] + [Fact] public void Select_Dynamic_SystemType1() { // Arrange - var config = new ParsingConfig { AllowNewToEvaluateAnyType = true }; + var config = new ParsingConfig + { + AllowNewToEvaluateAnyType = true + }; + config.UseDefaultDynamicLinqCustomTypeProvider([typeof(DirectoryInfo)]); var queryable = new[] { "test" }.AsQueryable(); // Act From 23a56ea9bea129a2b6f618664107c30acd251c64 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 22 Jan 2025 22:49:05 +0100 Subject: [PATCH 4/8] Add more tests --- .../Helpers/Models/AppSettings.cs | 13 +++++ .../SecurityTests.cs | 49 +++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs index 5196bbcc9..260ffef25 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/AppSettings.cs @@ -29,4 +29,17 @@ public static class AppSettings2 { "jwt", "test" } }; } + + public class AppSettings3 + { + public static Dictionary SettingsProp { get; } = new() + { + { "jwt", "test" } + }; + + public static Dictionary SettingsField = new() + { + { "jwt", "test" } + }; + } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs index 6ecda498c..c716fb2b1 100644 --- a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs @@ -87,7 +87,7 @@ public void UsingSystemReflectionAssembly_ThrowsException(string selector) [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] - public void UsingStaticClass_ThrowsException(string selector) + public void UsingStaticClassAsType_ThrowsException(string selector) { // Arrange var queryable = new[] @@ -102,16 +102,36 @@ public void UsingStaticClass_ThrowsException(string selector) action.Should().Throw().WithMessage("Type 'System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings' not found"); } + [Theory] + [InlineData("new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3().SettingsProp[\"jwt\"]")] + [InlineData("new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3().SettingsField[\"jwt\"]")] + [InlineData("c => new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3().SettingsProp[\"jwt\"]")] + [InlineData("c => new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3().SettingsField[\"jwt\"]")] + public void UsingClassAsType_ThrowsException(string selector) + { + // Arrange + var queryable = new[] + { + new Message("Alice", "Bob") + }.AsQueryable(); + + // Act + Action action = () => queryable.Select(selector); + + // Assert + action.Should().Throw().WithMessage("Type 'System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3' not found"); + } + [Theory] [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsProp[\"jwt\"]")] [InlineData("c => System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings.SettingsField[\"jwt\"]")] - public void UsingStaticClass_WhenAddedDefaultDynamicLinqCustomTypeProvider_ShouldBeOk(string selector) + public void UsingStaticClassAsType_WhenAddedToDefaultDynamicLinqCustomTypeProvider_ShouldBeOk(string selector) { // Arrange var config = new ParsingConfig(); - config.UseDefaultDynamicLinqCustomTypeProvider([typeof(Helpers.Models.AppSettings)]); + config.UseDefaultDynamicLinqCustomTypeProvider([typeof(Helpers.Models.AppSettings), typeof(Helpers.Models.AppSettings3)]); var queryable = new[] { @@ -125,6 +145,29 @@ public void UsingStaticClass_WhenAddedDefaultDynamicLinqCustomTypeProvider_Shoul action.Should().NotThrow(); } + [Theory(Skip = "Bug: Accessing static members on instance class is not supported")] + [InlineData("new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsProp[\"jwt\"]")] + [InlineData("new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsField[\"jwt\"]")] + [InlineData("c => new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsProp[\"jwt\"]")] + [InlineData("c => new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsField[\"jwt\"]")] + public void UsingClassAsType_WhenAddedToDefaultDynamicLinqCustomTypeProvider_ShouldBeOk(string selector1, string selector2) + { + // Arrange + var config = new ParsingConfig(); + config.UseDefaultDynamicLinqCustomTypeProvider([typeof(Helpers.Models.AppSettings), typeof(Helpers.Models.AppSettings3)]); + + var queryable = new[] + { + new Message("Alice", "Bob") + }.AsQueryable(); + + // Act + Action action = () => queryable.Select(config, selector1).Select(config, selector2); + + // Assert + action.Should().NotThrow(); + } + [Theory] [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsProp[\"jwt\"]")] [InlineData("System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings2.SettingsField[\"jwt\"]")] From 0855174381232d5e3a1394d03882730d87ec9513 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 23 Jan 2025 06:41:32 +0100 Subject: [PATCH 5/8] re-enable old constructor for DefaultDynamicLinqCustomTypeProvider --- .../DefaultDynamicLinqCustomTypeProvider.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs index ce4f184fa..0e4da9b6f 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs @@ -20,6 +20,16 @@ public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTyp private HashSet? _cachedCustomTypes; private Dictionary>? _cachedExtensionMethods; + /// + /// Initializes a new instance of the class. + /// Backwards compatibility for issue https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/830. + /// + /// Defines whether to cache the CustomTypes (including extension methods) which are found in the Application Domain. Default set to 'true'. + [Obsolete("Please use the DefaultDynamicLinqCustomTypeProvider(ParsingConfig config, IList additionalTypes, bool cacheCustomTypes = true) constructor.")] + public DefaultDynamicLinqCustomTypeProvider(bool cacheCustomTypes = true) : this(ParsingConfig.Default, cacheCustomTypes) + { + } + /// /// Initializes a new instance of the class. /// From b1c76b4926299d5ede25c643142ad87879b0b5da Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 23 Jan 2025 07:31:48 +0100 Subject: [PATCH 6/8] IDynamicLinkCustomTypeProvider --- .../CustomTypeProviders/IDynamicLinkCustomTypeProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/IDynamicLinkCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/IDynamicLinkCustomTypeProvider.cs index 83fde4940..ff8e85ff8 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/IDynamicLinkCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/IDynamicLinkCustomTypeProvider.cs @@ -2,9 +2,9 @@ { /// /// Interface for providing functionality to find custom types for or resolve any type. - /// Note that this interface will be marked obsolete in the next version. Use instead. /// + [Obsolete("Please use the IDynamicLinqCustomTypeProvider interface instead.")] public interface IDynamicLinkCustomTypeProvider : IDynamicLinqCustomTypeProvider { } -} +} \ No newline at end of file From 8be33e18f31a3cf2dbf54c01ccd97540c6a41c56 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 23 Jan 2025 07:58:29 +0100 Subject: [PATCH 7/8] add comment to PredefinedTypesHelper --- src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs index 4ea30b16b..e43d676bf 100644 --- a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs @@ -28,6 +28,7 @@ internal static class PredefinedTypesHelper public static readonly IDictionary PredefinedTypes = new ConcurrentDictionary(new Dictionary { + // { typeof(object), 0 }, Removed because of CVE-2024-51417 { typeof(bool), 0 }, { typeof(char), 0 }, { typeof(string), 0 }, From f10a47183af34d450e426172f75d32a430df0af0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 23 Jan 2025 13:37:08 +0100 Subject: [PATCH 8/8] [Theory(Skip = "873")] --- test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs index c716fb2b1..a16f259af 100644 --- a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs @@ -145,7 +145,7 @@ public void UsingStaticClassAsType_WhenAddedToDefaultDynamicLinqCustomTypeProvid action.Should().NotThrow(); } - [Theory(Skip = "Bug: Accessing static members on instance class is not supported")] + [Theory(Skip = "873")] [InlineData("new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsProp[\"jwt\"]")] [InlineData("new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsField[\"jwt\"]")] [InlineData("c => new System.Linq.Dynamic.Core.Tests.Helpers.Models.AppSettings3()", "SettingsProp[\"jwt\"]")]