from django.contrib.auth.checks import check_models_permissions, check_user_model from django.contrib.auth.models import AbstractBaseUser 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): """ .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()), [])