123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737 |
- import datetime
- import re
- import sys
- import urllib.parse
- from unittest import mock
- from django import forms
- from django.contrib.auth.forms import (
- AdminPasswordChangeForm,
- AdminUserCreationForm,
- AuthenticationForm,
- BaseUserCreationForm,
- PasswordChangeForm,
- PasswordResetForm,
- ReadOnlyPasswordHashField,
- ReadOnlyPasswordHashWidget,
- SetPasswordForm,
- SetPasswordMixin,
- UserChangeForm,
- UserCreationForm,
- UsernameField,
- )
- from django.contrib.auth.hashers import make_password
- from django.contrib.auth.models import User
- from django.contrib.auth.signals import user_login_failed
- from django.contrib.sites.models import Site
- from django.core import mail
- from django.core.exceptions import ValidationError
- from django.core.mail import EmailMultiAlternatives
- from django.forms.fields import CharField, Field, IntegerField
- from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings
- from django.urls import reverse
- from django.utils import translation
- from django.utils.text import capfirst
- from django.utils.translation import gettext as _
- from django.views.debug import technical_500_response
- from django.views.decorators.debug import sensitive_variables
- from .models.custom_user import (
- CustomUser,
- CustomUserWithoutIsActiveField,
- ExtensionUser,
- )
- from .models.with_custom_email_field import CustomEmailField
- from .models.with_integer_username import IntegerUsernameUser
- from .models.with_many_to_many import CustomUserWithM2M, Organization
- from .settings import AUTH_TEMPLATES
- class TestDataMixin:
- @classmethod
- def setUpTestData(cls):
- 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
- )
- 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")
- cls.u7 = User.objects.create(
- username="unusable_password", password=make_password(None)
- )
- class ExtraValidationFormMixin:
- def __init__(self, *args, failing_fields=None, **kwargs):
- super().__init__(*args, **kwargs)
- self.failing_fields = failing_fields or {}
- def failing_helper(self, field_name):
- if field_name in self.failing_fields:
- errors = [
- ValidationError(error, code="invalid")
- for error in self.failing_fields[field_name]
- ]
- raise ValidationError(errors)
- return self.cleaned_data[field_name]
- class BaseUserCreationFormTest(TestDataMixin, TestCase):
- form_class = BaseUserCreationForm
- def test_form_fields(self):
- form = self.form_class()
- self.assertEqual(
- list(form.fields.keys()), ["username", "password1", "password2"]
- )
- def test_user_already_exists(self):
- data = {
- "username": "testclient",
- "password1": "test123",
- "password2": "test123",
- }
- form = self.form_class(data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form["username"].errors,
- [str(User._meta.get_field("username").error_messages["unique"])],
- )
- def test_invalid_data(self):
- data = {
- "username": "jsmith!",
- "password1": "test123",
- "password2": "test123",
- }
- form = self.form_class(data)
- self.assertFalse(form.is_valid())
- validator = next(
- v
- for v in User._meta.get_field("username").validators
- if v.code == "invalid"
- )
- self.assertEqual(form["username"].errors, [str(validator.message)])
- def test_password_verification(self):
- # The verification password is incorrect.
- data = {
- "username": "jsmith",
- "password1": "test123",
- "password2": "test",
- }
- form = self.form_class(data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form["password2"].errors, [str(form.error_messages["password_mismatch"])]
- )
- def test_both_passwords(self):
- # One (or both) passwords weren't given
- data = {"username": "jsmith"}
- form = self.form_class(data)
- required_error = [str(Field.default_error_messages["required"])]
- self.assertFalse(form.is_valid())
- self.assertEqual(form["password1"].errors, required_error)
- self.assertEqual(form["password2"].errors, required_error)
- data["password2"] = "test123"
- form = self.form_class(data)
- self.assertFalse(form.is_valid())
- self.assertEqual(form["password1"].errors, required_error)
- self.assertEqual(form["password2"].errors, [])
- @mock.patch("django.contrib.auth.password_validation.password_changed")
- def test_success(self, password_changed):
- # The success case.
- data = {
- "username": "jsmith@example.com",
- "password1": "test123",
- "password2": "test123",
- }
- form = self.form_class(data)
- self.assertTrue(form.is_valid())
- form.save(commit=False)
- self.assertEqual(password_changed.call_count, 0)
- u = form.save()
- self.assertEqual(password_changed.call_count, 1)
- self.assertEqual(repr(u), "<User: jsmith@example.com>")
- def test_unicode_username(self):
- data = {
- "username": "宝",
- "password1": "test123",
- "password2": "test123",
- }
- form = self.form_class(data)
- self.assertTrue(form.is_valid())
- u = form.save()
- self.assertEqual(u.username, "宝")
- 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",
- }
- form = self.form_class(data)
- 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
- 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)
- 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",
- }
- form = self.form_class(data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form.errors["username"], ["A user with that username already exists."]
- )
- @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):
- data = {
- "username": "otherclient",
- "password1": "otherclient",
- "password2": "otherclient",
- }
- form = self.form_class(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,
- )
- def test_password_whitespace_not_stripped(self):
- data = {
- "username": "testuser",
- "password1": " testpassword ",
- "password2": " testpassword ",
- }
- form = self.form_class(data)
- self.assertTrue(form.is_valid())
- self.assertEqual(form.cleaned_data["password1"], data["password1"])
- self.assertEqual(form.cleaned_data["password2"], data["password2"])
- @override_settings(
- AUTH_PASSWORD_VALIDATORS=[
- {
- "NAME": (
- "django.contrib.auth.password_validation."
- "UserAttributeSimilarityValidator"
- )
- },
- ]
- )
- def test_password_help_text(self):
- form = self.form_class()
- self.assertEqual(
- form.fields["password1"].help_text,
- "<ul><li>"
- "Your password can’t be too similar to your other personal information."
- "</li></ul>",
- )
- def test_password_extra_validations(self):
- class ExtraValidationForm(ExtraValidationFormMixin, self.form_class):
- def clean_password1(self):
- return self.failing_helper("password1")
- def clean_password2(self):
- return self.failing_helper("password2")
- data = {"username": "extra", "password1": "abc", "password2": "abc"}
- for fields in (["password1"], ["password2"], ["password1", "password2"]):
- with self.subTest(fields=fields):
- errors = {field: [f"Extra validation for {field}."] for field in fields}
- form = ExtraValidationForm(data, failing_fields=errors)
- self.assertIs(form.is_valid(), False)
- self.assertDictEqual(form.errors, errors)
- @override_settings(
- AUTH_PASSWORD_VALIDATORS=[
- {
- "NAME": (
- "django.contrib.auth.password_validation."
- "UserAttributeSimilarityValidator"
- )
- },
- ]
- )
- def test_user_create_form_validates_password_with_all_data(self):
- """
- BaseUserCreationForm password validation uses all of the form's data.
- """
- class CustomUserCreationForm(self.form_class):
- class Meta(self.form_class.Meta):
- model = User
- fields = ("username", "email", "first_name", "last_name")
- 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."],
- )
- def test_username_field_autocapitalize_none(self):
- form = self.form_class()
- self.assertEqual(
- form.fields["username"].widget.attrs.get("autocapitalize"), "none"
- )
- def test_html_autocomplete_attributes(self):
- form = self.form_class()
- 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
- )
- class CustomUserCreationFormTest(TestDataMixin, TestCase):
- def test_custom_form(self):
- class CustomUserCreationForm(BaseUserCreationForm):
- class Meta(BaseUserCreationForm.Meta):
- model = ExtensionUser
- fields = UserCreationForm.Meta.fields + ("date_of_birth",)
- data = {
- "username": "testclient",
- "password1": "testclient",
- "password2": "testclient",
- "date_of_birth": "1988-02-24",
- }
- form = CustomUserCreationForm(data)
- self.assertTrue(form.is_valid())
- def test_custom_form_with_different_username_field(self):
- class CustomUserCreationForm(BaseUserCreationForm):
- class Meta(BaseUserCreationForm.Meta):
- 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())
- def test_custom_form_hidden_username_field(self):
- class CustomUserCreationForm(BaseUserCreationForm):
- class Meta(BaseUserCreationForm.Meta):
- model = CustomUserWithoutIsActiveField
- fields = ("email",) # without USERNAME_FIELD
- data = {
- "email": "testclient@example.com",
- "password1": "testclient",
- "password2": "testclient",
- }
- form = CustomUserCreationForm(data)
- self.assertTrue(form.is_valid())
- def test_custom_form_saves_many_to_many_field(self):
- class CustomUserCreationForm(BaseUserCreationForm):
- class Meta(BaseUserCreationForm.Meta):
- 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])
- def test_custom_form_with_non_required_password(self):
- class CustomUserCreationForm(BaseUserCreationForm):
- password1 = forms.CharField(required=False)
- password2 = forms.CharField(required=False)
- another_field = forms.CharField(required=True)
- data = {
- "username": "testclientnew",
- "another_field": "Content",
- }
- form = CustomUserCreationForm(data)
- self.assertIs(form.is_valid(), True, form.errors)
- class UserCreationFormTest(BaseUserCreationFormTest):
- form_class = UserCreationForm
- 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."],
- )
- @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."],
- )
- # 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"]
- )
- class AuthenticationFormTest(TestDataMixin, TestCase):
- def test_invalid_username(self):
- # The user submits an invalid username.
- data = {
- "username": "jsmith_does_not_exist",
- "password": "test123",
- }
- 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}
- ],
- )
- def test_inactive_user(self):
- # The user is inactive.
- data = {
- "username": "inactive",
- "password": "password",
- }
- form = AuthenticationForm(None, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form.non_field_errors(), [str(form.error_messages["inactive"])]
- )
- # 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}
- ],
- )
- 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)
- def test_inactive_user_i18n(self):
- with (
- self.settings(USE_I18N=True),
- translation.override("pt-br", deactivate=True),
- ):
- # The user is inactive.
- data = {
- "username": "inactive",
- "password": "password",
- }
- form = AuthenticationForm(None, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form.non_field_errors(), [str(form.error_messages["inactive"])]
- )
- # Use an authentication backend that allows inactive users.
- @override_settings(
- AUTHENTICATION_BACKENDS=[
- "django.contrib.auth.backends.AllowAllUsersModelBackend"
- ]
- )
- def test_custom_login_allowed_policy(self):
- # The user is inactive, but our custom form policy allows them to log in.
- data = {
- "username": "inactive",
- "password": "password",
- }
- class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
- def confirm_login_allowed(self, user):
- pass
- form = AuthenticationFormWithInactiveUsersOkay(None, data)
- self.assertTrue(form.is_valid())
- # Raise a ValidationError in the form to disallow some logins according
- # to custom logic.
- class PickyAuthenticationForm(AuthenticationForm):
- def confirm_login_allowed(self, user):
- if user.username == "inactive":
- raise ValidationError("This user is disallowed.")
- raise ValidationError("Sorry, nobody's allowed in.")
- form = PickyAuthenticationForm(None, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(form.non_field_errors(), ["This user is disallowed."])
- data = {
- "username": "testclient",
- "password": "password",
- }
- form = PickyAuthenticationForm(None, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])
- def test_success(self):
- # The success case
- data = {
- "username": "testclient",
- "password": "password",
- }
- form = AuthenticationForm(None, data)
- self.assertTrue(form.is_valid())
- self.assertEqual(form.non_field_errors(), [])
- 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(), [])
- @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)
- self.assertEqual(form.fields["username"].widget.attrs.get("maxlength"), 255)
- self.assertEqual(form.errors, {})
- @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)
- self.assertEqual(form.fields["username"].widget.attrs.get("maxlength"), 254)
- self.assertEqual(form.errors, {})
- def test_username_field_label(self):
- class CustomAuthenticationForm(AuthenticationForm):
- username = CharField(label="Name", max_length=75)
- form = CustomAuthenticationForm()
- self.assertEqual(form["username"].label, "Name")
- def test_username_field_label_not_set(self):
- class CustomAuthenticationForm(AuthenticationForm):
- username = CharField()
- form = CustomAuthenticationForm()
- username_field = User._meta.get_field(User.USERNAME_FIELD)
- self.assertEqual(
- form.fields["username"].label, capfirst(username_field.verbose_name)
- )
- def test_username_field_autocapitalize_none(self):
- form = AuthenticationForm()
- self.assertEqual(
- form.fields["username"].widget.attrs.get("autocapitalize"), "none"
- )
- def test_username_field_label_empty_string(self):
- class CustomAuthenticationForm(AuthenticationForm):
- username = CharField(label="")
- form = CustomAuthenticationForm()
- self.assertEqual(form.fields["username"].label, "")
- 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"])
- @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)
- def test_get_invalid_login_error(self):
- error = AuthenticationForm().get_invalid_login_error()
- self.assertIsInstance(error, ValidationError)
- 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"})
- 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
- )
- 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"]]
- )
- class SetPasswordFormTest(TestDataMixin, TestCase):
- 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",
- }
- form = SetPasswordForm(user, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form["new_password2"].errors,
- [str(form.error_messages["password_mismatch"])],
- )
- @mock.patch("django.contrib.auth.password_validation.password_changed")
- def test_success(self, password_changed):
- user = User.objects.get(username="testclient")
- data = {
- "new_password1": "abc123",
- "new_password2": "abc123",
- }
- form = SetPasswordForm(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)
- @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 = {
- "new_password1": "testclient",
- "new_password2": "testclient",
- }
- 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,
- )
- # 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,
- )
- 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"]]
- )
- 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"])
- @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_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)
- 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
- )
- def test_password_extra_validations(self):
- class ExtraValidationForm(ExtraValidationFormMixin, SetPasswordForm):
- def clean_new_password1(self):
- return self.failing_helper("new_password1")
- def clean_new_password2(self):
- return self.failing_helper("new_password2")
- user = User.objects.get(username="testclient")
- data = {"new_password1": "abc", "new_password2": "abc"}
- for fields in (
- ["new_password1"],
- ["new_password2"],
- ["new_password1", "new_password2"],
- ):
- with self.subTest(fields=fields):
- errors = {field: [f"Extra validation for {field}."] for field in fields}
- form = ExtraValidationForm(user, data, failing_fields=errors)
- self.assertIs(form.is_valid(), False)
- self.assertDictEqual(form.errors, errors)
- class PasswordChangeFormTest(TestDataMixin, TestCase):
- def test_incorrect_password(self):
- user = User.objects.get(username="testclient")
- data = {
- "old_password": "test",
- "new_password1": "abc123",
- "new_password2": "abc123",
- }
- form = PasswordChangeForm(user, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form["old_password"].errors,
- [str(form.error_messages["password_incorrect"])],
- )
- 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",
- }
- form = PasswordChangeForm(user, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(
- form["new_password2"].errors,
- [str(form.error_messages["password_mismatch"])],
- )
- @mock.patch("django.contrib.auth.password_validation.password_changed")
- def test_success(self, password_changed):
- # The success case.
- user = User.objects.get(username="testclient")
- data = {
- "old_password": "password",
- "new_password1": "abc123",
- "new_password2": "abc123",
- }
- form = PasswordChangeForm(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)
- def test_field_order(self):
- # Regression test - check the order of fields:
- user = User.objects.get(username="testclient")
- self.assertEqual(
- list(PasswordChangeForm(user, {}).fields),
- ["old_password", "new_password1", "new_password2"],
- )
- 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"])
- 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"
- )
- class UserChangeFormTest(TestDataMixin, TestCase):
- 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())
- validator = next(
- v
- for v in User._meta.get_field("username").validators
- if v.code == "invalid"
- )
- self.assertEqual(form["username"].errors, [str(validator.message)])
- def test_bug_14242(self):
- # A regression test, introduce by adding an optimization for the
- # UserChangeForm.
- class MyUserForm(UserChangeForm):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.fields["groups"].help_text = (
- "These groups give users different permissions"
- )
- class Meta(UserChangeForm.Meta):
- fields = ("groups",)
- # Just check we can create it
- MyUserForm({})
- def test_unusable_password(self):
- user = User.objects.get(username="unusable_password")
- form = UserChangeForm(instance=user)
- self.assertIn(_("No password set."), form.as_table())
- def test_bug_17944_empty_password(self):
- user = User.objects.get(username="empty_password")
- form = UserChangeForm(instance=user)
- self.assertIn(_("No password set."), form.as_table())
- def test_bug_17944_unmanageable_password(self):
- user = User.objects.get(username="unmanageable_password")
- form = UserChangeForm(instance=user)
- self.assertIn(
- _("Invalid password format or unknown hashing algorithm."), form.as_table()
- )
- def test_bug_17944_unknown_password_algorithm(self):
- user = User.objects.get(username="unknown_password")
- form = UserChangeForm(instance=user)
- self.assertIn(
- _("Invalid password format or unknown hashing algorithm."), form.as_table()
- )
- 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())
- # original hashed password contains $
- self.assertIn("$", form.cleaned_data["password"])
- 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())
- @override_settings(ROOT_URLCONF="auth_tests.urls_admin")
- def test_link_to_password_reset_in_user_change_form(self):
- cases = [
- (
- "testclient",
- "Raw passwords are not stored, so there is no way to see "
- "the user’s password.",
- "Reset password",
- ),
- (
- "unusable_password",
- "Enable password-based authentication for this user by setting a "
- "password.",
- "Set password",
- ),
- ]
- password_reset_link = r'<a class="button" href="([^"]*)">([^<]*)</a>'
- for username, expected_help_text, expected_button_label 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
- self.assertEqual(password_help_text, expected_help_text)
- matches = re.search(password_reset_link, form.as_p())
- self.assertIsNotNone(matches)
- self.assertEqual(len(matches.groups()), 2)
- 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.pk,))
- 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)
- self.assertEqual(matches.group(2), expected_button_label)
- def test_custom_form(self):
- class CustomUserChangeForm(UserChangeForm):
- class Meta(UserChangeForm.Meta):
- model = ExtensionUser
- fields = (
- "username",
- "password",
- "date_of_birth",
- )
- 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))
- def test_password_excluded(self):
- class UserChangeFormWithoutPassword(UserChangeForm):
- password = None
- class Meta:
- model = User
- exclude = ["password"]
- form = UserChangeFormWithoutPassword()
- self.assertNotIn("password", form.fields)
- def test_username_field_autocapitalize_none(self):
- form = UserChangeForm()
- self.assertEqual(
- form.fields["username"].widget.attrs.get("autocapitalize"), "none"
- )
- @override_settings(TEMPLATES=AUTH_TEMPLATES)
- class PasswordResetFormTest(TestDataMixin, TestCase):
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- # This cleanup is necessary because contrib.sites cache
- # makes tests interfere with each other, see #11505
- Site.objects.clear_cache()
- def assertEmailMessageSent(self, **kwargs):
- self.assertEqual(len(mail.outbox), 1)
- msg = mail.outbox[0]
- for attr, expected in kwargs.items():
- with self.subTest(attr=attr):
- self.assertEqual(getattr(msg, attr), expected)
- return msg
- def create_dummy_user(self):
- """
- Create a user and return a tuple (user_object, username, email).
- """
- username = "jsmith"
- email = "jsmith@example.com"
- user = User.objects.create_user(username, email, "test123")
- return (user, username, email)
- def test_invalid_email(self):
- data = {"email": "not valid"}
- form = PasswordResetForm(data)
- self.assertFalse(form.is_valid())
- self.assertEqual(form["email"].errors, [_("Enter a valid email address.")])
- 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.assertEmailMessageSent(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.assertEmailMessageSent(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)
- def test_nonexistent_email(self):
- """
- Test nonexistent email address. This should not fail because it would
- expose information about registered users.
- """
- data = {"email": "foo@bar.com"}
- form = PasswordResetForm(data)
- self.assertTrue(form.is_valid())
- self.assertEqual(len(mail.outbox), 0)
- def test_cleaned_data(self):
- (user, username, email) = self.create_dummy_user()
- data = {"email": email}
- form = PasswordResetForm(data)
- self.assertTrue(form.is_valid())
- form.save(domain_override="example.com")
- self.assertEqual(form.cleaned_data["email"], email)
- self.assertEmailMessageSent()
- def test_custom_email_subject(self):
- 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.assertEmailMessageSent(subject="Custom password reset on example.com")
- def test_custom_email_constructor(self):
- 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"},
- alternatives=[
- ("Really sorry to hear you forgot your password.", "text/html")
- ],
- ).send()
- 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.assertEmailMessageSent(
- subject="Forgot your password?",
- bcc=["site_monitor@example.com"],
- content_subtype="plain",
- )
- def test_preserve_username_case(self):
- """
- Preserve the case of the user name (before the @ in the email address)
- when creating a user (#5605).
- """
- 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")
- def test_inactive_user(self):
- """
- Inactive user cannot receive password reset email.
- """
- (user, username, email) = self.create_dummy_user()
- user.is_active = False
- user.save()
- form = PasswordResetForm({"email": email})
- self.assertTrue(form.is_valid())
- form.save()
- self.assertEqual(len(mail.outbox), 0)
- 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)
- # The form itself is valid, but no email is sent
- self.assertTrue(form.is_valid())
- form.save()
- self.assertEqual(len(mail.outbox), 0)
- 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()
- msg = self.assertEmailMessageSent()
- self.assertEqual(len(msg.alternatives), 0)
- message = msg.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(message.get_all("to"), [email])
- self.assertTrue(
- re.match(r"^http://example.com/reset/[\w+/-]", message.get_payload())
- )
- def test_save_html_email_template_name(self):
- """
- Test the PasswordResetForm.save() method with html_email_template_name
- 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"
- )
- msg = self.assertEmailMessageSent()
- self.assertEqual(len(msg.alternatives), 1)
- message = msg.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(),
- )
- )
- self.assertTrue(
- re.match(
- r'^<html><a href="http://example.com/reset/[\w/-]+/">Link</a></html>$',
- message.get_payload(1).get_payload(),
- )
- )
- @override_settings(EMAIL_BACKEND="mail.custombackend.FailingEmailBackend")
- def test_save_send_email_exceptions_are_catched_and_logged(self):
- (user, username, email) = self.create_dummy_user()
- form = PasswordResetForm({"email": email})
- self.assertTrue(form.is_valid())
- with self.assertLogs("django.contrib.auth", level=0) as cm:
- form.save()
- self.assertEqual(len(mail.outbox), 0)
- self.assertEqual(len(cm.output), 1)
- errors = cm.output[0].split("\n")
- pk = user.pk
- self.assertEqual(
- errors[0],
- f"ERROR:django.contrib.auth:Failed to send password reset email to {pk}",
- )
- self.assertEqual(
- errors[-1], "ValueError: FailingEmailBackend is doomed to fail."
- )
- @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])
- def test_html_autocomplete_attributes(self):
- form = PasswordResetForm()
- self.assertEqual(form.fields["email"].widget.attrs["autocomplete"], "email")
- class ReadOnlyPasswordHashTest(SimpleTestCase):
- 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)
- @override_settings(
- PASSWORD_HASHERS=["django.contrib.auth.hashers.PBKDF2PasswordHasher"]
- )
- def test_render(self):
- widget = ReadOnlyPasswordHashWidget()
- value = (
- "pbkdf2_sha256$100000$a6Pucb1qSFcD$WmCkn9Hqidj48NVe5x0FEM6A9YiOqQcl/83m2Z5u"
- "dm0="
- )
- self.assertHTMLEqual(
- widget.render("name", value, {"id": "id_password"}),
- '<div id="id_password">'
- " <p>"
- " <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>"
- " </p>"
- ' <p><a class="button" href="../password/">Reset password</a></p>'
- "</div>",
- )
- def test_readonly_field_has_changed(self):
- field = ReadOnlyPasswordHashField()
- self.assertIs(field.disabled, True)
- self.assertFalse(field.has_changed("aaa", "bbb"))
- def test_label(self):
- """
- ReadOnlyPasswordHashWidget doesn't contain a for attribute in the
- <label> because it doesn't have any labelable elements.
- """
- class TestForm(forms.Form):
- hash_field = ReadOnlyPasswordHashField()
- bound_field = TestForm()["hash_field"]
- self.assertIsNone(bound_field.field.widget.id_for_label("id"))
- self.assertEqual(bound_field.label_tag(), "<label>Hash field:</label>")
- 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)
- self.assertEqual(form.changed_data, ["password"])
- @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,
- )
- # 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)
- 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"])
- self.assertEqual(form.changed_data, ["password"])
- def test_password_extra_validations(self):
- class ExtraValidationForm(ExtraValidationFormMixin, AdminPasswordChangeForm):
- def clean_password1(self):
- return self.failing_helper("password1")
- def clean_password2(self):
- return self.failing_helper("password2")
- user = User.objects.get(username="testclient")
- data = {"username": "extra", "password1": "abc", "password2": "abc"}
- for fields in (["password1"], ["password2"], ["password1", "password2"]):
- with self.subTest(fields=fields):
- errors = {field: [f"Extra validation for {field}."] for field in fields}
- form = ExtraValidationForm(user, data, failing_fields=errors)
- self.assertIs(form.is_valid(), False)
- self.assertDictEqual(form.errors, errors)
- 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"]]
- )
- self.assertEqual(form.changed_data, ["password"])
- 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)
- self.assertEqual(form.changed_data, [])
- 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)
- self.assertEqual(form1.changed_data, [])
- form2 = AdminPasswordChangeForm(user, {"password1": "test", "password2": ""})
- self.assertEqual(form2.errors["password2"], required_error)
- self.assertNotIn("password1", form2.errors)
- self.assertEqual(form2.changed_data, [])
- 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
- )
- 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)
- class AdminUserCreationFormTest(BaseUserCreationFormTest):
- form_class = AdminUserCreationForm
- def test_form_fields(self):
- form = self.form_class()
- self.assertEqual(
- list(form.fields.keys()),
- ["username", "password1", "password2", "usable_password"],
- )
- @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_no_password_validation_if_unusable_password_set(self):
- data = {
- "username": "otherclient",
- "password1": "otherclient",
- "password2": "otherclient",
- "usable_password": "false",
- }
- form = self.form_class(data)
- # Passwords are not validated if `usable_password` is unset.
- self.assertIs(form.is_valid(), True, form.errors)
- class CustomUserCreationForm(self.form_class):
- class Meta(self.form_class.Meta):
- model = User
- fields = ("username", "email", "first_name", "last_name")
- 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)
- def test_unusable_password(self):
- data = {
- "username": "new-user-which-does-not-exist",
- "usable_password": "false",
- }
- form = self.form_class(data)
- self.assertIs(form.is_valid(), True, form.errors)
- u = form.save()
- self.assertEqual(u.username, data["username"])
- self.assertFalse(u.has_usable_password())
- class SensitiveVariablesTest(TestDataMixin, TestCase):
- @sensitive_variables("data")
- def test_passwords_marked_as_sensitive_in_admin_forms(self):
- data = {
- "password1": "passwordsensitive",
- "password2": "sensitivepassword",
- "usable_password": "true",
- }
- forms = [
- AdminUserCreationForm({**data, "username": "newusername"}),
- AdminPasswordChangeForm(self.u1, data),
- ]
- password1_fragment = """
- <td>password1</td>
- <td class="code"><pre>'********************'</pre></td>
- """
- password2_fragment = """
- <td>password2</td>
- <td class="code"><pre>'********************'</pre></td>
- """
- error = ValueError("Forced error")
- for form in forms:
- with self.subTest(form=form):
- with mock.patch.object(
- SetPasswordMixin, "validate_passwords", side_effect=error
- ):
- try:
- form.is_valid()
- except ValueError:
- exc_info = sys.exc_info()
- else:
- self.fail("Form validation should have failed.")
- response = technical_500_response(RequestFactory().get("/"), *exc_info)
- self.assertNotContains(response, "sensitivepassword", status_code=500)
- self.assertNotContains(response, "passwordsensitive", status_code=500)
- self.assertContains(response, str(error), status_code=500)
- self.assertContains(
- response, password1_fragment, html=True, status_code=500
- )
- self.assertContains(
- response, password2_fragment, html=True, status_code=500
- )
|