Skip to content

Commit d394cba

Browse files
[FC-0099] refactor: disable enforcer auto save when content libraries V2 is off (#126)
1 parent d1b0f30 commit d394cba

6 files changed

Lines changed: 411 additions & 27 deletions

File tree

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ Unreleased
1616

1717
*
1818

19+
0.11.0 - 2025-10-29
20+
********************
21+
22+
Added
23+
=====
24+
25+
* Disable auto-save and auto-load of policies if Content Library V2 is disabled.
26+
1927
0.10.1 - 2025-10-28
2028
********************
2129

openedx_authz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
import os
66

7-
__version__ = "0.10.1"
7+
__version__ = "0.11.0"
88

99
ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))

openedx_authz/engine/enforcer.py

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323

2424
from openedx_authz.engine.adapter import ExtendedAdapter
2525

26+
try:
27+
from cms.djangoapps.contentstore.toggles import libraries_v2_enabled
28+
except ImportError:
29+
# If the CMS is not available, define a dummy toggle that is always enabled
30+
class DummyToggle:
31+
@staticmethod
32+
def is_enabled():
33+
return True
34+
35+
libraries_v2_enabled = DummyToggle()
36+
37+
2638
logger = logging.getLogger(__name__)
2739

2840

@@ -57,6 +69,84 @@ def __new__(cls):
5769
cls._enforcer = cls._initialize_enforcer()
5870
return cls._enforcer
5971

72+
@classmethod
73+
def configure_enforcer_auto_loading(cls, auto_load_policy_interval: int = None):
74+
"""Enable auto-load policy and auto-save on the enforcer.
75+
76+
This method ensures that the singleton enforcer instance is created
77+
and ready for use.
78+
79+
Returns:
80+
None
81+
"""
82+
if not cls._enforcer.is_auto_loading_running():
83+
cls._enforcer.start_auto_load_policy(auto_load_policy_interval)
84+
85+
@classmethod
86+
def is_auto_save_enabled(cls) -> bool:
87+
"""Check if auto-save is currently enabled on the enforcer.
88+
89+
Returns:
90+
bool: True if auto-save is enabled, False otherwise
91+
"""
92+
if cls._enforcer is None:
93+
return False
94+
return cls._enforcer._e.auto_save # pylint: disable=protected-access
95+
96+
@classmethod
97+
def configure_enforcer_auto_save(cls, auto_save_policy: bool):
98+
"""Configure auto-save on the enforcer.
99+
100+
This method ensures that auto-save is enabled or disabled based on
101+
the auto_save_policy parameter.
102+
103+
Args:
104+
auto_save_policy: True to enable auto-save, False to disable
105+
106+
Returns:
107+
None
108+
"""
109+
if cls.is_auto_save_enabled() != auto_save_policy:
110+
cls._enforcer.enable_auto_save(auto_save_policy)
111+
112+
@classmethod
113+
def deactivate_enforcer(cls):
114+
"""Deactivate the current enforcer instance, if any.
115+
116+
This method stops the auto-load policy thread. It can be used in testing
117+
or when re-initialization of the enforcer is needed. IT DOES NOT
118+
clear the singleton instance to avoid initializing it again unintentionally.
119+
120+
Returns:
121+
None
122+
"""
123+
if cls._enforcer is not None:
124+
try:
125+
cls._enforcer.stop_auto_load_policy()
126+
cls._enforcer.enable_auto_save(False)
127+
except Exception as e: # pylint: disable=broad-exception-caught
128+
logger.error(f"Error stopping auto-load policy thread: {e}")
129+
130+
@classmethod
131+
def configure_enforcer_auto_save_and_load(cls):
132+
"""Enable auto-load policy and auto-save on the enforcer.
133+
134+
This method ensures that the singleton enforcer instance is configured
135+
for auto-load and auto-save based on settings.
136+
137+
Returns:
138+
None
139+
"""
140+
auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", 0)
141+
auto_save_policy = getattr(settings, "CASBIN_AUTO_SAVE_POLICY", True)
142+
143+
if auto_load_policy_interval > 0:
144+
cls.configure_enforcer_auto_loading(auto_load_policy_interval)
145+
else:
146+
logger.warning("CASBIN_AUTO_LOAD_POLICY_INTERVAL is not set or zero; auto-load is disabled.")
147+
148+
cls.configure_enforcer_auto_save(auto_save_policy)
149+
60150
@classmethod
61151
def get_enforcer(cls) -> SyncedEnforcer:
62152
"""Get the enforcer instance, creating it if needed.
@@ -66,10 +156,21 @@ def get_enforcer(cls) -> SyncedEnforcer:
66156
"""
67157
if cls._enforcer is None:
68158
cls._enforcer = cls._initialize_enforcer()
159+
160+
# HACK: This code block will only be useful when in Ulmo to deactivate
161+
# the enforcer when the new library experience is disabled. It should be
162+
# removed for the next release cycle.
163+
# When replaced, we will only need to configure the enforcer here. Which
164+
# is in charge of enabling/disabling auto-load and auto-save.
165+
if libraries_v2_enabled.is_enabled():
166+
cls.configure_enforcer_auto_save_and_load()
167+
else:
168+
cls.deactivate_enforcer()
169+
69170
return cls._enforcer
70171

