2016-02-14 15:30:01 +00:00
|
|
|
|
import datetime
|
2013-07-31 02:29:34 +00:00
|
|
|
|
import re
|
2022-09-30 08:50:28 +00:00
|
|
|
|
import urllib.parse
|
2017-01-19 17:16:04 +00:00
|
|
|
|
from unittest import mock
|
2013-05-06 10:45:56 +00:00
|
|
|
|
|
2015-01-28 12:35:27 +00:00
|
|
|
|
from django.contrib.auth.forms import (
|
2015-07-06 22:12:26 +00:00
|
|
|
|
AdminPasswordChangeForm,
|
|
|
|
|
AuthenticationForm,
|
2022-09-24 14:26:14 +00:00
|
|
|
|
BaseUserCreationForm,
|
2015-07-06 22:12:26 +00:00
|
|
|
|
PasswordChangeForm,
|
|
|
|
|
PasswordResetForm,
|
|
|
|
|
ReadOnlyPasswordHashField,
|
|
|
|
|
ReadOnlyPasswordHashWidget,
|
|
|
|
|
SetPasswordForm,
|
|
|
|
|
UserChangeForm,
|
|
|
|
|
UserCreationForm,
|
2023-10-17 09:48:32 +00:00
|
|
|
|
UsernameField,
|
2015-01-28 12:35:27 +00:00
|
|
|
|
)
|
2024-02-20 15:12:37 +00:00
|
|
|
|
from django.contrib.auth.hashers import make_password
|
2012-03-20 20:51:16 +00:00
|
|
|
|
from django.contrib.auth.models import User
|
2016-02-19 00:58:30 +00:00
|
|
|
|
from django.contrib.auth.signals import user_login_failed
|
2015-02-15 03:03:25 +00:00
|
|
|
|
from django.contrib.sites.models import Site
|
2018-07-02 22:10:36 +00:00
|
|
|
|
from django.core import mail
|
2020-02-12 13:48:49 +00:00
|
|
|
|
from django.core.exceptions import ValidationError
|
2014-05-09 06:48:41 +00:00
|
|
|
|
from django.core.mail import EmailMultiAlternatives
|
2021-05-19 04:05:04 +00:00
|
|
|
|
from django.forms import forms
|
2016-07-26 13:50:29 +00:00
|
|
|
|
from django.forms.fields import CharField, Field, IntegerField
|
2017-01-19 17:16:04 +00:00
|
|
|
|
from django.test import SimpleTestCase, TestCase, override_settings
|
2022-09-30 08:50:28 +00:00
|
|
|
|
from django.urls import reverse
|
2016-12-01 10:38:01 +00:00
|
|
|
|
from django.utils import translation
|
2013-05-06 10:45:56 +00:00
|
|
|
|
from django.utils.text import capfirst
|
2017-01-26 19:58:33 +00:00
|
|
|
|
from django.utils.translation import gettext as _
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2016-08-24 17:20:12 +00:00
|
|
|
|
from .models.custom_user import (
|
|
|
|
|
CustomUser,
|
|
|
|
|
CustomUserWithoutIsActiveField,
|
|
|
|
|
ExtensionUser,
|
|
|
|
|
)
|
2016-09-19 12:55:18 +00:00
|
|
|
|
from .models.with_custom_email_field import CustomEmailField
|
2016-07-26 13:50:29 +00:00
|
|
|
|
from .models.with_integer_username import IntegerUsernameUser
|
2022-11-27 19:49:02 +00:00
|
|
|
|
from .models.with_many_to_many import CustomUserWithM2M, Organization
|
2014-12-17 21:10:57 +00:00
|
|
|
|
from .settings import AUTH_TEMPLATES
|
|
|
|
|
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class TestDataMixin:
|
2015-02-23 00:53:57 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def setUpTestData(cls):
|
2016-02-05 20:56:52 +00:00
|
|
|
|
cls.u1 = User.objects.create_user(
|
|
|
|
|
username="testclient", password="password", email="testclient@example.com"
|
|
|
|
|
)
|
|
|
|
|
cls.u2 = User.objects.create_user(
|
|
|
|
|
username="inactive", password="password", is_active=False
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2016-02-05 20:56:52 +00:00
|
|
|
|
cls.u3 = User.objects.create_user(username="staff", password="password")
|
|
|
|
|
cls.u4 = User.objects.create(username="empty_password", password="")
|
|
|
|
|
cls.u5 = User.objects.create(username="unmanageable_password", password="$")
|
|
|
|
|
cls.u6 = User.objects.create(username="unknown_password", password="foo$bar")
|
2024-02-20 15:12:37 +00:00
|
|
|
|
cls.u7 = User.objects.create(
|
|
|
|
|
username="unusable_password", password=make_password(None)
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-02-23 00:53:57 +00:00
|
|
|
|
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class BaseUserCreationFormTest(TestDataMixin, TestCase):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_user_already_exists(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["username"].errors,
|
2017-01-24 11:22:42 +00:00
|
|
|
|
[str(User._meta.get_field("username").error_messages["unique"])],
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_invalid_data(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "jsmith!",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
self.assertFalse(form.is_valid())
|
2014-04-06 15:30:46 +00:00
|
|
|
|
validator = next(
|
|
|
|
|
v
|
|
|
|
|
for v in User._meta.get_field("username").validators
|
|
|
|
|
if v.code == "invalid"
|
|
|
|
|
)
|
2017-01-24 11:22:42 +00:00
|
|
|
|
self.assertEqual(form["username"].errors, [str(validator.message)])
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_password_verification(self):
|
|
|
|
|
# The verification password is incorrect.
|
|
|
|
|
data = {
|
|
|
|
|
"username": "jsmith",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(
|
2017-01-24 11:22:42 +00:00
|
|
|
|
form["password2"].errors, [str(form.error_messages["password_mismatch"])]
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_both_passwords(self):
|
|
|
|
|
# One (or both) passwords weren't given
|
|
|
|
|
data = {"username": "jsmith"}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2017-01-24 11:22:42 +00:00
|
|
|
|
required_error = [str(Field.default_error_messages["required"])]
|
2010-09-09 23:21:16 +00:00
|
|
|
|
self.assertFalse(form.is_valid())
|
2011-12-15 16:12:46 +00:00
|
|
|
|
self.assertEqual(form["password1"].errors, required_error)
|
|
|
|
|
self.assertEqual(form["password2"].errors, required_error)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
data["password2"] = "test123"
|
|
|
|
|
form = UserCreationForm(data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2011-12-15 16:12:46 +00:00
|
|
|
|
self.assertEqual(form["password1"].errors, required_error)
|
2012-08-04 12:55:13 +00:00
|
|
|
|
self.assertEqual(form["password2"].errors, [])
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-07-06 22:12:26 +00:00
|
|
|
|
@mock.patch("django.contrib.auth.password_validation.password_changed")
|
|
|
|
|
def test_success(self, password_changed):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
# The success case.
|
|
|
|
|
data = {
|
|
|
|
|
"username": "jsmith@example.com",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
self.assertTrue(form.is_valid())
|
2015-07-06 22:12:26 +00:00
|
|
|
|
form.save(commit=False)
|
|
|
|
|
self.assertEqual(password_changed.call_count, 0)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
u = form.save()
|
2015-07-06 22:12:26 +00:00
|
|
|
|
self.assertEqual(password_changed.call_count, 1)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
self.assertEqual(repr(u), "<User: jsmith@example.com>")
|
|
|
|
|
|
2016-04-22 17:39:13 +00:00
|
|
|
|
def test_unicode_username(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "宝",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2016-12-01 10:38:01 +00:00
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
u = form.save()
|
|
|
|
|
self.assertEqual(u.username, "宝")
|
|
|
|
|
|
2016-06-21 13:06:34 +00:00
|
|
|
|
def test_normalize_username(self):
|
|
|
|
|
# The normalization happens in AbstractBaseUser.clean() and ModelForm
|
|
|
|
|
# validation calls Model.clean().
|
|
|
|
|
ohm_username = "testΩ" # U+2126 OHM SIGN
|
|
|
|
|
data = {
|
|
|
|
|
"username": ohm_username,
|
|
|
|
|
"password1": "pwd2",
|
|
|
|
|
"password2": "pwd2",
|
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2016-06-21 13:06:34 +00:00
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
user = form.save()
|
|
|
|
|
self.assertNotEqual(user.username, ohm_username)
|
|
|
|
|
self.assertEqual(user.username, "testΩ") # U+03A9 GREEK CAPITAL LETTER OMEGA
|
|
|
|
|
|
2023-10-17 09:48:32 +00:00
|
|
|
|
def test_invalid_username_no_normalize(self):
|
|
|
|
|
field = UsernameField(max_length=254)
|
|
|
|
|
# Usernames are not normalized if they are too long.
|
|
|
|
|
self.assertEqual(field.to_python("½" * 255), "½" * 255)
|
|
|
|
|
self.assertEqual(field.to_python("ff" * 254), "ff" * 254)
|
|
|
|
|
|
2016-04-22 19:17:42 +00:00
|
|
|
|
def test_duplicate_normalized_unicode(self):
|
|
|
|
|
"""
|
|
|
|
|
To prevent almost identical usernames, visually identical but differing
|
|
|
|
|
by their unicode code points only, Unicode NFKC normalization should
|
|
|
|
|
make appear them equal to Django.
|
|
|
|
|
"""
|
|
|
|
|
omega_username = "iamtheΩ" # U+03A9 GREEK CAPITAL LETTER OMEGA
|
|
|
|
|
ohm_username = "iamtheΩ" # U+2126 OHM SIGN
|
|
|
|
|
self.assertNotEqual(omega_username, ohm_username)
|
|
|
|
|
User.objects.create_user(username=omega_username, password="pwd")
|
|
|
|
|
data = {
|
|
|
|
|
"username": ohm_username,
|
|
|
|
|
"password1": "pwd2",
|
|
|
|
|
"password2": "pwd2",
|
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2016-04-22 19:17:42 +00:00
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.errors["username"], ["A user with that username already exists."]
|
|
|
|
|
)
|
|
|
|
|
|
2015-07-06 18:14:59 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation."
|
|
|
|
|
"UserAttributeSimilarityValidator"
|
2022-02-04 07:08:27 +00:00
|
|
|
|
)
|
2015-07-06 18:14:59 +00:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation.MinimumLengthValidator"
|
|
|
|
|
),
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"min_length": 12,
|
|
|
|
|
},
|
2022-02-03 19:24:19 +00:00
|
|
|
|
},
|
2015-07-06 18:14:59 +00:00
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
def test_validates_password(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2015-07-06 18:14:59 +00:00
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(len(form["password2"].errors), 2)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"The password is too similar to the username.", form["password2"].errors
|
|
|
|
|
)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"This password is too short. It must contain at least 12 characters.",
|
|
|
|
|
form["password2"].errors,
|
|
|
|
|
)
|
|
|
|
|
|
2024-01-23 15:45:18 +00:00
|
|
|
|
# passwords are not validated if `usable_password` is unset
|
|
|
|
|
data = {
|
|
|
|
|
"username": "othertestclient",
|
|
|
|
|
"password1": "othertestclient",
|
|
|
|
|
"password2": "othertestclient",
|
|
|
|
|
"usable_password": "false",
|
|
|
|
|
}
|
|
|
|
|
form = BaseUserCreationForm(data)
|
|
|
|
|
self.assertIs(form.is_valid(), True, form.errors)
|
|
|
|
|
|
2016-02-14 15:30:01 +00:00
|
|
|
|
def test_custom_form(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class CustomUserCreationForm(BaseUserCreationForm):
|
|
|
|
|
class Meta(BaseUserCreationForm.Meta):
|
2018-07-02 22:10:36 +00:00
|
|
|
|
model = ExtensionUser
|
|
|
|
|
fields = UserCreationForm.Meta.fields + ("date_of_birth",)
|
2017-11-15 12:27:53 +00:00
|
|
|
|
|
2018-07-02 22:10:36 +00:00
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
"date_of_birth": "1988-02-24",
|
|
|
|
|
}
|
|
|
|
|
form = CustomUserCreationForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
2016-03-20 10:24:51 +00:00
|
|
|
|
|
|
|
|
|
def test_custom_form_with_different_username_field(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class CustomUserCreationForm(BaseUserCreationForm):
|
|
|
|
|
class Meta(BaseUserCreationForm.Meta):
|
2016-03-20 10:24:51 +00:00
|
|
|
|
model = CustomUser
|
|
|
|
|
fields = ("email", "date_of_birth")
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@client222.com",
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
"date_of_birth": "1988-02-24",
|
|
|
|
|
}
|
|
|
|
|
form = CustomUserCreationForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
2016-02-14 15:30:01 +00:00
|
|
|
|
|
2016-08-24 17:20:12 +00:00
|
|
|
|
def test_custom_form_hidden_username_field(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class CustomUserCreationForm(BaseUserCreationForm):
|
|
|
|
|
class Meta(BaseUserCreationForm.Meta):
|
2016-08-24 17:20:12 +00:00
|
|
|
|
model = CustomUserWithoutIsActiveField
|
|
|
|
|
fields = ("email",) # without USERNAME_FIELD
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"email": "testclient@example.com",
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
}
|
|
|
|
|
form = CustomUserCreationForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
|
2022-11-27 19:49:02 +00:00
|
|
|
|
def test_custom_form_saves_many_to_many_field(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class CustomUserCreationForm(BaseUserCreationForm):
|
|
|
|
|
class Meta(BaseUserCreationForm.Meta):
|
2022-11-27 19:49:02 +00:00
|
|
|
|
model = CustomUserWithM2M
|
|
|
|
|
fields = UserCreationForm.Meta.fields + ("orgs",)
|
|
|
|
|
|
|
|
|
|
organization = Organization.objects.create(name="organization 1")
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient@example.com",
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
"orgs": [str(organization.pk)],
|
|
|
|
|
}
|
|
|
|
|
form = CustomUserCreationForm(data)
|
|
|
|
|
self.assertIs(form.is_valid(), True)
|
|
|
|
|
user = form.save(commit=True)
|
|
|
|
|
self.assertSequenceEqual(user.orgs.all(), [organization])
|
|
|
|
|
|
2016-03-14 20:21:05 +00:00
|
|
|
|
def test_password_whitespace_not_stripped(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testuser",
|
|
|
|
|
"password1": " testpassword ",
|
|
|
|
|
"password2": " testpassword ",
|
|
|
|
|
}
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm(data)
|
2016-03-14 20:21:05 +00:00
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.cleaned_data["password1"], data["password1"])
|
|
|
|
|
self.assertEqual(form.cleaned_data["password2"], data["password2"])
|
2022-02-03 19:24:19 +00:00
|
|
|
|
|
2016-04-14 17:12:30 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation."
|
|
|
|
|
"UserAttributeSimilarityValidator"
|
2022-02-04 07:08:27 +00:00
|
|
|
|
)
|
2016-04-14 17:12:30 +00:00
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
def test_password_help_text(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm()
|
2016-09-10 22:38:28 +00:00
|
|
|
|
self.assertEqual(
|
2016-04-14 17:12:30 +00:00
|
|
|
|
form.fields["password1"].help_text,
|
2019-06-27 16:39:47 +00:00
|
|
|
|
"<ul><li>"
|
|
|
|
|
"Your password can’t be too similar to your other personal information."
|
|
|
|
|
"</li></ul>",
|
2016-04-14 17:12:30 +00:00
|
|
|
|
)
|
|
|
|
|
|
2017-04-25 19:26:58 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation."
|
|
|
|
|
"UserAttributeSimilarityValidator"
|
2022-02-04 07:08:27 +00:00
|
|
|
|
)
|
2017-04-25 19:26:58 +00:00
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
def test_user_create_form_validates_password_with_all_data(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
"""
|
|
|
|
|
BaseUserCreationForm password validation uses all of the form's data.
|
|
|
|
|
"""
|
2022-02-03 19:24:19 +00:00
|
|
|
|
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class CustomUserCreationForm(BaseUserCreationForm):
|
|
|
|
|
class Meta(BaseUserCreationForm.Meta):
|
2017-04-25 19:26:58 +00:00
|
|
|
|
model = User
|
|
|
|
|
fields = ("username", "email", "first_name", "last_name")
|
2022-02-03 19:24:19 +00:00
|
|
|
|
|
2017-04-25 19:26:58 +00:00
|
|
|
|
form = CustomUserCreationForm(
|
|
|
|
|
{
|
|
|
|
|
"username": "testuser",
|
|
|
|
|
"password1": "testpassword",
|
|
|
|
|
"password2": "testpassword",
|
|
|
|
|
"first_name": "testpassword",
|
|
|
|
|
"last_name": "lastname",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.errors["password2"],
|
|
|
|
|
["The password is too similar to the first name."],
|
|
|
|
|
)
|
|
|
|
|
|
2024-01-23 15:45:18 +00:00
|
|
|
|
# passwords are not validated if `usable_password` is unset
|
|
|
|
|
form = CustomUserCreationForm(
|
|
|
|
|
{
|
|
|
|
|
"username": "testuser",
|
|
|
|
|
"password1": "testpassword",
|
|
|
|
|
"password2": "testpassword",
|
|
|
|
|
"first_name": "testpassword",
|
|
|
|
|
"last_name": "lastname",
|
|
|
|
|
"usable_password": "false",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
self.assertIs(form.is_valid(), True, form.errors)
|
|
|
|
|
|
2019-03-24 18:53:31 +00:00
|
|
|
|
def test_username_field_autocapitalize_none(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm()
|
2019-03-24 18:53:31 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields["username"].widget.attrs.get("autocapitalize"), "none"
|
|
|
|
|
)
|
|
|
|
|
|
2019-03-10 10:03:42 +00:00
|
|
|
|
def test_html_autocomplete_attributes(self):
|
2022-09-24 14:26:14 +00:00
|
|
|
|
form = BaseUserCreationForm()
|
2019-03-10 10:03:42 +00:00
|
|
|
|
tests = (
|
|
|
|
|
("username", "username"),
|
|
|
|
|
("password1", "new-password"),
|
|
|
|
|
("password2", "new-password"),
|
|
|
|
|
)
|
|
|
|
|
for field_name, autocomplete in tests:
|
|
|
|
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields[field_name].widget.attrs["autocomplete"], autocomplete
|
|
|
|
|
)
|
|
|
|
|
|
2024-01-23 15:45:18 +00:00
|
|
|
|
def test_unusable_password(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "new-user-which-does-not-exist",
|
|
|
|
|
"usable_password": "false",
|
|
|
|
|
}
|
|
|
|
|
form = BaseUserCreationForm(data)
|
|
|
|
|
self.assertIs(form.is_valid(), True, form.errors)
|
|
|
|
|
u = form.save()
|
|
|
|
|
self.assertEqual(u.username, data["username"])
|
|
|
|
|
self.assertFalse(u.has_usable_password())
|
|
|
|
|
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2022-09-24 14:26:14 +00:00
|
|
|
|
class UserCreationFormTest(TestDataMixin, TestCase):
|
|
|
|
|
def test_case_insensitive_username(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "TeStClIeNt",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
|
|
|
|
}
|
|
|
|
|
form = UserCreationForm(data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["username"].errors,
|
|
|
|
|
["A user with that username already exists."],
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-27 12:26:06 +00:00
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.ExtensionUser")
|
|
|
|
|
def test_case_insensitive_username_custom_user_and_error_message(self):
|
|
|
|
|
class CustomUserCreationForm(UserCreationForm):
|
|
|
|
|
class Meta(UserCreationForm.Meta):
|
|
|
|
|
model = ExtensionUser
|
|
|
|
|
fields = UserCreationForm.Meta.fields + ("date_of_birth",)
|
|
|
|
|
error_messages = {
|
|
|
|
|
"username": {"unique": "This username has already been taken."}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExtensionUser.objects.create_user(
|
|
|
|
|
username="testclient",
|
|
|
|
|
password="password",
|
|
|
|
|
email="testclient@example.com",
|
|
|
|
|
date_of_birth=datetime.date(1984, 3, 5),
|
|
|
|
|
)
|
|
|
|
|
data = {
|
|
|
|
|
"username": "TeStClIeNt",
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
|
|
|
|
"date_of_birth": "1980-01-01",
|
|
|
|
|
}
|
|
|
|
|
form = CustomUserCreationForm(data)
|
|
|
|
|
self.assertIs(form.is_valid(), False)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["username"].errors,
|
|
|
|
|
["This username has already been taken."],
|
|
|
|
|
)
|
|
|
|
|
|
2022-09-24 14:26:14 +00:00
|
|
|
|
|
2018-01-23 18:20:18 +00:00
|
|
|
|
# To verify that the login form rejects inactive users, use an authentication
|
|
|
|
|
# backend that allows them.
|
|
|
|
|
@override_settings(
|
|
|
|
|
AUTHENTICATION_BACKENDS=["django.contrib.auth.backends.AllowAllUsersModelBackend"]
|
|
|
|
|
)
|
2015-02-23 00:53:57 +00:00
|
|
|
|
class AuthenticationFormTest(TestDataMixin, TestCase):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_invalid_username(self):
|
|
|
|
|
# The user submits an invalid username.
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"username": "jsmith_does_not_exist",
|
|
|
|
|
"password": "test123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.non_field_errors(),
|
|
|
|
|
[
|
2017-01-24 11:22:42 +00:00
|
|
|
|
form.error_messages["invalid_login"]
|
2013-10-17 22:27:45 +00:00
|
|
|
|
% {"username": User._meta.get_field("username").verbose_name}
|
2016-04-08 02:04:45 +00:00
|
|
|
|
],
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_inactive_user(self):
|
|
|
|
|
# The user is inactive.
|
|
|
|
|
data = {
|
|
|
|
|
"username": "inactive",
|
|
|
|
|
"password": "password",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2017-01-24 11:22:42 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.non_field_errors(), [str(form.error_messages["inactive"])]
|
|
|
|
|
)
|
2011-12-15 16:12:46 +00:00
|
|
|
|
|
2018-01-23 18:20:18 +00:00
|
|
|
|
# Use an authentication backend that rejects inactive users.
|
|
|
|
|
@override_settings(
|
|
|
|
|
AUTHENTICATION_BACKENDS=["django.contrib.auth.backends.ModelBackend"]
|
|
|
|
|
)
|
|
|
|
|
def test_inactive_user_incorrect_password(self):
|
|
|
|
|
"""An invalid login doesn't leak the inactive status of a user."""
|
|
|
|
|
data = {
|
|
|
|
|
"username": "inactive",
|
|
|
|
|
"password": "incorrect",
|
|
|
|
|
}
|
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.non_field_errors(),
|
|
|
|
|
[
|
|
|
|
|
form.error_messages["invalid_login"]
|
|
|
|
|
% {"username": User._meta.get_field("username").verbose_name}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
2016-02-19 00:58:30 +00:00
|
|
|
|
def test_login_failed(self):
|
|
|
|
|
signal_calls = []
|
|
|
|
|
|
|
|
|
|
def signal_handler(**kwargs):
|
|
|
|
|
signal_calls.append(kwargs)
|
|
|
|
|
|
|
|
|
|
user_login_failed.connect(signal_handler)
|
|
|
|
|
fake_request = object()
|
|
|
|
|
try:
|
|
|
|
|
form = AuthenticationForm(
|
|
|
|
|
fake_request,
|
|
|
|
|
{
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password": "incorrect",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertIs(signal_calls[0]["request"], fake_request)
|
|
|
|
|
finally:
|
|
|
|
|
user_login_failed.disconnect(signal_handler)
|
|
|
|
|
|
2011-12-15 16:12:46 +00:00
|
|
|
|
def test_inactive_user_i18n(self):
|
2013-08-16 18:12:10 +00:00
|
|
|
|
with (
|
|
|
|
|
self.settings(USE_I18N=True),
|
|
|
|
|
translation.override("pt-br", deactivate=True),
|
|
|
|
|
):
|
|
|
|
|
# The user is inactive.
|
|
|
|
|
data = {
|
|
|
|
|
"username": "inactive",
|
|
|
|
|
"password": "password",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2013-08-16 18:12:10 +00:00
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2017-01-24 11:22:42 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.non_field_errors(), [str(form.error_messages["inactive"])]
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2017-11-08 08:32:49 +00:00
|
|
|
|
# Use an authentication backend that allows inactive users.
|
|
|
|
|
@override_settings(
|
|
|
|
|
AUTHENTICATION_BACKENDS=[
|
|
|
|
|
"django.contrib.auth.backends.AllowAllUsersModelBackend"
|
2022-02-03 19:24:19 +00:00
|
|
|
|
]
|
2017-11-08 08:32:49 +00:00
|
|
|
|
)
|
2013-07-30 12:24:13 +00:00
|
|
|
|
def test_custom_login_allowed_policy(self):
|
2013-11-30 13:37:15 +00:00
|
|
|
|
# The user is inactive, but our custom form policy allows them to log in.
|
2013-07-30 12:24:13 +00:00
|
|
|
|
data = {
|
|
|
|
|
"username": "inactive",
|
|
|
|
|
"password": "password",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2013-07-30 12:24:13 +00:00
|
|
|
|
|
|
|
|
|
class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
|
|
|
|
|
def confirm_login_allowed(self, user):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
form = AuthenticationFormWithInactiveUsersOkay(None, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
|
2020-02-12 13:48:49 +00:00
|
|
|
|
# Raise a ValidationError in the form to disallow some logins according
|
|
|
|
|
# to custom logic.
|
2013-07-30 12:24:13 +00:00
|
|
|
|
class PickyAuthenticationForm(AuthenticationForm):
|
|
|
|
|
def confirm_login_allowed(self, user):
|
|
|
|
|
if user.username == "inactive":
|
2020-02-12 13:48:49 +00:00
|
|
|
|
raise ValidationError("This user is disallowed.")
|
|
|
|
|
raise ValidationError("Sorry, nobody's allowed in.")
|
2013-07-30 12:24:13 +00:00
|
|
|
|
|
|
|
|
|
form = PickyAuthenticationForm(None, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(form.non_field_errors(), ["This user is disallowed."])
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password": "password",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2013-07-30 12:24:13 +00:00
|
|
|
|
form = PickyAuthenticationForm(None, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])
|
|
|
|
|
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_success(self):
|
|
|
|
|
# The success case
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password": "password",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.non_field_errors(), [])
|
|
|
|
|
|
2016-04-22 17:39:13 +00:00
|
|
|
|
def test_unicode_username(self):
|
|
|
|
|
User.objects.create_user(username="Σαρα", password="pwd")
|
|
|
|
|
data = {
|
|
|
|
|
"username": "Σαρα",
|
|
|
|
|
"password": "pwd",
|
|
|
|
|
}
|
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.non_field_errors(), [])
|
|
|
|
|
|
2017-08-17 21:08:56 +00:00
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomEmailField")
|
|
|
|
|
def test_username_field_max_length_matches_user_model(self):
|
|
|
|
|
self.assertEqual(CustomEmailField._meta.get_field("username").max_length, 255)
|
|
|
|
|
data = {
|
|
|
|
|
"username": "u" * 255,
|
|
|
|
|
"password": "pwd",
|
|
|
|
|
"email": "test@example.com",
|
|
|
|
|
}
|
|
|
|
|
CustomEmailField.objects.create_user(**data)
|
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertEqual(form.fields["username"].max_length, 255)
|
2019-09-17 14:21:50 +00:00
|
|
|
|
self.assertEqual(form.fields["username"].widget.attrs.get("maxlength"), 255)
|
2017-08-17 21:08:56 +00:00
|
|
|
|
self.assertEqual(form.errors, {})
|
|
|
|
|
|
2017-10-20 15:09:03 +00:00
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.IntegerUsernameUser")
|
|
|
|
|
def test_username_field_max_length_defaults_to_254(self):
|
|
|
|
|
self.assertIsNone(IntegerUsernameUser._meta.get_field("username").max_length)
|
|
|
|
|
data = {
|
|
|
|
|
"username": "0123456",
|
|
|
|
|
"password": "password",
|
|
|
|
|
}
|
|
|
|
|
IntegerUsernameUser.objects.create_user(**data)
|
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertEqual(form.fields["username"].max_length, 254)
|
2019-09-17 14:21:50 +00:00
|
|
|
|
self.assertEqual(form.fields["username"].widget.attrs.get("maxlength"), 254)
|
2017-10-20 15:09:03 +00:00
|
|
|
|
self.assertEqual(form.errors, {})
|
|
|
|
|
|
2013-01-10 08:05:01 +00:00
|
|
|
|
def test_username_field_label(self):
|
|
|
|
|
class CustomAuthenticationForm(AuthenticationForm):
|
|
|
|
|
username = CharField(label="Name", max_length=75)
|
|
|
|
|
|
|
|
|
|
form = CustomAuthenticationForm()
|
|
|
|
|
self.assertEqual(form["username"].label, "Name")
|
|
|
|
|
|
2013-05-06 10:45:56 +00:00
|
|
|
|
def test_username_field_label_not_set(self):
|
|
|
|
|
class CustomAuthenticationForm(AuthenticationForm):
|
|
|
|
|
username = CharField()
|
|
|
|
|
|
|
|
|
|
form = CustomAuthenticationForm()
|
2014-04-06 15:30:46 +00:00
|
|
|
|
username_field = User._meta.get_field(User.USERNAME_FIELD)
|
2013-05-06 10:45:56 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields["username"].label, capfirst(username_field.verbose_name)
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2013-05-06 10:45:56 +00:00
|
|
|
|
|
2019-03-24 18:53:31 +00:00
|
|
|
|
def test_username_field_autocapitalize_none(self):
|
|
|
|
|
form = AuthenticationForm()
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields["username"].widget.attrs.get("autocapitalize"), "none"
|
|
|
|
|
)
|
|
|
|
|
|
2013-05-06 10:45:56 +00:00
|
|
|
|
def test_username_field_label_empty_string(self):
|
|
|
|
|
class CustomAuthenticationForm(AuthenticationForm):
|
|
|
|
|
username = CharField(label="")
|
|
|
|
|
|
|
|
|
|
form = CustomAuthenticationForm()
|
|
|
|
|
self.assertEqual(form.fields["username"].label, "")
|
|
|
|
|
|
2016-03-14 20:21:05 +00:00
|
|
|
|
def test_password_whitespace_not_stripped(self):
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testuser",
|
|
|
|
|
"password": " pass ",
|
|
|
|
|
}
|
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
form.is_valid() # Not necessary to have valid credentails for the test.
|
|
|
|
|
self.assertEqual(form.cleaned_data["password"], data["password"])
|
|
|
|
|
|
2016-07-26 13:50:29 +00:00
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.IntegerUsernameUser")
|
|
|
|
|
def test_integer_username(self):
|
|
|
|
|
class CustomAuthenticationForm(AuthenticationForm):
|
|
|
|
|
username = IntegerField()
|
|
|
|
|
|
|
|
|
|
user = IntegerUsernameUser.objects.create_user(username=0, password="pwd")
|
|
|
|
|
data = {
|
|
|
|
|
"username": 0,
|
|
|
|
|
"password": "pwd",
|
|
|
|
|
}
|
|
|
|
|
form = CustomAuthenticationForm(None, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.cleaned_data["username"], data["username"])
|
|
|
|
|
self.assertEqual(form.cleaned_data["password"], data["password"])
|
|
|
|
|
self.assertEqual(form.errors, {})
|
|
|
|
|
self.assertEqual(form.user_cache, user)
|
|
|
|
|
|
2017-10-12 04:39:22 +00:00
|
|
|
|
def test_get_invalid_login_error(self):
|
|
|
|
|
error = AuthenticationForm().get_invalid_login_error()
|
2020-02-12 13:48:49 +00:00
|
|
|
|
self.assertIsInstance(error, ValidationError)
|
2017-10-12 04:39:22 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
error.message,
|
|
|
|
|
"Please enter a correct %(username)s and password. Note that both "
|
|
|
|
|
"fields may be case-sensitive.",
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(error.code, "invalid_login")
|
|
|
|
|
self.assertEqual(error.params, {"username": "username"})
|
|
|
|
|
|
2019-03-10 10:03:42 +00:00
|
|
|
|
def test_html_autocomplete_attributes(self):
|
|
|
|
|
form = AuthenticationForm()
|
|
|
|
|
tests = (
|
|
|
|
|
("username", "username"),
|
|
|
|
|
("password", "current-password"),
|
|
|
|
|
)
|
|
|
|
|
for field_name, autocomplete in tests:
|
|
|
|
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields[field_name].widget.attrs["autocomplete"], autocomplete
|
|
|
|
|
)
|
|
|
|
|
|
2022-10-23 11:42:40 +00:00
|
|
|
|
def test_no_password(self):
|
|
|
|
|
data = {"username": "username"}
|
|
|
|
|
form = AuthenticationForm(None, data)
|
|
|
|
|
self.assertIs(form.is_valid(), False)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["password"].errors, [Field.default_error_messages["required"]]
|
|
|
|
|
)
|
|
|
|
|
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-02-23 00:53:57 +00:00
|
|
|
|
class SetPasswordFormTest(TestDataMixin, TestCase):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_password_verification(self):
|
|
|
|
|
# The two new passwords do not match.
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"new_password1": "abc123",
|
|
|
|
|
"new_password2": "abc",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = SetPasswordForm(user, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["new_password2"].errors,
|
2017-01-24 11:22:42 +00:00
|
|
|
|
[str(form.error_messages["password_mismatch"])],
|
2016-04-08 02:04:45 +00:00
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-07-06 22:12:26 +00:00
|
|
|
|
@mock.patch("django.contrib.auth.password_validation.password_changed")
|
|
|
|
|
def test_success(self, password_changed):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"new_password1": "abc123",
|
|
|
|
|
"new_password2": "abc123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = SetPasswordForm(user, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
2015-07-06 22:12:26 +00:00
|
|
|
|
form.save(commit=False)
|
|
|
|
|
self.assertEqual(password_changed.call_count, 0)
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(password_changed.call_count, 1)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-03-08 14:07:57 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation."
|
|
|
|
|
"UserAttributeSimilarityValidator"
|
2022-02-04 07:08:27 +00:00
|
|
|
|
)
|
2015-03-08 14:07:57 +00:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation.MinimumLengthValidator"
|
|
|
|
|
),
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"min_length": 12,
|
|
|
|
|
},
|
2022-02-03 19:24:19 +00:00
|
|
|
|
},
|
2015-03-08 14:07:57 +00:00
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
def test_validates_password(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"new_password1": "testclient",
|
|
|
|
|
"new_password2": "testclient",
|
|
|
|
|
}
|
|
|
|
|
form = SetPasswordForm(user, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(len(form["new_password2"].errors), 2)
|
2015-06-08 17:27:47 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
"The password is too similar to the username.", form["new_password2"].errors
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2015-06-08 17:27:47 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
"This password is too short. It must contain at least 12 characters.",
|
|
|
|
|
form["new_password2"].errors,
|
|
|
|
|
)
|
2015-03-08 14:07:57 +00:00
|
|
|
|
|
2024-01-23 15:45:18 +00:00
|
|
|
|
# SetPasswordForm does not consider usable_password for form validation
|
|
|
|
|
data = {
|
|
|
|
|
"new_password1": "testclient",
|
|
|
|
|
"new_password2": "testclient",
|
|
|
|
|
"usable_password": "false",
|
|
|
|
|
}
|
|
|
|
|
form = SetPasswordForm(user, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(len(form["new_password2"].errors), 2)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"The password is too similar to the username.", form["new_password2"].errors
|
|
|
|
|
)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"This password is too short. It must contain at least 12 characters.",
|
|
|
|
|
form["new_password2"].errors,
|
|
|
|
|
)
|
|
|
|
|
|
2022-10-23 11:42:40 +00:00
|
|
|
|
def test_no_password(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {"new_password1": "new-password"}
|
|
|
|
|
form = SetPasswordForm(user, data)
|
|
|
|
|
self.assertIs(form.is_valid(), False)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["new_password2"].errors, [Field.default_error_messages["required"]]
|
|
|
|
|
)
|
|
|
|
|
form = SetPasswordForm(user, {})
|
|
|
|
|
self.assertIs(form.is_valid(), False)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["new_password1"].errors, [Field.default_error_messages["required"]]
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["new_password2"].errors, [Field.default_error_messages["required"]]
|
|
|
|
|
)
|
|
|
|
|
|
2016-03-14 20:21:05 +00:00
|
|
|
|
def test_password_whitespace_not_stripped(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"new_password1": " password ",
|
|
|
|
|
"new_password2": " password ",
|
|
|
|
|
}
|
|
|
|
|
form = SetPasswordForm(user, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.cleaned_data["new_password1"], data["new_password1"])
|
|
|
|
|
self.assertEqual(form.cleaned_data["new_password2"], data["new_password2"])
|
2022-02-03 19:24:19 +00:00
|
|
|
|
|
2016-05-06 10:23:52 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation."
|
|
|
|
|
"UserAttributeSimilarityValidator"
|
2022-02-04 07:08:27 +00:00
|
|
|
|
)
|
2016-05-06 10:23:52 +00:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation.MinimumLengthValidator"
|
|
|
|
|
),
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"min_length": 12,
|
|
|
|
|
},
|
2022-02-03 19:24:19 +00:00
|
|
|
|
},
|
2016-05-06 10:23:52 +00:00
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
def test_help_text_translation(self):
|
|
|
|
|
french_help_texts = [
|
|
|
|
|
"Votre mot de passe ne peut pas trop ressembler à vos autres informations "
|
|
|
|
|
"personnelles.",
|
|
|
|
|
"Votre mot de passe doit contenir au minimum 12 caractères.",
|
|
|
|
|
]
|
|
|
|
|
form = SetPasswordForm(self.u1)
|
|
|
|
|
with translation.override("fr"):
|
|
|
|
|
html = form.as_p()
|
|
|
|
|
for french_text in french_help_texts:
|
|
|
|
|
self.assertIn(french_text, html)
|
|
|
|
|
|
2019-03-10 10:03:42 +00:00
|
|
|
|
def test_html_autocomplete_attributes(self):
|
|
|
|
|
form = SetPasswordForm(self.u1)
|
|
|
|
|
tests = (
|
|
|
|
|
("new_password1", "new-password"),
|
|
|
|
|
("new_password2", "new-password"),
|
|
|
|
|
)
|
|
|
|
|
for field_name, autocomplete in tests:
|
|
|
|
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields[field_name].widget.attrs["autocomplete"], autocomplete
|
|
|
|
|
)
|
|
|
|
|
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-02-23 00:53:57 +00:00
|
|
|
|
class PasswordChangeFormTest(TestDataMixin, TestCase):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_incorrect_password(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"old_password": "test",
|
|
|
|
|
"new_password1": "abc123",
|
|
|
|
|
"new_password2": "abc123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = PasswordChangeForm(user, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2017-01-24 11:22:42 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["old_password"].errors,
|
|
|
|
|
[str(form.error_messages["password_incorrect"])],
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_password_verification(self):
|
|
|
|
|
# The two new passwords do not match.
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"old_password": "password",
|
|
|
|
|
"new_password1": "abc123",
|
|
|
|
|
"new_password2": "abc",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = PasswordChangeForm(user, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2017-01-24 11:22:42 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
form["new_password2"].errors,
|
|
|
|
|
[str(form.error_messages["password_mismatch"])],
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2015-07-06 22:12:26 +00:00
|
|
|
|
@mock.patch("django.contrib.auth.password_validation.password_changed")
|
|
|
|
|
def test_success(self, password_changed):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
# The success case.
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"old_password": "password",
|
|
|
|
|
"new_password1": "abc123",
|
|
|
|
|
"new_password2": "abc123",
|
2013-10-18 09:02:43 +00:00
|
|
|
|
}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = PasswordChangeForm(user, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
2015-07-06 22:12:26 +00:00
|
|
|
|
form.save(commit=False)
|
|
|
|
|
self.assertEqual(password_changed.call_count, 0)
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(password_changed.call_count, 1)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_field_order(self):
|
|
|
|
|
# Regression test - check the order of fields:
|
|
|
|
|
user = User.objects.get(username="testclient")
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
list(PasswordChangeForm(user, {}).fields),
|
|
|
|
|
["old_password", "new_password1", "new_password2"],
|
|
|
|
|
)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2016-03-14 20:21:05 +00:00
|
|
|
|
def test_password_whitespace_not_stripped(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
user.set_password(" oldpassword ")
|
|
|
|
|
data = {
|
|
|
|
|
"old_password": " oldpassword ",
|
|
|
|
|
"new_password1": " pass ",
|
|
|
|
|
"new_password2": " pass ",
|
|
|
|
|
}
|
|
|
|
|
form = PasswordChangeForm(user, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.cleaned_data["old_password"], data["old_password"])
|
|
|
|
|
self.assertEqual(form.cleaned_data["new_password1"], data["new_password1"])
|
|
|
|
|
self.assertEqual(form.cleaned_data["new_password2"], data["new_password2"])
|
|
|
|
|
|
2019-03-10 10:03:42 +00:00
|
|
|
|
def test_html_autocomplete_attributes(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
form = PasswordChangeForm(user)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields["old_password"].widget.attrs["autocomplete"], "current-password"
|
|
|
|
|
)
|
|
|
|
|
|
2011-12-15 16:12:46 +00:00
|
|
|
|
|
2018-07-02 22:10:36 +00:00
|
|
|
|
class UserChangeFormTest(TestDataMixin, TestCase):
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_username_validity(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {"username": "not valid"}
|
|
|
|
|
form = UserChangeForm(data, instance=user)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2014-04-06 15:30:46 +00:00
|
|
|
|
validator = next(
|
|
|
|
|
v
|
|
|
|
|
for v in User._meta.get_field("username").validators
|
|
|
|
|
if v.code == "invalid"
|
|
|
|
|
)
|
2017-01-24 11:22:42 +00:00
|
|
|
|
self.assertEqual(form["username"].errors, [str(validator.message)])
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2010-09-09 23:31:54 +00:00
|
|
|
|
def test_bug_14242(self):
|
|
|
|
|
# A regression test, introduce by adding an optimization for the
|
|
|
|
|
# UserChangeForm.
|
|
|
|
|
|
|
|
|
|
class MyUserForm(UserChangeForm):
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
2017-01-21 13:13:44 +00:00
|
|
|
|
super().__init__(*args, **kwargs)
|
2010-09-09 23:31:54 +00:00
|
|
|
|
self.fields["groups"].help_text = (
|
|
|
|
|
"These groups give users different permissions"
|
2024-01-26 11:45:07 +00:00
|
|
|
|
)
|
2010-09-09 23:31:54 +00:00
|
|
|
|
|
|
|
|
|
class Meta(UserChangeForm.Meta):
|
|
|
|
|
fields = ("groups",)
|
|
|
|
|
|
|
|
|
|
# Just check we can create it
|
2013-08-04 16:17:10 +00:00
|
|
|
|
MyUserForm({})
|
2010-09-09 23:31:54 +00:00
|
|
|
|
|
2015-12-02 23:55:50 +00:00
|
|
|
|
def test_unusable_password(self):
|
2024-02-20 15:12:37 +00:00
|
|
|
|
user = User.objects.get(username="unusable_password")
|
2012-09-12 09:21:58 +00:00
|
|
|
|
form = UserChangeForm(instance=user)
|
|
|
|
|
self.assertIn(_("No password set."), form.as_table())
|
|
|
|
|
|
2012-03-22 08:10:19 +00:00
|
|
|
|
def test_bug_17944_empty_password(self):
|
|
|
|
|
user = User.objects.get(username="empty_password")
|
|
|
|
|
form = UserChangeForm(instance=user)
|
2012-09-12 09:56:58 +00:00
|
|
|
|
self.assertIn(_("No password set."), form.as_table())
|
2012-03-22 08:10:19 +00:00
|
|
|
|
|
|
|
|
|
def test_bug_17944_unmanageable_password(self):
|
|
|
|
|
user = User.objects.get(username="unmanageable_password")
|
|
|
|
|
form = UserChangeForm(instance=user)
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
_("Invalid password format or unknown hashing algorithm."), form.as_table()
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2012-03-22 08:10:19 +00:00
|
|
|
|
|
|
|
|
|
def test_bug_17944_unknown_password_algorithm(self):
|
|
|
|
|
user = User.objects.get(username="unknown_password")
|
|
|
|
|
form = UserChangeForm(instance=user)
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
_("Invalid password format or unknown hashing algorithm."), form.as_table()
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2012-03-22 08:10:19 +00:00
|
|
|
|
|
2012-10-20 03:41:54 +00:00
|
|
|
|
def test_bug_19133(self):
|
|
|
|
|
"The change form does not return the password value"
|
|
|
|
|
# Use the form to construct the POST data
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
form_for_data = UserChangeForm(instance=user)
|
|
|
|
|
post_data = form_for_data.initial
|
|
|
|
|
|
|
|
|
|
# The password field should be readonly, so anything
|
|
|
|
|
# posted here should be ignored; the form will be
|
|
|
|
|
# valid, and give back the 'initial' value for the
|
|
|
|
|
# password field.
|
|
|
|
|
post_data["password"] = "new password"
|
|
|
|
|
form = UserChangeForm(instance=user, data=post_data)
|
|
|
|
|
|
|
|
|
|
self.assertTrue(form.is_valid())
|
2016-02-05 20:56:52 +00:00
|
|
|
|
# original hashed password contains $
|
|
|
|
|
self.assertIn("$", form.cleaned_data["password"])
|
2012-10-20 03:41:54 +00:00
|
|
|
|
|
2012-12-01 11:17:00 +00:00
|
|
|
|
def test_bug_19349_bound_password_field(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
form = UserChangeForm(data={}, instance=user)
|
|
|
|
|
# When rendering the bound password field,
|
|
|
|
|
# ReadOnlyPasswordHashWidget needs the initial
|
|
|
|
|
# value to render correctly
|
|
|
|
|
self.assertEqual(form.initial["password"], form["password"].value())
|
|
|
|
|
|
2022-09-30 08:50:28 +00:00
|
|
|
|
@override_settings(ROOT_URLCONF="auth_tests.urls_admin")
|
|
|
|
|
def test_link_to_password_reset_in_helptext_via_to_field(self):
|
2024-01-23 15:45:18 +00:00
|
|
|
|
cases = [
|
|
|
|
|
(
|
|
|
|
|
"testclient",
|
|
|
|
|
'you can change or unset the password using <a href="(.*?)">',
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"unusable_password",
|
|
|
|
|
"Enable password-based authentication for this user by setting "
|
|
|
|
|
'a password using <a href="(.*?)">this form</a>.',
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
for username, expected_help_text in cases:
|
|
|
|
|
with self.subTest(username=username):
|
|
|
|
|
user = User.objects.get(username=username)
|
|
|
|
|
form = UserChangeForm(data={}, instance=user)
|
|
|
|
|
password_help_text = form.fields["password"].help_text
|
|
|
|
|
matches = re.search(expected_help_text, password_help_text)
|
|
|
|
|
|
|
|
|
|
url_prefix = f"admin:{user._meta.app_label}_{user._meta.model_name}"
|
|
|
|
|
# URL to UserChangeForm in admin via to_field (instead of pk).
|
|
|
|
|
user_change_url = reverse(f"{url_prefix}_change", args=(user.username,))
|
|
|
|
|
joined_url = urllib.parse.urljoin(user_change_url, matches.group(1))
|
|
|
|
|
|
|
|
|
|
pw_change_url = reverse(
|
|
|
|
|
f"{url_prefix}_password_change", args=(user.pk,)
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(joined_url, pw_change_url)
|
2022-09-30 08:50:28 +00:00
|
|
|
|
|
2016-02-14 15:30:01 +00:00
|
|
|
|
def test_custom_form(self):
|
2018-07-02 22:10:36 +00:00
|
|
|
|
class CustomUserChangeForm(UserChangeForm):
|
|
|
|
|
class Meta(UserChangeForm.Meta):
|
|
|
|
|
model = ExtensionUser
|
|
|
|
|
fields = (
|
|
|
|
|
"username",
|
|
|
|
|
"password",
|
|
|
|
|
"date_of_birth",
|
|
|
|
|
)
|
2017-11-15 12:27:53 +00:00
|
|
|
|
|
2018-07-02 22:10:36 +00:00
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"username": "testclient",
|
|
|
|
|
"password": "testclient",
|
|
|
|
|
"date_of_birth": "1998-02-24",
|
|
|
|
|
}
|
|
|
|
|
form = CustomUserChangeForm(data, instance=user)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(form.cleaned_data["username"], "testclient")
|
|
|
|
|
self.assertEqual(form.cleaned_data["date_of_birth"], datetime.date(1998, 2, 24))
|
2016-02-14 15:30:01 +00:00
|
|
|
|
|
2018-03-29 09:35:53 +00:00
|
|
|
|
def test_password_excluded(self):
|
|
|
|
|
class UserChangeFormWithoutPassword(UserChangeForm):
|
|
|
|
|
password = None
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = User
|
|
|
|
|
exclude = ["password"]
|
|
|
|
|
|
|
|
|
|
form = UserChangeFormWithoutPassword()
|
|
|
|
|
self.assertNotIn("password", form.fields)
|
|
|
|
|
|
2019-03-24 18:53:31 +00:00
|
|
|
|
def test_username_field_autocapitalize_none(self):
|
|
|
|
|
form = UserChangeForm()
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields["username"].widget.attrs.get("autocapitalize"), "none"
|
|
|
|
|
)
|
|
|
|
|
|
2012-03-22 08:10:19 +00:00
|
|
|
|
|
2016-02-05 20:56:52 +00:00
|
|
|
|
@override_settings(TEMPLATES=AUTH_TEMPLATES)
|
2015-02-23 00:53:57 +00:00
|
|
|
|
class PasswordResetFormTest(TestDataMixin, TestCase):
|
2015-02-15 03:03:25 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
2017-01-21 13:13:44 +00:00
|
|
|
|
super().setUpClass()
|
2015-02-15 03:03:25 +00:00
|
|
|
|
# This cleanup is necessary because contrib.sites cache
|
|
|
|
|
# makes tests interfere with each other, see #11505
|
|
|
|
|
Site.objects.clear_cache()
|
|
|
|
|
|
2011-03-14 21:14:10 +00:00
|
|
|
|
def create_dummy_user(self):
|
2013-10-19 08:29:17 +00:00
|
|
|
|
"""
|
|
|
|
|
Create a user and return a tuple (user_object, username, email).
|
2011-03-14 21:14:10 +00:00
|
|
|
|
"""
|
|
|
|
|
username = "jsmith"
|
|
|
|
|
email = "jsmith@example.com"
|
|
|
|
|
user = User.objects.create_user(username, email, "test123")
|
|
|
|
|
return (user, username, email)
|
|
|
|
|
|
2010-09-09 23:21:16 +00:00
|
|
|
|
def test_invalid_email(self):
|
2011-12-15 16:12:46 +00:00
|
|
|
|
data = {"email": "not valid"}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
2013-03-14 15:19:59 +00:00
|
|
|
|
self.assertEqual(form["email"].errors, [_("Enter a valid email address.")])
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2019-12-17 02:51:57 +00:00
|
|
|
|
def test_user_email_unicode_collision(self):
|
|
|
|
|
User.objects.create_user("mike123", "mike@example.org", "test123")
|
|
|
|
|
User.objects.create_user("mike456", "mıke@example.org", "test123")
|
|
|
|
|
data = {"email": "mıke@example.org"}
|
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
self.assertEqual(mail.outbox[0].to, ["mıke@example.org"])
|
|
|
|
|
|
|
|
|
|
def test_user_email_domain_unicode_collision(self):
|
|
|
|
|
User.objects.create_user("mike123", "mike@ixample.org", "test123")
|
|
|
|
|
User.objects.create_user("mike456", "mike@ıxample.org", "test123")
|
|
|
|
|
data = {"email": "mike@ıxample.org"}
|
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
self.assertEqual(mail.outbox[0].to, ["mike@ıxample.org"])
|
|
|
|
|
|
|
|
|
|
def test_user_email_unicode_collision_nonexistent(self):
|
|
|
|
|
User.objects.create_user("mike123", "mike@example.org", "test123")
|
|
|
|
|
data = {"email": "mıke@example.org"}
|
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
|
|
|
|
|
|
|
|
|
def test_user_email_domain_unicode_collision_nonexistent(self):
|
|
|
|
|
User.objects.create_user("mike123", "mike@ixample.org", "test123")
|
|
|
|
|
data = {"email": "mike@ıxample.org"}
|
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
|
|
|
|
|
2014-11-03 22:48:03 +00:00
|
|
|
|
def test_nonexistent_email(self):
|
2013-10-19 08:29:17 +00:00
|
|
|
|
"""
|
2014-04-26 17:18:45 +00:00
|
|
|
|
Test nonexistent email address. This should not fail because it would
|
2013-10-19 08:29:17 +00:00
|
|
|
|
expose information about registered users.
|
|
|
|
|
"""
|
2011-12-15 16:12:46 +00:00
|
|
|
|
data = {"email": "foo@bar.com"}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = PasswordResetForm(data)
|
2013-02-23 12:39:21 +00:00
|
|
|
|
self.assertTrue(form.is_valid())
|
2013-02-24 10:15:17 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
|
|
|
|
def test_cleaned_data(self):
|
2011-03-14 21:14:10 +00:00
|
|
|
|
(user, username, email) = self.create_dummy_user()
|
|
|
|
|
data = {"email": email}
|
2010-09-09 23:21:16 +00:00
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
2013-02-23 12:39:21 +00:00
|
|
|
|
form.save(domain_override="example.com")
|
2011-03-14 21:14:10 +00:00
|
|
|
|
self.assertEqual(form.cleaned_data["email"], email)
|
2013-02-23 12:39:21 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2011-06-19 11:24:39 +00:00
|
|
|
|
def test_custom_email_subject(self):
|
2013-02-18 08:20:26 +00:00
|
|
|
|
data = {"email": "testclient@example.com"}
|
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
# Since we're not providing a request object, we must provide a
|
|
|
|
|
# domain_override to prevent the save operation from failing in the
|
|
|
|
|
# potential case where contrib.sites is not installed. Refs #16412.
|
|
|
|
|
form.save(domain_override="example.com")
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
self.assertEqual(mail.outbox[0].subject, "Custom password reset on example.com")
|
2010-09-09 23:21:16 +00:00
|
|
|
|
|
2014-05-09 06:48:41 +00:00
|
|
|
|
def test_custom_email_constructor(self):
|
2014-12-17 21:10:57 +00:00
|
|
|
|
data = {"email": "testclient@example.com"}
|
|
|
|
|
|
|
|
|
|
class CustomEmailPasswordResetForm(PasswordResetForm):
|
|
|
|
|
def send_mail(
|
|
|
|
|
self,
|
|
|
|
|
subject_template_name,
|
|
|
|
|
email_template_name,
|
|
|
|
|
context,
|
|
|
|
|
from_email,
|
|
|
|
|
to_email,
|
|
|
|
|
html_email_template_name=None,
|
|
|
|
|
):
|
|
|
|
|
EmailMultiAlternatives(
|
|
|
|
|
"Forgot your password?",
|
|
|
|
|
"Sorry to hear you forgot your password.",
|
|
|
|
|
None,
|
|
|
|
|
[to_email],
|
|
|
|
|
["site_monitor@example.com"],
|
|
|
|
|
headers={"Reply-To": "webmaster@example.com"},
|
2016-04-08 02:04:45 +00:00
|
|
|
|
alternatives=[
|
|
|
|
|
("Really sorry to hear you forgot your password.", "text/html")
|
|
|
|
|
],
|
|
|
|
|
).send()
|
2014-12-17 21:10:57 +00:00
|
|
|
|
|
|
|
|
|
form = CustomEmailPasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
# Since we're not providing a request object, we must provide a
|
|
|
|
|
# domain_override to prevent the save operation from failing in the
|
|
|
|
|
# potential case where contrib.sites is not installed. Refs #16412.
|
|
|
|
|
form.save(domain_override="example.com")
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
self.assertEqual(mail.outbox[0].subject, "Forgot your password?")
|
|
|
|
|
self.assertEqual(mail.outbox[0].bcc, ["site_monitor@example.com"])
|
|
|
|
|
self.assertEqual(mail.outbox[0].content_subtype, "plain")
|
2014-05-09 06:48:41 +00:00
|
|
|
|
|
2013-10-19 08:29:17 +00:00
|
|
|
|
def test_preserve_username_case(self):
|
|
|
|
|
"""
|
|
|
|
|
Preserve the case of the user name (before the @ in the email address)
|
|
|
|
|
when creating a user (#5605).
|
|
|
|
|
"""
|
2010-09-09 23:21:16 +00:00
|
|
|
|
user = User.objects.create_user("forms_test2", "tesT@EXAMple.com", "test")
|
|
|
|
|
self.assertEqual(user.email, "tesT@example.com")
|
|
|
|
|
user = User.objects.create_user("forms_test3", "tesT", "test")
|
|
|
|
|
self.assertEqual(user.email, "tesT")
|
2011-03-14 21:14:10 +00:00
|
|
|
|
|
|
|
|
|
def test_inactive_user(self):
|
2013-10-19 08:29:17 +00:00
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Inactive user cannot receive password reset email.
|
2013-10-19 08:29:17 +00:00
|
|
|
|
"""
|
2011-03-14 21:14:10 +00:00
|
|
|
|
(user, username, email) = self.create_dummy_user()
|
|
|
|
|
user.is_active = False
|
|
|
|
|
user.save()
|
|
|
|
|
form = PasswordResetForm({"email": email})
|
2013-02-23 12:39:21 +00:00
|
|
|
|
self.assertTrue(form.is_valid())
|
2013-10-19 08:40:20 +00:00
|
|
|
|
form.save()
|
2013-02-23 12:39:21 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
2011-06-26 16:51:34 +00:00
|
|
|
|
|
|
|
|
|
def test_unusable_password(self):
|
|
|
|
|
user = User.objects.create_user("testuser", "test@example.com", "test")
|
|
|
|
|
data = {"email": "test@example.com"}
|
|
|
|
|
form = PasswordResetForm(data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
user.set_unusable_password()
|
|
|
|
|
user.save()
|
|
|
|
|
form = PasswordResetForm(data)
|
2013-02-23 12:39:21 +00:00
|
|
|
|
# The form itself is valid, but no email is sent
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
2013-02-24 10:15:17 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
2012-12-01 11:17:00 +00:00
|
|
|
|
|
2013-07-31 02:29:34 +00:00
|
|
|
|
def test_save_plaintext_email(self):
|
|
|
|
|
"""
|
|
|
|
|
Test the PasswordResetForm.save() method with no html_email_template_name
|
|
|
|
|
parameter passed in.
|
|
|
|
|
Test to ensure original behavior is unchanged after the parameter was added.
|
|
|
|
|
"""
|
|
|
|
|
(user, username, email) = self.create_dummy_user()
|
|
|
|
|
form = PasswordResetForm({"email": email})
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
message = mail.outbox[0].message()
|
|
|
|
|
self.assertFalse(message.is_multipart())
|
|
|
|
|
self.assertEqual(message.get_content_type(), "text/plain")
|
|
|
|
|
self.assertEqual(message.get("subject"), "Custom password reset on example.com")
|
|
|
|
|
self.assertEqual(len(mail.outbox[0].alternatives), 0)
|
|
|
|
|
self.assertEqual(message.get_all("to"), [email])
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
re.match(r"^http://example.com/reset/[\w+/-]", message.get_payload())
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2013-07-31 02:29:34 +00:00
|
|
|
|
|
|
|
|
|
def test_save_html_email_template_name(self):
|
|
|
|
|
"""
|
2019-05-15 10:14:59 +00:00
|
|
|
|
Test the PasswordResetForm.save() method with html_email_template_name
|
2013-07-31 02:29:34 +00:00
|
|
|
|
parameter specified.
|
|
|
|
|
Test to ensure that a multipart email is sent with both text/plain
|
|
|
|
|
and text/html parts.
|
|
|
|
|
"""
|
|
|
|
|
(user, username, email) = self.create_dummy_user()
|
|
|
|
|
form = PasswordResetForm({"email": email})
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save(
|
|
|
|
|
html_email_template_name="registration/html_password_reset_email.html"
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
self.assertEqual(len(mail.outbox[0].alternatives), 1)
|
|
|
|
|
message = mail.outbox[0].message()
|
|
|
|
|
self.assertEqual(message.get("subject"), "Custom password reset on example.com")
|
|
|
|
|
self.assertEqual(len(message.get_payload()), 2)
|
|
|
|
|
self.assertTrue(message.is_multipart())
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_content_type(), "text/plain")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_content_type(), "text/html")
|
|
|
|
|
self.assertEqual(message.get_all("to"), [email])
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
re.match(
|
|
|
|
|
r"^http://example.com/reset/[\w/-]+",
|
|
|
|
|
message.get_payload(0).get_payload(),
|
|
|
|
|
)
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertTrue(
|
|
|
|
|
re.match(
|
|
|
|
|
r'^<html><a href="http://example.com/reset/[\w/-]+/">Link</a></html>$',
|
|
|
|
|
message.get_payload(1).get_payload(),
|
|
|
|
|
)
|
2022-02-03 19:24:19 +00:00
|
|
|
|
)
|
|
|
|
|
|
2016-09-19 12:55:18 +00:00
|
|
|
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomEmailField")
|
|
|
|
|
def test_custom_email_field(self):
|
|
|
|
|
email = "test@mail.com"
|
|
|
|
|
CustomEmailField.objects.create_user("test name", "test password", email)
|
|
|
|
|
form = PasswordResetForm({"email": email})
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(form.cleaned_data["email"], email)
|
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
|
self.assertEqual(mail.outbox[0].to, [email])
|
|
|
|
|
|
2019-03-10 10:03:42 +00:00
|
|
|
|
def test_html_autocomplete_attributes(self):
|
|
|
|
|
form = PasswordResetForm()
|
|
|
|
|
self.assertEqual(form.fields["email"].widget.attrs["autocomplete"], "email")
|
|
|
|
|
|
2012-12-01 11:17:00 +00:00
|
|
|
|
|
2015-04-17 21:38:20 +00:00
|
|
|
|
class ReadOnlyPasswordHashTest(SimpleTestCase):
|
2012-12-01 11:17:00 +00:00
|
|
|
|
def test_bug_19349_render_with_none_value(self):
|
|
|
|
|
# Rendering the widget with value set to None
|
|
|
|
|
# mustn't raise an exception.
|
|
|
|
|
widget = ReadOnlyPasswordHashWidget()
|
|
|
|
|
html = widget.render(name="password", value=None, attrs={})
|
|
|
|
|
self.assertIn(_("No password set."), html)
|
2013-01-25 20:27:49 +00:00
|
|
|
|
|
2017-04-19 16:59:30 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
PASSWORD_HASHERS=["django.contrib.auth.hashers.PBKDF2PasswordHasher"]
|
|
|
|
|
)
|
|
|
|
|
def test_render(self):
|
|
|
|
|
widget = ReadOnlyPasswordHashWidget()
|
|
|
|
|
value = (
|
|
|
|
|
"pbkdf2_sha256$100000$a6Pucb1qSFcD$WmCkn9Hqidj48NVe5x0FEM6A9YiOqQcl/83m2Z5u"
|
|
|
|
|
"dm0="
|
2022-02-04 07:08:27 +00:00
|
|
|
|
)
|
2017-04-19 16:59:30 +00:00
|
|
|
|
self.assertHTMLEqual(
|
|
|
|
|
widget.render("name", value, {"id": "id_password"}),
|
2022-08-31 21:39:08 +00:00
|
|
|
|
'<div id="id_password">'
|
|
|
|
|
" <strong>algorithm</strong>: <bdi>pbkdf2_sha256</bdi>"
|
|
|
|
|
" <strong>iterations</strong>: <bdi>100000</bdi>"
|
|
|
|
|
" <strong>salt</strong>: <bdi>a6Pucb******</bdi>"
|
|
|
|
|
" <strong>hash</strong>: "
|
|
|
|
|
" <bdi>WmCkn9**************************************</bdi>"
|
|
|
|
|
"</div>",
|
2017-04-19 16:59:30 +00:00
|
|
|
|
)
|
|
|
|
|
|
2013-01-25 20:27:49 +00:00
|
|
|
|
def test_readonly_field_has_changed(self):
|
|
|
|
|
field = ReadOnlyPasswordHashField()
|
2020-12-02 10:23:52 +00:00
|
|
|
|
self.assertIs(field.disabled, True)
|
2014-08-07 02:56:23 +00:00
|
|
|
|
self.assertFalse(field.has_changed("aaa", "bbb"))
|
2015-07-06 22:12:26 +00:00
|
|
|
|
|
2021-05-19 04:05:04 +00:00
|
|
|
|
def test_label(self):
|
|
|
|
|
"""
|
|
|
|
|
ReadOnlyPasswordHashWidget doesn't contain a for attribute in the
|
|
|
|
|
<label> because it doesn't have any labelable elements.
|
|
|
|
|
"""
|
2022-02-03 19:24:19 +00:00
|
|
|
|
|
2021-05-19 04:05:04 +00:00
|
|
|
|
class TestForm(forms.Form):
|
|
|
|
|
hash_field = ReadOnlyPasswordHashField()
|
|
|
|
|
|
|
|
|
|
bound_field = TestForm()["hash_field"]
|
2021-04-16 21:19:25 +00:00
|
|
|
|
self.assertIsNone(bound_field.field.widget.id_for_label("id"))
|
2021-05-19 04:05:04 +00:00
|
|
|
|
self.assertEqual(bound_field.label_tag(), "<label>Hash field:</label>")
|
|
|
|
|
|
2015-07-06 22:12:26 +00:00
|
|
|
|
|
|
|
|
|
class AdminPasswordChangeFormTest(TestDataMixin, TestCase):
|
|
|
|
|
@mock.patch("django.contrib.auth.password_validation.password_changed")
|
|
|
|
|
def test_success(self, password_changed):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"password1": "test123",
|
|
|
|
|
"password2": "test123",
|
|
|
|
|
}
|
|
|
|
|
form = AdminPasswordChangeForm(user, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
form.save(commit=False)
|
|
|
|
|
self.assertEqual(password_changed.call_count, 0)
|
|
|
|
|
form.save()
|
|
|
|
|
self.assertEqual(password_changed.call_count, 1)
|
2022-10-23 11:42:40 +00:00
|
|
|
|
self.assertEqual(form.changed_data, ["password"])
|
2016-03-14 20:21:05 +00:00
|
|
|
|
|
2024-01-12 20:27:55 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation."
|
|
|
|
|
"UserAttributeSimilarityValidator"
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"NAME": (
|
|
|
|
|
"django.contrib.auth.password_validation.MinimumLengthValidator"
|
|
|
|
|
),
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"min_length": 12,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
def test_validates_password(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
}
|
|
|
|
|
form = AdminPasswordChangeForm(user, data)
|
|
|
|
|
self.assertFalse(form.is_valid())
|
|
|
|
|
self.assertEqual(len(form["password2"].errors), 2)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"The password is too similar to the username.",
|
|
|
|
|
form["password2"].errors,
|
|
|
|
|
)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"This password is too short. It must contain at least 12 characters.",
|
|
|
|
|
form["password2"].errors,
|
|
|
|
|
)
|
|
|
|
|
|
2024-01-23 15:45:18 +00:00
|
|
|
|
# passwords are not validated if `usable_password` is unset
|
|
|
|
|
data = {
|
|
|
|
|
"password1": "testclient",
|
|
|
|
|
"password2": "testclient",
|
|
|
|
|
"usable_password": "false",
|
|
|
|
|
}
|
|
|
|
|
form = AdminPasswordChangeForm(user, data)
|
|
|
|
|
self.assertIs(form.is_valid(), True, form.errors)
|
|
|
|
|
|
2016-03-14 20:21:05 +00:00
|
|
|
|
def test_password_whitespace_not_stripped(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {
|
|
|
|
|
"password1": " pass ",
|
|
|
|
|
"password2": " pass ",
|
|
|
|
|
}
|
|
|
|
|
form = AdminPasswordChangeForm(user, data)
|
|
|
|
|
self.assertTrue(form.is_valid())
|
|
|
|
|
self.assertEqual(form.cleaned_data["password1"], data["password1"])
|
|
|
|
|
self.assertEqual(form.cleaned_data["password2"], data["password2"])
|
2022-10-23 11:42:40 +00:00
|
|
|
|
self.assertEqual(form.changed_data, ["password"])
|
2018-04-03 15:32:58 +00:00
|
|
|
|
|
|
|
|
|
def test_non_matching_passwords(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {"password1": "password1", "password2": "password2"}
|
|
|
|
|
form = AdminPasswordChangeForm(user, data)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.errors["password2"], [form.error_messages["password_mismatch"]]
|
|
|
|
|
)
|
2022-10-23 11:42:40 +00:00
|
|
|
|
self.assertEqual(form.changed_data, ["password"])
|
2018-04-03 15:32:58 +00:00
|
|
|
|
|
|
|
|
|
def test_missing_passwords(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
data = {"password1": "", "password2": ""}
|
|
|
|
|
form = AdminPasswordChangeForm(user, data)
|
|
|
|
|
required_error = [Field.default_error_messages["required"]]
|
|
|
|
|
self.assertEqual(form.errors["password1"], required_error)
|
|
|
|
|
self.assertEqual(form.errors["password2"], required_error)
|
2022-10-23 11:42:40 +00:00
|
|
|
|
self.assertEqual(form.changed_data, [])
|
2018-04-03 15:32:58 +00:00
|
|
|
|
|
|
|
|
|
def test_one_password(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
form1 = AdminPasswordChangeForm(user, {"password1": "", "password2": "test"})
|
|
|
|
|
required_error = [Field.default_error_messages["required"]]
|
|
|
|
|
self.assertEqual(form1.errors["password1"], required_error)
|
|
|
|
|
self.assertNotIn("password2", form1.errors)
|
2022-10-23 11:42:40 +00:00
|
|
|
|
self.assertEqual(form1.changed_data, [])
|
2018-04-03 15:32:58 +00:00
|
|
|
|
form2 = AdminPasswordChangeForm(user, {"password1": "test", "password2": ""})
|
|
|
|
|
self.assertEqual(form2.errors["password2"], required_error)
|
|
|
|
|
self.assertNotIn("password1", form2.errors)
|
2022-10-23 11:42:40 +00:00
|
|
|
|
self.assertEqual(form2.changed_data, [])
|
2019-03-10 10:03:42 +00:00
|
|
|
|
|
|
|
|
|
def test_html_autocomplete_attributes(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
form = AdminPasswordChangeForm(user)
|
|
|
|
|
tests = (
|
|
|
|
|
("password1", "new-password"),
|
|
|
|
|
("password2", "new-password"),
|
|
|
|
|
)
|
|
|
|
|
for field_name, autocomplete in tests:
|
|
|
|
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
form.fields[field_name].widget.attrs["autocomplete"], autocomplete
|
|
|
|
|
)
|
2024-01-23 15:45:18 +00:00
|
|
|
|
|
|
|
|
|
def test_enable_password_authentication(self):
|
|
|
|
|
user = User.objects.get(username="unusable_password")
|
|
|
|
|
form = AdminPasswordChangeForm(
|
|
|
|
|
user,
|
|
|
|
|
{"password1": "complexpassword", "password2": "complexpassword"},
|
|
|
|
|
)
|
|
|
|
|
self.assertNotIn("usable_password", form.fields)
|
|
|
|
|
self.assertIs(form.is_valid(), True)
|
|
|
|
|
user = form.save(commit=True)
|
|
|
|
|
self.assertIs(user.has_usable_password(), True)
|
|
|
|
|
|
|
|
|
|
def test_disable_password_authentication(self):
|
|
|
|
|
user = User.objects.get(username="testclient")
|
|
|
|
|
form = AdminPasswordChangeForm(
|
|
|
|
|
user,
|
|
|
|
|
{"usable_password": "false", "password1": "", "password2": "test"},
|
|
|
|
|
)
|
|
|
|
|
self.assertIn("usable_password", form.fields)
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"If disabled, the current password for this user will be lost.",
|
|
|
|
|
form.fields["usable_password"].help_text,
|
|
|
|
|
)
|
|
|
|
|
self.assertIs(form.is_valid(), True) # Valid despite password empty/mismatch.
|
|
|
|
|
user = form.save(commit=True)
|
|
|
|
|
self.assertIs(user.has_usable_password(), False)
|