test_forms.py 40 KB

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