Skip to content

Commit 67dd0a4

Browse files
authored
feat: add LoadIncrementalFilteredPolicy() API for incremental filtered policy loading (#392)
1 parent e9b080e commit 67dd0a4

File tree

4 files changed

+392
-22
lines changed

4 files changed

+392
-22
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System.IO;
2+
using System.Threading.Tasks;
3+
using Casbin.Model;
4+
using Casbin.Persist;
5+
using Casbin.Persist.Adapter.File;
6+
using Xunit;
7+
8+
namespace Casbin.UnitTests.PersistTests;
9+
10+
public class IncrementalFilteredAdapterTest
11+
{
12+
[Fact]
13+
public void TestLoadIncrementalFilteredPolicy()
14+
{
15+
Enforcer e = new("Examples/rbac_model.conf");
16+
Assert.False(e.Enforce("alice", "data1", "read"));
17+
18+
FileAdapter a = new("Examples/rbac_policy.csv");
19+
e.SetAdapter(a);
20+
21+
// Load only p policies for alice
22+
e.LoadFilteredPolicy(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["alice"])));
23+
24+
// alice can read data1 (from p policy)
25+
Assert.True(e.Enforce("alice", "data1", "read"));
26+
// bob cannot write data2 (not loaded yet)
27+
Assert.False(e.Enforce("bob", "data2", "write"));
28+
29+
// Incrementally load p policies for data2_admin role
30+
e.LoadIncrementalFilteredPolicy(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["data2_admin"])));
31+
32+
// alice still can only read data1 (no role link yet)
33+
Assert.True(e.Enforce("alice", "data1", "read"));
34+
Assert.False(e.Enforce("alice", "data2", "read"));
35+
36+
// Incrementally load g policies for alice
37+
e.LoadIncrementalFilteredPolicy(new PolicyFilter(PermConstants.DefaultRoleType, 0, Policy.ValuesFrom(["alice"])));
38+
39+
// Now alice can read data2 through role inheritance
40+
Assert.True(e.Enforce("alice", "data2", "read"));
41+
Assert.True(e.Enforce("alice", "data2", "write"));
42+
// bob still cannot write data2 (not loaded)
43+
Assert.False(e.Enforce("bob", "data2", "write"));
44+
}
45+
46+
[Fact]
47+
public async Task TestLoadIncrementalFilteredPolicyAsync()
48+
{
49+
Enforcer e = new("Examples/rbac_model.conf");
50+
Assert.False(await e.EnforceAsync("alice", "data1", "read"));
51+
52+
FileAdapter a = new("Examples/rbac_policy.csv");
53+
e.SetAdapter(a);
54+
55+
// Load only p policies for alice
56+
await e.LoadFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["alice"])));
57+
58+
// alice can read data1 (from p policy)
59+
Assert.True(await e.EnforceAsync("alice", "data1", "read"));
60+
// bob cannot write data2 (not loaded yet)
61+
Assert.False(await e.EnforceAsync("bob", "data2", "write"));
62+
63+
// Incrementally load p policies for data2_admin role
64+
await e.LoadIncrementalFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["data2_admin"])));
65+
66+
// alice still can only read data1 (no role link yet)
67+
Assert.True(await e.EnforceAsync("alice", "data1", "read"));
68+
Assert.False(await e.EnforceAsync("alice", "data2", "read"));
69+
70+
// Incrementally load g policies for alice
71+
await e.LoadIncrementalFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultRoleType, 0, Policy.ValuesFrom(["alice"])));
72+
73+
// Now alice can read data2 through role inheritance
74+
Assert.True(await e.EnforceAsync("alice", "data2", "read"));
75+
Assert.True(await e.EnforceAsync("alice", "data2", "write"));
76+
// bob still cannot write data2 (not loaded)
77+
Assert.False(await e.EnforceAsync("bob", "data2", "write"));
78+
}
79+
80+
[Fact]
81+
public void TestLoadIncrementalFilteredPolicyMultiplePTypes()
82+
{
83+
Enforcer e = new("Examples/rbac_model.conf");
84+
Assert.False(e.Enforce("alice", "data1", "read"));
85+
86+
FileAdapter a = new("Examples/rbac_policy.csv");
87+
e.SetAdapter(a);
88+
89+
// Load only p policies for alice
90+
e.LoadFilteredPolicy(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["alice"])));
91+
92+
// alice can read data1
93+
Assert.True(e.Enforce("alice", "data1", "read"));
94+
// bob cannot write data2
95+
Assert.False(e.Enforce("bob", "data2", "write"));
96+
97+
// Incrementally load p policies for bob
98+
e.LoadIncrementalFilteredPolicy(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["bob"])));
99+
100+
// alice still can read data1
101+
Assert.True(e.Enforce("alice", "data1", "read"));
102+
// Now bob can write data2
103+
Assert.True(e.Enforce("bob", "data2", "write"));
104+
}
105+
106+
[Fact]
107+
public async Task TestLoadIncrementalFilteredPolicyMultiplePTypesAsync()
108+
{
109+
Enforcer e = new("Examples/rbac_model.conf");
110+
Assert.False(await e.EnforceAsync("alice", "data1", "read"));
111+
112+
FileAdapter a = new("Examples/rbac_policy.csv");
113+
e.SetAdapter(a);
114+
115+
// Load only p policies for alice
116+
await e.LoadFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["alice"])));
117+
118+
// alice can read data1
119+
Assert.True(await e.EnforceAsync("alice", "data1", "read"));
120+
// bob cannot write data2
121+
Assert.False(await e.EnforceAsync("bob", "data2", "write"));
122+
123+
// Incrementally load p policies for bob
124+
await e.LoadIncrementalFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["bob"])));
125+
126+
// alice still can read data1
127+
Assert.True(await e.EnforceAsync("alice", "data1", "read"));
128+
// Now bob can write data2
129+
Assert.True(await e.EnforceAsync("bob", "data2", "write"));
130+
}
131+
132+
[Fact]
133+
public void TestLoadFilteredPolicyClearsExistingPolicies()
134+
{
135+
Enforcer e = new("Examples/rbac_model.conf");
136+
FileAdapter a = new("Examples/rbac_policy.csv");
137+
e.SetAdapter(a);
138+
139+
// Load only p policies for alice
140+
e.LoadFilteredPolicy(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["alice"])));
141+
Assert.True(e.Enforce("alice", "data1", "read"));
142+
143+
// Load filtered policy again (should clear previous policies)
144+
e.LoadFilteredPolicy(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["bob"])));
145+
146+
// alice can no longer read data1 (previous policies cleared)
147+
Assert.False(e.Enforce("alice", "data1", "read"));
148+
// bob can write data2 (new filtered policies loaded)
149+
Assert.True(e.Enforce("bob", "data2", "write"));
150+
}
151+
152+
[Fact]
153+
public async Task TestLoadFilteredPolicyClearsExistingPoliciesAsync()
154+
{
155+
Enforcer e = new("Examples/rbac_model.conf");
156+
FileAdapter a = new("Examples/rbac_policy.csv");
157+
e.SetAdapter(a);
158+
159+
// Load only p policies for alice
160+
await e.LoadFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["alice"])));
161+
Assert.True(await e.EnforceAsync("alice", "data1", "read"));
162+
163+
// Load filtered policy again (should clear previous policies)
164+
await e.LoadFilteredPolicyAsync(new PolicyFilter(PermConstants.DefaultPolicyType, 0, Policy.ValuesFrom(["bob"])));
165+
166+
// alice can no longer read data1 (previous policies cleared)
167+
Assert.False(await e.EnforceAsync("alice", "data1", "read"));
168+
// bob can write data2 (new filtered policies loaded)
169+
Assert.True(await e.EnforceAsync("bob", "data2", "write"));
170+
}
171+
}