71-
@staticmethod
72-
def _initialize_enforcer() -> SyncedEnforcer:
172+
@classmethod
173+
def _initialize_enforcer(cls) -> SyncedEnforcer:
73174
"""
74175
Create and configure the Casbin SyncedEnforcer instance.
75176
@@ -93,12 +194,5 @@ def _initialize_enforcer() -> SyncedEnforcer:
93194

94195
adapter = ExtendedAdapter()
95196
enforcer = SyncedEnforcer(settings.CASBIN_MODEL, adapter)
96-
auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", 0)
97-
if auto_load_policy_interval > 0:
98-
enforcer.start_auto_load_policy(auto_load_policy_interval)
99-
enforcer.enable_auto_save(True)
100-
else:
101-
# Disable auto-save to prevent unnecessary database writes
102-
enforcer.enable_auto_save(False)
103197

104198
return enforcer

openedx_authz/settings/common.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,22 @@ def plugin_settings(settings):
2020
casbin_adapter_app = "openedx_authz.engine.apps.CasbinAdapterConfig"
2121
if casbin_adapter_app not in settings.INSTALLED_APPS:
2222
settings.INSTALLED_APPS.append(casbin_adapter_app)
23-
# Add Casbin configuration
24-
settings.CASBIN_MODEL = os.path.join(ROOT_DIRECTORY, "engine", "config", "model.conf")
23+
24+
# Casbin settings for model and policy synchronization
25+
26+
# Set default CASBIN_MODEL if not already set, this points to the model.conf file
27+
# which defines the access control model for Casbin.
28+
if not hasattr(settings, "CASBIN_MODEL"):
29+
settings.CASBIN_MODEL = os.path.join(ROOT_DIRECTORY, "engine", "config", "model.conf")
30+
31+
# Set default CASBIN_AUTO_LOAD_POLICY_INTERVAL if not already set.
32+
# This setting defines how often (in seconds) the Casbin enforcer should
33+
# automatically reload policies from the database.
2534
if not hasattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL"):
2635
settings.CASBIN_AUTO_LOAD_POLICY_INTERVAL = 5
36+
37+
# Set default CASBIN_AUTO_SAVE_POLICY if not already set.
38+
# This setting defines whether the Casbin enforcer should automatically
39+
# save policy changes back to the database.
40+
if not hasattr(settings, "CASBIN_AUTO_SAVE_POLICY"):
41+
settings.CASBIN_AUTO_SAVE_POLICY = True

openedx_authz/settings/test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ def plugin_settings(settings): # pylint: disable=unused-argument
7070
# Casbin configuration
7171
CASBIN_MODEL = os.path.join(ROOT_DIRECTORY, "engine", "config", "model.conf")
7272
CASBIN_AUTO_LOAD_POLICY_INTERVAL = 0
73+
CASBIN_AUTO_SAVE_POLICY = True

0 commit comments

Comments
 (0)