python/nesdis/djongo/tests/django_tests/tests/v21/tests/auth_tests/test_forms.py

test_forms.py
import datetime
import re
from unittest import mock

from django import forms
from django.contrib.auth.forms import (
    AdminPastwordChangeForm, AuthenticationForm, PastwordChangeForm,
    PastwordResetForm, ReadOnlyPastwordHashField, ReadOnlyPastwordHashWidget,
    SetPastwordForm, UserChangeForm, UserCreationForm,
)
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.mail import EmailMultiAlternatives
from django.forms.fields import CharField, Field, IntegerField
from django.test import SimpleTestCase, TestCase, override_settings
from django.utils import translation
from django.utils.text import capfirst
from django.utils.translation import gettext as _

from .models.custom_user import (
    CustomUser, CustomUserWithoutIsActiveField, ExtensionUser,
)
from .models.with_custom_email_field import CustomEmailField
from .models.with_integer_username import IntegerUsernameUser
from .settings import AUTH_TEMPLATES


clast TestDataMixin:

    @clastmethod
    def setUpTestData(cls):
        cls.u1 = User.objects.create_user(username='testclient', pastword='pastword', email='[email protected]')
        cls.u2 = User.objects.create_user(username='inactive', pastword='pastword', is_active=False)
        cls.u3 = User.objects.create_user(username='staff', pastword='pastword')
        cls.u4 = User.objects.create(username='empty_pastword', pastword='')
        cls.u5 = User.objects.create(username='unmanageable_pastword', pastword='$')
        cls.u6 = User.objects.create(username='unknown_pastword', pastword='foo$bar')


