From cc9cf06c60daf08271a8fcddc8c9123862814b51 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Fri, 24 May 2024 15:08:19 +0200 Subject: [PATCH 1/4] :fire: Delete CachingMixin The new config-driven flow doesn't require managing the cache as intensively anymore, it now follows the django-solo caching configuration. --- CHANGELOG.rst | 6 +- .../migrations/0002_migrate_to_claim_field.py | 7 +- mozilla_django_oidc_db/models.py | 82 ++++--------------- mozilla_django_oidc_db/settings.py | 7 -- testapp/settings.py | 1 - tests/test_models.py | 53 +++--------- 6 files changed, 31 insertions(+), 125 deletions(-) delete mode 100644 mozilla_django_oidc_db/settings.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4116b45..5a1a853 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,7 +21,11 @@ aspects could not be avoided. if you were subclassing this backend to override these attributes. You can provide these in your custom configuration model(s) as the - ``oidcdb_sensitive_claims`` and ``oidcdb_username_claim`` model fields or properties. See the implementation of the ``OpenIDConnectConfigBase`` model for more details. + ``oidcdb_sensitive_claims`` and ``oidcdb_username_claim`` model fields or properties. + See the implementation of the ``OpenIDConnectConfigBase`` model for more details. + +* ``mozilla_django_oidc_db.models.CachingMixin`` is removed. Our base model overrides the + generated cache key so that it uniquely points to a specific Django model. 0.16.0 (2024-05-02) =================== diff --git a/mozilla_django_oidc_db/migrations/0002_migrate_to_claim_field.py b/mozilla_django_oidc_db/migrations/0002_migrate_to_claim_field.py index 51df481..d1e5101 100644 --- a/mozilla_django_oidc_db/migrations/0002_migrate_to_claim_field.py +++ b/mozilla_django_oidc_db/migrations/0002_migrate_to_claim_field.py @@ -6,15 +6,10 @@ import mozilla_django_oidc_db.fields import mozilla_django_oidc_db.models -import mozilla_django_oidc_db.settings as oidc_settings def flush_cache(): - cache_name = getattr( - settings, - "MOZILLA_DJANGO_OIDC_DB_CACHE", - oidc_settings.MOZILLA_DJANGO_OIDC_DB_CACHE, - ) + cache_name = getattr(settings, "MOZILLA_DJANGO_OIDC_DB_CACHE", "oidc") if not cache_name: return caches[cache_name].clear() diff --git a/mozilla_django_oidc_db/models.py b/mozilla_django_oidc_db/models.py index 2e47bd7..b816a37 100644 --- a/mozilla_django_oidc_db/models.py +++ b/mozilla_django_oidc_db/models.py @@ -9,13 +9,11 @@ from django.core.exceptions import FieldDoesNotExist, ValidationError from django.db import models from django.utils.encoding import force_str -from django.utils.functional import classproperty from django.utils.translation import gettext_lazy as _ from django_jsonform.models.fields import ArrayField -from solo.models import SingletonModel, get_cache - -import mozilla_django_oidc_db.settings as oidc_settings +from solo import settings as solo_settings +from solo.models import SingletonModel from .fields import ClaimField from .typing import ClaimPath, DjangoView @@ -51,62 +49,6 @@ def get_default_groups_claim() -> list[str]: return ["roles"] -class CachingMixin: - @classmethod - def clear_cache(cls): - cache_name = getattr( - settings, "OIDC_CACHE", oidc_settings.MOZILLA_DJANGO_OIDC_DB_CACHE - ) - if cache_name: - cache = get_cache(cache_name) - cache_key = cls.get_cache_key() - cache.delete(cache_key) - - def set_to_cache(self): - cache_name = getattr( - settings, - "MOZILLA_DJANGO_OIDC_DB_CACHE", - oidc_settings.MOZILLA_DJANGO_OIDC_DB_CACHE, - ) - if not cache_name: - return None - cache = get_cache(cache_name) - cache_key = self.get_cache_key() - timeout = getattr( - settings, - "MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT", - oidc_settings.MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT, - ) - cache.set(cache_key, self, timeout) - - @classmethod - def get_cache_key(cls) -> str: - prefix = cls.custom_oidc_db_prefix or getattr( - settings, - "MOZILLA_DJANGO_OIDC_DB_PREFIX", - oidc_settings.MOZILLA_DJANGO_OIDC_DB_PREFIX, - ) - return "%s:%s" % (prefix, cls.__name__.lower()) - - @classmethod - def get_solo(cls) -> SingletonModel: - cache_name = getattr( - settings, - "MOZILLA_DJANGO_OIDC_DB_CACHE", - oidc_settings.MOZILLA_DJANGO_OIDC_DB_CACHE, - ) - if not cache_name: - obj, created = cls.objects.get_or_create(pk=cls.singleton_instance_id) - return obj - cache = get_cache(cache_name) - cache_key = cls.get_cache_key() - obj = cache.get(cache_key) - if not obj: - obj, created = cls.objects.get_or_create(pk=cls.singleton_instance_id) - obj.set_to_cache() - return obj - - class OpenIDConnectConfigBase(SingletonModel): """ Defines the required fields for a config to establish an OIDC connection @@ -251,6 +193,17 @@ class Meta: def __str__(self) -> str: return force_str(self._meta.verbose_name) + @classmethod + def get_cache_key(cls): + """ + Overridden cache key to take into account the app label. + """ + solo_prefix = getattr( + settings, "SOLO_CACHE_PREFIX", solo_settings.SOLO_CACHE_PREFIX + ) + prefix: str = getattr(settings, "MOZILLA_DJANGO_OIDC_DB_PREFIX", solo_prefix) + return ":".join([prefix, cls._meta.app_label, str(cls._meta.model_name)]) + @property def oidc_rp_scopes(self) -> str: """ @@ -287,7 +240,7 @@ def get_callback_view(self) -> DjangoView: return default_callback_view -class OpenIDConnectConfig(CachingMixin, OpenIDConnectConfigBase): +class OpenIDConnectConfig(OpenIDConnectConfigBase): """ Configuration for authentication/authorization via OpenID connect """ @@ -387,13 +340,6 @@ def clean(self): } ) - @classproperty - def custom_oidc_db_prefix(cls) -> str: - """ - Cache prefix that can be overridden - """ - return oidc_settings.MOZILLA_DJANGO_OIDC_DB_PREFIX - @property def oidcdb_username_claim(self) -> ClaimPath: """ diff --git a/mozilla_django_oidc_db/settings.py b/mozilla_django_oidc_db/settings.py deleted file mode 100644 index d1f0870..0000000 --- a/mozilla_django_oidc_db/settings.py +++ /dev/null @@ -1,7 +0,0 @@ -# The cache that should be used, e.g. 'default'. Refers to Django CACHES setting. -# Set to None to disable caching. -MOZILLA_DJANGO_OIDC_DB_CACHE = "oidc" - -MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT = 60 * 5 - -MOZILLA_DJANGO_OIDC_DB_PREFIX = "oidc" diff --git a/testapp/settings.py b/testapp/settings.py index ce16f61..1ac9c6d 100644 --- a/testapp/settings.py +++ b/testapp/settings.py @@ -72,7 +72,6 @@ "mozilla_django_oidc_db.backends.OIDCAuthenticationBackend", ] -MOZILLA_DJANGO_OIDC_DB_PREFIX = "default" # These settings are evaluated at import-time of the urlconf in mozilla_django_oidc.urls. # Changing them via @override_settings (or the pytest-django settings fixture) has no # effect. diff --git a/tests/test_models.py b/tests/test_models.py index 7c3dadd..30803a2 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,41 +1,24 @@ from django.core.exceptions import ValidationError -from django.test.utils import isolate_apps from django.utils.translation import gettext as _ import pytest -from mozilla_django_oidc_db.models import OpenIDConnectConfig, classproperty +from mozilla_django_oidc_db.models import OpenIDConnectConfig def test_default_cache_key(): - assert OpenIDConnectConfig.get_cache_key() == "oidc:openidconnectconfig" - - -@isolate_apps("testapp") -def test_override_cache_key_with_class(): - class CustomConfig(OpenIDConnectConfig): - class Meta: - app_label = "custom" - - @classproperty - def custom_oidc_db_prefix(cls): - return "custom" - - assert CustomConfig.get_cache_key() == "custom:customconfig" - - -@isolate_apps("testapp") -def test_custom_config_override_cache_key_with_settings(): - class CustomConfig(OpenIDConnectConfig): - class Meta: - app_label = "custom" + assert ( + OpenIDConnectConfig.get_cache_key() + == "solo:mozilla_django_oidc_db:openidconnectconfig" + ) - @classproperty - def custom_oidc_db_prefix(cls): - return "" - # Prefix taken from `testapp/settings.py` - assert CustomConfig.get_cache_key() == "default:customconfig" +def test_different_default_provided(settings): + settings.MOZILLA_DJANGO_OIDC_DB_PREFIX = "otherprefix" + assert ( + OpenIDConnectConfig.get_cache_key() + == "otherprefix:mozilla_django_oidc_db:openidconnectconfig" + ) @pytest.fixture() @@ -43,20 +26,6 @@ def unset_oidc_cache_prefix(settings): del settings.MOZILLA_DJANGO_OIDC_DB_PREFIX -@isolate_apps("testapp") -def test_custom_config_cache_key_fallback(unset_oidc_cache_prefix): - class CustomConfig(OpenIDConnectConfig): - class Meta: - app_label = "custom" - - @classproperty - def custom_oidc_db_prefix(cls): - return "" - - # Prefix taken from `mozilla_django_oidc_dv/settings.py` - assert CustomConfig.get_cache_key() == "oidc:customconfig" - - def test_validate_claim_mapping_fields(): instance = OpenIDConnectConfig( claim_mapping={ From 9e166c047e0fd5a51abf62c28d1aeb39b7652b01 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Fri, 24 May 2024 15:46:45 +0200 Subject: [PATCH 2/4] :recycle: Refactor middleware implementation --- mozilla_django_oidc_db/middleware.py | 79 +++++++++-- tests/test_middleware.py | 196 +++++++++++---------------- 2 files changed, 143 insertions(+), 132 deletions(-) diff --git a/mozilla_django_oidc_db/middleware.py b/mozilla_django_oidc_db/middleware.py index 5105e5e..84cc836 100644 --- a/mozilla_django_oidc_db/middleware.py +++ b/mozilla_django_oidc_db/middleware.py @@ -1,22 +1,75 @@ -from mozilla_django_oidc.middleware import SessionRefresh as _SessionRefresh +from typing import Any, ClassVar, Generic, TypeVar, cast -from .mixins import GetAttributeMixin, SoloConfigMixin +from django.http import HttpRequest +from mozilla_django_oidc.middleware import SessionRefresh as BaseSessionRefresh +from typing_extensions import override + +from .config import dynamic_setting, get_setting_from_config +from .models import OpenIDConnectConfig, OpenIDConnectConfigBase + +T = TypeVar("T", bound=OpenIDConnectConfigBase) + + +class BaseRefreshMiddleware(Generic[T], BaseSessionRefresh): + """ + Point the middleware to a particular config class to use. + + This base class sets up the dynamic settings mechanism. + """ + + _config: T + config_class: ClassVar[type[OpenIDConnectConfigBase]] + """ + The config model/class to get the endpoints/credentials from. + """ + + OIDC_EXEMPT_URLS = dynamic_setting[list[str]](default=[]) + OIDC_OP_AUTHORIZATION_ENDPOINT = dynamic_setting[str]() + OIDC_RP_CLIENT_ID = dynamic_setting[str]() + OIDC_STATE_SIZE = dynamic_setting[int](default=32) + OIDC_AUTHENTICATION_CALLBACK_URL = dynamic_setting[str]( + default="oidc_authentication_callback" + ) + OIDC_RP_SCOPES = dynamic_setting[str](default="openid email") + OIDC_USE_NONCE = dynamic_setting[bool](default=True) + OIDC_NONCE_SIZE = dynamic_setting[int](default=32) -class SessionRefresh(GetAttributeMixin, SoloConfigMixin, _SessionRefresh): def __init__(self, get_response): - # `super().__init__` is not called here, because this attempts to initialize - # the settings (which should be retrieved from `OpenIDConnectConfig`). + # `super().__init__` is not called here, because it calls self.get_setting() + # The retrieval of these settings is handled via dynamic settings above. + super(BaseSessionRefresh, self).__init__(get_response=get_response) - # The retrieval of these settings has been moved to runtime (`__getattribute__` from the `GetAttributeMixin`) - super(_SessionRefresh, self).__init__(get_response=get_response) + @override + def get_settings(self, attr: str, *args: Any) -> Any: # type: ignore + """ + Look up the request setting from the database config. - def process_request(self, request): - # Initialize to retrieve the settings from config model - super().__init__(self.get_response) + We cache the resolved config instance on the middleware instance for when + settings are repeatedly looked up. Note however, that we also override the + __call__ method to delete this cached property, as a middleware instance lives + for the entire lifecycle of the applicatation, and each request must not look + at stale cached configuration. + """ + if (config := getattr(self, "_config", None)) is None: + # django-solo and type checking is challenging, but a new release is on the + # way and should fix that :fingers_crossed: + config = cast(T, self.config_class.get_solo()) + self._config = config + return get_setting_from_config(config, attr, *args) - self.refresh_config() - if not self.config.enabled: - return + def __call__(self, request: HttpRequest): + # reset the python-level cache for each request + if hasattr(self, "_config"): + del self._config + return super().__call__(request) + def process_request(self, request): + # do nothing if the configuration is not enabled + if not self.get_settings("ENABLED"): + return None return super().process_request(request) + + +class SessionRefresh(BaseRefreshMiddleware[OpenIDConnectConfig]): + config_class = OpenIDConnectConfig diff --git a/tests/test_middleware.py b/tests/test_middleware.py index fc7fea8..1e2b385 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -1,4 +1,3 @@ -from unittest.mock import patch from urllib.parse import parse_qs, urlparse from django.contrib.sessions.middleware import SessionMiddleware @@ -11,146 +10,105 @@ from mozilla_django_oidc_db.models import OpenIDConnectConfig -def get_response(*args, **kwargs): - pass +@pytest.fixture(scope="session") +def dummy_view(): + def get_response(*args, **kwargs): + pass + return get_response -@patch("mozilla_django_oidc_db.models.OpenIDConnectConfig.get_solo") -def test_sessionrefresh_oidc_not_enabled(mock_get_solo): - mock_get_solo.return_value = OpenIDConnectConfig( - enabled=False, - oidc_rp_client_id="testid", - oidc_rp_client_secret="secret", - oidc_rp_sign_algo="HS256", - oidc_rp_scopes_list=["openid", "email"], - oidc_op_jwks_endpoint="http://some.endpoint/v1/jwks", - oidc_op_authorization_endpoint="http://some.endpoint/v1/auth", - oidc_op_token_endpoint="http://some.endpoint/v1/token", - oidc_op_user_endpoint="http://some.endpoint/v1/user", - ) - request = RequestFactory().get("/") +@pytest.fixture(scope="session") +def session_middleware(dummy_view): + return SessionMiddleware(dummy_view) + + +@pytest.fixture(scope="session") +def session_refresh(dummy_view): + return SessionRefresh(dummy_view) + + +@pytest.mark.oidcconfig(enabled=False) +def test_sessionrefresh_oidc_not_enabled( + dummy_config: OpenIDConnectConfig, + rf: RequestFactory, + session_refresh: SessionRefresh, +): + request = rf.get("/") # Running the middleware should return None, since OIDC is disabled - result = SessionRefresh(get_response).process_request(request) + result = session_refresh(request) assert result is None -@patch("mozilla_django_oidc_db.models.OpenIDConnectConfig.get_solo") -def test_sessionrefresh_config_always_refreshed(mock_get_solo): +@pytest.mark.oidcconfig( + enabled=True, + oidc_rp_client_id="initial-client-id", + oidc_rp_scopes_list=["openid", "email"], +) +def test_sessionrefresh_config_always_refreshed( + dummy_config: OpenIDConnectConfig, + rf: RequestFactory, + session_middleware: SessionMiddleware, + session_refresh: SessionRefresh, + mocker, +): """ - Middleware should refresh the config on every call of `process_request` + Middleware should refresh the config on every call """ - mock_get_solo.return_value = OpenIDConnectConfig( - enabled=True, - oidc_rp_client_id="testid", - oidc_rp_client_secret="secret", - oidc_rp_sign_algo="HS256", - oidc_rp_scopes_list=["openid", "email"], - oidc_op_jwks_endpoint="http://some.endpoint/v1/jwks", - oidc_op_authorization_endpoint="http://some.endpoint/v1/auth", - oidc_op_token_endpoint="http://some.endpoint/v1/token", - oidc_op_user_endpoint="http://some.endpoint/v1/user", - ) - - middleware = SessionRefresh(get_response) - request = RequestFactory().get("/") - SessionMiddleware(get_response).process_request(request) - - with patch( - "mozilla_django_oidc_db.middleware.SessionRefresh.is_refreshable_url", - return_value=True, - ): - with patch("mozilla_django_oidc.middleware.reverse", return_value="/callback"): - result1 = middleware.process_request(request) - - # Update the config and call the middleware again (without reinstantiating) - mock_get_solo.return_value = OpenIDConnectConfig( - enabled=True, - oidc_rp_client_id="some-other-id", - oidc_rp_client_secret="secret", - oidc_rp_sign_algo="HS256", - oidc_rp_scopes_list=["openid", "email", "other_scope"], - oidc_op_jwks_endpoint="http://some.endpoint/v1/jwks", - oidc_op_authorization_endpoint="http://some.endpoint/v1/auth", - oidc_op_token_endpoint="http://some.endpoint/v1/token", - oidc_op_user_endpoint="http://some.endpoint/v1/user", - ) - result2 = middleware.process_request(request) + mocker.patch.object(session_refresh, "is_refreshable_url", return_value=True) + request = rf.get("/") + session_middleware(request) + result1 = session_refresh(request) assert isinstance(result1, HttpResponseRedirect) - assert isinstance(result2, HttpResponseRedirect) - - parsed1 = parse_qs(urlparse(result1.url).query) - assert parsed1["client_id"] == ["testid"] - assert parsed1["scope"] == ["openid email"] + query1 = parse_qs(urlparse(result1.url).query) + assert query1["client_id"] == ["initial-client-id"] + assert query1["scope"] == ["openid email"] - parsed2 = parse_qs(urlparse(result2.url).query) - assert parsed2["client_id"] == ["some-other-id"] - assert parsed2["scope"] == ["openid email other_scope"] + dummy_config.oidc_rp_client_id = "some-other-id" + dummy_config.oidc_rp_scopes_list = ["openid", "email", "other_scope"] # type: ignore + dummy_config.save() - -@patch("mozilla_django_oidc_db.models.OpenIDConnectConfig.get_solo") -def test_sessionrefresh_config_use_defaults(mock_get_solo): + result2 = session_refresh(request) + assert isinstance(result2, HttpResponseRedirect) + query2 = parse_qs(urlparse(result2.url).query) + assert query2["client_id"] == ["some-other-id"] + assert query2["scope"] == ["openid email other_scope"] + + +@pytest.mark.oidcconfig(enabled=True) +def test_sessionrefresh_config_use_defaults( + dummy_config, + settings, + session_middleware: SessionMiddleware, + session_refresh: SessionRefresh, + rf: RequestFactory, + mocker, +): """ - Middleware should use defaults from `mozilla-django-oidc`, for instance if - `OIDC_AUTHENTICATION_CALLBACK_URL` is not explicity provided + Middleware should respect fallbacks to settings/defaults. """ - mock_get_solo.return_value = OpenIDConnectConfig( - enabled=True, - oidc_rp_client_id="testid", - oidc_rp_client_secret="secret", - oidc_rp_sign_algo="HS256", - oidc_rp_scopes_list=["openid", "email"], - oidc_op_jwks_endpoint="http://some.endpoint/v1/jwks", - oidc_op_authorization_endpoint="http://some.endpoint/v1/auth", - oidc_op_token_endpoint="http://some.endpoint/v1/token", - oidc_op_user_endpoint="http://some.endpoint/v1/user", - ) - - middleware = SessionRefresh(get_response) - request = RequestFactory().get("/") - SessionMiddleware(get_response).process_request(request) - - with patch( - "mozilla_django_oidc_db.middleware.SessionRefresh.is_refreshable_url", - return_value=True, - ): - result1 = middleware.process_request(request) - - # Update the config and call the middleware again (without reinstantiating) - mock_get_solo.return_value = OpenIDConnectConfig( - enabled=True, - oidc_rp_client_id="some-other-id", - oidc_rp_client_secret="secret", - oidc_rp_sign_algo="HS256", - oidc_rp_scopes_list=["openid", "email", "other_scope"], - oidc_op_jwks_endpoint="http://some.endpoint/v1/jwks", - oidc_op_authorization_endpoint="http://some.endpoint/v1/auth", - oidc_op_token_endpoint="http://some.endpoint/v1/token", - oidc_op_user_endpoint="http://some.endpoint/v1/user", - ) - result2 = middleware.process_request(request) - - assert isinstance(result1, HttpResponseRedirect) - assert isinstance(result2, HttpResponseRedirect) + settings.OIDC_AUTHENTICATION_CALLBACK_URL = "admin:index" + mocker.patch.object(session_refresh, "is_refreshable_url", return_value=True) - parsed1 = parse_qs(urlparse(result1.url).query) - assert parsed1["client_id"] == ["testid"] - assert parsed1["scope"] == ["openid email"] + request = rf.get("/") + session_middleware(request) - parsed2 = parse_qs(urlparse(result2.url).query) - assert parsed2["client_id"] == ["some-other-id"] - assert parsed2["scope"] == ["openid email other_scope"] + result = session_refresh(request) + assert isinstance(result, HttpResponseRedirect) + query = parse_qs(urlparse(result.url).query) + assert query["redirect_uri"] == ["http://testserver/admin/"] + assert len(query["nonce"][0]) == 32 # default set on middleware dynamic_setting -@pytest.mark.django_db -def test_attributeerror_for_non_oidc_attribute(): - middleware = SessionRefresh(get_response) +def test_attributeerror_for_non_oidc_attribute( + dummy_config, session_refresh: SessionRefresh +): with pytest.raises(AttributeError): - middleware.__name__ + session_refresh.__name__ # type: ignore # OIDC attributes should never raise AttributeErrors - middleware.OIDC_AUTHENTICATION_CALLBACK_URL + assert session_refresh.OIDC_AUTHENTICATION_CALLBACK_URL From 25cd8ce548287196e6306e3a036ede9e9e9b386f Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Fri, 24 May 2024 15:47:25 +0200 Subject: [PATCH 3/4] :coffin: Delete dead code --- mozilla_django_oidc_db/mixins.py | 54 -------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 mozilla_django_oidc_db/mixins.py diff --git a/mozilla_django_oidc_db/mixins.py b/mozilla_django_oidc_db/mixins.py deleted file mode 100644 index c999c45..0000000 --- a/mozilla_django_oidc_db/mixins.py +++ /dev/null @@ -1,54 +0,0 @@ -import warnings -from typing import Any, ClassVar, Generic, TypeVar, cast - -from .config import get_setting_from_config -from .models import OpenIDConnectConfig, OpenIDConnectConfigBase - -T = TypeVar("T", bound=OpenIDConnectConfigBase) - - -class SoloConfigMixin(Generic[T]): - config_class: ClassVar[type[OpenIDConnectConfigBase]] = OpenIDConnectConfig - _solo_config: T - - @property - def config(self) -> T: - if not hasattr(self, "_solo_config"): - # django-solo and type checking is challenging, but a new release is on the - # way and should fix that :fingers_crossed: - config = self.config_class.get_solo() - self._solo_config = cast(T, config) - return self._solo_config - - def refresh_config(self) -> None: - """ - Refreshes the cached config on the instance, required for middleware - since middleware is only instantiated once (during the Django startup phase) - """ - if hasattr(self, "_solo_config"): - del self._solo_config - - def get_settings(self, attr: str, *args: Any): - return get_setting_from_config(self.config, attr, *args) - - -class GetAttributeMixin: - def __getattribute__(self, attr: str): - """ - Mixin used to avoid calls to the config model on __init__ and instead - do these calls runtime - """ - if not attr.startswith("OIDC"): - return super().__getattribute__(attr) - - warnings.warn( - "GetAttributeMixin will be deprecated, instead use an explicit descriptor", - category=PendingDeprecationWarning, - stacklevel=2, - ) - - try: - default = super().__getattribute__(attr) - except AttributeError: - default = None - return self.get_settings(attr, default) From 141e41e1128c638c491445544b6e4a2234054b35 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Tue, 28 May 2024 09:18:58 +0200 Subject: [PATCH 4/4] :pencil: Update docs about cache settings --- docs/quickstart.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 72e0fdd..26282dd 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -76,8 +76,9 @@ Furthermore, ensure the following settings are configured: OIDC_AUTHENTICATE_CLASS = "mozilla_django_oidc_db.views.OIDCAuthenticationRequestView" OIDC_CALLBACK_CLASS = "mozilla_django_oidc_db.views.OIDCCallbackView" - MOZILLA_DJANGO_OIDC_DB_CACHE = "oidc" - MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT = 1 + # Optionally, configure django-solo caching + # SOLO_CACHE = "solo" + # SOLO_CACHE_TIMEOUT = 1 In order to properly catch admin login errors, add the following to urlpatterns: @@ -91,15 +92,19 @@ In order to properly catch admin login errors, add the following to urlpatterns: ... ] -``MOZILLA_DJANGO_OIDC_DB_CACHE`` is used to cache the configuration that is stored in the database, -to prevent a lot of database lookups. Ensure this cache is configured in ``CACHES`` (using the backend of choice): +You can optionally enable the configuration cache (prevent database lookups) by enabling +the django-solo cache: ``SOLO_CACHE = "solo"``. Ensure this cache is configured in +``CACHES`` (using the backend of choice): .. code-block:: python CACHES = { "default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, ... - "oidc": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, + "solo": { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": "redis://127.0.0.1:6379", + }, } Add the urlpatterns: