Skip to content

feat: Add priority sort with subject hierarchy. #259

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

Merged
merged 1 commit into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Casbin.UnitTest/Casbin.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@
<None Update="Examples\rbac_with_resource_roles_policy.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Examples\subject_priority_model.conf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Examples\subject_priority_policy.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Examples\subject_priority_model_with_domain.conf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Examples\subject_priority_policy_with_domain.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Examples\support_count_model.conf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
4 changes: 4 additions & 0 deletions Casbin.UnitTest/Fixtures/TestModelFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public class TestModelFixture
internal readonly string _rbacWithHierarchyPolicyText = ReadTestFile("rbac_with_hierarchy_policy.csv");
internal readonly string _rbacWithHierarchyWithDomainsPolicyText = ReadTestFile("rbac_with_hierarchy_with_domains_policy.csv");
internal readonly string _rbacWithResourceRolePolicyText = ReadTestFile("rbac_with_resource_roles_policy.csv");
internal readonly string _subjectPriorityModelText = ReadTestFile("subject_priority_model.conf");
internal readonly string _subjectPriorityPolicyText = ReadTestFile("subject_priority_policy.csv");
internal readonly string _subjectPriorityWithDomainModelText = ReadTestFile("subject_priority_model_with_domain.conf");
internal readonly string _subjectPriorityWithDomainPolicyText = ReadTestFile("subject_priority_policy_with_domain.csv");

// https://github.com/casbin/Casbin.NET/issues/154
internal readonly string _rbacMultipleModelText = ReadTestFile("rbac_multiple_rolemanager_model.conf");
Expand Down
30 changes: 30 additions & 0 deletions Casbin.UnitTest/ModelTests/EnforcerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1032,5 +1032,35 @@ public void TestEnforceWithLazyLoadPolicy()
e = DefaultEnforcer.Create(m, a);
Assert.NotEmpty(e.GetPolicy());
}

[Fact]
public void TestEnforceSubjectPriority()
{
var e = new Enforcer(TestModelFixture.GetNewTestModel(
_testModelFixture._subjectPriorityModelText,
_testModelFixture._subjectPriorityPolicyText));

TestEnforce(e, "jane", "data1", "read", true);
TestEnforce(e, "alice", "data1", "read", true);
}

[Fact]
public void TestEnforceSubjectPriorityWithDomain()
{
var e = new Enforcer(
Path.Combine("Examples", "subject_priority_model_with_domain.conf"),
Path.Combine("Examples", "subject_priority_policy_with_domain.csv"));

Assert.True(e.Enforce(
"alice",
"data1",
"domain1",
"write"));
Assert.True(e.Enforce(
"bob",
"data2",
"domain2",
"write"));
}
}
}
14 changes: 14 additions & 0 deletions Casbin.UnitTest/examples/subject_priority_model.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act, eft

[role_definition]
g = _, _

[policy_effect]
e = subjectPriority(p.eft) || deny

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
14 changes: 14 additions & 0 deletions Casbin.UnitTest/examples/subject_priority_model_with_domain.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, dom, act

[policy_definition]
p = sub, obj, dom, act, eft

[role_definition]
g = _, _, _

[policy_effect]
e = subjectPriority(p.eft) || deny

[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
17 changes: 17 additions & 0 deletions Casbin.UnitTest/examples/subject_priority_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
p, root, data1, read, deny
p, admin, data1, read, deny

p, editor, data1, read, deny
p, subscriber, data1, read, deny
p, subscriber, data1, write, deny

p, jane, data1, read, allow
p, alice, data1, read, allow

g, admin, root

g, editor, admin
g, subscriber, admin

g, jane, editor
g, alice, subscriber
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
p, admin, data1, domain1, write, deny
p, alice, data1, domain1, write, allow
p, admin, data2, domain2, write, deny
p, bob, data2, domain2, write, allow

g, alice, admin, domain1
g, bob, admin, domain2
2 changes: 2 additions & 0 deletions Casbin/Abstractions/Model/IModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ public void BuildIncrementalRoleLinks(PolicyOperation policyOperation,
public void RefreshPolicyStringSet();

public void SortPoliciesByPriority();

public void SortPoliciesBySubjectHierarchy();
}
}
1 change: 1 addition & 0 deletions Casbin/Effect/DefaultEffector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public PolicyEffect MergeEffects(string effectExpression, IReadOnlyList<PolicyEf
PermConstants.PolicyEffect.DenyOverride => EffectExpressionType.DenyOverride,
PermConstants.PolicyEffect.AllowAndDeny => EffectExpressionType.AllowAndDeny,
PermConstants.PolicyEffect.Priority => EffectExpressionType.Priority,
PermConstants.PolicyEffect.SubjectPriority => EffectExpressionType.PriorityAllOverride,
PermConstants.PolicyEffect.PriorityDenyOverride => EffectExpressionType.PriorityDenyOverride,
_ => throw new NotSupportedException("Not supported policy effect.")
};
Expand Down
3 changes: 2 additions & 1 deletion Casbin/Effect/EffectExpressionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum EffectExpressionType
AllowAndDeny,
DenyOverride,
Priority,
PriorityDenyOverride
PriorityDenyOverride,
PriorityAllOverride
}
}
2 changes: 2 additions & 0 deletions Casbin/Enforcer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public Enforcer(IModel model, IReadOnlyAdapter adapter = null, bool lazyLoadPoli
{
this.LoadPolicy();
}
Model.SortPoliciesByPriority();
Model.SortPoliciesBySubjectHierarchy();
}

