From c2a60b7492d151fb2b7a738aca3605a187d750e4 Mon Sep 17 00:00:00 2001 From: Thor Arne Johansen Date: Thu, 6 Nov 2025 12:55:36 +0100 Subject: [PATCH] fix: respect AutoSave flag for grouping policy operations The EnableAutoSave(false) setting was not being respected for RBAC/grouping policy operations. When AutoSave was disabled, policy operations (AddPolicy, RemovePolicy, UpdatePolicy) correctly skipped adapter saving, but grouping policy operations (AddGroupingPolicy, RemoveGroupingPolicy, etc.) still saved to the adapter. Root Cause: The SetAutoSave() method only iterated through policy assertions (section "p") to set the AutoSave flag. It never set the flag for role assertions (section "g"), even though RoleAssertion has its own PolicyManager with an AutoSave property. Solution: Modified SetAutoSave() to also configure the AutoSave flag for role/grouping assertions, with a safety check for models without role definitions. Changes: - Casbin/Extensions/Model/ModelExtension.cs: Updated SetAutoSave() to handle both policy and role assertions - Casbin.UnitTests/ModelTests/EnforcerTest.cs: Added comprehensive tests for sync and async grouping policy operations - Casbin.UnitTests/Mock/MockSingleAdapter.cs: Created test helper to track adapter save operations All 363 unit tests pass with no regressions. --- Casbin.UnitTests/Mock/MockSingleAdapter.cs | 66 +++++++++++++++++++++ Casbin.UnitTests/ModelTests/EnforcerTest.cs | 53 +++++++++++++++++ Casbin/Extensions/Model/ModelExtension.cs | 10 ++++ 3 files changed, 129 insertions(+) create mode 100644 Casbin.UnitTests/Mock/MockSingleAdapter.cs 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,