[2.2.x] Fixed #30237 -- Made Authentication/SessionMiddleware and ModelBackend admin checks allow subclasses.

Backport of f976ab1b11 from master.
This commit is contained in:
Herman S 2019-03-05 22:22:09 +01:00 committed by Tim Graham
parent b150d99460
commit d8704a4d4f
2 changed files with 73 additions and 6 deletions

View File

@ -18,6 +18,7 @@ from django.template import engines
from django.template.backends.django import DjangoTemplates from django.template.backends.django import DjangoTemplates
from django.utils.deprecation import RemovedInDjango30Warning from django.utils.deprecation import RemovedInDjango30Warning
from django.utils.inspect import get_func_args from django.utils.inspect import get_func_args
from django.utils.module_loading import import_string
def _issubclass(cls, classinfo): def _issubclass(cls, classinfo):
@ -31,6 +32,23 @@ def _issubclass(cls, classinfo):
return False return False
def _contains_subclass(class_path, candidate_paths):
"""
Return whether or not a dotted class path (or a subclass of that class) is
found in a list of candidate paths.
"""
cls = import_string(class_path)
for path in candidate_paths:
try:
candidate_cls = import_string(path)
except ImportError:
# ImportErrors are raised elsewhere.
continue
if _issubclass(candidate_cls, cls):
return True
return False
def check_admin_app(app_configs, **kwargs): def check_admin_app(app_configs, **kwargs):
from django.contrib.admin.sites import all_sites from django.contrib.admin.sites import all_sites
errors = [] errors = []
@ -75,8 +93,7 @@ def check_dependencies(**kwargs):
else: else:
if ('django.contrib.auth.context_processors.auth' if ('django.contrib.auth.context_processors.auth'
not in django_templates_instance.context_processors and not in django_templates_instance.context_processors and
'django.contrib.auth.backends.ModelBackend' _contains_subclass('django.contrib.auth.backends.ModelBackend', settings.AUTHENTICATION_BACKENDS)):
in settings.AUTHENTICATION_BACKENDS):
errors.append(checks.Error( errors.append(checks.Error(
"'django.contrib.auth.context_processors.auth' must be " "'django.contrib.auth.context_processors.auth' must be "
"enabled in DjangoTemplates (TEMPLATES) if using the default " "enabled in DjangoTemplates (TEMPLATES) if using the default "
@ -91,15 +108,14 @@ def check_dependencies(**kwargs):
"the admin application.", "the admin application.",
id='admin.E404', id='admin.E404',
)) ))
if ('django.contrib.auth.middleware.AuthenticationMiddleware'
not in settings.MIDDLEWARE): if not _contains_subclass('django.contrib.auth.middleware.AuthenticationMiddleware', settings.MIDDLEWARE):
errors.append(checks.Error( errors.append(checks.Error(
"'django.contrib.auth.middleware.AuthenticationMiddleware' must " "'django.contrib.auth.middleware.AuthenticationMiddleware' must "
"be in MIDDLEWARE in order to use the admin application.", "be in MIDDLEWARE in order to use the admin application.",
id='admin.E408', id='admin.E408',
)) ))
if ('django.contrib.messages.middleware.MessageMiddleware' if not _contains_subclass('django.contrib.messages.middleware.MessageMiddleware', settings.MIDDLEWARE):
not in settings.MIDDLEWARE):
errors.append(checks.Error( errors.append(checks.Error(
"'django.contrib.messages.middleware.MessageMiddleware' must " "'django.contrib.messages.middleware.MessageMiddleware' must "
"be in MIDDLEWARE in order to use the admin application.", "be in MIDDLEWARE in order to use the admin application.",

View File

@ -1,7 +1,10 @@
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import AdminSite from django.contrib.admin import AdminSite
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.contrib.contenttypes.admin import GenericStackedInline from django.contrib.contenttypes.admin import GenericStackedInline
from django.contrib.messages.middleware import MessageMiddleware
from django.core import checks from django.core import checks
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
@ -37,6 +40,18 @@ class MyAdmin(admin.ModelAdmin):
return ['error!'] return ['error!']
class AuthenticationMiddlewareSubclass(AuthenticationMiddleware):
pass
class MessageMiddlewareSubclass(MessageMiddleware):
pass
class ModelBackendSubclass(ModelBackend):
pass
@override_settings( @override_settings(
SILENCED_SYSTEM_CHECKS=['fields.W342'], # ForeignKey(unique=True) SILENCED_SYSTEM_CHECKS=['fields.W342'], # ForeignKey(unique=True)
INSTALLED_APPS=[ INSTALLED_APPS=[
@ -129,6 +144,27 @@ class SystemChecksTestCase(SimpleTestCase):
with self.settings(AUTHENTICATION_BACKENDS=[]): with self.settings(AUTHENTICATION_BACKENDS=[]):
self.assertEqual(admin.checks.check_dependencies(), expected[1:]) self.assertEqual(admin.checks.check_dependencies(), expected[1:])
@override_settings(
AUTHENTICATION_BACKENDS=['admin_checks.tests.ModelBackendSubclass'],
TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': ['django.contrib.messages.context_processors.messages'],
},
}],
)
def test_context_processor_dependencies_model_backend_subclass(self):
self.assertEqual(admin.checks.check_dependencies(), [
checks.Error(
"'django.contrib.auth.context_processors.auth' must be "
"enabled in DjangoTemplates (TEMPLATES) if using the default "
"auth backend in order to use the admin application.",
id='admin.E402',
),
])
@override_settings( @override_settings(
TEMPLATES=[ TEMPLATES=[
{ {
@ -169,6 +205,21 @@ class SystemChecksTestCase(SimpleTestCase):
] ]
self.assertEqual(errors, expected) self.assertEqual(errors, expected)
@override_settings(MIDDLEWARE=[
'admin_checks.tests.AuthenticationMiddlewareSubclass',
'admin_checks.tests.MessageMiddlewareSubclass',
])
def test_middleware_subclasses(self):
self.assertEqual(admin.checks.check_dependencies(), [])
@override_settings(MIDDLEWARE=[
'django.contrib.does.not.Exist',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
])
def test_admin_check_ignores_import_error_in_middleware(self):
self.assertEqual(admin.checks.check_dependencies(), [])
def test_custom_adminsite(self): def test_custom_adminsite(self):
class CustomAdminSite(admin.AdminSite): class CustomAdminSite(admin.AdminSite):
pass pass