#region Options
Expand Down
14 changes: 14 additions & 0 deletions Casbin/Evaluation/EffiectEvaluation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ internal static bool TryEvaluate(PolicyEffect effect, EffectExpressionType effec
}
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;
Expand Down
2 changes: 2 additions & 0 deletions Casbin/Extensions/Enforcer/EnforcerExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ public static bool LoadPolicy(this IEnforcer enforcer)

enforcer.ClearCache();
enforcer.Model.RefreshPolicyStringSet();

if (enforcer.AutoBuildRoleLinks)
{
enforcer.BuildRoleLinks();
Expand All @@ -258,6 +259,7 @@ public static async Task<bool> LoadPolicyAsync(this IEnforcer enforcer)

enforcer.ClearCache();
enforcer.Model.RefreshPolicyStringSet();

if (enforcer.AutoBuildRoleLinks)
{
enforcer.BuildRoleLinks();
Expand Down
50 changes: 50 additions & 0 deletions Casbin/Model/Assertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,5 +237,55 @@ int PolicyComparison(IPolicyValues p1, IPolicyValues p2)
_policy.Sort(PolicyComparison);
return true;
}

private bool TryGetSubjectHierarchyDomainIndex(out int index)
{
if (Tokens is null)
{
index = -1;
return false;
}
return Tokens.TryGetValue("dom", out index);
}

private bool TryGetSubjectHierarchySubjectIndex(out int index)
{
if (Tokens is null)
{
index = -1;
return false;
}
return Tokens.TryGetValue("sub", out index);
}


internal bool TrySortPoliciesBySubjectHierarchy(Dictionary<string, int> subjectHierarchyMap, Func<string, string, string> nameFormatter)
{
if(TryGetSubjectHierarchyDomainIndex(out int domainIndex) is false)
{
domainIndex = -1;
}
if (TryGetSubjectHierarchySubjectIndex(out int subjectIndex) is false)
{
return false;
}


int PolicyComparison(IPolicyValues p1, IPolicyValues p2)
{
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];
}
_policy.Sort(PolicyComparison);
return true;
}
}
}
70 changes: 70 additions & 0 deletions Casbin/Model/DefaultModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,76 @@ public void SortPoliciesByPriority()
}
}

public void SortPoliciesBySubjectHierarchy()
{
if (Sections.Count == 0)
{
return;
}
if (!Sections[PermConstants.DefaultPolicyEffectType][PermConstants.DefaultPolicyEffectType].Value.Equals(PermConstants.PolicyEffect.SubjectPriority))
{
return;
}
var subjectHierarchyMap = GetSubjectHierarchyMap(Sections[PermConstants.DefaultRoleType][PermConstants.DefaultRoleType].Policy);
foreach (var keyValuePair in Sections[PermConstants.DefaultPolicyType])
{
var assertion = keyValuePair.Value;
assertion.TrySortPoliciesBySubjectHierarchy(subjectHierarchyMap, GetNameWithDomain);
}
}

private string GetNameWithDomain(string domain, string name)
{
return domain + PermConstants.SubjectPrioritySeparatorString + name;
}

private Dictionary<string, int> GetSubjectHierarchyMap(IReadOnlyList<IPolicyValues> policies)
{
Dictionary<string, int> refer = new Dictionary<string, int>();
Dictionary<string, int> res = new Dictionary<string, int>();
Dictionary<string, List<string>> policyChildenMap = new Dictionary<string, List<string>>();
foreach(var policy in policies)
{
string domain = policy.Count > 2 ? policy[2] : null;
string child = GetNameWithDomain(domain, policy[0]);
string parent = GetNameWithDomain(domain, policy[1]);
if (policyChildenMap.ContainsKey(parent))
{
policyChildenMap[parent].Add(child);
}
else
{
policyChildenMap[parent] = new List<string>(new string[] { child });
}
refer[parent] = refer[child] = 0;
}
Queue<string> q = new Queue<string>();
foreach(var keyValuePair in refer)
{
if (keyValuePair.Value != 0) continue;
int level = 0;
q.Enqueue(keyValuePair.Key);
while (q.Count > 0)
{
int size = q.Count;
while (size-- > 0)
{
var node = q.Dequeue();
res[node] = level;
if (policyChildenMap.ContainsKey(node))
{
foreach(var child in policyChildenMap[node])
{
q.Enqueue(child);
}
}
}
level++;
}
}
return res;
}

private void LoadModel(IConfig config)
{
LoadSection(config, PermConstants.Section.RequestSection);
Expand Down
2 changes: 2 additions & 0 deletions Casbin/PermConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public static class PermConstants
{
public const char PolicySeparatorChar = ',';
public const string PolicySeparatorString = ", "; // include a white space
public const string SubjectPrioritySeparatorString = "::";

public const string DefaultRequestType = "r";
public const string RequestType2 = "r2";
Expand Down Expand Up @@ -60,6 +61,7 @@ public static class PolicyEffect
public const string DenyOverride = "!some(where (p.eft == deny))";
public const string AllowAndDeny = "some(where (p.eft == allow)) && !some(where (p.eft == deny))";
public const string Priority = "priority(p.eft) || deny";
public const string SubjectPriority = "subjectPriority(p.eft) || deny";
public const string PriorityDenyOverride = "priority(p.eft) && !some(where (p.eft == deny))";
}
}
Expand Down