mirror of
				https://github.com/django/django.git
				synced 2025-10-28 16:16:12 +00:00 
			
		
		
		
	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",
 | |
|                 )
 | |
|             ],
 | |
|         )
 |