This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-Casbin.NET.git
The following commit(s) were added to refs/heads/master by this push:
new c84c486 fix: match Go in in operator, subject priority, eval and
inline comments
c84c486 is described below
commit c84c4864f06f405a8f1fb57281b892b2b81c84bc
Author: Yang Luo <[email protected]>
AuthorDate: Wed Oct 21 01:49:35 2026 +0800
fix: match Go in in operator, subject priority, eval and inline comments
---
.github/workflows/build.yml | 9 +++
Casbin.UnitTests/ModelTests/ConformanceTest.cs | 76 +++++++++++++++++++++++++
Casbin.UnitTests/UtilTests/StringUtilTest.cs | 16 +++---
Casbin/Config/DefaultConfig.cs | 2 +-
Casbin/EnforceView.cs | 1 +
Casbin/Enforcer.Internal.cs | 8 ++-
Casbin/Evaluation/EffiectEvaluation.cs | 15 +----
Casbin/Extensions/Enforcer/EnforcerExtension.cs | 2 +
Casbin/Extensions/Model/ModelExtension.cs | 33 +++++++++++
Casbin/Model/DefaultPolicyStore.Node.cs | 21 +++----
Casbin/Model/RoleAssertion.cs | 27 +++++++--
Casbin/Util/StringUtil.cs | 54 +++++++++++++++++-
12 files changed, 220 insertions(+), 44 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a273b1b..6f60590 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -26,6 +26,13 @@ jobs:
with:
fetch-depth: 0
+ - name: Checkout conformance cases
+ uses: actions/checkout@v4
+ with:
+ repository: apache/casbin
+ path: casbin-conformance
+ sparse-checkout: conformance
+
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
@@ -48,6 +55,8 @@ jobs:
run: dotnet build -c Release --no-restore
- name: Test solution
+ env:
+ CASBIN_CONFORMANCE_DIR: ${{ github.workspace
}}/casbin-conformance/conformance
run: dotnet test -c Release --no-build --no-restore --results-directory
test-results --verbosity normal --collect:"XPlat Code Coverage" `
--
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=json,cobertura,lcov,teamcity,opencover
diff --git a/Casbin.UnitTests/ModelTests/ConformanceTest.cs
b/Casbin.UnitTests/ModelTests/ConformanceTest.cs
new file mode 100644
index 0000000..7e3b4a7
--- /dev/null
+++ b/Casbin.UnitTests/ModelTests/ConformanceTest.cs
@@ -0,0 +1,76 @@
+#if !NET452 && !NET461 && !NET462
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using Xunit;
+
+namespace Casbin.UnitTests.ModelTests;
+
+public class ConformanceTest
+{
+ private class ConformanceCase
+ {
+ public string model { get; set; }
+ public string policy { get; set; }
+ public List<ConformanceRequest> tests { get; set; }
+ }
+
+ private class ConformanceRequest
+ {
+ public List<string> request { get; set; }
+ public bool allow { get; set; }
+ public List<string> explain { get; set; }
+ }
+
+ [Fact]
+ public void TestConformance()
+ {
+ string dir =
Environment.GetEnvironmentVariable("CASBIN_CONFORMANCE_DIR");
+ if (string.IsNullOrEmpty(dir))
+ {
+ return;
+ }
+
+ string[] files = Directory.GetFiles(Path.Combine(dir, "cases"),
"*.json").OrderBy(f => f, StringComparer.Ordinal).ToArray();
+ Assert.NotEmpty(files);
+
+ List<string> failures = new();
+ foreach (string file in files)
+ {
+ string name = Path.GetFileName(file);
+ ConformanceCase c =
JsonSerializer.Deserialize<ConformanceCase>(File.ReadAllText(file));
+ Enforcer e;
+ try
+ {
+ e = new Enforcer(Path.Combine(dir, "data", c.model),
Path.Combine(dir, "data", c.policy));
+ }
+ catch (Exception ex)
+ {
+ failures.Add($"{name}: {ex.Message}");
+ continue;
+ }
+
+ foreach (ConformanceRequest test in c.tests)
+ {
+ try
+ {
+ var (allow, explains) =
e.EnforceEx(test.request.ToArray());
+ List<string> explain = explains.FirstOrDefault()?.ToList()
?? new List<string>();
+ if (allow != test.allow ||
!explain.SequenceEqual(test.explain))
+ {
+ failures.Add($"{name} [{string.Join(", ",
test.request)}]: got ({allow}, [{string.Join(", ", explain)}]), want
({test.allow}, [{string.Join(", ", test.explain)}])");
+ }
+ }
+ catch (Exception ex)
+ {
+ failures.Add($"{name} [{string.Join(", ", test.request)}]:
{ex.Message}");
+ }
+ }
+ }
+
+ Assert.True(failures.Count == 0, $"{failures.Count} conformance
failures:\n{string.Join("\n", failures)}");
+ }
+}
+#endif
diff --git a/Casbin.UnitTests/UtilTests/StringUtilTest.cs
b/Casbin.UnitTests/UtilTests/StringUtilTest.cs
index dec3aff..bd5daf7 100644
--- a/Casbin.UnitTests/UtilTests/StringUtilTest.cs
+++ b/Casbin.UnitTests/UtilTests/StringUtilTest.cs
@@ -17,10 +17,10 @@ public class StringUtilTest
public static IEnumerable<object[]> ReplaceEvalTestData =
[
[
- "eval(rule1)", "a == b",
+ "eval(rule1)", "(a == b)",
new Dictionary<string, string> { ["rule1"] = "a == b" }],
[
- "eval(rule1) && c && d", "a == b && c && d",
+ "eval(rule1) && c && d", "(a == b) && c && d",
new Dictionary<string, string> { ["rule1"] = "a == b" }
],
[
@@ -30,27 +30,27 @@ public class StringUtilTest
"eval(rule1) && c && d", "eval(rule1) && c && d", null
],
[
- "eval(rule1) || eval(rule2)", "a == b || a == c",
+ "eval(rule1) || eval(rule2)", "(a == b) || (a == c)",
new Dictionary<string, string> { ["rule1"] = "a == b", ["rule2"] =
"a == c" }
],
[
- "eval(rule1) || eval(rule2) && c && d", "a == b || a == c && c &&
d",
+ "eval(rule1) || eval(rule2) && c && d", "(a == b) || (a == c) && c
&& d",
new Dictionary<string, string> { ["rule1"] = "a == b", ["rule2"] =
"a == c" }
],
[
- "eval(rule1) || eval(rule2)", "a == b || eval(rule2)",
+ "eval(rule1) || eval(rule2)", "(a == b) || eval(rule2)",
new Dictionary<string, string> { ["rule1"] = "a == b" }
],
[
- "eval(rule1) || eval(rule2) && c && d", "a == b || eval(rule2) &&
c && d",
+ "eval(rule1) || eval(rule2) && c && d", "(a == b) || eval(rule2)
&& c && d",
new Dictionary<string, string> { ["rule1"] = "a == b" }
],
[
- "eval(rule1) || eval(rule2)", "eval(rule1) || a == c",
+ "eval(rule1) || eval(rule2)", "eval(rule1) || (a == c)",
new Dictionary<string, string> { ["rule2"] = "a == c" }
],
[
- "eval(rule1) || eval(rule2) && c && d", "eval(rule1) || a == c &&
c && d",
+ "eval(rule1) || eval(rule2) && c && d", "eval(rule1) || (a == c)
&& c && d",
new Dictionary<string, string> { ["rule2"] = "a == c" }
],
[
diff --git a/Casbin/Config/DefaultConfig.cs b/Casbin/Config/DefaultConfig.cs
index 116b8df..9269fa4 100644
--- a/Casbin/Config/DefaultConfig.cs
+++ b/Casbin/Config/DefaultConfig.cs
@@ -182,7 +182,7 @@ namespace Casbin.Config
using var streamReader = new StreamReader(stream);
while ((line = streamReader.ReadLine()) != null)
{
- line = line.Split(_defaultComment[0]).First().Trim();
+ line = line.Split(_defaultComment[0], ';').First().Trim();
if (line.EndsWith(_defaultFeed))
{
processedValue += line.Split(_defaultFeed[0]).First();
diff --git a/Casbin/EnforceView.cs b/Casbin/EnforceView.cs
index 0d5bb33..2555107 100644
--- a/Casbin/EnforceView.cs
+++ b/Casbin/EnforceView.cs
@@ -60,6 +60,7 @@ namespace Casbin
string effectType = PermConstants.DefaultPolicyEffectType)
{
matcher = matcher.Replace('\'', '"');
+ matcher = StringUtil.ReplaceInListOperator(matcher);
IReadOnlyAssertion requestAssertion =
model.Sections.GetRequestAssertion(requestType);
PolicyAssertion policyAssertion =
model.Sections.GetPolicyAssertion(policyType);
IReadOnlyAssertion effectAssertion =
model.Sections.GetPolicyEffectAssertion(effectType);
diff --git a/Casbin/Enforcer.Internal.cs b/Casbin/Enforcer.Internal.cs
index 02dad2b..f72ffa5 100644
--- a/Casbin/Enforcer.Internal.cs
+++ b/Casbin/Enforcer.Internal.cs
@@ -302,6 +302,11 @@ public partial class Enforcer
if (context.Explain && effectChain.HitPolicy)
{
+ if (effectChain.CanChain is false && effectChain.Result is false)
+ {
+ context.Explanations.Clear();
+ }
+
context.Explanations.Add(policy);
}
@@ -328,7 +333,7 @@ public partial class Enforcer
Dictionary<string, string> rules = new();
foreach (KeyValuePair<string, int> rule in context.View.EvalRules)
{
- rules[rule.Key] = policyValues[rule.Value];
+ rules[rule.Key] = policyValues[rule.Value].Replace('\'', '"');
}
expressionString = StringUtil.ReplaceEval(expressionString, rules);
@@ -342,6 +347,7 @@ public partial class Enforcer
return expressionString;
}
+ expressionString = StringUtil.ReplaceInListOperator(expressionString);
expressionString = StringUtil.ReplaceInOperator(expressionString);
return expressionString;
}
diff --git a/Casbin/Evaluation/EffiectEvaluation.cs
b/Casbin/Evaluation/EffiectEvaluation.cs
index a9c6d08..993a41b 100644
--- a/Casbin/Evaluation/EffiectEvaluation.cs
+++ b/Casbin/Evaluation/EffiectEvaluation.cs
@@ -51,6 +51,7 @@ namespace Casbin.Evaluation
break;
case EffectExpressionType.Priority:
+ case EffectExpressionType.PriorityAllOverride:
switch (effect)
{
case PolicyEffect.Allow:
@@ -78,20 +79,6 @@ namespace Casbin.Evaluation
}
break;
- case EffectExpressionType.PriorityAllOverride:
- switch (effect)
- {
- case PolicyEffect.Allow:
- result = true;
- hitPolicy = true;
- return false;
- case PolicyEffect.Deny:
- result = false;
- hitPolicy = true;
- return false;
- }
- break;
-
case EffectExpressionType.Custom:
// TODO: Support custom policy effect.
break;
diff --git a/Casbin/Extensions/Enforcer/EnforcerExtension.cs
b/Casbin/Extensions/Enforcer/EnforcerExtension.cs
index 2889435..e44c87e 100644
--- a/Casbin/Extensions/Enforcer/EnforcerExtension.cs
+++ b/Casbin/Extensions/Enforcer/EnforcerExtension.cs
@@ -28,6 +28,7 @@ namespace Casbin
}
enforcer.Model = DefaultModel.CreateFromFile(enforcer.Model.Path);
+ enforcer.Model.RegisterDomainMatchingFunc();
}
#endregion
@@ -129,6 +130,7 @@ namespace Casbin
public static IEnforcer SetModel(this IEnforcer enforcer, IModel model)
{
enforcer.Model = model;
+ model?.RegisterDomainMatchingFunc();
enforcer.EnforceCache?.Clear();
#if !NET452
enforcer.Logger?.LogInformation("Enforcer Cache, Cleared all
enforce cache");
diff --git a/Casbin/Extensions/Model/ModelExtension.cs
b/Casbin/Extensions/Model/ModelExtension.cs
index 224a9e1..b2ee445 100644
--- a/Casbin/Extensions/Model/ModelExtension.cs
+++ b/Casbin/Extensions/Model/ModelExtension.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Casbin.Functions;
using Casbin.Persist;
@@ -262,6 +263,38 @@ namespace Casbin.Model
assertion.RoleManager,
model.GFunctionCachePool.GetCache(roleType)));
}
+ internal static void RegisterDomainMatchingFunc(this IModel model)
+ {
+ if
(model.Sections.ContainsSection(PermConstants.Section.RoleSection) is false
+ ||
model.Sections.TryGetAssertion(PermConstants.Section.RequestSection,
+ PermConstants.DefaultRequestType, out RequestAssertion
requestAssertion) is false
+ ||
model.Sections.TryGetAssertion(PermConstants.Section.PolicySection,
+ PermConstants.DefaultPolicyType, out PolicyAssertion
policyAssertion) is false
+ ||
model.Sections.TryGetAssertion(PermConstants.Section.MatcherSection,
+ PermConstants.DefaultMatcherType, out MatcherAssertion
matcherAssertion) is false
+ || requestAssertion.Tokens.Count < 2 ||
policyAssertion.Tokens.Count < 2)
+ {
+ return;
+ }
+
+ string requestDomainToken = requestAssertion.Tokens.First(token =>
token.Value is 1).Key;
+ string policyDomainToken = policyAssertion.Tokens.First(token =>
token.Value is 1).Key;
+ string matchFunc =
+
$"keyMatch({PermConstants.DefaultRequestType}.{requestDomainToken},
{PermConstants.DefaultPolicyType}.{policyDomainToken})";
+ if (matcherAssertion.Value.Contains(matchFunc) is false)
+ {
+ return;
+ }
+
+ foreach (KeyValuePair<string, RoleAssertion> pair in
model.Sections.GetRoleAssertions())
+ {
+ if (pair.Value.GroupPolicyCount > 2)
+ {
+
pair.Value.RoleManager.AddDomainMatchingFunc(BuiltInFunctions.KeyMatch);
+ }
+ }
+ }
+
/// <summary>
/// Initializes the roles in RBAC.
/// </summary>
diff --git a/Casbin/Model/DefaultPolicyStore.Node.cs
b/Casbin/Model/DefaultPolicyStore.Node.cs
index e44e96d..6030481 100644
--- a/Casbin/Model/DefaultPolicyStore.Node.cs
+++ b/Casbin/Model/DefaultPolicyStore.Node.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
namespace Casbin.Model;
@@ -313,25 +314,19 @@ public partial class DefaultPolicyStore
}
- int PolicyComparison(IPolicyValues p1, IPolicyValues p2)
+ int GetSubjectLevel(IPolicyValues policy)
{
- string domain1 = "", domain2 = "";
- if (domainIndex != -1)
- {
- domain1 = p1[domainIndex];
- domain2 = p2[domainIndex];
- }
-
- string name1 = nameFormatter(domain1, p1[subjectIndex]);
- string name2 = nameFormatter(domain2, p2[subjectIndex]);
-
- return subjectHierarchyMap[name1] - subjectHierarchyMap[name2];
+ string domain = domainIndex != -1 ? policy[domainIndex] : "";
+ string name = nameFormatter(domain, policy[subjectIndex]);
+ return subjectHierarchyMap.TryGetValue(name, out int level) ?
level : 0;
}
Lock.EnterWriteLock();
try
{
- Policy.Sort(PolicyComparison);
+ List<IPolicyValues> sortedPolicy =
Policy.OrderByDescending(GetSubjectLevel).ToList();
+ Policy.Clear();
+ Policy.AddRange(sortedPolicy);
}
finally
{
diff --git a/Casbin/Model/RoleAssertion.cs b/Casbin/Model/RoleAssertion.cs
index 1537079..fa87a47 100644
--- a/Casbin/Model/RoleAssertion.cs
+++ b/Casbin/Model/RoleAssertion.cs
@@ -11,9 +11,19 @@ public class RoleAssertion : PolicyAssertion
public IRoleManager RoleManager { get; internal set; }
+ internal int GroupPolicyCount
+ {
+ get
+ {
+ int paramsIndex = Value.IndexOf('(');
+ string value = paramsIndex is -1 ? Value : Value.Substring(0,
paramsIndex);
+ return value.Count(c => c is '_');
+ }
+ }
+
public void BuildRoleLinks()
{
- int count = Value.Count(c => c is '_');
+ int count = GroupPolicyCount;
if (count < 2)
{
throw new InvalidOperationException("the number of \"_\" in role
definition should be at least 2.");
@@ -27,7 +37,7 @@ public class RoleAssertion : PolicyAssertion
internal void BuildIncrementalRoleLink(PolicyOperation policyOperation,
IEnumerable<string> rule)
{
- int count = Value.Count(c => c is '_');
+ int count = GroupPolicyCount;
if (count < 2)
{
throw new InvalidOperationException("the number of \"_\" in role
definition should be at least 2.");
@@ -39,7 +49,7 @@ public class RoleAssertion : PolicyAssertion
internal void BuildIncrementalRoleLink(PolicyOperation policyOperation,
IEnumerable<string> oldRule, IEnumerable<string> newRule)
{
- int count = Value.Count(c => c is '_');
+ int count = GroupPolicyCount;
if (count < 2)
{
throw new InvalidOperationException("the number of \"_\" in role
definition should be at least 2.");
@@ -50,7 +60,7 @@ public class RoleAssertion : PolicyAssertion
internal void BuildIncrementalRoleLinks(PolicyOperation policyOperation,
IEnumerable<IEnumerable<string>> rules)
{
- int count = Value.Count(c => c is '_');
+ int count = GroupPolicyCount;
if (count < 2)
{
throw new InvalidOperationException("the number of \"_\" in role
definition should be at least 2.");
@@ -65,7 +75,7 @@ public class RoleAssertion : PolicyAssertion
internal void BuildIncrementalRoleLinks(PolicyOperation policyOperation,
IEnumerable<IEnumerable<string>> oldRules,
IEnumerable<IEnumerable<string>> newRules)
{
- int count = Value.Count(c => c is '_');
+ int count = GroupPolicyCount;
if (count < 2)
{
throw new InvalidOperationException("the number of \"_\" in role
definition should be at least 2.");
@@ -198,7 +208,12 @@ public class RoleAssertion : PolicyAssertion
policyChildrenMap[parent] = new List<string>(new[] { child });
}
- refer[parent] = refer[child] = 0;
+ if (refer.ContainsKey(parent) is false)
+ {
+ refer[parent] = 0;
+ }
+
+ refer[child] = 1;
}
Queue<string> queue = new();
diff --git a/Casbin/Util/StringUtil.cs b/Casbin/Util/StringUtil.cs
index 1377cb6..c6cbd73 100644
--- a/Casbin/Util/StringUtil.cs
+++ b/Casbin/Util/StringUtil.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text.RegularExpressions;
namespace Casbin.Util
@@ -8,6 +9,7 @@ namespace Casbin.Util
{
private static readonly Regex s_evalRegex =
new(@"\beval\((?<rule>[^)]*)\)");
private static readonly Regex s_inOperatorRegex =
new(@"([a-zA-Z0-9_.()""]+) in ([a-zA-Z0-9_.()""]+)");
+ private static readonly Regex s_inListOperatorRegex =
new(@"([a-zA-Z0-9_.]+)\s+in\s+(?:\(([^()]*)\)|\[([^\[\]]*)\])");
/// <summary>
/// Removes the comments starting with # in the text.
@@ -86,7 +88,7 @@ namespace Casbin.Util
{
return match.Value;
}
- return rule;
+ return "(" + rule + ")";
});
}
@@ -109,5 +111,55 @@ namespace Casbin.Util
});
return res;
}
+
+ /// <summary>
+ /// Replace in operator with a literal list, such as "a in ('b',
'c')", with "(a == 'b' || a == 'c')".
+ /// </summary>
+ /// <param name="expressString"></param>
+ /// <returns></returns>
+ internal static string ReplaceInListOperator(string expressString)
+ {
+ return s_inListOperatorRegex.Replace(expressString, match =>
+ {
+ string item = match.Groups[1].Value;
+ string list = match.Groups[2].Success ? match.Groups[2].Value
: match.Groups[3].Value;
+ List<string> values = SplitListValues(list);
+ if (values.Count < 2 && (values.Count is 0 ||
values[0].StartsWith("\"") is false))
+ {
+ return match.Value;
+ }
+
+ return "(" + string.Join(" || ", values.Select(value => item +
" == " + value)) + ")";
+ });
+ }
+
+ private static List<string> SplitListValues(string list)
+ {
+ List<string> values = new();
+ int start = 0;
+ bool inQuotes = false;
+ for (int i = 0; i <= list.Length; i++)
+ {
+ if (i < list.Length && list[i] is '"')
+ {
+ inQuotes = !inQuotes;
+ }
+
+ if (i < list.Length && (inQuotes || list[i] is not ','))
+ {
+ continue;
+ }
+
+ string value = list.Substring(start, i - start).Trim();
+ if (value.Length is not 0)
+ {
+ values.Add(value);
+ }
+
+ start = i + 1;
+ }
+
+ return values;
+ }
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]