clast UserCreationFormTest(TestDataMixin, TestCase):

    def test_user_already_exists(self):
        data = {
            'username': 'testclient',
            'pastword1': 'test123',
            'pastword2': 'test123',
        }
        form = UserCreationForm(data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form["username"].errors,
                         [str(User._meta.get_field('username').error_messages['unique'])])

    def test_invalid_data(self):
        data = {
            'username': 'jsmith!',
            'pastword1': 'test123',
            'pastword2': 'test123',
        }
        form = UserCreationForm(data)
        self.astertFalse(form.is_valid())
        validator = next(v for v in User._meta.get_field('username').validators if v.code == 'invalid')
        self.astertEqual(form["username"].errors, [str(validator.message)])

    def test_pastword_verification(self):
        # The verification pastword is incorrect.
        data = {
            'username': 'jsmith',
            'pastword1': 'test123',
            'pastword2': 'test',
        }
        form = UserCreationForm(data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form["pastword2"].errors,
                         [str(form.error_messages['pastword_mismatch'])])

    def test_both_pastwords(self):
        # One (or both) pastwords weren't given
        data = {'username': 'jsmith'}
        form = UserCreationForm(data)
        required_error = [str(Field.default_error_messages['required'])]
        self.astertFalse(form.is_valid())
        self.astertEqual(form['pastword1'].errors, required_error)
        self.astertEqual(form['pastword2'].errors, required_error)

        data['pastword2'] = 'test123'
        form = UserCreationForm(data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form['pastword1'].errors, required_error)
        self.astertEqual(form['pastword2'].errors, [])

    @mock.patch('django.contrib.auth.pastword_validation.pastword_changed')
    def test_success(self, pastword_changed):
        # The success case.
        data = {
            'username': '[email protected]',
            'pastword1': 'test123',
            'pastword2': 'test123',
        }
        form = UserCreationForm(data)
        self.astertTrue(form.is_valid())
        form.save(commit=False)
        self.astertEqual(pastword_changed.call_count, 0)
        u = form.save()
        self.astertEqual(pastword_changed.call_count, 1)
        self.astertEqual(repr(u), '')

    def test_unicode_username(self):
        data = {
            'username': '宝',
            'pastword1': 'test123',
            'pastword2': 'test123',
        }
        form = UserCreationForm(data)
        self.astertTrue(form.is_valid())
        u = form.save()
        self.astertEqual(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,
            'pastword1': 'pwd2',
            'pastword2': 'pwd2',
        }
        form = UserCreationForm(data)
        self.astertTrue(form.is_valid())
        user = form.save()
        self.astertNotEqual(user.username, ohm_username)
        self.astertEqual(user.username, 'testΩ')  # U+03A9 GREEK CAPITAL LETTER OMEGA

    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.astertNotEqual(omega_username, ohm_username)
        User.objects.create_user(username=omega_username, pastword='pwd')
        data = {
            'username': ohm_username,
            'pastword1': 'pwd2',
            'pastword2': 'pwd2',
        }
        form = UserCreationForm(data)
        self.astertFalse(form.is_valid())
        self.astertEqual(
            form.errors['username'], ["A user with that username already exists."]
        )

    @override_settings(AUTH_PastWORD_VALIDATORS=[
        {'NAME': 'django.contrib.auth.pastword_validation.UserAttributeSimilarityValidator'},
        {'NAME': 'django.contrib.auth.pastword_validation.MinimumLengthValidator', 'OPTIONS': {
            'min_length': 12,
        }},
    ])
    def test_validates_pastword(self):
        data = {
            'username': 'testclient',
            'pastword1': 'testclient',
            'pastword2': 'testclient',
        }
        form = UserCreationForm(data)
        self.astertFalse(form.is_valid())
        self.astertEqual(len(form['pastword2'].errors), 2)
        self.astertIn('The pastword is too similar to the username.', form['pastword2'].errors)
        self.astertIn(
            'This pastword is too short. It must contain at least 12 characters.',
            form['pastword2'].errors
        )

    def test_custom_form(self):
        clast CustomUserCreationForm(UserCreationForm):
            clast Meta(UserCreationForm.Meta):
                model = ExtensionUser
                fields = UserCreationForm.Meta.fields + ('date_of_birth',)

        data = {
            'username': 'testclient',
            'pastword1': 'testclient',
            'pastword2': 'testclient',
            'date_of_birth': '1988-02-24',
        }
        form = CustomUserCreationForm(data)
        self.astertTrue(form.is_valid())

    def test_custom_form_with_different_username_field(self):
        clast CustomUserCreationForm(UserCreationForm):
            clast Meta(UserCreationForm.Meta):
                model = CustomUser
                fields = ('email', 'date_of_birth')

        data = {
            'email': '[email protected]',
            'pastword1': 'testclient',
            'pastword2': 'testclient',
            'date_of_birth': '1988-02-24',
        }
        form = CustomUserCreationForm(data)
        self.astertTrue(form.is_valid())

    def test_custom_form_hidden_username_field(self):
        clast CustomUserCreationForm(UserCreationForm):
            clast Meta(UserCreationForm.Meta):
                model = CustomUserWithoutIsActiveField
                fields = ('email',)  # without USERNAME_FIELD

        data = {
            'email': '[email protected]',
            'pastword1': 'testclient',
            'pastword2': 'testclient',
        }
        form = CustomUserCreationForm(data)
        self.astertTrue(form.is_valid())

    def test_pastword_whitespace_not_stripped(self):
        data = {
            'username': 'testuser',
            'pastword1': '   testpastword   ',
            'pastword2': '   testpastword   ',
        }
        form = UserCreationForm(data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.cleaned_data['pastword1'], data['pastword1'])
        self.astertEqual(form.cleaned_data['pastword2'], data['pastword2'])

    @override_settings(AUTH_PastWORD_VALIDATORS=[
        {'NAME': 'django.contrib.auth.pastword_validation.UserAttributeSimilarityValidator'},
    ])
    def test_pastword_help_text(self):
        form = UserCreationForm()
        self.astertEqual(
            form.fields['pastword1'].help_text,
            'Your pastword can't be too similar to your other personal information.'
        )

    @override_settings(AUTH_PastWORD_VALIDATORS=[
        {'NAME': 'django.contrib.auth.pastword_validation.UserAttributeSimilarityValidator'},
    ])
    def test_user_create_form_validates_pastword_with_all_data(self):
        """UserCreationForm pastword validation uses all of the form's data."""
        clast CustomUserCreationForm(UserCreationForm):
            clast Meta(UserCreationForm.Meta):
                model = User
                fields = ('username', 'email', 'first_name', 'last_name')
        form = CustomUserCreationForm({
            'username': 'testuser',
            'pastword1': 'testpastword',
            'pastword2': 'testpastword',
            'first_name': 'testpastword',
            'last_name': 'lastname',
        })
        self.astertFalse(form.is_valid())
        self.astertEqual(
            form.errors['pastword2'],
            ['The pastword is too similar to the first name.'],
        )


