1
0
mirror of https://github.com/django/django.git synced 2024-12-23 17:46:27 +00:00
django/tests/auth_tests/test_checks.py
Hannes Ljungberg f7963615eb Fixed #32121 -- Fixed detecting uniqueness of USERNAME_FIELD when using Meta.constraints.
Co-authored-by: Simon Charette <charettes@users.noreply.github.com>
2020-10-20 07:23:51 +02:00

296 lines
12 KiB
Python

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