mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
c7fc9f20b4
Co-authored-by: Adam Johnson <me@adamj.eu> Co-authored-by: Mehmet İnce <mehmet@mehmetince.net> Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
456 lines
16 KiB
Python
456 lines
16 KiB
Python
from django.contrib.auth.checks import (
|
|
check_middleware,
|
|
check_models_permissions,
|
|
check_user_model,
|
|
)
|
|
from django.contrib.auth.middleware import (
|
|
AuthenticationMiddleware,
|
|
LoginRequiredMiddleware,
|
|
)
|
|
from django.contrib.auth.models import AbstractBaseUser
|
|
from django.contrib.sessions.middleware import SessionMiddleware
|
|
from django.core import checks
|
|
from django.db import models
|
|
from django.db.models import Q, UniqueConstraint
|
|
from django.test import SimpleTestCase, override_settings, override_system_checks
|
|
from django.test.utils import isolate_apps
|
|
|
|
from .models import CustomUserNonUniqueUsername
|
|
|
|
|
|
@isolate_apps("auth_tests", attr_name="apps")
|
|
@override_system_checks([check_user_model])
|
|
class UserModelChecksTests(SimpleTestCase):
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUserNonListRequiredFields")
|
|
def test_required_fields_is_list(self):
|
|
"""REQUIRED_FIELDS should be a list."""
|
|
|
|
class CustomUserNonListRequiredFields(AbstractBaseUser):
|
|
username = models.CharField(max_length=30, unique=True)
|
|
date_of_birth = models.DateField()
|
|
|
|
USERNAME_FIELD = "username"
|
|
REQUIRED_FIELDS = "date_of_birth"
|
|
|
|
errors = checks.run_checks(app_configs=self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"'REQUIRED_FIELDS' must be a list or tuple.",
|
|
obj=CustomUserNonListRequiredFields,
|
|
id="auth.E001",
|
|
),
|
|
],
|
|
)
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUserBadRequiredFields")
|
|
def test_username_not_in_required_fields(self):
|
|
"""USERNAME_FIELD should not appear in REQUIRED_FIELDS."""
|
|
|
|
class CustomUserBadRequiredFields(AbstractBaseUser):
|
|
username = models.CharField(max_length=30, unique=True)
|
|
date_of_birth = models.DateField()
|
|
|
|
USERNAME_FIELD = "username"
|
|
REQUIRED_FIELDS = ["username", "date_of_birth"]
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The field named as the 'USERNAME_FIELD' for a custom user model "
|
|
"must not be included in 'REQUIRED_FIELDS'.",
|
|
hint=(
|
|
"The 'USERNAME_FIELD' is currently set to 'username', you "
|
|
"should remove 'username' from the 'REQUIRED_FIELDS'."
|
|
),
|
|
obj=CustomUserBadRequiredFields,
|
|
id="auth.E002",
|
|
),
|
|
],
|
|
)
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUserNonUniqueUsername")
|
|
def test_username_non_unique(self):
|
|
"""
|
|
A non-unique USERNAME_FIELD raises an error only if the default
|
|
authentication backend is used. Otherwise, a warning is raised.
|
|
"""
|
|
errors = checks.run_checks()
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"'CustomUserNonUniqueUsername.username' must be "
|
|
"unique because it is named as the 'USERNAME_FIELD'.",
|
|
obj=CustomUserNonUniqueUsername,
|
|
id="auth.E003",
|
|
),
|
|
],
|
|
)
|
|
with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Warning(
|
|
"'CustomUserNonUniqueUsername.username' is named as "
|
|
"the 'USERNAME_FIELD', but it is not unique.",
|
|
hint=(
|
|
"Ensure that your authentication backend(s) can handle "
|
|
"non-unique usernames."
|
|
),
|
|
obj=CustomUserNonUniqueUsername,
|
|
id="auth.W004",
|
|
),
|
|
],
|
|
)
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUserPartiallyUnique")
|
|
def test_username_partially_unique(self):
|
|
class CustomUserPartiallyUnique(AbstractBaseUser):
|
|
username = models.CharField(max_length=30)
|
|
USERNAME_FIELD = "username"
|
|
|
|
class Meta:
|
|
constraints = [
|
|
UniqueConstraint(
|
|
fields=["username"],
|
|
name="partial_username_unique",
|
|
condition=Q(password__isnull=False),
|
|
),
|
|
]
|
|
|
|
errors = checks.run_checks(app_configs=self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"'CustomUserPartiallyUnique.username' must be unique because "
|
|
"it is named as the 'USERNAME_FIELD'.",
|
|
obj=CustomUserPartiallyUnique,
|
|
id="auth.E003",
|
|
),
|
|
],
|
|
)
|
|
with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
|
|
errors = checks.run_checks(app_configs=self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Warning(
|
|
"'CustomUserPartiallyUnique.username' is named as the "
|
|
"'USERNAME_FIELD', but it is not unique.",
|
|
hint=(
|
|
"Ensure that your authentication backend(s) can "
|
|
"handle non-unique usernames."
|
|
),
|
|
obj=CustomUserPartiallyUnique,
|
|
id="auth.W004",
|
|
),
|
|
],
|
|
)
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUserUniqueConstraint")
|
|
def test_username_unique_with_model_constraint(self):
|
|
class CustomUserUniqueConstraint(AbstractBaseUser):
|
|
username = models.CharField(max_length=30)
|
|
USERNAME_FIELD = "username"
|
|
|
|
class Meta:
|
|
constraints = [
|
|
UniqueConstraint(fields=["username"], name="username_unique"),
|
|
]
|
|
|
|
self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [])
|
|
with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
|
|
errors = checks.run_checks(app_configs=self.apps.get_app_configs())
|
|
self.assertEqual(errors, [])
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.BadUser")
|
|
def test_is_anonymous_authenticated_methods(self):
|
|
"""
|
|
<User Model>.is_anonymous/is_authenticated must not be methods.
|
|
"""
|
|
|
|
class BadUser(AbstractBaseUser):
|
|
username = models.CharField(max_length=30, unique=True)
|
|
USERNAME_FIELD = "username"
|
|
|
|
def is_anonymous(self):
|
|
return True
|
|
|
|
def is_authenticated(self):
|
|
return True
|
|
|
|
errors = checks.run_checks(app_configs=self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Critical(
|
|
"%s.is_anonymous must be an attribute or property rather than "
|
|
"a method. Ignoring this is a security issue as anonymous "
|
|
"users will be treated as authenticated!" % BadUser,
|
|
obj=BadUser,
|
|
id="auth.C009",
|
|
),
|
|
checks.Critical(
|
|
"%s.is_authenticated must be an attribute or property rather "
|
|
"than a method. Ignoring this is a security issue as anonymous "
|
|
"users will be treated as authenticated!" % BadUser,
|
|
obj=BadUser,
|
|
id="auth.C010",
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
@isolate_apps("auth_tests", attr_name="apps")
|
|
@override_system_checks([check_models_permissions])
|
|
class ModelsPermissionsChecksTests(SimpleTestCase):
|
|
def test_clashing_default_permissions(self):
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
permissions = [("change_checked", "Can edit permission (duplicate)")]
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The permission codenamed 'change_checked' clashes with a builtin "
|
|
"permission for model 'auth_tests.Checked'.",
|
|
obj=Checked,
|
|
id="auth.E005",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_non_clashing_custom_permissions(self):
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
permissions = [
|
|
("my_custom_permission", "Some permission"),
|
|
("other_one", "Some other permission"),
|
|
]
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(errors, [])
|
|
|
|
def test_clashing_custom_permissions(self):
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
permissions = [
|
|
("my_custom_permission", "Some permission"),
|
|
("other_one", "Some other permission"),
|
|
(
|
|
"my_custom_permission",
|
|
"Some permission with duplicate permission code",
|
|
),
|
|
]
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The permission codenamed 'my_custom_permission' is duplicated for "
|
|
"model 'auth_tests.Checked'.",
|
|
obj=Checked,
|
|
id="auth.E006",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_verbose_name_max_length(self):
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
verbose_name = (
|
|
"some ridiculously long verbose name that is out of control" * 5
|
|
)
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The verbose_name of model 'auth_tests.Checked' must be at most "
|
|
"244 characters for its builtin permission names to be at most 255 "
|
|
"characters.",
|
|
obj=Checked,
|
|
id="auth.E007",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_model_name_max_length(self):
|
|
model_name = "X" * 94
|
|
model = type(model_name, (models.Model,), {"__module__": self.__module__})
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The name of model 'auth_tests.%s' must be at most 93 "
|
|
"characters for its builtin permission codenames to be at "
|
|
"most 100 characters." % model_name,
|
|
obj=model,
|
|
id="auth.E011",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_custom_permission_name_max_length(self):
|
|
custom_permission_name = (
|
|
"some ridiculously long verbose name that is out of control" * 5
|
|
)
|
|
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
permissions = [
|
|
("my_custom_permission", custom_permission_name),
|
|
]
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The permission named '%s' of model 'auth_tests.Checked' is longer "
|
|
"than 255 characters." % custom_permission_name,
|
|
obj=Checked,
|
|
id="auth.E008",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_custom_permission_codename_max_length(self):
|
|
custom_permission_codename = "x" * 101
|
|
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
permissions = [
|
|
(custom_permission_codename, "Custom permission"),
|
|
]
|
|
|
|
errors = checks.run_checks(self.apps.get_app_configs())
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"The permission codenamed '%s' of model 'auth_tests.Checked' "
|
|
"is longer than 100 characters." % custom_permission_codename,
|
|
obj=Checked,
|
|
id="auth.E012",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_empty_default_permissions(self):
|
|
class Checked(models.Model):
|
|
class Meta:
|
|
default_permissions = ()
|
|
|
|
self.assertEqual(checks.run_checks(self.apps.get_app_configs()), [])
|
|
|
|
|
|
class LoginRequiredMiddlewareSubclass(LoginRequiredMiddleware):
|
|
redirect_field_name = "redirect_to"
|
|
|
|
|
|
class AuthenticationMiddlewareSubclass(AuthenticationMiddleware):
|
|
pass
|
|
|
|
|
|
class SessionMiddlewareSubclass(SessionMiddleware):
|
|
pass
|
|
|
|
|
|
@override_system_checks([check_middleware])
|
|
class MiddlewareChecksTests(SimpleTestCase):
|
|
@override_settings(
|
|
MIDDLEWARE=[
|
|
"auth_tests.test_checks.SessionMiddlewareSubclass",
|
|
"auth_tests.test_checks.AuthenticationMiddlewareSubclass",
|
|
"auth_tests.test_checks.LoginRequiredMiddlewareSubclass",
|
|
]
|
|
)
|
|
def test_middleware_subclasses(self):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(errors, [])
|
|
|
|
@override_settings(
|
|
MIDDLEWARE=[
|
|
"auth_tests.test_checks",
|
|
"auth_tests.test_checks.NotExist",
|
|
]
|
|
)
|
|
def test_invalid_middleware_skipped(self):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(errors, [])
|
|
|
|
@override_settings(
|
|
MIDDLEWARE=[
|
|
"django.contrib.does.not.Exist",
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
|
]
|
|
)
|
|
def test_check_ignores_import_error_in_middleware(self):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(errors, [])
|
|
|
|
@override_settings(
|
|
MIDDLEWARE=[
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
|
]
|
|
)
|
|
def test_correct_order_with_login_required_middleware(self):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(errors, [])
|
|
|
|
@override_settings(
|
|
MIDDLEWARE=[
|
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
]
|
|
)
|
|
def test_incorrect_order_with_login_required_middleware(self):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"In order to use django.contrib.auth.middleware."
|
|
"LoginRequiredMiddleware, django.contrib.auth.middleware."
|
|
"AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
|
|
id="auth.E013",
|
|
)
|
|
],
|
|
)
|
|
|
|
@override_settings(
|
|
MIDDLEWARE=[
|
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
|
]
|
|
)
|
|
def test_missing_authentication_with_login_required_middleware(self):
|
|
errors = checks.run_checks()
|
|
self.assertEqual(
|
|
errors,
|
|
[
|
|
checks.Error(
|
|
"In order to use django.contrib.auth.middleware."
|
|
"LoginRequiredMiddleware, django.contrib.auth.middleware."
|
|
"AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
|
|
id="auth.E013",
|
|
)
|
|
],
|
|
)
|