1. from django.contrib.auth.checks import check_models_permissions, check_user_model
    
  2. from django.contrib.auth.models import AbstractBaseUser
    
  3. from django.core import checks
    
  4. from django.db import models
    
  5. from django.db.models import Q, UniqueConstraint
    
  6. from django.test import SimpleTestCase, override_settings, override_system_checks
    
  7. from django.test.utils import isolate_apps
    
  8. 
    
  9. from .models import CustomUserNonUniqueUsername
    
  10. 
    
  11. 
    
  12. @isolate_apps("auth_tests", attr_name="apps")
    
  13. @override_system_checks([check_user_model])
    
  14. class UserModelChecksTests(SimpleTestCase):
    
  15.     @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserNonListRequiredFields")
    
  16.     def test_required_fields_is_list(self):
    
  17.         """REQUIRED_FIELDS should be a list."""
    
  18. 
    
  19.         class CustomUserNonListRequiredFields(AbstractBaseUser):
    
  20.             username = models.CharField(max_length=30, unique=True)
    
  21.             date_of_birth = models.DateField()
    
  22. 
    
  23.             USERNAME_FIELD = "username"
    
  24.             REQUIRED_FIELDS = "date_of_birth"
    
  25. 
    
  26.         errors = checks.run_checks(app_configs=self.apps.get_app_configs())
    
  27.         self.assertEqual(
    
  28.             errors,
    
  29.             [
    
  30.                 checks.Error(
    
  31.                     "'REQUIRED_FIELDS' must be a list or tuple.",
    
  32.                     obj=CustomUserNonListRequiredFields,
    
  33.                     id="auth.E001",
    
  34.                 ),
    
  35.             ],
    
  36.         )
    
  37. 
    
  38.     @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserBadRequiredFields")
    
  39.     def test_username_not_in_required_fields(self):
    
  40.         """USERNAME_FIELD should not appear in REQUIRED_FIELDS."""
    
  41. 
    
  42.         class CustomUserBadRequiredFields(AbstractBaseUser):
    
  43.             username = models.CharField(max_length=30, unique=True)
    
  44.             date_of_birth = models.DateField()
    
  45. 
    
  46.             USERNAME_FIELD = "username"
    
  47.             REQUIRED_FIELDS = ["username", "date_of_birth"]
    
  48. 
    
  49.         errors = checks.run_checks(self.apps.get_app_configs())
    
  50.         self.assertEqual(
    
  51.             errors,
    
  52.             [
    
  53.                 checks.Error(
    
  54.                     "The field named as the 'USERNAME_FIELD' for a custom user model "
    
  55.                     "must not be included in 'REQUIRED_FIELDS'.",
    
  56.                     hint=(
    
  57.                         "The 'USERNAME_FIELD' is currently set to 'username', you "
    
  58.                         "should remove 'username' from the 'REQUIRED_FIELDS'."
    
  59.                     ),
    
  60.                     obj=CustomUserBadRequiredFields,
    
  61.                     id="auth.E002",
    
  62.                 ),
    
  63.             ],
    
  64.         )
    
  65. 
    
  66.     @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserNonUniqueUsername")
    
  67.     def test_username_non_unique(self):
    
  68.         """
    
  69.         A non-unique USERNAME_FIELD raises an error only if the default
    
  70.         authentication backend is used. Otherwise, a warning is raised.
    
  71.         """
    
  72.         errors = checks.run_checks()
    
  73.         self.assertEqual(
    
  74.             errors,
    
  75.             [
    
  76.                 checks.Error(
    
  77.                     "'CustomUserNonUniqueUsername.username' must be "
    
  78.                     "unique because it is named as the 'USERNAME_FIELD'.",
    
  79.                     obj=CustomUserNonUniqueUsername,
    
  80.                     id="auth.E003",
    
  81.                 ),
    
  82.             ],
    
  83.         )
    
  84.         with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
    
  85.             errors = checks.run_checks()
    
  86.             self.assertEqual(
    
  87.                 errors,
    
  88.                 [
    
  89.                     checks.Warning(
    
  90.                         "'CustomUserNonUniqueUsername.username' is named as "
    
  91.                         "the 'USERNAME_FIELD', but it is not unique.",
    
  92.                         hint=(
    
  93.                             "Ensure that your authentication backend(s) can handle "
    
  94.                             "non-unique usernames."
    
  95.                         ),
    
  96.                         obj=CustomUserNonUniqueUsername,
    
  97.                         id="auth.W004",
    
  98.                     ),
    
  99.                 ],
    
  100.             )
    
  101. 
    
  102.     @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserPartiallyUnique")
    
  103.     def test_username_partially_unique(self):
    
  104.         class CustomUserPartiallyUnique(AbstractBaseUser):
    
  105.             username = models.CharField(max_length=30)
    
  106.             USERNAME_FIELD = "username"
    
  107. 
    
  108.             class Meta:
    
  109.                 constraints = [
    
  110.                     UniqueConstraint(
    
  111.                         fields=["username"],
    
  112.                         name="partial_username_unique",
    
  113.                         condition=Q(password__isnull=False),
    
  114.                     ),
    
  115.                 ]
    
  116. 
    
  117.         errors = checks.run_checks(app_configs=self.apps.get_app_configs())
    
  118.         self.assertEqual(
    
  119.             errors,
    
  120.             [
    
  121.                 checks.Error(
    
  122.                     "'CustomUserPartiallyUnique.username' must be unique because "
    
  123.                     "it is named as the 'USERNAME_FIELD'.",
    
  124.                     obj=CustomUserPartiallyUnique,
    
  125.                     id="auth.E003",
    
  126.                 ),
    
  127.             ],
    
  128.         )
    
  129.         with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
    
  130.             errors = checks.run_checks(app_configs=self.apps.get_app_configs())
    
  131.             self.assertEqual(
    
  132.                 errors,
    
  133.                 [
    
  134.                     checks.Warning(
    
  135.                         "'CustomUserPartiallyUnique.username' is named as the "
    
  136.                         "'USERNAME_FIELD', but it is not unique.",
    
  137.                         hint=(
    
  138.                             "Ensure that your authentication backend(s) can "
    
  139.                             "handle non-unique usernames."
    
  140.                         ),
    
  141.                         obj=CustomUserPartiallyUnique,
    
  142.                         id="auth.W004",
    
  143.                     ),
    
  144.                 ],
    
  145.             )
    
  146. 
    
  147.     @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserUniqueConstraint")
    
  148.     def test_username_unique_with_model_constraint(self):
    
  149.         class CustomUserUniqueConstraint(AbstractBaseUser):
    
  150.             username = models.CharField(max_length=30)
    
  151.             USERNAME_FIELD = "username"
    
  152. 
    
  153.             class Meta:
    
  154.                 constraints = [
    
  155.                     UniqueConstraint(fields=["username"], name="username_unique"),
    
  156.                 ]
    
  157. 
    
  158.         self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [])
    
  159.         with self.settings(AUTHENTICATION_BACKENDS=["my.custom.backend"]):
    
  160.             errors = checks.run_checks(app_configs=self.apps.get_app_configs())
    
  161.             self.assertEqual(errors, [])
    
  162. 
    
  163.     @override_settings(AUTH_USER_MODEL="auth_tests.BadUser")
    
  164.     def test_is_anonymous_authenticated_methods(self):
    
  165.         """
    
  166.         <User Model>.is_anonymous/is_authenticated must not be methods.
    
  167.         """
    
  168. 
    
  169.         class BadUser(AbstractBaseUser):
    
  170.             username = models.CharField(max_length=30, unique=True)
    
  171.             USERNAME_FIELD = "username"
    
  172. 
    
  173.             def is_anonymous(self):
    
  174.                 return True
    
  175. 
    
  176.             def is_authenticated(self):
    
  177.                 return True
    
  178. 
    
  179.         errors = checks.run_checks(app_configs=self.apps.get_app_configs())
    
  180.         self.assertEqual(
    
  181.             errors,
    
  182.             [
    
  183.                 checks.Critical(
    
  184.                     "%s.is_anonymous must be an attribute or property rather than "
    
  185.                     "a method. Ignoring this is a security issue as anonymous "
    
  186.                     "users will be treated as authenticated!" % BadUser,
    
  187.                     obj=BadUser,
    
  188.                     id="auth.C009",
    
  189.                 ),
    
  190.                 checks.Critical(
    
  191.                     "%s.is_authenticated must be an attribute or property rather "
    
  192.                     "than a method. Ignoring this is a security issue as anonymous "
    
  193.                     "users will be treated as authenticated!" % BadUser,
    
  194.                     obj=BadUser,
    
  195.                     id="auth.C010",
    
  196.                 ),
    
  197.             ],
    
  198.         )
    
  199. 
    
  200. 
    
  201. @isolate_apps("auth_tests", attr_name="apps")
    
  202. @override_system_checks([check_models_permissions])
    
  203. class ModelsPermissionsChecksTests(SimpleTestCase):
    
  204.     def test_clashing_default_permissions(self):
    
  205.         class Checked(models.Model):
    
  206.             class Meta:
    
  207.                 permissions = [("change_checked", "Can edit permission (duplicate)")]
    
  208. 
    
  209.         errors = checks.run_checks(self.apps.get_app_configs())
    
  210.         self.assertEqual(
    
  211.             errors,
    
  212.             [
    
  213.                 checks.Error(
    
  214.                     "The permission codenamed 'change_checked' clashes with a builtin "
    
  215.                     "permission for model 'auth_tests.Checked'.",
    
  216.                     obj=Checked,
    
  217.                     id="auth.E005",
    
  218.                 ),
    
  219.             ],
    
  220.         )
    
  221. 
    
  222.     def test_non_clashing_custom_permissions(self):
    
  223.         class Checked(models.Model):
    
  224.             class Meta:
    
  225.                 permissions = [
    
  226.                     ("my_custom_permission", "Some permission"),
    
  227.                     ("other_one", "Some other permission"),
    
  228.                 ]
    
  229. 
    
  230.         errors = checks.run_checks(self.apps.get_app_configs())
    
  231.         self.assertEqual(errors, [])
    
  232. 
    
  233.     def test_clashing_custom_permissions(self):
    
  234.         class Checked(models.Model):
    
  235.             class Meta:
    
  236.                 permissions = [
    
  237.                     ("my_custom_permission", "Some permission"),
    
  238.                     ("other_one", "Some other permission"),
    
  239.                     (
    
  240.                         "my_custom_permission",
    
  241.                         "Some permission with duplicate permission code",
    
  242.                     ),
    
  243.                 ]
    
  244. 
    
  245.         errors = checks.run_checks(self.apps.get_app_configs())
    
  246.         self.assertEqual(
    
  247.             errors,
    
  248.             [
    
  249.                 checks.Error(
    
  250.                     "The permission codenamed 'my_custom_permission' is duplicated for "
    
  251.                     "model 'auth_tests.Checked'.",
    
  252.                     obj=Checked,
    
  253.                     id="auth.E006",
    
  254.                 ),
    
  255.             ],
    
  256.         )
    
  257. 
    
  258.     def test_verbose_name_max_length(self):
    
  259.         class Checked(models.Model):
    
  260.             class Meta:
    
  261.                 verbose_name = (
    
  262.                     "some ridiculously long verbose name that is out of control" * 5
    
  263.                 )
    
  264. 
    
  265.         errors = checks.run_checks(self.apps.get_app_configs())
    
  266.         self.assertEqual(
    
  267.             errors,
    
  268.             [
    
  269.                 checks.Error(
    
  270.                     "The verbose_name of model 'auth_tests.Checked' must be at most "
    
  271.                     "244 characters for its builtin permission names to be at most 255 "
    
  272.                     "characters.",
    
  273.                     obj=Checked,
    
  274.                     id="auth.E007",
    
  275.                 ),
    
  276.             ],
    
  277.         )
    
  278. 
    
  279.     def test_model_name_max_length(self):
    
  280.         model_name = "X" * 94
    
  281.         model = type(model_name, (models.Model,), {"__module__": self.__module__})
    
  282.         errors = checks.run_checks(self.apps.get_app_configs())
    
  283.         self.assertEqual(
    
  284.             errors,
    
  285.             [
    
  286.                 checks.Error(
    
  287.                     "The name of model 'auth_tests.%s' must be at most 93 "
    
  288.                     "characters for its builtin permission codenames to be at "
    
  289.                     "most 100 characters." % model_name,
    
  290.                     obj=model,
    
  291.                     id="auth.E011",
    
  292.                 ),
    
  293.             ],
    
  294.         )
    
  295. 
    
  296.     def test_custom_permission_name_max_length(self):
    
  297.         custom_permission_name = (
    
  298.             "some ridiculously long verbose name that is out of control" * 5
    
  299.         )
    
  300. 
    
  301.         class Checked(models.Model):
    
  302.             class Meta:
    
  303.                 permissions = [
    
  304.                     ("my_custom_permission", custom_permission_name),
    
  305.                 ]
    
  306. 
    
  307.         errors = checks.run_checks(self.apps.get_app_configs())
    
  308.         self.assertEqual(
    
  309.             errors,
    
  310.             [
    
  311.                 checks.Error(
    
  312.                     "The permission named '%s' of model 'auth_tests.Checked' is longer "
    
  313.                     "than 255 characters." % custom_permission_name,
    
  314.                     obj=Checked,
    
  315.                     id="auth.E008",
    
  316.                 ),
    
  317.             ],
    
  318.         )
    
  319. 
    
  320.     def test_custom_permission_codename_max_length(self):
    
  321.         custom_permission_codename = "x" * 101
    
  322. 
    
  323.         class Checked(models.Model):
    
  324.             class Meta:
    
  325.                 permissions = [
    
  326.                     (custom_permission_codename, "Custom permission"),
    
  327.                 ]
    
  328. 
    
  329.         errors = checks.run_checks(self.apps.get_app_configs())
    
  330.         self.assertEqual(
    
  331.             errors,
    
  332.             [
    
  333.                 checks.Error(
    
  334.                     "The permission codenamed '%s' of model 'auth_tests.Checked' "
    
  335.                     "is longer than 100 characters." % custom_permission_codename,
    
  336.                     obj=Checked,
    
  337.                     id="auth.E012",
    
  338.                 ),
    
  339.             ],
    
  340.         )
    
  341. 
    
  342.     def test_empty_default_permissions(self):
    
  343.         class Checked(models.Model):
    
  344.             class Meta:
    
  345.                 default_permissions = ()
    
  346. 
    
  347.         self.assertEqual(checks.run_checks(self.apps.get_app_configs()), [])