test_forms.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  1. import datetime
  2. import re
  3. from unittest import mock
  4. from django import forms
  5. from django.contrib.auth.forms import (
  6. AdminPasswordChangeForm, AuthenticationForm, PasswordChangeForm,
  7. PasswordResetForm, ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget,
  8. SetPasswordForm, UserChangeForm, UserCreationForm,
  9. )
  10. from django.contrib.auth.models import User
  11. from django.contrib.auth.signals import user_login_failed
  12. from django.contrib.sites.models import Site
  13. from django.core import mail
  14. from django.core.mail import EmailMultiAlternatives
  15. from django.forms.fields import CharField, Field, IntegerField
  16. from django.test import SimpleTestCase, TestCase, override_settings
  17. from django.utils import translation
  18. from django.utils.text import capfirst
  19. from django.utils.translation import gettext as _
  20. from .models.custom_user import (
  21. CustomUser, CustomUserWithoutIsActiveField, ExtensionUser,
  22. )
  23. from .models.with_custom_email_field import CustomEmailField
  24. from .models.with_integer_username import IntegerUsernameUser
  25. from .settings import AUTH_TEMPLATES
  26. class TestDataMixin:
  27. @classmethod
  28. def setUpTestData(cls):
  29. cls.u1 = User.objects.create_user(username='testclient', password='password', email='testclient@example.com')
  30. cls.u2 = User.objects.create_user(username='inactive', password='password', is_active=False)
  31. cls.u3 = User.objects.create_user(username='staff', password='password')
  32. cls.u4 = User.objects.create(username='empty_password', password='')
  33. cls.u5 = User.objects.create(username='unmanageable_password', password='$')
  34. cls.u6 = User.objects.create(username='unknown_password', password='foo$bar')
  35. class UserCreationFormTest(TestDataMixin, TestCase):
  36. def test_user_already_exists(self):
  37. data = {
  38. 'username': 'testclient',
  39. 'password1': 'test123',
  40. 'password2': 'test123',
  41. }
  42. form = UserCreationForm(data)
  43. self.assertFalse(form.is_valid())
  44. self.assertEqual(form["username"].errors,
  45. [str(User._meta.get_field('username').error_messages['unique'])])
  46. def test_invalid_data(self):
  47. data = {
  48. 'username': 'jsmith!',
  49. 'password1': 'test123',
  50. 'password2': 'test123',
  51. }
  52. form = UserCreationForm(data)
  53. self.assertFalse(form.is_valid())
  54. validator = next(v for v in User._meta.get_field('username').validators if v.code == 'invalid')
  55. self.assertEqual(form["username"].errors, [str(validator.message)])
  56. def test_password_verification(self):
  57. # The verification password is incorrect.
  58. data = {
  59. 'username': 'jsmith',
  60. 'password1': 'test123',
  61. 'password2': 'test',
  62. }
  63. form = UserCreationForm(data)
  64. self.assertFalse(form.is_valid())
  65. self.assertEqual(form["password2"].errors,
  66. [str(form.error_messages['password_mismatch'])])
  67. def test_both_passwords(self):
  68. # One (or both) passwords weren't given
  69. data = {'username': 'jsmith'}
  70. form = UserCreationForm(data)
  71. required_error = [str(Field.default_error_messages['required'])]
  72. self.assertFalse(form.is_valid())
  73. self.assertEqual(form['password1'].errors, required_error)
  74. self.assertEqual(form['password2'].errors, required_error)
  75. data['password2'] = 'test123'
  76. form = UserCreationForm(data)
  77. self.assertFalse(form.is_valid())
  78. self.assertEqual(form['password1'].errors, required_error)
  79. self.assertEqual(form['password2'].errors, [])
  80. @mock.patch('django.contrib.auth.password_validation.password_changed')
  81. def test_success(self, password_changed):
  82. # The success case.
  83. data = {
  84. 'username': 'jsmith@example.com',
  85. 'password1': 'test123',
  86. 'password2': 'test123',
  87. }
  88. form = UserCreationForm(data)
  89. self.assertTrue(form.is_valid())
  90. form.save(commit=False)
  91. self.assertEqual(password_changed.call_count, 0)
  92. u = form.save()
  93. self.assertEqual(password_changed.call_count, 1)
  94. self.assertEqual(repr(u), '<User: jsmith@example.com>')
  95. def test_unicode_username(self):
  96. data = {
  97. 'username': '宝',
  98. 'password1': 'test123',
  99. 'password2': 'test123',
  100. }
  101. form = UserCreationForm(data)
  102. self.assertTrue(form.is_valid())
  103. u = form.save()
  104. self.assertEqual(u.username, '宝')
  105. def test_normalize_username(self):
  106. # The normalization happens in AbstractBaseUser.clean() and ModelForm
  107. # validation calls Model.clean().
  108. ohm_username = 'testΩ' # U+2126 OHM SIGN
  109. data = {
  110. 'username': ohm_username,
  111. 'password1': 'pwd2',
  112. 'password2': 'pwd2',
  113. }
  114. form = UserCreationForm(data)
  115. self.assertTrue(form.is_valid())
  116. user = form.save()
  117. self.assertNotEqual(user.username, ohm_username)
  118. self.assertEqual(user.username, 'testΩ') # U+03A9 GREEK CAPITAL LETTER OMEGA
  119. def test_duplicate_normalized_unicode(self):
  120. """
  121. To prevent almost identical usernames, visually identical but differing
  122. by their unicode code points only, Unicode NFKC normalization should
  123. make appear them equal to Django.
  124. """
  125. omega_username = 'iamtheΩ' # U+03A9 GREEK CAPITAL LETTER OMEGA
  126. ohm_username = 'iamtheΩ' # U+2126 OHM SIGN
  127. self.assertNotEqual(omega_username, ohm_username)
  128. User.objects.create_user(username=omega_username, password='pwd')
  129. data = {
  130. 'username': ohm_username,
  131. 'password1': 'pwd2',
  132. 'password2': 'pwd2',
  133. }
  134. form = UserCreationForm(data)
  135. self.assertFalse(form.is_valid())
  136. self.assertEqual(
  137. form.errors['username'], ["A user with that username already exists."]
  138. )
  139. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  140. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  141. {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {
  142. 'min_length': 12,
  143. }},
  144. ])
  145. def test_validates_password(self):
  146. data = {
  147. 'username': 'testclient',
  148. 'password1': 'testclient',
  149. 'password2': 'testclient',
  150. }
  151. form = UserCreationForm(data)
  152. self.assertFalse(form.is_valid())
  153. self.assertEqual(len(form['password2'].errors), 2)
  154. self.assertIn('The password is too similar to the username.', form['password2'].errors)
  155. self.assertIn(
  156. 'This password is too short. It must contain at least 12 characters.',
  157. form['password2'].errors
  158. )
  159. def test_custom_form(self):
  160. class CustomUserCreationForm(UserCreationForm):
  161. class Meta(UserCreationForm.Meta):
  162. model = ExtensionUser
  163. fields = UserCreationForm.Meta.fields + ('date_of_birth',)
  164. data = {
  165. 'username': 'testclient',
  166. 'password1': 'testclient',
  167. 'password2': 'testclient',
  168. 'date_of_birth': '1988-02-24',
  169. }
  170. form = CustomUserCreationForm(data)
  171. self.assertTrue(form.is_valid())
  172. def test_custom_form_with_different_username_field(self):
  173. class CustomUserCreationForm(UserCreationForm):
  174. class Meta(UserCreationForm.Meta):
  175. model = CustomUser
  176. fields = ('email', 'date_of_birth')
  177. data = {
  178. 'email': 'test@client222.com',
  179. 'password1': 'testclient',
  180. 'password2': 'testclient',
  181. 'date_of_birth': '1988-02-24',
  182. }
  183. form = CustomUserCreationForm(data)
  184. self.assertTrue(form.is_valid())
  185. def test_custom_form_hidden_username_field(self):
  186. class CustomUserCreationForm(UserCreationForm):
  187. class Meta(UserCreationForm.Meta):
  188. model = CustomUserWithoutIsActiveField
  189. fields = ('email',) # without USERNAME_FIELD
  190. data = {
  191. 'email': 'testclient@example.com',
  192. 'password1': 'testclient',
  193. 'password2': 'testclient',
  194. }
  195. form = CustomUserCreationForm(data)
  196. self.assertTrue(form.is_valid())
  197. def test_password_whitespace_not_stripped(self):
  198. data = {
  199. 'username': 'testuser',
  200. 'password1': ' testpassword ',
  201. 'password2': ' testpassword ',
  202. }
  203. form = UserCreationForm(data)
  204. self.assertTrue(form.is_valid())
  205. self.assertEqual(form.cleaned_data['password1'], data['password1'])
  206. self.assertEqual(form.cleaned_data['password2'], data['password2'])
  207. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  208. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  209. ])
  210. def test_password_help_text(self):
  211. form = UserCreationForm()
  212. self.assertEqual(
  213. form.fields['password1'].help_text,
  214. '<ul><li>Your password can&#39;t be too similar to your other personal information.</li></ul>'
  215. )
  216. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  217. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  218. ])
  219. def test_user_create_form_validates_password_with_all_data(self):
  220. """UserCreationForm password validation uses all of the form's data."""
  221. class CustomUserCreationForm(UserCreationForm):
  222. class Meta(UserCreationForm.Meta):
  223. model = User
  224. fields = ('username', 'email', 'first_name', 'last_name')
  225. form = CustomUserCreationForm({
  226. 'username': 'testuser',
  227. 'password1': 'testpassword',
  228. 'password2': 'testpassword',
  229. 'first_name': 'testpassword',
  230. 'last_name': 'lastname',
  231. })
  232. self.assertFalse(form.is_valid())
  233. self.assertEqual(
  234. form.errors['password2'],
  235. ['The password is too similar to the first name.'],
  236. )
  237. # To verify that the login form rejects inactive users, use an authentication
  238. # backend that allows them.
  239. @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend'])
  240. class AuthenticationFormTest(TestDataMixin, TestCase):
  241. def test_invalid_username(self):
  242. # The user submits an invalid username.
  243. data = {
  244. 'username': 'jsmith_does_not_exist',
  245. 'password': 'test123',
  246. }
  247. form = AuthenticationForm(None, data)
  248. self.assertFalse(form.is_valid())
  249. self.assertEqual(
  250. form.non_field_errors(), [
  251. form.error_messages['invalid_login'] % {
  252. 'username': User._meta.get_field('username').verbose_name
  253. }
  254. ]
  255. )
  256. def test_inactive_user(self):
  257. # The user is inactive.
  258. data = {
  259. 'username': 'inactive',
  260. 'password': 'password',
  261. }
  262. form = AuthenticationForm(None, data)
  263. self.assertFalse(form.is_valid())
  264. self.assertEqual(form.non_field_errors(), [str(form.error_messages['inactive'])])
  265. # Use an authentication backend that rejects inactive users.
  266. @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.ModelBackend'])
  267. def test_inactive_user_incorrect_password(self):
  268. """An invalid login doesn't leak the inactive status of a user."""
  269. data = {
  270. 'username': 'inactive',
  271. 'password': 'incorrect',
  272. }
  273. form = AuthenticationForm(None, data)
  274. self.assertFalse(form.is_valid())
  275. self.assertEqual(
  276. form.non_field_errors(), [
  277. form.error_messages['invalid_login'] % {
  278. 'username': User._meta.get_field('username').verbose_name
  279. }
  280. ]
  281. )
  282. def test_login_failed(self):
  283. signal_calls = []
  284. def signal_handler(**kwargs):
  285. signal_calls.append(kwargs)
  286. user_login_failed.connect(signal_handler)
  287. fake_request = object()
  288. try:
  289. form = AuthenticationForm(fake_request, {
  290. 'username': 'testclient',
  291. 'password': 'incorrect',
  292. })
  293. self.assertFalse(form.is_valid())
  294. self.assertIs(signal_calls[0]['request'], fake_request)
  295. finally:
  296. user_login_failed.disconnect(signal_handler)
  297. def test_inactive_user_i18n(self):
  298. with self.settings(USE_I18N=True), translation.override('pt-br', deactivate=True):
  299. # The user is inactive.
  300. data = {
  301. 'username': 'inactive',
  302. 'password': 'password',
  303. }
  304. form = AuthenticationForm(None, data)
  305. self.assertFalse(form.is_valid())
  306. self.assertEqual(form.non_field_errors(), [str(form.error_messages['inactive'])])
  307. # Use an authentication backend that allows inactive users.
  308. @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend'])
  309. def test_custom_login_allowed_policy(self):
  310. # The user is inactive, but our custom form policy allows them to log in.
  311. data = {
  312. 'username': 'inactive',
  313. 'password': 'password',
  314. }
  315. class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
  316. def confirm_login_allowed(self, user):
  317. pass
  318. form = AuthenticationFormWithInactiveUsersOkay(None, data)
  319. self.assertTrue(form.is_valid())
  320. # If we want to disallow some logins according to custom logic,
  321. # we should raise a django.forms.ValidationError in the form.
  322. class PickyAuthenticationForm(AuthenticationForm):
  323. def confirm_login_allowed(self, user):
  324. if user.username == "inactive":
  325. raise forms.ValidationError("This user is disallowed.")
  326. raise forms.ValidationError("Sorry, nobody's allowed in.")
  327. form = PickyAuthenticationForm(None, data)
  328. self.assertFalse(form.is_valid())
  329. self.assertEqual(form.non_field_errors(), ['This user is disallowed.'])
  330. data = {
  331. 'username': 'testclient',
  332. 'password': 'password',
  333. }
  334. form = PickyAuthenticationForm(None, data)
  335. self.assertFalse(form.is_valid())
  336. self.assertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])
  337. def test_success(self):
  338. # The success case
  339. data = {
  340. 'username': 'testclient',
  341. 'password': 'password',
  342. }
  343. form = AuthenticationForm(None, data)
  344. self.assertTrue(form.is_valid())
  345. self.assertEqual(form.non_field_errors(), [])
  346. def test_unicode_username(self):
  347. User.objects.create_user(username='Σαρα', password='pwd')
  348. data = {
  349. 'username': 'Σαρα',
  350. 'password': 'pwd',
  351. }
  352. form = AuthenticationForm(None, data)
  353. self.assertTrue(form.is_valid())
  354. self.assertEqual(form.non_field_errors(), [])
  355. @override_settings(AUTH_USER_MODEL='auth_tests.CustomEmailField')
  356. def test_username_field_max_length_matches_user_model(self):
  357. self.assertEqual(CustomEmailField._meta.get_field('username').max_length, 255)
  358. data = {
  359. 'username': 'u' * 255,
  360. 'password': 'pwd',
  361. 'email': 'test@example.com',
  362. }
  363. CustomEmailField.objects.create_user(**data)
  364. form = AuthenticationForm(None, data)
  365. self.assertEqual(form.fields['username'].max_length, 255)
  366. self.assertEqual(form.errors, {})
  367. @override_settings(AUTH_USER_MODEL='auth_tests.IntegerUsernameUser')
  368. def test_username_field_max_length_defaults_to_254(self):
  369. self.assertIsNone(IntegerUsernameUser._meta.get_field('username').max_length)
  370. data = {
  371. 'username': '0123456',
  372. 'password': 'password',
  373. }
  374. IntegerUsernameUser.objects.create_user(**data)
  375. form = AuthenticationForm(None, data)
  376. self.assertEqual(form.fields['username'].max_length, 254)
  377. self.assertEqual(form.errors, {})
  378. def test_username_field_label(self):
  379. class CustomAuthenticationForm(AuthenticationForm):
  380. username = CharField(label="Name", max_length=75)
  381. form = CustomAuthenticationForm()
  382. self.assertEqual(form['username'].label, "Name")
  383. def test_username_field_label_not_set(self):
  384. class CustomAuthenticationForm(AuthenticationForm):
  385. username = CharField()
  386. form = CustomAuthenticationForm()
  387. username_field = User._meta.get_field(User.USERNAME_FIELD)
  388. self.assertEqual(form.fields['username'].label, capfirst(username_field.verbose_name))
  389. def test_username_field_label_empty_string(self):
  390. class CustomAuthenticationForm(AuthenticationForm):
  391. username = CharField(label='')
  392. form = CustomAuthenticationForm()
  393. self.assertEqual(form.fields['username'].label, "")
  394. def test_password_whitespace_not_stripped(self):
  395. data = {
  396. 'username': 'testuser',
  397. 'password': ' pass ',
  398. }
  399. form = AuthenticationForm(None, data)
  400. form.is_valid() # Not necessary to have valid credentails for the test.
  401. self.assertEqual(form.cleaned_data['password'], data['password'])
  402. @override_settings(AUTH_USER_MODEL='auth_tests.IntegerUsernameUser')
  403. def test_integer_username(self):
  404. class CustomAuthenticationForm(AuthenticationForm):
  405. username = IntegerField()
  406. user = IntegerUsernameUser.objects.create_user(username=0, password='pwd')
  407. data = {
  408. 'username': 0,
  409. 'password': 'pwd',
  410. }
  411. form = CustomAuthenticationForm(None, data)
  412. self.assertTrue(form.is_valid())
  413. self.assertEqual(form.cleaned_data['username'], data['username'])
  414. self.assertEqual(form.cleaned_data['password'], data['password'])
  415. self.assertEqual(form.errors, {})
  416. self.assertEqual(form.user_cache, user)
  417. def test_get_invalid_login_error(self):
  418. error = AuthenticationForm().get_invalid_login_error()
  419. self.assertIsInstance(error, forms.ValidationError)
  420. self.assertEqual(
  421. error.message,
  422. 'Please enter a correct %(username)s and password. Note that both '
  423. 'fields may be case-sensitive.',
  424. )
  425. self.assertEqual(error.code, 'invalid_login')
  426. self.assertEqual(error.params, {'username': 'username'})
  427. class SetPasswordFormTest(TestDataMixin, TestCase):
  428. def test_password_verification(self):
  429. # The two new passwords do not match.
  430. user = User.objects.get(username='testclient')
  431. data = {
  432. 'new_password1': 'abc123',
  433. 'new_password2': 'abc',
  434. }
  435. form = SetPasswordForm(user, data)
  436. self.assertFalse(form.is_valid())
  437. self.assertEqual(
  438. form["new_password2"].errors,
  439. [str(form.error_messages['password_mismatch'])]
  440. )
  441. @mock.patch('django.contrib.auth.password_validation.password_changed')
  442. def test_success(self, password_changed):
  443. user = User.objects.get(username='testclient')
  444. data = {
  445. 'new_password1': 'abc123',
  446. 'new_password2': 'abc123',
  447. }
  448. form = SetPasswordForm(user, data)
  449. self.assertTrue(form.is_valid())
  450. form.save(commit=False)
  451. self.assertEqual(password_changed.call_count, 0)
  452. form.save()
  453. self.assertEqual(password_changed.call_count, 1)
  454. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  455. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  456. {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {
  457. 'min_length': 12,
  458. }},
  459. ])
  460. def test_validates_password(self):
  461. user = User.objects.get(username='testclient')
  462. data = {
  463. 'new_password1': 'testclient',
  464. 'new_password2': 'testclient',
  465. }
  466. form = SetPasswordForm(user, data)
  467. self.assertFalse(form.is_valid())
  468. self.assertEqual(len(form["new_password2"].errors), 2)
  469. self.assertIn('The password is too similar to the username.', form["new_password2"].errors)
  470. self.assertIn(
  471. 'This password is too short. It must contain at least 12 characters.',
  472. form["new_password2"].errors
  473. )
  474. def test_password_whitespace_not_stripped(self):
  475. user = User.objects.get(username='testclient')
  476. data = {
  477. 'new_password1': ' password ',
  478. 'new_password2': ' password ',
  479. }
  480. form = SetPasswordForm(user, data)
  481. self.assertTrue(form.is_valid())
  482. self.assertEqual(form.cleaned_data['new_password1'], data['new_password1'])
  483. self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
  484. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  485. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  486. {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {
  487. 'min_length': 12,
  488. }},
  489. ])
  490. def test_help_text_translation(self):
  491. french_help_texts = [
  492. 'Votre mot de passe ne peut pas trop ressembler à vos autres informations personnelles.',
  493. 'Votre mot de passe doit contenir au minimum 12 caractères.',
  494. ]
  495. form = SetPasswordForm(self.u1)
  496. with translation.override('fr'):
  497. html = form.as_p()
  498. for french_text in french_help_texts:
  499. self.assertIn(french_text, html)
  500. class PasswordChangeFormTest(TestDataMixin, TestCase):
  501. def test_incorrect_password(self):
  502. user = User.objects.get(username='testclient')
  503. data = {
  504. 'old_password': 'test',
  505. 'new_password1': 'abc123',
  506. 'new_password2': 'abc123',
  507. }
  508. form = PasswordChangeForm(user, data)
  509. self.assertFalse(form.is_valid())
  510. self.assertEqual(form["old_password"].errors, [str(form.error_messages['password_incorrect'])])
  511. def test_password_verification(self):
  512. # The two new passwords do not match.
  513. user = User.objects.get(username='testclient')
  514. data = {
  515. 'old_password': 'password',
  516. 'new_password1': 'abc123',
  517. 'new_password2': 'abc',
  518. }
  519. form = PasswordChangeForm(user, data)
  520. self.assertFalse(form.is_valid())
  521. self.assertEqual(form["new_password2"].errors, [str(form.error_messages['password_mismatch'])])
  522. @mock.patch('django.contrib.auth.password_validation.password_changed')
  523. def test_success(self, password_changed):
  524. # The success case.
  525. user = User.objects.get(username='testclient')
  526. data = {
  527. 'old_password': 'password',
  528. 'new_password1': 'abc123',
  529. 'new_password2': 'abc123',
  530. }
  531. form = PasswordChangeForm(user, data)
  532. self.assertTrue(form.is_valid())
  533. form.save(commit=False)
  534. self.assertEqual(password_changed.call_count, 0)
  535. form.save()
  536. self.assertEqual(password_changed.call_count, 1)
  537. def test_field_order(self):
  538. # Regression test - check the order of fields:
  539. user = User.objects.get(username='testclient')
  540. self.assertEqual(list(PasswordChangeForm(user, {}).fields), ['old_password', 'new_password1', 'new_password2'])
  541. def test_password_whitespace_not_stripped(self):
  542. user = User.objects.get(username='testclient')
  543. user.set_password(' oldpassword ')
  544. data = {
  545. 'old_password': ' oldpassword ',
  546. 'new_password1': ' pass ',
  547. 'new_password2': ' pass ',
  548. }
  549. form = PasswordChangeForm(user, data)
  550. self.assertTrue(form.is_valid())
  551. self.assertEqual(form.cleaned_data['old_password'], data['old_password'])
  552. self.assertEqual(form.cleaned_data['new_password1'], data['new_password1'])
  553. self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
  554. class UserChangeFormTest(TestDataMixin, TestCase):
  555. def test_username_validity(self):
  556. user = User.objects.get(username='testclient')
  557. data = {'username': 'not valid'}
  558. form = UserChangeForm(data, instance=user)
  559. self.assertFalse(form.is_valid())
  560. validator = next(v for v in User._meta.get_field('username').validators if v.code == 'invalid')
  561. self.assertEqual(form["username"].errors, [str(validator.message)])
  562. def test_bug_14242(self):
  563. # A regression test, introduce by adding an optimization for the
  564. # UserChangeForm.
  565. class MyUserForm(UserChangeForm):
  566. def __init__(self, *args, **kwargs):
  567. super().__init__(*args, **kwargs)
  568. self.fields['groups'].help_text = 'These groups give users different permissions'
  569. class Meta(UserChangeForm.Meta):
  570. fields = ('groups',)
  571. # Just check we can create it
  572. MyUserForm({})
  573. def test_unusable_password(self):
  574. user = User.objects.get(username='empty_password')
  575. user.set_unusable_password()
  576. user.save()
  577. form = UserChangeForm(instance=user)
  578. self.assertIn(_("No password set."), form.as_table())
  579. def test_bug_17944_empty_password(self):
  580. user = User.objects.get(username='empty_password')
  581. form = UserChangeForm(instance=user)
  582. self.assertIn(_("No password set."), form.as_table())
  583. def test_bug_17944_unmanageable_password(self):
  584. user = User.objects.get(username='unmanageable_password')
  585. form = UserChangeForm(instance=user)
  586. self.assertIn(_("Invalid password format or unknown hashing algorithm."), form.as_table())
  587. def test_bug_17944_unknown_password_algorithm(self):
  588. user = User.objects.get(username='unknown_password')
  589. form = UserChangeForm(instance=user)
  590. self.assertIn(_("Invalid password format or unknown hashing algorithm."), form.as_table())
  591. def test_bug_19133(self):
  592. "The change form does not return the password value"
  593. # Use the form to construct the POST data
  594. user = User.objects.get(username='testclient')
  595. form_for_data = UserChangeForm(instance=user)
  596. post_data = form_for_data.initial
  597. # The password field should be readonly, so anything
  598. # posted here should be ignored; the form will be
  599. # valid, and give back the 'initial' value for the
  600. # password field.
  601. post_data['password'] = 'new password'
  602. form = UserChangeForm(instance=user, data=post_data)
  603. self.assertTrue(form.is_valid())
  604. # original hashed password contains $
  605. self.assertIn('$', form.cleaned_data['password'])
  606. def test_bug_19349_bound_password_field(self):
  607. user = User.objects.get(username='testclient')
  608. form = UserChangeForm(data={}, instance=user)
  609. # When rendering the bound password field,
  610. # ReadOnlyPasswordHashWidget needs the initial
  611. # value to render correctly
  612. self.assertEqual(form.initial['password'], form['password'].value())
  613. def test_custom_form(self):
  614. class CustomUserChangeForm(UserChangeForm):
  615. class Meta(UserChangeForm.Meta):
  616. model = ExtensionUser
  617. fields = ('username', 'password', 'date_of_birth',)
  618. user = User.objects.get(username='testclient')
  619. data = {
  620. 'username': 'testclient',
  621. 'password': 'testclient',
  622. 'date_of_birth': '1998-02-24',
  623. }
  624. form = CustomUserChangeForm(data, instance=user)
  625. self.assertTrue(form.is_valid())
  626. form.save()
  627. self.assertEqual(form.cleaned_data['username'], 'testclient')
  628. self.assertEqual(form.cleaned_data['date_of_birth'], datetime.date(1998, 2, 24))
  629. def test_password_excluded(self):
  630. class UserChangeFormWithoutPassword(UserChangeForm):
  631. password = None
  632. class Meta:
  633. model = User
  634. exclude = ['password']
  635. form = UserChangeFormWithoutPassword()
  636. self.assertNotIn('password', form.fields)
  637. @override_settings(TEMPLATES=AUTH_TEMPLATES)
  638. class PasswordResetFormTest(TestDataMixin, TestCase):
  639. @classmethod
  640. def setUpClass(cls):
  641. super().setUpClass()
  642. # This cleanup is necessary because contrib.sites cache
  643. # makes tests interfere with each other, see #11505
  644. Site.objects.clear_cache()
  645. def create_dummy_user(self):
  646. """
  647. Create a user and return a tuple (user_object, username, email).
  648. """
  649. username = 'jsmith'
  650. email = 'jsmith@example.com'
  651. user = User.objects.create_user(username, email, 'test123')
  652. return (user, username, email)
  653. def test_invalid_email(self):
  654. data = {'email': 'not valid'}
  655. form = PasswordResetForm(data)
  656. self.assertFalse(form.is_valid())
  657. self.assertEqual(form['email'].errors, [_('Enter a valid email address.')])
  658. def test_nonexistent_email(self):
  659. """
  660. Test nonexistent email address. This should not fail because it would
  661. expose information about registered users.
  662. """
  663. data = {'email': 'foo@bar.com'}
  664. form = PasswordResetForm(data)
  665. self.assertTrue(form.is_valid())
  666. self.assertEqual(len(mail.outbox), 0)
  667. def test_cleaned_data(self):
  668. (user, username, email) = self.create_dummy_user()
  669. data = {'email': email}
  670. form = PasswordResetForm(data)
  671. self.assertTrue(form.is_valid())
  672. form.save(domain_override='example.com')
  673. self.assertEqual(form.cleaned_data['email'], email)
  674. self.assertEqual(len(mail.outbox), 1)
  675. def test_custom_email_subject(self):
  676. data = {'email': 'testclient@example.com'}
  677. form = PasswordResetForm(data)
  678. self.assertTrue(form.is_valid())
  679. # Since we're not providing a request object, we must provide a
  680. # domain_override to prevent the save operation from failing in the
  681. # potential case where contrib.sites is not installed. Refs #16412.
  682. form.save(domain_override='example.com')
  683. self.assertEqual(len(mail.outbox), 1)
  684. self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com')
  685. def test_custom_email_constructor(self):
  686. data = {'email': 'testclient@example.com'}
  687. class CustomEmailPasswordResetForm(PasswordResetForm):
  688. def send_mail(self, subject_template_name, email_template_name,
  689. context, from_email, to_email,
  690. html_email_template_name=None):
  691. EmailMultiAlternatives(
  692. "Forgot your password?",
  693. "Sorry to hear you forgot your password.",
  694. None, [to_email],
  695. ['site_monitor@example.com'],
  696. headers={'Reply-To': 'webmaster@example.com'},
  697. alternatives=[
  698. ("Really sorry to hear you forgot your password.", "text/html")
  699. ],
  700. ).send()
  701. form = CustomEmailPasswordResetForm(data)
  702. self.assertTrue(form.is_valid())
  703. # Since we're not providing a request object, we must provide a
  704. # domain_override to prevent the save operation from failing in the
  705. # potential case where contrib.sites is not installed. Refs #16412.
  706. form.save(domain_override='example.com')
  707. self.assertEqual(len(mail.outbox), 1)
  708. self.assertEqual(mail.outbox[0].subject, 'Forgot your password?')
  709. self.assertEqual(mail.outbox[0].bcc, ['site_monitor@example.com'])
  710. self.assertEqual(mail.outbox[0].content_subtype, "plain")
  711. def test_preserve_username_case(self):
  712. """
  713. Preserve the case of the user name (before the @ in the email address)
  714. when creating a user (#5605).
  715. """
  716. user = User.objects.create_user('forms_test2', 'tesT@EXAMple.com', 'test')
  717. self.assertEqual(user.email, 'tesT@example.com')
  718. user = User.objects.create_user('forms_test3', 'tesT', 'test')
  719. self.assertEqual(user.email, 'tesT')
  720. def test_inactive_user(self):
  721. """
  722. Inactive user cannot receive password reset email.
  723. """
  724. (user, username, email) = self.create_dummy_user()
  725. user.is_active = False
  726. user.save()
  727. form = PasswordResetForm({'email': email})
  728. self.assertTrue(form.is_valid())
  729. form.save()
  730. self.assertEqual(len(mail.outbox), 0)
  731. def test_unusable_password(self):
  732. user = User.objects.create_user('testuser', 'test@example.com', 'test')
  733. data = {"email": "test@example.com"}
  734. form = PasswordResetForm(data)
  735. self.assertTrue(form.is_valid())
  736. user.set_unusable_password()
  737. user.save()
  738. form = PasswordResetForm(data)
  739. # The form itself is valid, but no email is sent
  740. self.assertTrue(form.is_valid())
  741. form.save()
  742. self.assertEqual(len(mail.outbox), 0)
  743. def test_save_plaintext_email(self):
  744. """
  745. Test the PasswordResetForm.save() method with no html_email_template_name
  746. parameter passed in.
  747. Test to ensure original behavior is unchanged after the parameter was added.
  748. """
  749. (user, username, email) = self.create_dummy_user()
  750. form = PasswordResetForm({"email": email})
  751. self.assertTrue(form.is_valid())
  752. form.save()
  753. self.assertEqual(len(mail.outbox), 1)
  754. message = mail.outbox[0].message()
  755. self.assertFalse(message.is_multipart())
  756. self.assertEqual(message.get_content_type(), 'text/plain')
  757. self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
  758. self.assertEqual(len(mail.outbox[0].alternatives), 0)
  759. self.assertEqual(message.get_all('to'), [email])
  760. self.assertTrue(re.match(r'^http://example.com/reset/[\w+/-]', message.get_payload()))
  761. def test_save_html_email_template_name(self):
  762. """
  763. Test the PasswordResetFOrm.save() method with html_email_template_name
  764. parameter specified.
  765. Test to ensure that a multipart email is sent with both text/plain
  766. and text/html parts.
  767. """
  768. (user, username, email) = self.create_dummy_user()
  769. form = PasswordResetForm({"email": email})
  770. self.assertTrue(form.is_valid())
  771. form.save(html_email_template_name='registration/html_password_reset_email.html')
  772. self.assertEqual(len(mail.outbox), 1)
  773. self.assertEqual(len(mail.outbox[0].alternatives), 1)
  774. message = mail.outbox[0].message()
  775. self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
  776. self.assertEqual(len(message.get_payload()), 2)
  777. self.assertTrue(message.is_multipart())
  778. self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain')
  779. self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
  780. self.assertEqual(message.get_all('to'), [email])
  781. self.assertTrue(re.match(r'^http://example.com/reset/[\w/-]+', message.get_payload(0).get_payload()))
  782. self.assertTrue(re.match(
  783. r'^<html><a href="http://example.com/reset/[\w/-]+/">Link</a></html>$',
  784. message.get_payload(1).get_payload()
  785. ))
  786. @override_settings(AUTH_USER_MODEL='auth_tests.CustomEmailField')
  787. def test_custom_email_field(self):
  788. email = 'test@mail.com'
  789. CustomEmailField.objects.create_user('test name', 'test password', email)
  790. form = PasswordResetForm({'email': email})
  791. self.assertTrue(form.is_valid())
  792. form.save()
  793. self.assertEqual(form.cleaned_data['email'], email)
  794. self.assertEqual(len(mail.outbox), 1)
  795. self.assertEqual(mail.outbox[0].to, [email])
  796. class ReadOnlyPasswordHashTest(SimpleTestCase):
  797. def test_bug_19349_render_with_none_value(self):
  798. # Rendering the widget with value set to None
  799. # mustn't raise an exception.
  800. widget = ReadOnlyPasswordHashWidget()
  801. html = widget.render(name='password', value=None, attrs={})
  802. self.assertIn(_("No password set."), html)
  803. @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.PBKDF2PasswordHasher'])
  804. def test_render(self):
  805. widget = ReadOnlyPasswordHashWidget()
  806. value = 'pbkdf2_sha256$100000$a6Pucb1qSFcD$WmCkn9Hqidj48NVe5x0FEM6A9YiOqQcl/83m2Z5udm0='
  807. self.assertHTMLEqual(
  808. widget.render('name', value, {'id': 'id_password'}),
  809. """
  810. <div id="id_password">
  811. <strong>algorithm</strong>: pbkdf2_sha256
  812. <strong>iterations</strong>: 100000
  813. <strong>salt</strong>: a6Pucb******
  814. <strong>hash</strong>: WmCkn9**************************************
  815. </div>
  816. """
  817. )
  818. def test_readonly_field_has_changed(self):
  819. field = ReadOnlyPasswordHashField()
  820. self.assertFalse(field.has_changed('aaa', 'bbb'))
  821. class AdminPasswordChangeFormTest(TestDataMixin, TestCase):
  822. @mock.patch('django.contrib.auth.password_validation.password_changed')
  823. def test_success(self, password_changed):
  824. user = User.objects.get(username='testclient')
  825. data = {
  826. 'password1': 'test123',
  827. 'password2': 'test123',
  828. }
  829. form = AdminPasswordChangeForm(user, data)
  830. self.assertTrue(form.is_valid())
  831. form.save(commit=False)
  832. self.assertEqual(password_changed.call_count, 0)
  833. form.save()
  834. self.assertEqual(password_changed.call_count, 1)
  835. def test_password_whitespace_not_stripped(self):
  836. user = User.objects.get(username='testclient')
  837. data = {
  838. 'password1': ' pass ',
  839. 'password2': ' pass ',
  840. }
  841. form = AdminPasswordChangeForm(user, data)
  842. self.assertTrue(form.is_valid())
  843. self.assertEqual(form.cleaned_data['password1'], data['password1'])
  844. self.assertEqual(form.cleaned_data['password2'], data['password2'])
  845. def test_non_matching_passwords(self):
  846. user = User.objects.get(username='testclient')
  847. data = {'password1': 'password1', 'password2': 'password2'}
  848. form = AdminPasswordChangeForm(user, data)
  849. self.assertEqual(form.errors['password2'], [form.error_messages['password_mismatch']])
  850. def test_missing_passwords(self):
  851. user = User.objects.get(username='testclient')
  852. data = {'password1': '', 'password2': ''}
  853. form = AdminPasswordChangeForm(user, data)
  854. required_error = [Field.default_error_messages['required']]
  855. self.assertEqual(form.errors['password1'], required_error)
  856. self.assertEqual(form.errors['password2'], required_error)
  857. def test_one_password(self):
  858. user = User.objects.get(username='testclient')
  859. form1 = AdminPasswordChangeForm(user, {'password1': '', 'password2': 'test'})
  860. required_error = [Field.default_error_messages['required']]
  861. self.assertEqual(form1.errors['password1'], required_error)
  862. self.assertNotIn('password2', form1.errors)
  863. form2 = AdminPasswordChangeForm(user, {'password1': 'test', 'password2': ''})
  864. self.assertEqual(form2.errors['password2'], required_error)
  865. self.assertNotIn('password1', form2.errors)