Skip to content

Commit ff0d841

Browse files
committed
squash!: Changed approach to use DB instead of cache
1 parent cd14a07 commit ff0d841

4 files changed

Lines changed: 83 additions & 17 deletions

File tree

openedx_authz/engine/enforcer.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
from casbin import SyncedEnforcer
2222
from casbin_adapter.enforcer import initialize_enforcer
2323
from django.conf import settings
24-
from django.core.cache import cache
2524

2625
from openedx_authz.engine.adapter import ExtendedAdapter
2726
from openedx_authz.engine.matcher import is_admin_or_superuser_check
27+
from openedx_authz.models.engine import PolicyCacheControl
2828

2929

3030
def libraries_v2_enabled() -> bool:
@@ -69,8 +69,6 @@ class AuthzEnforcer:
6969
_adapter (ExtendedAdapter): The singleton adapter instance.
7070
"""
7171

72-
CACHE_KEY = "authz_policy_last_modified_timestamp"
73-
7472
_enforcer = None
7573
_adapter = None
7674
_last_policy_load_timestamp = None
@@ -152,7 +150,6 @@ def configure_enforcer_auto_save_and_load(cls):
152150
auto_load_policy_interval = getattr(settings, "CASBIN_AUTO_LOAD_POLICY_INTERVAL", 0)
153151
auto_save_policy = getattr(settings, "CASBIN_AUTO_SAVE_POLICY", True)
154152

155-
# TODO: remove autoload in favor of cache invalidation?
156153
if auto_load_policy_interval > 0:
157154
cls.configure_enforcer_auto_loading(auto_load_policy_interval)
158155
else:
@@ -171,20 +168,20 @@ def load_policy_if_needed(cls):
171168
Returns:
172169
None
173170
"""
174-
last_modified_timestamp = cache.get(cls.CACHE_KEY)
171+
last_modified_timestamp = PolicyCacheControl.get_last_modified_timestamp()
175172

176173
current_timestamp = time.time()
177174

178175
if last_modified_timestamp is None:
179176
# No timestamp in cache; initialize it
180-
cache.set(cls.CACHE_KEY, current_timestamp, None)
181-
logger.info(f">>>> Initialized policy last modified timestamp in cache. {current_timestamp}")
177+
PolicyCacheControl.set_last_modified_timestamp(current_timestamp)
178+
logger.info("Initialized policy last modified timestamp in cache control.")
182179

183180
if cls._last_policy_load_timestamp is None or last_modified_timestamp > cls._last_policy_load_timestamp:
184181
# Policy has been modified since last load; reload it
185182
cls._enforcer.load_policy()
186183
cls._last_policy_load_timestamp = current_timestamp
187-
logger.info(f">>>> Reloaded policy at {current_timestamp}")
184+
logger.info(f"Reloaded policy at {current_timestamp}")
188185

189186
@classmethod
190187
def invalidate_policy_cache(cls):
@@ -197,8 +194,8 @@ def invalidate_policy_cache(cls):
197194
None
198195
"""
199196
current_timestamp = time.time()
200-
cache.set(cls.CACHE_KEY, current_timestamp, None)
201-
logger.info(f">>>> Invalidated policy cache at {current_timestamp}")
197+
PolicyCacheControl.set_last_modified_timestamp(current_timestamp)
198+
logger.info(f"Invalidated policy cache at {current_timestamp}")
202199

203200
@classmethod
204201
def get_enforcer(cls) -> SyncedEnforcer:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 4.2.24 on 2025-11-13 22:32
2+
3+
import datetime
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("openedx_authz", "0004_contentlibraryscope"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="PolicyCacheControl",
16+
fields=[
17+
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
18+
("last_modified", models.DateTimeField(default=datetime.datetime.now)),
19+
],
20+
),
21+
]

openedx_authz/models/engine.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Models for the authorization engine."""
2+
3+
from datetime import datetime
4+
5+
from django.db import models
6+
7+
8+
class PolicyCacheControl(models.Model):
9+
"""Model to control policy cache invalidation.
10+
11+
This model can be used to trigger cache invalidation for authorization policies
12+
by updating its timestamp. Whenever this model is updated, the authorization
13+
engine should invalidate its cached policies.
14+
"""
15+
16+
last_modified = models.DateTimeField(default=datetime.now)
17+
18+
def save(self, *args, **kwargs):
19+
"""Override save to update the timestamp."""
20+
self.pk = 1 # Ensure a single instance
21+
super().save(*args, **kwargs)
22+
23+
@classmethod
24+
def get(cls):
25+
"""Get the singleton instance of the model."""
26+
obj, _ = cls.objects.get_or_create(pk=1)
27+
return obj
28+
29+
@classmethod
30+
def get_last_modified_timestamp(cls):
31+
"""Get the last modified timestamp for policy cache control.
32+
33+
Returns:
34+
float: The timestamp of the last update.
35+
"""
36+
instance = cls.get()
37+
return instance.last_modified.timestamp()
38+
39+
@classmethod
40+
def set_last_modified_timestamp(cls, timestamp: float):
41+
"""Update the last modified timestamp to the current time.
42+
43+
This method updates the timestamp, which can be used to signal
44+
that the policy cache should be invalidated.
45+
"""
46+
instance = cls.get()
47+
instance.last_modified = datetime.fromtimestamp(timestamp)
48+
49+
instance.save()

openedx_authz/tests/test_enforcer.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
from ddt import data as ddt_data
1313
from ddt import ddt
1414
from django.conf import settings
15-
from django.core.cache import cache
1615
from django.test import TestCase, TransactionTestCase, override_settings
1716

1817
from openedx_authz.engine.enforcer import AuthzEnforcer
1918
from openedx_authz.engine.filter import Filter
2019
from openedx_authz.engine.utils import migrate_policy_between_enforcers
20+
from openedx_authz.models.engine import PolicyCacheControl
2121
from openedx_authz.tests.test_utils import make_action_key, make_role_key, make_scope_key, make_user_key
2222

2323

@@ -845,12 +845,11 @@ def test_load_policy_if_needed_initializes_cache_timestamp(self, mock_toggle):
845845
mock_toggle.return_value = True
846846

847847
AuthzEnforcer._last_policy_load_timestamp = None # pylint: disable=protected-access
848-
cache.clear()
849848

850849
# get_enforcer calls load_policy_if_needed internally
851850
AuthzEnforcer.get_enforcer()
852851

853-
cached_timestamp = cache.get(AuthzEnforcer.CACHE_KEY)
852+
cached_timestamp = PolicyCacheControl.get_last_modified_timestamp()
854853
self.assertIsNotNone(cached_timestamp)
855854
self.assertIsNotNone(AuthzEnforcer._last_policy_load_timestamp) # pylint: disable=protected-access
856855

@@ -871,7 +870,7 @@ def test_load_policy_if_needed_loads_when_stale(self, mock_toggle):
871870
# Set last load timestamp to stale value
872871
AuthzEnforcer._last_policy_load_timestamp = stale_timestamp # pylint: disable=protected-access
873872
# Set last cache invalidation to a more recent time
874-
cache.set(AuthzEnforcer.CACHE_KEY, now, None)
873+
PolicyCacheControl.set_last_modified_timestamp(now)
875874

876875
# get_enforcer calls load_policy_if_needed internally
877876
AuthzEnforcer.get_enforcer()
@@ -898,7 +897,7 @@ def test_load_policy_if_needed_doesnt_reload_when_not_stale(self, mock_toggle):
898897
# Set last load timestamp to current time
899898
AuthzEnforcer._last_policy_load_timestamp = now # pylint: disable=protected-access
900899
# Set last cache invalidation to an earlier time
901-
cache.set(AuthzEnforcer.CACHE_KEY, now - 60, None) # 60 seconds ago
900+
PolicyCacheControl.set_last_modified_timestamp(now - 60) # 60 seconds ago
902901

903902
# get_enforcer calls load_policy_if_needed internally
904903
AuthzEnforcer.get_enforcer()
@@ -920,10 +919,10 @@ def test_invalidate_policy_cache(self, mock_toggle):
920919

921920
AuthzEnforcer._last_policy_load_timestamp = time.time() # pylint: disable=protected-access
922921
old_cache_value = time.time() - 60 # 60 seconds ago
923-
cache.set(AuthzEnforcer.CACHE_KEY, old_cache_value, None)
922+
PolicyCacheControl.set_last_modified_timestamp(old_cache_value)
924923

925924
AuthzEnforcer.invalidate_policy_cache()
926925

927-
new_cache_value = cache.get(AuthzEnforcer.CACHE_KEY)
926+
new_cache_value = PolicyCacheControl.get_last_modified_timestamp()
928927
self.assertIsNotNone(new_cache_value)
929928
self.assertGreater(new_cache_value, old_cache_value)

0 commit comments

Comments
 (0)