Casbin/Extensions/Enforcer/EnforcerExtension.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ public static bool LoadFilteredPolicy(this IEnforcer enforcer, IPolicyFilter fil
229229
return false;
230230
}
231231

232+
enforcer.ClearCache();
232233
if (enforcer.AutoBuildRoleLinks)
233234
{
234235
enforcer.BuildRoleLinks();
@@ -251,6 +252,7 @@ public static async Task<bool> LoadFilteredPolicyAsync(this IEnforcer enforcer,
251252
return false;
252253
}
253254

255+
enforcer.ClearCache();
254256
if (enforcer.AutoBuildRoleLinks)
255257
{
256258
enforcer.BuildRoleLinks();
@@ -269,6 +271,52 @@ public static async Task<bool> LoadFilteredPolicyAsync(this IEnforcer enforcer,
269271
public static Task<bool> LoadFilteredPolicyAsync(this IEnforcer enforcer, Filter filter) =>
270272
LoadFilteredPolicyAsync(enforcer, filter as IPolicyFilter);
271273

274+
/// <summary>
275+
/// Appends a filtered policy from file/database without clearing the existing policies.
276+
/// </summary>
277+
/// <param name="enforcer"></param>
278+
/// <param name="filter">The filter used to specify which type of policy should be loaded.</param>
279+
/// <returns></returns>
280+
public static bool LoadIncrementalFilteredPolicy(this IEnforcer enforcer, IPolicyFilter filter)
281+
{
282+
bool result = enforcer.Model.LoadIncrementalFilteredPolicy(filter);
283+
if (result is false)
284+
{
285+
return false;
286+
}
287+
288+
enforcer.ClearCache();
289+
if (enforcer.AutoBuildRoleLinks)
290+
{
291+
enforcer.BuildRoleLinks();
292+
}
293+
294+
return true;
295+
}
296+
297+
/// <summary>
298+
/// Appends a filtered policy from file/database without clearing the existing policies.
299+
/// </summary>
300+
/// <param name="enforcer"></param>
301+
/// <param name="filter">The filter used to specify which type of policy should be loaded.</param>
302+
/// <returns></returns>
303+
public static async Task<bool> LoadIncrementalFilteredPolicyAsync(this IEnforcer enforcer, IPolicyFilter filter)
304+
{
305+
bool result = await enforcer.Model.LoadIncrementalFilteredPolicyAsync(filter);
306+
if (result is false)
307+
{
308+
return false;
309+
}
310+
311+
enforcer.ClearCache();
312+
if (enforcer.AutoBuildRoleLinks)
313+
{
314+
enforcer.BuildRoleLinks();
315+
}
316+
317+
return true;
318+
}
319+
272320
/// <summary>
273321
/// Saves the current policy (usually after changed with Casbin API)
274322
/// back to file/database.

Casbin/Extensions/Model/ModelExtension.cs

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,28 @@ public static async Task<bool> LoadPolicyAsync(this IModel model)
9393
}
9494

9595
public static bool LoadFilteredPolicy(this IModel model, IPolicyFilter filter)
96+
{
97+
model.PolicyStoreHolder.PolicyStore?.ClearPolicy();
98+
return LoadFilteredPolicyInternal(model, filter);
99+
}
100+
101+
public static async Task<bool> LoadFilteredPolicyAsync(this IModel model, IPolicyFilter filter)
102+
{
103+
model.PolicyStoreHolder.PolicyStore?.ClearPolicy();
104+
return await LoadFilteredPolicyInternalAsync(model, filter);
105+
}
106+
107+
public static bool LoadIncrementalFilteredPolicy(this IModel model, IPolicyFilter filter)
108+
{
109+
return LoadFilteredPolicyInternal(model, filter);
110+
}
111+
112+
public static async Task<bool> LoadIncrementalFilteredPolicyAsync(this IModel model, IPolicyFilter filter)
113+
{
114+
return await LoadFilteredPolicyInternalAsync(model, filter);
115+
}
116+
117+
private static bool LoadFilteredPolicyInternal(IModel model, IPolicyFilter filter)
96118
{
97119
if (model.AdapterHolder.Adapter is null)
98120
{
@@ -104,26 +126,30 @@ public static bool LoadFilteredPolicy(this IModel model, IPolicyFilter filter)
104126
return false;
105127
}
106128

107-
DefaultPolicyStore policyStore = new();
108-
foreach (KeyValuePair<string, PolicyAssertion> pair in model.Sections.GetPolicyAssertions())
129+
// Initialize policy store if it doesn't exist
130+
if (model.PolicyStoreHolder.PolicyStore is null)
109131
{
110-
policyStore.AddNode(PermConstants.Section.PolicySection, pair.Key, pair.Value);
111-
}
132+
DefaultPolicyStore policyStore = new();
133+
foreach (KeyValuePair<string, PolicyAssertion> pair in model.Sections.GetPolicyAssertions())
134+
{
135+
policyStore.AddNode(PermConstants.Section.PolicySection, pair.Key, pair.Value);
136+
}
112137

113-
if (model.Sections.ContainsSection(PermConstants.Section.RoleSection))
114-
{
115-
foreach (KeyValuePair<string, RoleAssertion> pair in model.Sections.GetRoleAssertions())
138+
if (model.Sections.ContainsSection(PermConstants.Section.RoleSection))
116139
{
117-
policyStore.AddNode(PermConstants.Section.RoleSection, pair.Key, pair.Value);
140+
foreach (KeyValuePair<string, RoleAssertion> pair in model.Sections.GetRoleAssertions())
141+
{
142+
policyStore.AddNode(PermConstants.Section.RoleSection, pair.Key, pair.Value);
143+
}
118144
}
145+
model.PolicyStoreHolder.PolicyStore = policyStore;
119146
}
120147

121-
model.AdapterHolder.FilteredAdapter.LoadFilteredPolicy(policyStore, filter);
122-
model.PolicyStoreHolder.PolicyStore = policyStore;
148+
model.AdapterHolder.FilteredAdapter.LoadFilteredPolicy(model.PolicyStoreHolder.PolicyStore, filter);
123149
return true;
124150
}
125151

126-
public static async Task<bool> LoadFilteredPolicyAsync(this IModel model, IPolicyFilter filter)
152+
private static async Task<bool> LoadFilteredPolicyInternalAsync(IModel model, IPolicyFilter filter)
127153
{
128154
if (model.AdapterHolder.Adapter is null)
129155
{
@@ -135,23 +161,26 @@ public static async Task<bool> LoadFilteredPolicyAsync(this IModel model, IPolic
135161
return false;
136162
}
137163

138-
DefaultPolicyStore policyStore = new();
139-
foreach (KeyValuePair<string, PolicyAssertion> pair in model.Sections.GetPolicyAssertions())
164+
// Initialize policy store if it doesn't exist
165+
if (model.PolicyStoreHolder.PolicyStore is null)
140166
{
141-
policyStore.AddNode(PermConstants.Section.PolicySection, pair.Key, pair.Value);
142-
}
167+
DefaultPolicyStore policyStore = new();
168+
foreach (KeyValuePair<string, PolicyAssertion> pair in model.Sections.GetPolicyAssertions())
169+
{
170+
policyStore.AddNode(PermConstants.Section.PolicySection, pair.Key, pair.Value);
171+
}
143172

144-
if (model.Sections.ContainsSection(PermConstants.Section.RoleSection))
145-
{
146-
foreach (KeyValuePair<string, RoleAssertion> pair in model.Sections.GetRoleAssertions())
173+
if (model.Sections.ContainsSection(PermConstants.Section.RoleSection))
147174
{
148-
policyStore.AddNode(PermConstants.Section.RoleSection, pair.Key, pair.Value);
175+
foreach (KeyValuePair<string, RoleAssertion> pair in model.Sections.GetRoleAssertions())
176+
{
177+
policyStore.AddNode(PermConstants.Section.RoleSection, pair.Key, pair.Value);
178+
}
149179
}
180+
model.PolicyStoreHolder.PolicyStore = policyStore;
150181
}
151182

152-
await model.AdapterHolder.FilteredAdapter.LoadFilteredPolicyAsync(policyStore,
153-
filter);
154-
model.PolicyStoreHolder.PolicyStore = policyStore;
183+
await model.AdapterHolder.FilteredAdapter.LoadFilteredPolicyAsync(model.PolicyStoreHolder.PolicyStore, filter);
155184
return true;
156185
}
157186

0 commit comments

Comments
 (0)