From 47e7809591fea8140d0520664f7ea522e0090f75 Mon Sep 17 00:00:00 2001 From: Abbas Cyclewala Date: Sat, 8 Jul 2023 15:35:01 +0530 Subject: [PATCH] Users/abcy/libupdate (#496) * updated dependencies * - Improved global param compilation for multiple rules * update version number * updated the changelog --- CHANGELOG.md | 7 +++++++ .../RulesEngineBenchmark.csproj | 2 +- .../DemoApp.EFDataExample.csproj | 4 ++-- .../RuleExpressionParser.cs | 4 +++- .../Extensions/EnumerableExtensions.cs | 19 +++++++++++++++++ src/RulesEngine/Models/ReSettings.cs | 17 +++++++++++++++ src/RulesEngine/Models/Workflow.cs | 4 ++++ src/RulesEngine/RuleCompiler.cs | 14 ++++++++++--- src/RulesEngine/RulesEngine.cs | 21 ++++++++++++++----- src/RulesEngine/RulesEngine.csproj | 10 ++++----- .../BusinessRuleEngineTest.cs | 8 ++++--- test/RulesEngine.UnitTest/RuleCompilerTest.cs | 4 ++-- .../RulesEngine.UnitTest.csproj | 10 ++++----- test/RulesEngine.UnitTest/ScopedParamsTest.cs | 7 ++++++- 14 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 src/RulesEngine/Extensions/EnumerableExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d17761b4..6c1ce981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [5.0.0] +- Fixed security bug related to System.Dynamic.Linq.Core + +### Breaking Changes +- As a part of security bug fix, method call for only registered types via reSettings will be allowed. This only impacts strongly typed inputs and nested types + + ## [4.0.0] - RulesEngine is now available in both dotnet 6 and netstandard 2.0 - Dependency on ILogger, MemoryCache have been removed diff --git a/benchmark/RulesEngineBenchmark/RulesEngineBenchmark.csproj b/benchmark/RulesEngineBenchmark/RulesEngineBenchmark.csproj index 798d5fc9..ae746fb7 100644 --- a/benchmark/RulesEngineBenchmark/RulesEngineBenchmark.csproj +++ b/benchmark/RulesEngineBenchmark/RulesEngineBenchmark.csproj @@ -6,7 +6,7 @@ - + diff --git a/demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj b/demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj index cd75c12b..0a588c15 100644 --- a/demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj +++ b/demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs b/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs index ac43d10c..8570fdbb 100644 --- a/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs +++ b/src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs @@ -37,7 +37,9 @@ private void PopulateMethodInfo() } public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType) { - var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) }; + var config = new ParsingConfig { + CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) + }; return new ExpressionParser(parameters, expression, new object[] { }, config).Parse(returnType); } diff --git a/src/RulesEngine/Extensions/EnumerableExtensions.cs b/src/RulesEngine/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..a3586f67 --- /dev/null +++ b/src/RulesEngine/Extensions/EnumerableExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RulesEngine.Extensions +{ + internal static class EnumerableExtensions + { + public static IEnumerable Safe(this IEnumerable enumerable) + { + return enumerable ?? Enumerable.Empty(); + } + } +} diff --git a/src/RulesEngine/Models/ReSettings.cs b/src/RulesEngine/Models/ReSettings.cs index 7b6d942d..8589092e 100644 --- a/src/RulesEngine/Models/ReSettings.cs +++ b/src/RulesEngine/Models/ReSettings.cs @@ -12,6 +12,23 @@ namespace RulesEngine.Models [ExcludeFromCodeCoverage] public class ReSettings { + + public ReSettings() { } + + // create a copy of settings + internal ReSettings(ReSettings reSettings) + { + CustomTypes = reSettings.CustomTypes; + CustomActions = reSettings.CustomActions; + EnableExceptionAsErrorMessage = reSettings.EnableExceptionAsErrorMessage; + IgnoreException = reSettings.IgnoreException; + EnableFormattedErrorMessage = reSettings.EnableFormattedErrorMessage; + EnableScopedParams = reSettings.EnableScopedParams; + NestedRuleExecutionMode = reSettings.NestedRuleExecutionMode; + CacheConfig = reSettings.CacheConfig; + } + + /// /// Get/Set the custom types to be used in Rule expressions /// diff --git a/src/RulesEngine/Models/Workflow.cs b/src/RulesEngine/Models/Workflow.cs index c264a5eb..91d6bdba 100644 --- a/src/RulesEngine/Models/Workflow.cs +++ b/src/RulesEngine/Models/Workflow.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Newtonsoft.Json.Converters; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -31,6 +33,8 @@ public IEnumerable WorkflowRulesToInject { } public IEnumerable WorkflowsToInject { get; set; } + public RuleExpressionType RuleExpressionType { get; set; } = RuleExpressionType.LambdaExpression; + /// /// Gets or Sets the global params which will be applicable to all rules /// diff --git a/src/RulesEngine/RuleCompiler.cs b/src/RulesEngine/RuleCompiler.cs index 2c3f33a6..d05bce88 100644 --- a/src/RulesEngine/RuleCompiler.cs +++ b/src/RulesEngine/RuleCompiler.cs @@ -47,7 +47,7 @@ internal RuleCompiler(RuleExpressionBuilderFactory expressionBuilderFactory, ReS /// /// /// Compiled func delegate - internal RuleFunc CompileRule(Rule rule, RuleParameter[] ruleParams, ScopedParam[] globalParams) + internal RuleFunc CompileRule(Rule rule, RuleExpressionType ruleExpressionType, RuleParameter[] ruleParams, Lazy globalParams) { if (rule == null) { @@ -56,10 +56,12 @@ internal RuleFunc CompileRule(Rule rule, RuleParameter[] rulePar } try { - var globalParamExp = GetRuleExpressionParameters(rule.RuleExpressionType,globalParams, ruleParams); + var globalParamExp = globalParams.Value; var extendedRuleParams = ruleParams.Concat(globalParamExp.Select(c => new RuleParameter(c.ParameterExpression.Name,c.ParameterExpression.Type))) .ToArray(); var ruleExpression = GetDelegateForRule(rule, extendedRuleParams); + + return GetWrappedRuleFunc(rule,ruleExpression,ruleParams,globalParamExp); } catch (Exception ex) @@ -100,7 +102,7 @@ private RuleFunc GetDelegateForRule(Rule rule, RuleParameter[] r return GetWrappedRuleFunc(rule, ruleFn, ruleParams, scopedParamList); } - private RuleExpressionParameter[] GetRuleExpressionParameters(RuleExpressionType ruleExpressionType,IEnumerable localParams, RuleParameter[] ruleParams) + internal RuleExpressionParameter[] GetRuleExpressionParameters(RuleExpressionType ruleExpressionType,IEnumerable localParams, RuleParameter[] ruleParams) { if(!_reSettings.EnableScopedParams) { @@ -227,6 +229,12 @@ private RuleFunc BuildNestedRuleFunc(Rule parentRule, Expression return (isSuccess, resultList); } + internal Func> CompileScopedParams(RuleExpressionType ruleExpressionType, RuleParameter[] ruleParameters,RuleExpressionParameter[] ruleExpParams) + { + return GetExpressionBuilder(ruleExpressionType).CompileScopedParams(ruleParameters, ruleExpParams); + + } + private RuleFunc GetWrappedRuleFunc(Rule rule, RuleFunc ruleFunc,RuleParameter[] ruleParameters,RuleExpressionParameter[] ruleExpParams) { if(ruleExpParams.Length == 0) diff --git a/src/RulesEngine/RulesEngine.cs b/src/RulesEngine/RulesEngine.cs index 1a7d62e2..9002f82e 100644 --- a/src/RulesEngine/RulesEngine.cs +++ b/src/RulesEngine/RulesEngine.cs @@ -7,6 +7,7 @@ using RulesEngine.Actions; using RulesEngine.Exceptions; using RulesEngine.ExpressionBuilders; +using RulesEngine.Extensions; using RulesEngine.HelperFunctions; using RulesEngine.Interfaces; using RulesEngine.Models; @@ -48,7 +49,7 @@ public RulesEngine(Workflow[] Workflows, ReSettings reSettings = null) : this(re public RulesEngine(ReSettings reSettings = null) { - _reSettings = reSettings ?? new ReSettings(); + _reSettings = reSettings == null ? new ReSettings(): new ReSettings(reSettings); if(_reSettings.CacheConfig == null) { _reSettings.CacheConfig = new MemCacheConfig(); @@ -286,9 +287,16 @@ private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams if (workflow != null) { var dictFunc = new Dictionary>(); + _reSettings.CustomTypes = _reSettings.CustomTypes.Safe().Union(ruleParams.Select(c => c.Type)).ToArray(); + // add separate compilation for global params + + var globalParamExp = new Lazy( + () => _ruleCompiler.GetRuleExpressionParameters(workflow.RuleExpressionType, workflow.GlobalParams, ruleParams) + ); + foreach (var rule in workflow.Rules.Where(c => c.Enabled)) { - dictFunc.Add(rule.RuleName, CompileRule(rule, ruleParams, workflow.GlobalParams?.ToArray())); + dictFunc.Add(rule.RuleName, CompileRule(rule,workflow.RuleExpressionType, ruleParams, globalParamExp)); } _rulesCache.AddOrUpdateCompiledRule(compileRulesKey, dictFunc); @@ -313,12 +321,15 @@ private RuleFunc CompileRule(string workflowName, string ruleNam { throw new ArgumentException($"Workflow `{workflowName}` does not contain any rule named `{ruleName}`"); } - return CompileRule(currentRule, ruleParameters, workflow.GlobalParams?.ToArray()); + var globalParamExp = new Lazy( + () => _ruleCompiler.GetRuleExpressionParameters(workflow.RuleExpressionType, workflow.GlobalParams, ruleParameters) + ); + return CompileRule(currentRule,workflow.RuleExpressionType, ruleParameters, globalParamExp); } - private RuleFunc CompileRule(Rule rule, RuleParameter[] ruleParams, ScopedParam[] scopedParams) + private RuleFunc CompileRule(Rule rule, RuleExpressionType ruleExpressionType, RuleParameter[] ruleParams, Lazy scopedParams) { - return _ruleCompiler.CompileRule(rule, ruleParams, scopedParams); + return _ruleCompiler.CompileRule(rule, ruleExpressionType, ruleParams, scopedParams); } diff --git a/src/RulesEngine/RulesEngine.csproj b/src/RulesEngine/RulesEngine.csproj index 7ad5a926..80f3998f 100644 --- a/src/RulesEngine/RulesEngine.csproj +++ b/src/RulesEngine/RulesEngine.csproj @@ -2,7 +2,7 @@ net6.0;netstandard2.0 - 4.0.0 + 5.0.0 Copyright (c) Microsoft Corporation. LICENSE https://github.com/microsoft/RulesEngine @@ -31,12 +31,12 @@ - - + + - + - + diff --git a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs index 48203dca..521741df 100644 --- a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs +++ b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs @@ -388,10 +388,12 @@ public async Task ExecuteRule_WithInjectedUtils_ReturnsListOfRuleResultTree(stri [InlineData("rules6.json")] public async Task ExecuteRule_RuleWithMethodExpression_ReturnsSucess(string ruleFileName) { - var re = GetRulesEngine(ruleFileName); - Func func = () => true; + var re = GetRulesEngine(ruleFileName, new ReSettings { + CustomTypes = new[] { typeof(Func) } + }); + dynamic input1 = new ExpandoObject(); input1.Property1 = "hello"; input1.Boolean = false; @@ -851,7 +853,7 @@ private static dynamic[] GetInputs4() } [ExcludeFromCodeCoverage] - private class TestInstanceUtils + public class TestInstanceUtils { public bool CheckExists(string str) { diff --git a/test/RulesEngine.UnitTest/RuleCompilerTest.cs b/test/RulesEngine.UnitTest/RuleCompilerTest.cs index 72abd678..ef9081e8 100644 --- a/test/RulesEngine.UnitTest/RuleCompilerTest.cs +++ b/test/RulesEngine.UnitTest/RuleCompilerTest.cs @@ -28,8 +28,8 @@ public void RuleCompiler_CompileRule_ThrowsException() var reSettings = new ReSettings(); var parser = new RuleExpressionParser(reSettings); var compiler = new RuleCompiler(new RuleExpressionBuilderFactory(reSettings, parser),null); - Assert.Throws(() => compiler.CompileRule(null, null,null)); - Assert.Throws(() => compiler.CompileRule(null, new RuleParameter[] { null },null)); + Assert.Throws(() => compiler.CompileRule(null, RuleExpressionType.LambdaExpression,null,null)); + Assert.Throws(() => compiler.CompileRule(null, RuleExpressionType.LambdaExpression, new RuleParameter[] { null },null)); } } } diff --git a/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj b/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj index e969f59f..70200802 100644 --- a/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj +++ b/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj @@ -6,16 +6,16 @@ True - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/RulesEngine.UnitTest/ScopedParamsTest.cs b/test/RulesEngine.UnitTest/ScopedParamsTest.cs index 5979f2b2..ebbbb345 100644 --- a/test/RulesEngine.UnitTest/ScopedParamsTest.cs +++ b/test/RulesEngine.UnitTest/ScopedParamsTest.cs @@ -101,6 +101,7 @@ public async Task DisabledScopedParam_ShouldReflect(string workflowName, bool[] [Theory] [InlineData("GlobalParamsOnly")] [InlineData("LocalParamsOnly2")] + [InlineData("GlobalParamsOnlyWithComplexInput")] public async Task ErrorInScopedParam_ShouldAppearAsErrorMessage(string workflowName) { var workflow = GetWorkflowList(); @@ -386,9 +387,13 @@ private Workflow[] GetWorkflowList() new Rule { RuleName = "TrueTest", Expression = "globalParam1 == \"hello\"" + }, + new Rule { + RuleName = "TrueTest2", + Expression = "globalParam1.ToUpper() == \"HELLO\"" } } - }, + } }; } }