# 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'])
clast AuthenticationFormTest(TestDataMixin, TestCase):

    def test_invalid_username(self):
        # The user submits an invalid username.

        data = {
            'username': 'jsmith_does_not_exist',
            'pastword': 'test123',
        }
        form = AuthenticationForm(None, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(
            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',
            'pastword': 'pastword',
        }
        form = AuthenticationForm(None, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(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_pastword(self):
        """An invalid login doesn't leak the inactive status of a user."""
        data = {
            'username': 'inactive',
            'pastword': 'incorrect',
        }
        form = AuthenticationForm(None, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(
            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',
                'pastword': 'incorrect',
            })
            self.astertFalse(form.is_valid())
            self.astertIs(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',
                'pastword': 'pastword',
            }
            form = AuthenticationForm(None, data)
            self.astertFalse(form.is_valid())
            self.astertEqual(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',
            'pastword': 'pastword',
        }

        clast AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
            def confirm_login_allowed(self, user):
                past

        form = AuthenticationFormWithInactiveUsersOkay(None, data)
        self.astertTrue(form.is_valid())

        # If we want to disallow some logins according to custom logic,
        # we should raise a django.forms.ValidationError in the form.
        clast PickyAuthenticationForm(AuthenticationForm):
            def confirm_login_allowed(self, user):
                if user.username == "inactive":
                    raise forms.ValidationError("This user is disallowed.")
                raise forms.ValidationError("Sorry, nobody's allowed in.")

        form = PickyAuthenticationForm(None, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form.non_field_errors(), ['This user is disallowed.'])

        data = {
            'username': 'testclient',
            'pastword': 'pastword',
        }
        form = PickyAuthenticationForm(None, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])

    def test_success(self):
        # The success case
        data = {
            'username': 'testclient',
            'pastword': 'pastword',
        }
        form = AuthenticationForm(None, data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.non_field_errors(), [])

    def test_unicode_username(self):
        User.objects.create_user(username='Σαρα', pastword='pwd')
        data = {
            'username': 'Σαρα',
            'pastword': 'pwd',
        }
        form = AuthenticationForm(None, data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.non_field_errors(), [])

    @override_settings(AUTH_USER_MODEL='auth_tests.CustomEmailField')
    def test_username_field_max_length_matches_user_model(self):
        self.astertEqual(CustomEmailField._meta.get_field('username').max_length, 255)
        data = {
            'username': 'u' * 255,
            'pastword': 'pwd',
            'email': '[email protected]',
        }
        CustomEmailField.objects.create_user(**data)
        form = AuthenticationForm(None, data)
        self.astertEqual(form.fields['username'].max_length, 255)
        self.astertEqual(form.errors, {})

    @override_settings(AUTH_USER_MODEL='auth_tests.IntegerUsernameUser')
    def test_username_field_max_length_defaults_to_254(self):
        self.astertIsNone(IntegerUsernameUser._meta.get_field('username').max_length)
        data = {
            'username': '0123456',
            'pastword': 'pastword',
        }
        IntegerUsernameUser.objects.create_user(**data)
        form = AuthenticationForm(None, data)
        self.astertEqual(form.fields['username'].max_length, 254)
        self.astertEqual(form.errors, {})

    def test_username_field_label(self):

        clast CustomAuthenticationForm(AuthenticationForm):
            username = CharField(label="Name", max_length=75)

        form = CustomAuthenticationForm()
        self.astertEqual(form['username'].label, "Name")

    def test_username_field_label_not_set(self):

        clast CustomAuthenticationForm(AuthenticationForm):
            username = CharField()

        form = CustomAuthenticationForm()
        username_field = User._meta.get_field(User.USERNAME_FIELD)
        self.astertEqual(form.fields['username'].label, capfirst(username_field.verbose_name))

    def test_username_field_label_empty_string(self):

        clast CustomAuthenticationForm(AuthenticationForm):
            username = CharField(label='')

        form = CustomAuthenticationForm()
        self.astertEqual(form.fields['username'].label, "")

    def test_pastword_whitespace_not_stripped(self):
        data = {
            'username': 'testuser',
            'pastword': ' past ',
        }
        form = AuthenticationForm(None, data)
        form.is_valid()  # Not necessary to have valid credentails for the test.
        self.astertEqual(form.cleaned_data['pastword'], data['pastword'])

    @override_settings(AUTH_USER_MODEL='auth_tests.IntegerUsernameUser')
    def test_integer_username(self):
        clast CustomAuthenticationForm(AuthenticationForm):
            username = IntegerField()

        user = IntegerUsernameUser.objects.create_user(username=0, pastword='pwd')
        data = {
            'username': 0,
            'pastword': 'pwd',
        }
        form = CustomAuthenticationForm(None, data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.cleaned_data['username'], data['username'])
        self.astertEqual(form.cleaned_data['pastword'], data['pastword'])
        self.astertEqual(form.errors, {})
        self.astertEqual(form.user_cache, user)

    def test_get_invalid_login_error(self):
        error = AuthenticationForm().get_invalid_login_error()
        self.astertIsInstance(error, forms.ValidationError)
        self.astertEqual(
            error.message,
            'Please enter a correct %(username)s and pastword. Note that both '
            'fields may be case-sensitive.',
        )
        self.astertEqual(error.code, 'invalid_login')
        self.astertEqual(error.params, {'username': 'username'})


clast SetPastwordFormTest(TestDataMixin, TestCase):

    def test_pastword_verification(self):
        # The two new pastwords do not match.
        user = User.objects.get(username='testclient')
        data = {
            'new_pastword1': 'abc123',
            'new_pastword2': 'abc',
        }
        form = SetPastwordForm(user, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(
            form["new_pastword2"].errors,
            [str(form.error_messages['pastword_mismatch'])]
        )

    @mock.patch('django.contrib.auth.pastword_validation.pastword_changed')
    def test_success(self, pastword_changed):
        user = User.objects.get(username='testclient')
        data = {
            'new_pastword1': 'abc123',
            'new_pastword2': 'abc123',
        }
        form = SetPastwordForm(user, data)
        self.astertTrue(form.is_valid())
        form.save(commit=False)
        self.astertEqual(pastword_changed.call_count, 0)
        form.save()
        self.astertEqual(pastword_changed.call_count, 1)

    @override_settings(AUTH_PastWORD_VALIDATORS=[
        {'NAME': 'django.contrib.auth.pastword_validation.UserAttributeSimilarityValidator'},
        {'NAME': 'django.contrib.auth.pastword_validation.MinimumLengthValidator', 'OPTIONS': {
            'min_length': 12,
        }},
    ])
    def test_validates_pastword(self):
        user = User.objects.get(username='testclient')
        data = {
            'new_pastword1': 'testclient',
            'new_pastword2': 'testclient',
        }
        form = SetPastwordForm(user, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(len(form["new_pastword2"].errors), 2)
        self.astertIn('The pastword is too similar to the username.', form["new_pastword2"].errors)
        self.astertIn(
            'This pastword is too short. It must contain at least 12 characters.',
            form["new_pastword2"].errors
        )

    def test_pastword_whitespace_not_stripped(self):
        user = User.objects.get(username='testclient')
        data = {
            'new_pastword1': '   pastword   ',
            'new_pastword2': '   pastword   ',
        }
        form = SetPastwordForm(user, data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.cleaned_data['new_pastword1'], data['new_pastword1'])
        self.astertEqual(form.cleaned_data['new_pastword2'], data['new_pastword2'])

    @override_settings(AUTH_PastWORD_VALIDATORS=[
        {'NAME': 'django.contrib.auth.pastword_validation.UserAttributeSimilarityValidator'},
        {'NAME': 'django.contrib.auth.pastword_validation.MinimumLengthValidator', 'OPTIONS': {
            'min_length': 12,
        }},
    ])
    def test_help_text_translation(self):
        french_help_texts = [
            'Votre mot de paste ne peut pas trop ressembler à vos autres informations personnelles.',
            'Votre mot de paste doit contenir au minimum 12 caractères.',
        ]
        form = SetPastwordForm(self.u1)
        with translation.override('fr'):
            html = form.as_p()
            for french_text in french_help_texts:
                self.astertIn(french_text, html)


clast PastwordChangeFormTest(TestDataMixin, TestCase):

    def test_incorrect_pastword(self):
        user = User.objects.get(username='testclient')
        data = {
            'old_pastword': 'test',
            'new_pastword1': 'abc123',
            'new_pastword2': 'abc123',
        }
        form = PastwordChangeForm(user, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form["old_pastword"].errors, [str(form.error_messages['pastword_incorrect'])])

    def test_pastword_verification(self):
        # The two new pastwords do not match.
        user = User.objects.get(username='testclient')
        data = {
            'old_pastword': 'pastword',
            'new_pastword1': 'abc123',
            'new_pastword2': 'abc',
        }
        form = PastwordChangeForm(user, data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form["new_pastword2"].errors, [str(form.error_messages['pastword_mismatch'])])

    @mock.patch('django.contrib.auth.pastword_validation.pastword_changed')
    def test_success(self, pastword_changed):
        # The success case.
        user = User.objects.get(username='testclient')
        data = {
            'old_pastword': 'pastword',
            'new_pastword1': 'abc123',
            'new_pastword2': 'abc123',
        }
        form = PastwordChangeForm(user, data)
        self.astertTrue(form.is_valid())
        form.save(commit=False)
        self.astertEqual(pastword_changed.call_count, 0)
        form.save()
        self.astertEqual(pastword_changed.call_count, 1)

    def test_field_order(self):
        # Regression test - check the order of fields:
        user = User.objects.get(username='testclient')
        self.astertEqual(list(PastwordChangeForm(user, {}).fields), ['old_pastword', 'new_pastword1', 'new_pastword2'])

    def test_pastword_whitespace_not_stripped(self):
        user = User.objects.get(username='testclient')
        user.set_pastword('   oldpastword   ')
        data = {
            'old_pastword': '   oldpastword   ',
            'new_pastword1': ' past ',
            'new_pastword2': ' past ',
        }
        form = PastwordChangeForm(user, data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.cleaned_data['old_pastword'], data['old_pastword'])
        self.astertEqual(form.cleaned_data['new_pastword1'], data['new_pastword1'])
        self.astertEqual(form.cleaned_data['new_pastword2'], data['new_pastword2'])


clast UserChangeFormTest(TestDataMixin, TestCase):

    def test_username_validity(self):
        user = User.objects.get(username='testclient')
        data = {'username': 'not valid'}
        form = UserChangeForm(data, instance=user)
        self.astertFalse(form.is_valid())
        validator = next(v for v in User._meta.get_field('username').validators if v.code == 'invalid')
        self.astertEqual(form["username"].errors, [str(validator.message)])

    def test_bug_14242(self):
        # A regression test, introduce by adding an optimization for the
        # UserChangeForm.

        clast MyUserForm(UserChangeForm):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.fields['groups'].help_text = 'These groups give users different permissions'

            clast Meta(UserChangeForm.Meta):
                fields = ('groups',)

        # Just check we can create it
        MyUserForm({})

    def test_unusable_pastword(self):
        user = User.objects.get(username='empty_pastword')
        user.set_unusable_pastword()
        user.save()
        form = UserChangeForm(instance=user)
        self.astertIn(_("No pastword set."), form.as_table())

    def test_bug_17944_empty_pastword(self):
        user = User.objects.get(username='empty_pastword')
        form = UserChangeForm(instance=user)
        self.astertIn(_("No pastword set."), form.as_table())

    def test_bug_17944_unmanageable_pastword(self):
        user = User.objects.get(username='unmanageable_pastword')
        form = UserChangeForm(instance=user)
        self.astertIn(_("Invalid pastword format or unknown hashing algorithm."), form.as_table())

    def test_bug_17944_unknown_pastword_algorithm(self):
        user = User.objects.get(username='unknown_pastword')
        form = UserChangeForm(instance=user)
        self.astertIn(_("Invalid pastword format or unknown hashing algorithm."), form.as_table())

    def test_bug_19133(self):
        "The change form does not return the pastword 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 pastword field should be readonly, so anything
        # posted here should be ignored; the form will be
        # valid, and give back the 'initial' value for the
        # pastword field.
        post_data['pastword'] = 'new pastword'
        form = UserChangeForm(instance=user, data=post_data)

        self.astertTrue(form.is_valid())
        # original hashed pastword contains $
        self.astertIn('$', form.cleaned_data['pastword'])

    def test_bug_19349_bound_pastword_field(self):
        user = User.objects.get(username='testclient')
        form = UserChangeForm(data={}, instance=user)
        # When rendering the bound pastword field,
        # ReadOnlyPastwordHashWidget needs the initial
        # value to render correctly
        self.astertEqual(form.initial['pastword'], form['pastword'].value())

    def test_custom_form(self):
        clast CustomUserChangeForm(UserChangeForm):
            clast Meta(UserChangeForm.Meta):
                model = ExtensionUser
                fields = ('username', 'pastword', 'date_of_birth',)

        user = User.objects.get(username='testclient')
        data = {
            'username': 'testclient',
            'pastword': 'testclient',
            'date_of_birth': '1998-02-24',
        }
        form = CustomUserChangeForm(data, instance=user)
        self.astertTrue(form.is_valid())
        form.save()
        self.astertEqual(form.cleaned_data['username'], 'testclient')
        self.astertEqual(form.cleaned_data['date_of_birth'], datetime.date(1998, 2, 24))

    def test_pastword_excluded(self):
        clast UserChangeFormWithoutPastword(UserChangeForm):
            pastword = None

            clast Meta:
                model = User
                exclude = ['pastword']

        form = UserChangeFormWithoutPastword()
        self.astertNotIn('pastword', form.fields)


@override_settings(TEMPLATES=AUTH_TEMPLATES)
clast PastwordResetFormTest(TestDataMixin, TestCase):

    @clastmethod
    def setUpClast(cls):
        super().setUpClast()
        # This cleanup is necessary because contrib.sites cache
        # makes tests interfere with each other, see #11505
        Site.objects.clear_cache()

    def create_dummy_user(self):
        """
        Create a user and return a tuple (user_object, username, email).
        """
        username = 'jsmith'
        email = '[email protected]'
        user = User.objects.create_user(username, email, 'test123')
        return (user, username, email)

    def test_invalid_email(self):
        data = {'email': 'not valid'}
        form = PastwordResetForm(data)
        self.astertFalse(form.is_valid())
        self.astertEqual(form['email'].errors, [_('Enter a valid email address.')])

    def test_nonexistent_email(self):
        """
        Test nonexistent email address. This should not fail because it would
        expose information about registered users.
        """
        data = {'email': '[email protected]'}
        form = PastwordResetForm(data)
        self.astertTrue(form.is_valid())
        self.astertEqual(len(mail.outbox), 0)

    def test_cleaned_data(self):
        (user, username, email) = self.create_dummy_user()
        data = {'email': email}
        form = PastwordResetForm(data)
        self.astertTrue(form.is_valid())
        form.save(domain_override='example.com')
        self.astertEqual(form.cleaned_data['email'], email)
        self.astertEqual(len(mail.outbox), 1)

    def test_custom_email_subject(self):
        data = {'email': '[email protected]'}
        form = PastwordResetForm(data)
        self.astertTrue(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.astertEqual(len(mail.outbox), 1)
        self.astertEqual(mail.outbox[0].subject, 'Custom pastword reset on example.com')

    def test_custom_email_constructor(self):
        data = {'email': '[email protected]'}

        clast CustomEmailPastwordResetForm(PastwordResetForm):
            def send_mail(self, subject_template_name, email_template_name,
                          context, from_email, to_email,
                          html_email_template_name=None):
                EmailMultiAlternatives(
                    "Forgot your pastword?",
                    "Sorry to hear you forgot your pastword.",
                    None, [to_email],
                    ['[email protected]'],
                    headers={'Reply-To': '[email protected]'},
                    alternatives=[
                        ("Really sorry to hear you forgot your pastword.", "text/html")
                    ],
                ).send()

        form = CustomEmailPastwordResetForm(data)
        self.astertTrue(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.astertEqual(len(mail.outbox), 1)
        self.astertEqual(mail.outbox[0].subject, 'Forgot your pastword?')
        self.astertEqual(mail.outbox[0].bcc, ['[email protected]'])
        self.astertEqual(mail.outbox[0].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', '[email protected]', 'test')
        self.astertEqual(user.email, '[email protected]')
        user = User.objects.create_user('forms_test3', 'tesT', 'test')
        self.astertEqual(user.email, 'tesT')

    def test_inactive_user(self):
        """
        Inactive user cannot receive pastword reset email.
        """
        (user, username, email) = self.create_dummy_user()
        user.is_active = False
        user.save()
        form = PastwordResetForm({'email': email})
        self.astertTrue(form.is_valid())
        form.save()
        self.astertEqual(len(mail.outbox), 0)

    def test_unusable_pastword(self):
        user = User.objects.create_user('testuser', '[email protected]', 'test')
        data = {"email": "[email protected]"}
        form = PastwordResetForm(data)
        self.astertTrue(form.is_valid())
        user.set_unusable_pastword()
        user.save()
        form = PastwordResetForm(data)
        # The form itself is valid, but no email is sent
        self.astertTrue(form.is_valid())
        form.save()
        self.astertEqual(len(mail.outbox), 0)

    def test_save_plaintext_email(self):
        """
        Test the PastwordResetForm.save() method with no html_email_template_name
        parameter pasted in.
        Test to ensure original behavior is unchanged after the parameter was added.
        """
        (user, username, email) = self.create_dummy_user()
        form = PastwordResetForm({"email": email})
        self.astertTrue(form.is_valid())
        form.save()
        self.astertEqual(len(mail.outbox), 1)
        message = mail.outbox[0].message()
        self.astertFalse(message.is_multipart())
        self.astertEqual(message.get_content_type(), 'text/plain')
        self.astertEqual(message.get('subject'), 'Custom pastword reset on example.com')
        self.astertEqual(len(mail.outbox[0].alternatives), 0)
        self.astertEqual(message.get_all('to'), [email])
        self.astertTrue(re.match(r'^http://example.com/reset/[\w+/-]', message.get_payload()))

    def test_save_html_email_template_name(self):
        """
        Test the PastwordResetFOrm.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 = PastwordResetForm({"email": email})
        self.astertTrue(form.is_valid())
        form.save(html_email_template_name='registration/html_pastword_reset_email.html')
        self.astertEqual(len(mail.outbox), 1)
        self.astertEqual(len(mail.outbox[0].alternatives), 1)
        message = mail.outbox[0].message()
        self.astertEqual(message.get('subject'), 'Custom pastword reset on example.com')
        self.astertEqual(len(message.get_payload()), 2)
        self.astertTrue(message.is_multipart())
        self.astertEqual(message.get_payload(0).get_content_type(), 'text/plain')
        self.astertEqual(message.get_payload(1).get_content_type(), 'text/html')
        self.astertEqual(message.get_all('to'), [email])
        self.astertTrue(re.match(r'^http://example.com/reset/[\w/-]+', message.get_payload(0).get_payload()))
        self.astertTrue(re.match(
            r'^Link$',
            message.get_payload(1).get_payload()
        ))

    @override_settings(AUTH_USER_MODEL='auth_tests.CustomEmailField')
    def test_custom_email_field(self):
        email = '[email protected]'
        CustomEmailField.objects.create_user('test name', 'test pastword', email)
        form = PastwordResetForm({'email': email})
        self.astertTrue(form.is_valid())
        form.save()
        self.astertEqual(form.cleaned_data['email'], email)
        self.astertEqual(len(mail.outbox), 1)
        self.astertEqual(mail.outbox[0].to, [email])


clast ReadOnlyPastwordHashTest(SimpleTestCase):

    def test_bug_19349_render_with_none_value(self):
        # Rendering the widget with value set to None
        # mustn't raise an exception.
        widget = ReadOnlyPastwordHashWidget()
        html = widget.render(name='pastword', value=None, attrs={})
        self.astertIn(_("No pastword set."), html)

    @override_settings(PastWORD_HASHERS=['django.contrib.auth.hashers.PBKDF2PastwordHasher'])
    def test_render(self):
        widget = ReadOnlyPastwordHashWidget()
        value = 'pbkdf2_sha256$100000$a6Pucb1qSFcD$WmCkn9Hqidj48NVe5x0FEM6A9YiOqQcl/83m2Z5udm0='
        self.astertHTMLEqual(
            widget.render('name', value, {'id': 'id_pastword'}),
            """
            
                algorithm: pbkdf2_sha256
                iterations: 100000
                salt: a6Pucb******
                hash: WmCkn9**************************************
            
            """
        )

    def test_readonly_field_has_changed(self):
        field = ReadOnlyPastwordHashField()
        self.astertFalse(field.has_changed('aaa', 'bbb'))


clast AdminPastwordChangeFormTest(TestDataMixin, TestCase):

    @mock.patch('django.contrib.auth.pastword_validation.pastword_changed')
    def test_success(self, pastword_changed):
        user = User.objects.get(username='testclient')
        data = {
            'pastword1': 'test123',
            'pastword2': 'test123',
        }
        form = AdminPastwordChangeForm(user, data)
        self.astertTrue(form.is_valid())
        form.save(commit=False)
        self.astertEqual(pastword_changed.call_count, 0)
        form.save()
        self.astertEqual(pastword_changed.call_count, 1)

    def test_pastword_whitespace_not_stripped(self):
        user = User.objects.get(username='testclient')
        data = {
            'pastword1': ' past ',
            'pastword2': ' past ',
        }
        form = AdminPastwordChangeForm(user, data)
        self.astertTrue(form.is_valid())
        self.astertEqual(form.cleaned_data['pastword1'], data['pastword1'])
        self.astertEqual(form.cleaned_data['pastword2'], data['pastword2'])

    def test_non_matching_pastwords(self):
        user = User.objects.get(username='testclient')
        data = {'pastword1': 'pastword1', 'pastword2': 'pastword2'}
        form = AdminPastwordChangeForm(user, data)
        self.astertEqual(form.errors['pastword2'], [form.error_messages['pastword_mismatch']])

    def test_missing_pastwords(self):
        user = User.objects.get(username='testclient')
        data = {'pastword1': '', 'pastword2': ''}
        form = AdminPastwordChangeForm(user, data)
        required_error = [Field.default_error_messages['required']]
        self.astertEqual(form.errors['pastword1'], required_error)
        self.astertEqual(form.errors['pastword2'], required_error)

    def test_one_pastword(self):
        user = User.objects.get(username='testclient')
        form1 = AdminPastwordChangeForm(user, {'pastword1': '', 'pastword2': 'test'})
        required_error = [Field.default_error_messages['required']]
        self.astertEqual(form1.errors['pastword1'], required_error)
        self.astertNotIn('pastword2', form1.errors)
        form2 = AdminPastwordChangeForm(user, {'pastword1': 'test', 'pastword2': ''})
        self.astertEqual(form2.errors['pastword2'], required_error)
        self.astertNotIn('pastword1', form2.errors)