From 79f9bf5e7bc686993f727ef0ff7456ce40a9f11e Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 23 Oct 2025 15:05:46 -0500 Subject: [PATCH 1/7] feat: allow disabling auto-load policy in AuthzEnforcer based on settings --- openedx_authz/engine/enforcer.py | 4 +- openedx_authz/tests/test_enforcer.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/openedx_authz/engine/enforcer.py b/openedx_authz/engine/enforcer.py index 0e45ac9c..67c20ef2 100644 --- a/openedx_authz/engine/enforcer.py +++ b/openedx_authz/engine/enforcer.py @@ -93,7 +93,9 @@ def _initialize_enforcer() -> SyncedEnforcer: adapter = ExtendedAdapter() enforcer = SyncedEnforcer(settings.CASBIN_MODEL, adapter) - enforcer.start_auto_load_policy(settings.CASBIN_AUTO_LOAD_POLICY_INTERVAL) + auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", -1) + if auto_load_policy_interval != -1: + enforcer.start_auto_load_policy(auto_load_policy_interval) enforcer.enable_auto_save(True) return enforcer diff --git a/openedx_authz/tests/test_enforcer.py b/openedx_authz/tests/test_enforcer.py index 6d2dccdc..64981379 100644 --- a/openedx_authz/tests/test_enforcer.py +++ b/openedx_authz/tests/test_enforcer.py @@ -520,3 +520,61 @@ def test_auto_load_policy_detects_changes(self): # After auto-load, the new role assignment should be loaded policies_after_auto_load = global_enforcer.get_grouping_policy() self.assertIn(new_assignment, policies_after_auto_load) + + @override_settings(CASBIN_AUTO_LOAD_POLICY_INTERVAL=-1) + def test_auto_load_disabled(self): + """Test that auto-load can be disabled by setting interval to -1. + + This test verifies that when CASBIN_AUTO_LOAD_POLICY_INTERVAL is set to -1, + the enforcer does NOT automatically load policies from the database. + + Expected result: + - Policies remain empty after seeding database + - Manual load_policy() is required to load policies + - New policies don't appear without manual reload + """ + global_enforcer = AuthzEnforcer.get_enforcer() + self._seed_database_with_policies() + + # Initial policy count should be 0 + initial_policy_count = len(global_enforcer.get_policy()) + self.assertEqual(initial_policy_count, 0) + + # Wait a bit to ensure auto-load would have occurred if enabled + time.sleep(1.0) + + # Policies should still be empty since auto-load is disabled + policies_after_wait = global_enforcer.get_policy() + self.assertEqual(len(policies_after_wait), 0) + + # Manually load policies + global_enforcer.load_policy() + policies_after_manual_load = global_enforcer.get_policy() + self.assertGreater(len(policies_after_manual_load), 0) + + # Add a new policy (which gets auto-saved to DB) + new_policy = [ + make_role_key("fake_role"), + make_action_key("fake_action"), + make_scope_key("lib", "*"), + "allow", + ] + global_enforcer.add_policy(*new_policy) + + # Wait to ensure auto-load would have occurred if enabled + time.sleep(1.0) + + # Clear the in-memory policies to simulate a fresh state + policy_count_before_clear = len(global_enforcer.get_policy()) + global_enforcer.clear_policy() + + # Wait again - auto-load should NOT reload policies + time.sleep(1.0) + policies_after_clear = global_enforcer.get_policy() + self.assertEqual(len(policies_after_clear), 0) + + # Manually reload to verify the new policy was saved to DB + global_enforcer.load_policy() + policies_after_reload = global_enforcer.get_policy() + self.assertEqual(len(policies_after_reload), policy_count_before_clear) + self.assertIn(new_policy, policies_after_reload) From e0463ed649c87ac33ac136cbf13ba922996250e5 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 23 Oct 2025 15:28:50 -0500 Subject: [PATCH 2/7] chore: add unreleased change in changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa5e87d3..4ec4b48a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ Unreleased * Migrate from using pycodestyle and isort to ruff for code quality checks and formatting. * Enhance enforcement command with dual operational modes (database and file mode). +* Allow disabling auto load policy interval. 0.7.0 - 2025-10-23 ****************** From 5589b4661521e8f9b04e23a6cf1c629bd4c48752 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Fri, 24 Oct 2025 10:56:14 -0500 Subject: [PATCH 3/7] refactor: update auto-load policy interval validation --- openedx_authz/engine/enforcer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openedx_authz/engine/enforcer.py b/openedx_authz/engine/enforcer.py index 67c20ef2..5c242f67 100644 --- a/openedx_authz/engine/enforcer.py +++ b/openedx_authz/engine/enforcer.py @@ -93,8 +93,8 @@ def _initialize_enforcer() -> SyncedEnforcer: adapter = ExtendedAdapter() enforcer = SyncedEnforcer(settings.CASBIN_MODEL, adapter) - auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", -1) - if auto_load_policy_interval != -1: + auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", 0) + if auto_load_policy_interval > 0: enforcer.start_auto_load_policy(auto_load_policy_interval) enforcer.enable_auto_save(True) From eee679f7cd01596c762be2341bd4686d89911d28 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Fri, 24 Oct 2025 11:26:37 -0500 Subject: [PATCH 4/7] fix: ensure auto-save is disabled when auto-load policy interval is not set --- openedx_authz/engine/enforcer.py | 5 ++- openedx_authz/tests/test_enforcer.py | 51 ++++++++-------------------- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/openedx_authz/engine/enforcer.py b/openedx_authz/engine/enforcer.py index 5c242f67..cc591276 100644 --- a/openedx_authz/engine/enforcer.py +++ b/openedx_authz/engine/enforcer.py @@ -96,6 +96,9 @@ def _initialize_enforcer() -> SyncedEnforcer: auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", 0) if auto_load_policy_interval > 0: enforcer.start_auto_load_policy(auto_load_policy_interval) - enforcer.enable_auto_save(True) + enforcer.enable_auto_save(True) + else: + # Disable auto-save to prevent unnecessary database writes + enforcer.enable_auto_save(False) return enforcer diff --git a/openedx_authz/tests/test_enforcer.py b/openedx_authz/tests/test_enforcer.py index 64981379..878a3c3c 100644 --- a/openedx_authz/tests/test_enforcer.py +++ b/openedx_authz/tests/test_enforcer.py @@ -534,47 +534,26 @@ def test_auto_load_disabled(self): - New policies don't appear without manual reload """ global_enforcer = AuthzEnforcer.get_enforcer() - self._seed_database_with_policies() # Initial policy count should be 0 initial_policy_count = len(global_enforcer.get_policy()) self.assertEqual(initial_policy_count, 0) - # Wait a bit to ensure auto-load would have occurred if enabled - time.sleep(1.0) - # Policies should still be empty since auto-load is disabled - policies_after_wait = global_enforcer.get_policy() - self.assertEqual(len(policies_after_wait), 0) - - # Manually load policies - global_enforcer.load_policy() - policies_after_manual_load = global_enforcer.get_policy() - self.assertGreater(len(policies_after_manual_load), 0) - - # Add a new policy (which gets auto-saved to DB) - new_policy = [ - make_role_key("fake_role"), - make_action_key("fake_action"), - make_scope_key("lib", "*"), - "allow", - ] - global_enforcer.add_policy(*new_policy) - - # Wait to ensure auto-load would have occurred if enabled - time.sleep(1.0) - - # Clear the in-memory policies to simulate a fresh state - policy_count_before_clear = len(global_enforcer.get_policy()) - global_enforcer.clear_policy() + # and no database queries should have been made + with self.assertNumQueries(0): + time.sleep(1.0) + policies_after_wait = global_enforcer.get_policy() + self.assertEqual(len(policies_after_wait), 0) - # Wait again - auto-load should NOT reload policies - time.sleep(1.0) - policies_after_clear = global_enforcer.get_policy() - self.assertEqual(len(policies_after_clear), 0) + # Seed the database with policies + self._seed_database_with_policies() - # Manually reload to verify the new policy was saved to DB - global_enforcer.load_policy() - policies_after_reload = global_enforcer.get_policy() - self.assertEqual(len(policies_after_reload), policy_count_before_clear) - self.assertIn(new_policy, policies_after_reload) + # Manually load policies + with self.assertNumQueries(1): + time.sleep(1.0) + global_enforcer.load_policy() + # Since auto-save is also disabled, the policies should still + # be empty after manual load + policies_after_manual_load = global_enforcer.get_policy() + self.assertEqual(len(policies_after_manual_load), 0) From a64a369412238c97b67f91749995f09247fecad8 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Fri, 24 Oct 2025 11:35:10 -0500 Subject: [PATCH 5/7] fix: update CASBIN_AUTO_LOAD_POLICY_INTERVAL to 0.5 to enable auto-save in tests --- openedx_authz/settings/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx_authz/settings/test.py b/openedx_authz/settings/test.py index c187760f..9d73438d 100644 --- a/openedx_authz/settings/test.py +++ b/openedx_authz/settings/test.py @@ -69,4 +69,4 @@ def plugin_settings(settings): # pylint: disable=unused-argument # Casbin configuration CASBIN_MODEL = os.path.join(ROOT_DIRECTORY, "engine", "config", "model.conf") -CASBIN_AUTO_LOAD_POLICY_INTERVAL = 0 +CASBIN_AUTO_LOAD_POLICY_INTERVAL = 0.5 From cf6f1dea55888eebc108d09a0da3cec42521c817 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Fri, 24 Oct 2025 12:22:42 -0500 Subject: [PATCH 6/7] chore: bump version to 0.8.0 --- CHANGELOG.rst | 14 +++++++++++++- openedx_authz/__init__.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4ec4b48a..4e1283d5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,9 +14,21 @@ Change Log Unreleased ********** +* + +0.8.0 - 2025-10-24 +****************** + +Added +===== + +* Allow disabling auto-load and auto-save of policies by setting CASBIN_AUTO_LOAD_POLICY_INTERVAL to -1. + +Changed +======= + * Migrate from using pycodestyle and isort to ruff for code quality checks and formatting. * Enhance enforcement command with dual operational modes (database and file mode). -* Allow disabling auto load policy interval. 0.7.0 - 2025-10-23 ****************** diff --git a/openedx_authz/__init__.py b/openedx_authz/__init__.py index 1415675a..28dd401f 100644 --- a/openedx_authz/__init__.py +++ b/openedx_authz/__init__.py @@ -4,6 +4,6 @@ import os -__version__ = "0.7.0" +__version__ = "0.8.0" ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) From a7bf0d40368bf754597176bdbcd42c2e3691eba0 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Fri, 24 Oct 2025 12:33:42 -0500 Subject: [PATCH 7/7] fix: set CASBIN_AUTO_LOAD_POLICY_INTERVAL to 0 and enable auto-save in tests --- openedx_authz/settings/test.py | 2 +- openedx_authz/tests/api/test_roles.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openedx_authz/settings/test.py b/openedx_authz/settings/test.py index 9d73438d..c187760f 100644 --- a/openedx_authz/settings/test.py +++ b/openedx_authz/settings/test.py @@ -69,4 +69,4 @@ def plugin_settings(settings): # pylint: disable=unused-argument # Casbin configuration CASBIN_MODEL = os.path.join(ROOT_DIRECTORY, "engine", "config", "model.conf") -CASBIN_AUTO_LOAD_POLICY_INTERVAL = 0.5 +CASBIN_AUTO_LOAD_POLICY_INTERVAL = 0 diff --git a/openedx_authz/tests/api/test_roles.py b/openedx_authz/tests/api/test_roles.py index ffac5f9c..0f4441ac 100644 --- a/openedx_authz/tests/api/test_roles.py +++ b/openedx_authz/tests/api/test_roles.py @@ -94,6 +94,9 @@ def setUpClass(cls): """ super().setUpClass() AuthzEnforcer.get_enforcer().stop_auto_load_policy() + # Enable auto-save to ensure policies are saved to the database + # This is necessary because the tests are not using auto-load policy + AuthzEnforcer.get_enforcer().enable_auto_save(True) cls._seed_database_with_policies() def setUp(self):