Skip to content

Commit

Permalink
Merge pull request #107 from maykinmedia/feature/102-system-checks
Browse files Browse the repository at this point in the history
Add system checks to the library
  • Loading branch information
sergei-maertens authored May 28, 2024
2 parents 7932231 + 9934fc8 commit 816f546
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 1 deletion.
3 changes: 3 additions & 0 deletions mozilla_django_oidc_db/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
class MozillaDjangoOidcDbConfig(AppConfig):
name = "mozilla_django_oidc_db"
default_auto_field = "django.db.models.AutoField"

def ready(self) -> None:
from . import checks # noqa
93 changes: 93 additions & 0 deletions mozilla_django_oidc_db/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import inspect
from collections.abc import Sequence
from typing import Any

from django.apps import AppConfig
from django.conf import settings
from django.core.checks import CheckMessage, Error, Warning, register
from django.utils.module_loading import import_string

from .views import OIDCCallbackView, OIDCInit


def _do_check(
app_configs: Sequence[AppConfig] | None,
dotted_path: Any,
type_error: Error,
subclass_reference: type,
subclass_warning: Warning,
) -> list[CheckMessage]:
if not (
app_configs is None
or any(config.name == "mozilla_django_oidc_db" for config in app_configs)
):
return []

if not isinstance(dotted_path, str):
return [type_error]

view_cls = import_string(dotted_path)
if not inspect.isclass(view_cls) or not issubclass(view_cls, subclass_reference):
return [subclass_warning]

return []


@register()
def check_authenticate_class(
*, app_configs: Sequence[AppConfig] | None, **kwargs
) -> list[CheckMessage]:
type_error = Error(
"'settings.OIDC_AUTHENTICATE_CLASS' must be a string that can be imported.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCAuthenticationRequestView' or a "
"subclass of 'mozilla_django_oidc_db.views.OIDCInit'."
),
id="mozilla_django_oidc_db.E001",
)
subclass_warning = Warning(
"'settings.OIDC_AUTHENTICATE_CLASS' should be a subclass of 'OIDCInit'.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCAuthenticationRequestView' or a "
"subclass of 'mozilla_django_oidc_db.views.OIDCInit'."
),
id="mozilla_django_oidc_db.W001",
)

return _do_check(
app_configs,
settings.OIDC_AUTHENTICATE_CLASS,
type_error=type_error,
subclass_reference=OIDCInit,
subclass_warning=subclass_warning,
)


@register()
def check_callback_class(
*, app_configs: Sequence[AppConfig] | None, **kwargs
) -> list[CheckMessage]:
type_error = Error(
"'settings.OIDC_CALLBACK_CLASS' must be a string that can be imported.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCCallbackView' or a "
"subclass of it."
),
id="mozilla_django_oidc_db.E002",
)
subclass_warning = Warning(
"'settings.OIDC_CALLBACK_CLASS' should be a subclass of 'OIDCInit'.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCCallbackView' or a "
"subclass of it."
),
id="mozilla_django_oidc_db.W002",
)

return _do_check(
app_configs,
settings.OIDC_CALLBACK_CLASS,
type_error=type_error,
subclass_reference=OIDCCallbackView,
subclass_warning=subclass_warning,
)
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ tests_require =
isort
black

[options.packages.find]
include = mozilla_django_oidc_db*

[options.extras_require]
tests =
psycopg2
Expand Down
1 change: 0 additions & 1 deletion testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class EmptyConfig(OpenIDConnectConfigBase):


class WrongConfigModel(SingletonModel):

@property
def oidc_op_authorization_endpoint(self):
return "bad"
Expand Down
103 changes: 103 additions & 0 deletions tests/test_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from django.apps import apps
from django.core.checks import Error, Warning

import pytest

from mozilla_django_oidc_db.checks import check_authenticate_class, check_callback_class


@pytest.fixture(scope="session")
def app_configs():
app_config = apps.get_app_config(app_label="mozilla_django_oidc_db")
return [app_config]


def test_nothing_reported_when_checking_other_app_label(settings):
settings.OIDC_AUTHENTICATE_CLASS = object() # this is invalid
app_config = apps.get_app_config(app_label="admin")

messages = check_authenticate_class(app_configs=[app_config])

assert len(messages) == 0


def test_check_authenticate_class_ok(app_configs, settings):
settings.OIDC_AUTHENTICATE_CLASS = (
"mozilla_django_oidc_db.views.OIDCAuthenticationRequestView"
)

messages = check_authenticate_class(app_configs=app_configs)

assert len(messages) == 0


def test_check_authenticate_class_not_a_string(app_configs, settings):
settings.OIDC_AUTHENTICATE_CLASS = object()

messages = check_authenticate_class(app_configs=app_configs)

assert len(messages) == 1
assert messages[0] == Error(
"'settings.OIDC_AUTHENTICATE_CLASS' must be a string that can be imported.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCAuthenticationRequestView' or a "
"subclass of 'mozilla_django_oidc_db.views.OIDCInit'."
),
id="mozilla_django_oidc_db.E001",
)


def test_check_authenticate_class_invalid_view(app_configs, settings):
settings.OIDC_AUTHENTICATE_CLASS = "django.views.View"

messages = check_authenticate_class(app_configs=app_configs)

assert len(messages) == 1
assert messages[0] == Warning(
"'settings.OIDC_AUTHENTICATE_CLASS' should be a subclass of 'OIDCInit'.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCAuthenticationRequestView' or a "
"subclass of 'mozilla_django_oidc_db.views.OIDCInit'."
),
id="mozilla_django_oidc_db.W001",
)


def test_check_callback_class_ok(app_configs, settings):
settings.OIDC_CALLBACK_CLASS = "mozilla_django_oidc_db.views.OIDCCallbackView"

messages = check_callback_class(app_configs=app_configs)

assert len(messages) == 0


def test_check_callback_class_not_a_string(app_configs, settings):
settings.OIDC_CALLBACK_CLASS = object()

messages = check_callback_class(app_configs=app_configs)

assert len(messages) == 1
assert messages[0] == Error(
"'settings.OIDC_CALLBACK_CLASS' must be a string that can be imported.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCCallbackView' or a "
"subclass of it."
),
id="mozilla_django_oidc_db.E002",
)


def test_check_callback_class_invalid_view(app_configs, settings):
settings.OIDC_CALLBACK_CLASS = "django.views.View"

messages = check_callback_class(app_configs=app_configs)

assert len(messages) == 1
assert messages[0] == Warning(
"'settings.OIDC_CALLBACK_CLASS' should be a subclass of 'OIDCInit'.",
hint=(
"Use 'mozilla_django_oidc_db.views.OIDCCallbackView' or a "
"subclass of it."
),
id="mozilla_django_oidc_db.W002",
)

0 comments on commit 816f546

Please sign in to comment.