Skip to content

Commit e8d27f6

Browse files
bmtcrilTycho Hob
andauthored
Cache the results of is_admin_or_superuser_check (openedx#146)
* refactor: cache the results of is_admin_or_superuser_check is_admin_or_superuser_check is being called once per policy when checking enforcement, creating a potential performance issue with numerous calls to the database. This adds a brief cache to offload some of the burden, but we will need a better fix long term. * refactor: Add RBAC admin cache constants * refactor: Use RequestCache for RBAC admin matcher By using the RequestCache instead of the Django cache we are able to have a thread-local memory copy of the user's superuser / staff state that exists only for the length of the request. This will save a large number of round trips to the cache backend. * chore: Update version and changelog --------- Co-authored-by: Tycho Hob <[email protected]>
1 parent 05a9ebb commit e8d27f6

15 files changed

Lines changed: 51 additions & 15 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.19.2 - 2025-11-25
20+
********************
21+
22+
Performance
23+
===========
24+
25+
* Use a RequestCache for is_admin_or_superuser matcher to improve performance.
26+
1927
0.19.1 - 2025-11-25
2028
********************
2129

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ PIP_COMPILE = pip-compile $(PIP_COMPILE_OPTS)
3737

3838
compile-requirements: ## compile the requirements/*.txt files with the latest packages satisfying requirements/*.in
3939
pip install -qr requirements/pip-tools.txt
40+
pip install -qr requirements/pip.txt
4041
pip-compile -v ${COMPILE_OPTS} --allow-unsafe --rebuild -o requirements/pip.txt requirements/pip.in
4142
pip-compile -v ${COMPILE_OPTS} -o requirements/pip-tools.txt requirements/pip-tools.in
4243
pip install -qr requirements/pip.txt

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.19.1"
7+
__version__ = "0.19.2"
88

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

openedx_authz/engine/enforcer.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,6 @@ def configure_enforcer_auto_save_and_load(cls):
151151

152152
if auto_load_policy_interval > 0:
153153
cls.configure_enforcer_auto_loading(auto_load_policy_interval)
154-
else:
155-
logger.warning("CASBIN_AUTO_LOAD_POLICY_INTERVAL is not set or zero; auto-load is disabled.")
156154

157155
cls.configure_enforcer_auto_save(auto_save_policy)
158156

openedx_authz/engine/matcher.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Custom condition checker. Note only used for data_library scope"""
22

33
from django.contrib.auth import get_user_model
4+
from edx_django_utils.cache import RequestCache
45

56
from openedx_authz.api.data import ContentLibraryData, ScopeData, UserData
67
from openedx_authz.rest_api.utils import get_user_by_username_or_email
@@ -26,15 +27,26 @@ def is_admin_or_superuser_check(request_user: str, request_action: str, request_
2627
ContentLibraryData scopes), False otherwise (including when user
2728
doesn't exist or scope type is not supported)
2829
"""
30+
31+
scope = ScopeData(namespaced_key=request_scope)
32+
username = UserData(namespaced_key=request_user).external_key
33+
request_cache = RequestCache("rbac_is_admin_or_superuser")
34+
35+
# TODO: This special case for superuser and staff users is currently only for
36+
# content libraries. See: https://github.com/openedx/openedx-authz/issues/87
37+
if not isinstance(scope, ContentLibraryData):
38+
return False
39+
40+
cached_response = request_cache.get_cached_response(username)
41+
if cached_response.is_found:
42+
return cached_response.value
43+
2944
try:
30-
username = UserData(namespaced_key=request_user).external_key
3145
user = get_user_by_username_or_email(username)
3246
except User.DoesNotExist:
3347
return False
3448

35-
scope = ScopeData(namespaced_key=request_scope)
36-
37-
if isinstance(scope, ContentLibraryData):
38-
return user.is_staff or user.is_superuser
49+
is_allowed = user.is_staff or user.is_superuser
50+
request_cache.set(username, is_allowed)
3951

40-
return False
52+
return is_allowed

openedx_authz/settings/test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def plugin_settings(settings): # pylint: disable=unused-argument
4444
"django.contrib.sessions.middleware.SessionMiddleware",
4545
"django.contrib.auth.middleware.AuthenticationMiddleware",
4646
"django.contrib.messages.middleware.MessageMiddleware",
47+
"edx_django_utils.cache.middleware.RequestCacheMiddleware",
4748
]
4849

4950
TEMPLATES = [

requirements/base.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ pycasbin # Authorization library for implementing access cont
99
casbin-django-orm-adapter # Adapter for Django ORM for Casbin
1010
edx-opaque-keys # Opaque keys for resource identification
1111
edx-api-doc-tools # Tools for API documentation
12+
edx-django-utils # Used for RequestCache
1213
edx-drf-extensions # Extensions for Django Rest Framework used by Open edX

requirements/base.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ cffi==2.0.0
1919
charset-normalizer==3.4.3
2020
# via requests
2121
click==8.3.0
22-
# via edx-django-utils
22+
# via
23+
# -c requirements/constraints.txt
24+
# edx-django-utils
2325
cryptography==46.0.2
2426
# via pyjwt
2527
django==4.2.24
@@ -57,7 +59,9 @@ drf-yasg==1.21.11
5759
edx-api-doc-tools==2.1.0
5860
# via -r requirements/base.in
5961
edx-django-utils==8.0.1
60-
# via edx-drf-extensions
62+
# via
63+
# -r requirements/base.in
64+
# edx-drf-extensions
6165
edx-drf-extensions==10.6.0
6266
# via -r requirements/base.in
6367
edx-opaque-keys==3.0.0

requirements/constraints.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010

1111
# Common constraints for edx repos
1212
-c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
13+
14+
# Different packages want different versions of click, we force the most compatible one here
15+
click==8.3.0

requirements/dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ charset-normalizer==3.4.3
4545
# requests
4646
click==8.3.0
4747
# via
48+
# -c requirements/constraints.txt
4849
# -r requirements/pip-tools.txt
4950
# -r requirements/quality.txt
5051
# click-log
@@ -196,7 +197,7 @@ packaging==25.0
196197
# tox
197198
path==16.16.0
198199
# via edx-i18n-tools
199-
pip-tools==7.5.1
200+
pip-tools==7.5.2
200201
# via -r requirements/pip-tools.txt
201202
platformdirs==4.4.0
202203
# via

0 commit comments

Comments
 (0)