diff --git a/Casbin.UnitTests/Mock/MockSingleAdapter.cs b/Casbin.UnitTests/Mock/MockSingleAdapter.cs new file mode 100644 index 0000000..2efc7dc --- /dev/null +++ b/Casbin.UnitTests/Mock/MockSingleAdapter.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Casbin.Model; +using Casbin.Persist; +using Casbin.Persist.Adapter.File; + +namespace Casbin.UnitTests.Mock; + +public class MockSingleAdapter : FileAdapter, ISingleAdapter +{ + public List SavedPolicies { get; } = new(); + + public MockSingleAdapter(string filePath) : base(filePath) + { + } + + public void AddPolicy(string section, string policyType, IPolicyValues rule) + { + SavedPolicies.Add($"AddPolicy: {section}.{policyType} {rule.ToText()}"); + } + + public Task AddPolicyAsync(string section, string policyType, IPolicyValues rule) + { + SavedPolicies.Add($"AddPolicyAsync: {section}.{policyType} {rule.ToText()}"); +#if NET452 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif + } + + public void UpdatePolicy(string section, string policyType, IPolicyValues oldRule, IPolicyValues newRule) + { + SavedPolicies.Add($"UpdatePolicy: {section}.{policyType} {oldRule.ToText()} -> {newRule.ToText()}"); + } + + public Task UpdatePolicyAsync(string section, string policyType, IPolicyValues oldRules, IPolicyValues newRules) + { + SavedPolicies.Add($"UpdatePolicyAsync: {section}.{policyType} {oldRules.ToText()} -> {newRules.ToText()}"); +#if NET452 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif + } + + public void RemovePolicy(string section, string policyType, IPolicyValues rule) + { + SavedPolicies.Add($"RemovePolicy: {section}.{policyType} {rule.ToText()}"); + } + + public Task RemovePolicyAsync(string section, string policyType, IPolicyValues rule) + { + SavedPolicies.Add($"RemovePolicyAsync: {section}.{policyType} {rule.ToText()}"); +#if NET452 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif + } + + public void ClearSavedPolicies() + { + SavedPolicies.Clear(); + } +} diff --git a/Casbin.UnitTests/ModelTests/EnforcerTest.cs b/Casbin.UnitTests/ModelTests/EnforcerTest.cs index e67c298..8a25eb5 100644 --- a/Casbin.UnitTests/ModelTests/EnforcerTest.cs +++ b/Casbin.UnitTests/ModelTests/EnforcerTest.cs @@ -939,6 +939,59 @@ public async Task TestEnableAutoSaveAsync() Assert.True(await e.EnforceAsync("bob", "data2", "write")); } + [Fact] + public void TestAutoSaveGroupingPolicy() + { + // This test verifies that AddGroupingPolicy() respects the AutoSave flag. + // When AutoSave is disabled, grouping policy changes should not be saved to the adapter. + + MockSingleAdapter adapter = new("Examples/rbac_policy.csv"); + Enforcer e = new("Examples/rbac_model.conf", adapter); + + // Verify initial state: alice has data2_admin role + Assert.True(e.HasGroupingPolicy("alice", "data2_admin")); + Assert.False(e.HasGroupingPolicy("bob", "data2_admin")); + + adapter.ClearSavedPolicies(); + e.EnableAutoSave(false); + + // Because AutoSave is disabled, the grouping policy change should only affect + // the policy in Casbin enforcer, it should NOT call the adapter. + e.AddGroupingPolicy("bob", "data2_admin"); + + // Verify the change is in memory + Assert.True(e.HasGroupingPolicy("bob", "data2_admin")); + + // Verify the adapter was NOT called because AutoSave is disabled + Assert.Empty(adapter.SavedPolicies); + } + + [Fact] + public async Task TestAutoSaveGroupingPolicyAsync() + { + // This test verifies that AddGroupingPolicyAsync() respects the AutoSave flag. + // When AutoSave is disabled, grouping policy changes should not be saved to the adapter. + + MockSingleAdapter adapter = new("Examples/rbac_policy.csv"); + Enforcer e = new("Examples/rbac_model.conf", adapter); + + // Verify initial state + Assert.True(e.HasGroupingPolicy("alice", "data2_admin")); + Assert.False(e.HasGroupingPolicy("bob", "data2_admin")); + + adapter.ClearSavedPolicies(); + e.EnableAutoSave(false); + + // Add grouping policy with AutoSave disabled + await e.AddGroupingPolicyAsync("bob", "data2_admin"); + + // Verify the change is in memory + Assert.True(e.HasGroupingPolicy("bob", "data2_admin")); + + // Verify the adapter was NOT called because AutoSave is disabled + Assert.Empty(adapter.SavedPolicies); + } + [Fact] public void TestInitWithAdapter() { diff --git a/Casbin/Extensions/Model/ModelExtension.cs b/Casbin/Extensions/Model/ModelExtension.cs index e933be5..224a9e1 100644 --- a/Casbin/Extensions/Model/ModelExtension.cs +++ b/Casbin/Extensions/Model/ModelExtension.cs @@ -228,10 +228,20 @@ public static async Task SavePolicyAsync(this IModel model) public static void SetAutoSave(this IModel model, bool autoSave) { + // Set AutoSave for policy assertions (section "p") foreach (KeyValuePair pair in model.Sections.GetPolicyAssertions()) { pair.Value.PolicyManager.AutoSave = autoSave; } + + // Set AutoSave for role/grouping assertions (section "g") if they exist + if (model.Sections.ContainsSection(PermConstants.Section.RoleSection)) + { + foreach (KeyValuePair pair in model.Sections.GetRoleAssertions()) + { + pair.Value.PolicyManager.AutoSave = autoSave; + } + } } public static IPolicyManager GetPolicyManager(this IModel model, string section,