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()), [])