test_user_management.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. """
  2. This module tests deSEC's user management.
  3. The tests are separated into two categories, where
  4. (a) the client has an associated user account and
  5. (b) does not have an associated user account.
  6. This involves testing five separate endpoints:
  7. (1) Registration endpoint,
  8. (2) Reset password endpoint,
  9. (3) Change email address endpoint,
  10. (4) delete user endpoint, and
  11. (5) verify endpoint.
  12. """
  13. import random
  14. import re
  15. from django.core import mail
  16. from rest_framework import status
  17. from rest_framework.reverse import reverse
  18. from rest_framework.test import APIClient
  19. from api import settings
  20. from desecapi.models import Domain, User, Captcha
  21. from desecapi.tests.base import DesecTestCase, PublicSuffixMockMixin
  22. class UserManagementClient(APIClient):
  23. def register(self, email, password, captcha_id, captcha_solution, **kwargs):
  24. return self.post(reverse('v1:register'), {
  25. 'email': email,
  26. 'password': password,
  27. 'captcha': {'id': captcha_id, 'solution': captcha_solution},
  28. **kwargs
  29. })
  30. def login_user(self, email, password):
  31. return self.post(reverse('v1:login'), {
  32. 'email': email,
  33. 'password': password,
  34. })
  35. def reset_password(self, email):
  36. return self.post(reverse('v1:account-reset-password'), {
  37. 'email': email,
  38. })
  39. def change_email(self, email, password, **payload):
  40. payload['email'] = email
  41. payload['password'] = password
  42. return self.post(reverse('v1:account-change-email'), payload)
  43. def change_email_token_auth(self, token, **payload):
  44. return self.post(reverse('v1:account-change-email'), payload, HTTP_AUTHORIZATION='Token {}'.format(token))
  45. def delete_account(self, email, password):
  46. return self.post(reverse('v1:account-delete'), {
  47. 'email': email,
  48. 'password': password,
  49. })
  50. def view_account(self, token):
  51. return self.get(reverse('v1:account'), HTTP_AUTHORIZATION='Token {}'.format(token))
  52. def verify(self, url, **kwargs):
  53. return self.post(url, kwargs) if kwargs else self.get(url)
  54. def obtain_captcha(self, **kwargs):
  55. return self.post(reverse('v1:captcha'))
  56. class UserManagementTestCase(DesecTestCase, PublicSuffixMockMixin):
  57. client_class = UserManagementClient
  58. password = None
  59. token = None
  60. def get_captcha(self):
  61. data = self.client.obtain_captcha().data
  62. id = data['id']
  63. solution = Captcha.objects.get(id=id).content
  64. return id, solution
  65. def register_user(self, email=None, password=None, **kwargs):
  66. email = email if email is not None else self.random_username()
  67. password = password if password is not None else self.random_password()
  68. captcha_id, captcha_solution = self.get_captcha()
  69. return email.strip(), password, self.client.register(email, password, captcha_id, captcha_solution, **kwargs)
  70. def login_user(self, email, password):
  71. response = self.client.login_user(email, password)
  72. token = response.data.get('auth_token')
  73. return token, response
  74. def reset_password(self, email):
  75. return self.client.reset_password(email)
  76. def change_email(self, new_email):
  77. return self.client.change_email(self.email, self.password, new_email=new_email)
  78. def delete_account(self, email, password):
  79. return self.client.delete_account(email, password)
  80. def assertContains(self, response, text, count=None, status_code=200, msg_prefix='', html=False):
  81. msg_prefix += '\nResponse: %s' % response.data
  82. super().assertContains(response, text, count, status_code, msg_prefix, html)
  83. def assertPassword(self, email, password):
  84. password = password.strip()
  85. self.assertTrue(User.objects.get(email=email).check_password(password),
  86. 'Expected user password to be "%s" (potentially trimmed), but check failed.' % password)
  87. def assertUserExists(self, email):
  88. try:
  89. User.objects.get(email=email)
  90. except User.DoesNotExist:
  91. self.fail('Expected user %s to exist, but did not.' % email)
  92. def assertUserDoesNotExist(self, email):
  93. # noinspection PyTypeChecker
  94. with self.assertRaises(User.DoesNotExist):
  95. User.objects.get(email=email)
  96. def assertNoEmailSent(self):
  97. self.assertFalse(mail.outbox, "Expected no email to be sent, but %i were sent. First subject line is '%s'." %
  98. (len(mail.outbox), mail.outbox[0].subject if mail.outbox else '<n/a>'))
  99. def assertEmailSent(self, subject_contains='', body_contains='', recipient=None, reset=True, pattern=None):
  100. total = 1
  101. self.assertEqual(len(mail.outbox), total, "Expected %i message in the outbox, but found %i." %
  102. (total, len(mail.outbox)))
  103. email = mail.outbox[-1]
  104. self.assertTrue(subject_contains in email.subject,
  105. "Expected '%s' in the email subject, but found '%s'" %
  106. (subject_contains, email.subject))
  107. self.assertTrue(body_contains in email.body,
  108. "Expected '%s' in the email body, but found '%s'" %
  109. (body_contains, email.body))
  110. if recipient is not None:
  111. if isinstance(recipient, list):
  112. self.assertListEqual(recipient, email.recipients())
  113. else:
  114. self.assertIn(recipient, email.recipients())
  115. body = email.body
  116. if reset:
  117. mail.outbox = []
  118. return body if not pattern else re.search(pattern, body).group(1)
  119. def assertRegistrationEmail(self, recipient, reset=True):
  120. return self.assertEmailSent(
  121. subject_contains='deSEC',
  122. body_contains='Thank you for registering with deSEC!',
  123. recipient=[recipient],
  124. reset=reset,
  125. pattern=r'following link:\s+([^\s]*)',
  126. )
  127. def assertResetPasswordEmail(self, recipient, reset=True):
  128. return self.assertEmailSent(
  129. subject_contains='Password reset',
  130. body_contains='We received a request to reset the password for your deSEC account.',
  131. recipient=[recipient],
  132. reset=reset,
  133. pattern=r'following link:\s+([^\s]*)',
  134. )
  135. def assertChangeEmailVerificationEmail(self, recipient, reset=True):
  136. return self.assertEmailSent(
  137. subject_contains='Confirmation required: Email address change',
  138. body_contains='You requested to change the email address associated',
  139. recipient=[recipient],
  140. reset=reset,
  141. pattern=r'following link:\s+([^\s]*)',
  142. )
  143. def assertChangeEmailNotificationEmail(self, recipient, reset=True):
  144. return self.assertEmailSent(
  145. subject_contains='Account email address changed',
  146. body_contains='We\'re writing to let you know that the email address associated with',
  147. recipient=[recipient],
  148. reset=reset,
  149. )
  150. def assertDeleteAccountEmail(self, recipient, reset=True):
  151. return self.assertEmailSent(
  152. subject_contains='Confirmation required: Delete account',
  153. body_contains='confirm once more',
  154. recipient=[recipient],
  155. reset=reset,
  156. pattern=r'following link:\s+([^\s]*)',
  157. )
  158. def assertRegistrationSuccessResponse(self, response):
  159. return self.assertContains(
  160. response=response,
  161. text="Welcome! Please check your mailbox.",
  162. status_code=status.HTTP_202_ACCEPTED
  163. )
  164. def assertLoginSuccessResponse(self, response):
  165. return self.assertContains(
  166. response=response,
  167. text="auth_token",
  168. status_code=status.HTTP_200_OK
  169. )
  170. def assertRegistrationFailurePasswordRequiredResponse(self, response):
  171. self.assertContains(
  172. response=response,
  173. text="This field may not be blank",
  174. status_code=status.HTTP_400_BAD_REQUEST
  175. )
  176. self.assertEqual(response.data['password'][0].code, 'blank')
  177. def assertRegistrationFailureDomainUnavailableResponse(self, response, domain):
  178. self.assertContains(
  179. response=response,
  180. text='This domain name is unavailable',
  181. status_code=status.HTTP_400_BAD_REQUEST,
  182. msg_prefix=str(response.data)
  183. )
  184. def assertRegistrationFailureDomainInvalidResponse(self, response, domain):
  185. self.assertContains(
  186. response=response,
  187. text="Invalid value (not a DNS name)",
  188. status_code=status.HTTP_400_BAD_REQUEST,
  189. msg_prefix=str(response.data)
  190. )
  191. def assertRegistrationFailureCaptchaInvalidResponse(self, response):
  192. self.assertContains(
  193. response=response,
  194. text='CAPTCHA could not be validated. Please obtain a new one and try again.',
  195. status_code=status.HTTP_400_BAD_REQUEST,
  196. msg_prefix=str(response.data)
  197. )
  198. def assertRegistrationVerificationSuccessResponse(self, response):
  199. return self.assertContains(
  200. response=response,
  201. text="Success! Please log in at",
  202. status_code=status.HTTP_200_OK
  203. )
  204. def assertRegistrationWithDomainVerificationSuccessResponse(self, response, domain=None):
  205. if domain and domain.endswith('.dedyn.io'):
  206. text = 'Success! Here is the password'
  207. else:
  208. text = 'Success! Please check the docs for the next steps'
  209. return self.assertContains(
  210. response=response,
  211. text=text,
  212. status_code=status.HTTP_200_OK
  213. )
  214. def assertResetPasswordSuccessResponse(self, response):
  215. return self.assertContains(
  216. response=response,
  217. text="Please check your mailbox for further password reset instructions.",
  218. status_code=status.HTTP_202_ACCEPTED
  219. )
  220. def assertResetPasswordVerificationSuccessResponse(self, response):
  221. return self.assertContains(
  222. response=response,
  223. text="Success! Your password has been changed.",
  224. status_code=status.HTTP_200_OK
  225. )
  226. def assertChangeEmailSuccessResponse(self, response):
  227. return self.assertContains(
  228. response=response,
  229. text="Please check your mailbox to confirm email address change.",
  230. status_code=status.HTTP_202_ACCEPTED
  231. )
  232. def assert401InvalidPasswordResponse(self, response):
  233. return self.assertContains(
  234. response=response,
  235. text="Invalid password.",
  236. status_code=status.HTTP_401_UNAUTHORIZED
  237. )
  238. def assertChangeEmailFailureAddressTakenResponse(self, response):
  239. return self.assertContains(
  240. response=response,
  241. text="You already have another account with this email address.",
  242. status_code=status.HTTP_400_BAD_REQUEST
  243. )
  244. def assertChangeEmailFailureSameAddressResponse(self, response):
  245. return self.assertContains(
  246. response=response,
  247. text="Email address unchanged.",
  248. status_code=status.HTTP_400_BAD_REQUEST
  249. )
  250. def assertChangeEmailVerificationSuccessResponse(self, response):
  251. return self.assertContains(
  252. response=response,
  253. text="Success! Your email address has been changed to",
  254. status_code=status.HTTP_200_OK
  255. )
  256. def assertChangeEmailVerificationFailureChangePasswordResponse(self, response):
  257. return self.assertContains(
  258. response=response,
  259. text="This field is not allowed for action ",
  260. status_code=status.HTTP_400_BAD_REQUEST
  261. )
  262. def assertDeleteAccountSuccessResponse(self, response):
  263. return self.assertContains(
  264. response=response,
  265. text="Please check your mailbox for further account deletion instructions.",
  266. status_code=status.HTTP_202_ACCEPTED
  267. )
  268. def assertDeleteAccountVerificationSuccessResponse(self, response):
  269. return self.assertContains(
  270. response=response,
  271. text="All your data has been deleted. Bye bye, see you soon! <3",
  272. status_code=status.HTTP_200_OK
  273. )
  274. def assertVerificationFailureInvalidCodeResponse(self, response):
  275. return self.assertContains(
  276. response=response,
  277. text="Bad signature.",
  278. status_code=status.HTTP_400_BAD_REQUEST
  279. )
  280. def assertVerificationFailureUnknownUserResponse(self, response):
  281. return self.assertContains(
  282. response=response,
  283. text="This user does not exist.",
  284. status_code=status.HTTP_400_BAD_REQUEST
  285. )
  286. def _test_registration(self, email=None, password=None, **kwargs):
  287. email, password, response = self.register_user(email, password, **kwargs)
  288. self.assertRegistrationSuccessResponse(response)
  289. self.assertUserExists(email)
  290. self.assertFalse(User.objects.get(email=email).is_active)
  291. self.assertPassword(email, password)
  292. confirmation_link = self.assertRegistrationEmail(email)
  293. self.assertRegistrationVerificationSuccessResponse(self.client.verify(confirmation_link))
  294. self.assertTrue(User.objects.get(email=email).is_active)
  295. self.assertPassword(email, password)
  296. return email, password
  297. def _test_registration_with_domain(self, email=None, password=None, domain=None, expect_failure_response=None):
  298. domain = domain or self.random_domain_name()
  299. email, password, response = self.register_user(email, password, domain=domain)
  300. if expect_failure_response:
  301. expect_failure_response(response, domain)
  302. self.assertUserDoesNotExist(email)
  303. return
  304. self.assertRegistrationSuccessResponse(response)
  305. self.assertUserExists(email)
  306. self.assertFalse(User.objects.get(email=email).is_active)
  307. self.assertPassword(email, password)
  308. confirmation_link = self.assertRegistrationEmail(email)
  309. if domain.endswith('.dedyn.io'):
  310. cm = self.requests_desec_domain_creation_auto_delegation(domain)
  311. else:
  312. cm = self.requests_desec_domain_creation(domain)
  313. with self.assertPdnsRequests(cm[:-1]):
  314. response = self.client.verify(confirmation_link)
  315. self.assertRegistrationWithDomainVerificationSuccessResponse(response, domain)
  316. self.assertTrue(User.objects.get(email=email).is_active)
  317. self.assertPassword(email, password)
  318. self.assertTrue(Domain.objects.filter(name=domain, owner__email=email).exists())
  319. return email, password, domain
  320. def _test_login(self):
  321. token, response = self.login_user(self.email, self.password)
  322. self.assertLoginSuccessResponse(response)
  323. return token
  324. def _test_reset_password(self, email, new_password=None, **kwargs):
  325. new_password = new_password or self.random_password()
  326. self.assertResetPasswordSuccessResponse(self.reset_password(email))
  327. confirmation_link = self.assertResetPasswordEmail(email)
  328. self.assertResetPasswordVerificationSuccessResponse(
  329. self.client.verify(confirmation_link, new_password=new_password, **kwargs))
  330. self.assertPassword(email, new_password)
  331. return new_password
  332. def _test_change_email(self):
  333. old_email = self.email
  334. new_email = ' {} '.format(self.random_username()) # test trimming
  335. self.assertChangeEmailSuccessResponse(self.change_email(new_email))
  336. new_email = new_email.strip()
  337. confirmation_link = self.assertChangeEmailVerificationEmail(new_email)
  338. self.assertChangeEmailVerificationSuccessResponse(self.client.verify(confirmation_link))
  339. self.assertChangeEmailNotificationEmail(old_email)
  340. self.assertUserExists(new_email)
  341. self.assertUserDoesNotExist(old_email)
  342. self.email = new_email
  343. return self.email
  344. def _test_delete_account(self, email, password):
  345. self.assertDeleteAccountSuccessResponse(self.delete_account(email, password))
  346. confirmation_link = self.assertDeleteAccountEmail(email)
  347. self.assertDeleteAccountVerificationSuccessResponse(self.client.verify(confirmation_link))
  348. self.assertUserDoesNotExist(email)
  349. class UserLifeCycleTestCase(UserManagementTestCase):
  350. def test_life_cycle(self):
  351. self.email, self.password = self._test_registration()
  352. self.password = self._test_reset_password(self.email)
  353. mail.outbox = []
  354. self.token = self._test_login()
  355. email = self._test_change_email()
  356. self._test_delete_account(email, self.password)
  357. class NoUserAccountTestCase(UserLifeCycleTestCase):
  358. def test_home(self):
  359. self.assertResponse(self.client.get(reverse('v1:root')), status.HTTP_200_OK)
  360. def test_registration(self):
  361. self._test_registration()
  362. def test_registration_trim_email(self):
  363. user_email = ' {} '.format(self.random_username())
  364. email, new_password = self._test_registration(user_email)
  365. self.assertEqual(email, user_email.strip())
  366. def test_registration_with_domain(self):
  367. PublicSuffixMockMixin.setUpMockPatch(self)
  368. with self.get_psl_context_manager('.'):
  369. _, _, domain = self._test_registration_with_domain()
  370. self._test_registration_with_domain(domain=domain, expect_failure_response=self.assertRegistrationFailureDomainUnavailableResponse)
  371. self._test_registration_with_domain(domain='töö--', expect_failure_response=self.assertRegistrationFailureDomainInvalidResponse)
  372. with self.get_psl_context_manager('co.uk'):
  373. self._test_registration_with_domain(domain='co.uk', expect_failure_response=self.assertRegistrationFailureDomainUnavailableResponse)
  374. local_public_suffix = random.sample(self.AUTO_DELEGATION_DOMAINS, 1)[0]
  375. with self.get_psl_context_manager(local_public_suffix):
  376. self._test_registration_with_domain(domain=self.random_domain_name(suffix=local_public_suffix))
  377. def test_registration_known_account(self):
  378. email, _ = self._test_registration()
  379. self.assertRegistrationSuccessResponse(self.register_user(email, self.random_password())[2])
  380. self.assertNoEmailSent()
  381. def test_registration_password_required(self):
  382. email = self.random_username()
  383. self.assertRegistrationFailurePasswordRequiredResponse(
  384. response=self.register_user(email=email, password='')[2]
  385. )
  386. self.assertNoEmailSent()
  387. self.assertUserDoesNotExist(email)
  388. def test_registration_spam_protection(self):
  389. email = self.random_username()
  390. self.assertRegistrationSuccessResponse(
  391. response=self.register_user(email=email)[2]
  392. )
  393. self.assertRegistrationEmail(email)
  394. for _ in range(5):
  395. self.assertRegistrationSuccessResponse(
  396. response=self.register_user(email=email)[2]
  397. )
  398. self.assertNoEmailSent()
  399. def test_registration_wrong_captcha(self):
  400. email = self.random_username()
  401. password = self.random_password()
  402. captcha_id, _ = self.get_captcha()
  403. self.assertRegistrationFailureCaptchaInvalidResponse(
  404. self.client.register(email, password, captcha_id, 'this is most definitely not a correct CAPTCHA solution')
  405. )
  406. class OtherUserAccountTestCase(UserManagementTestCase):
  407. def setUp(self):
  408. super().setUp()
  409. self.other_email, self.other_password = self._test_registration()
  410. def test_reset_password_unknown_user(self):
  411. self.assertResetPasswordSuccessResponse(
  412. response=self.reset_password(self.random_username())
  413. )
  414. self.assertNoEmailSent()
  415. class HasUserAccountTestCase(UserManagementTestCase):
  416. def __init__(self, methodName: str = ...) -> None:
  417. super().__init__(methodName)
  418. self.email = None
  419. self.password = None
  420. def setUp(self):
  421. super().setUp()
  422. self.email, self.password = self._test_registration()
  423. self.token = self._test_login()
  424. def _start_reset_password(self):
  425. self.assertResetPasswordSuccessResponse(
  426. response=self.reset_password(self.email)
  427. )
  428. return self.assertResetPasswordEmail(self.email)
  429. def _start_change_email(self):
  430. new_email = self.random_username()
  431. self.assertChangeEmailSuccessResponse(
  432. response=self.change_email(new_email)
  433. )
  434. return self.assertChangeEmailVerificationEmail(new_email), new_email
  435. def _start_delete_account(self):
  436. self.assertDeleteAccountSuccessResponse(self.delete_account(self.email, self.password))
  437. return self.assertDeleteAccountEmail(self.email)
  438. def _finish_reset_password(self, confirmation_link, expect_success=True):
  439. new_password = self.random_password()
  440. response = self.client.verify(confirmation_link, new_password=new_password)
  441. if expect_success:
  442. self.assertResetPasswordVerificationSuccessResponse(response=response)
  443. else:
  444. self.assertVerificationFailureInvalidCodeResponse(response)
  445. return new_password
  446. def _finish_change_email(self, confirmation_link, expect_success=True):
  447. response = self.client.verify(confirmation_link)
  448. if expect_success:
  449. self.assertChangeEmailVerificationSuccessResponse(response)
  450. self.assertChangeEmailNotificationEmail(self.email)
  451. else:
  452. self.assertVerificationFailureInvalidCodeResponse(response)
  453. def _finish_delete_account(self, confirmation_link):
  454. self.assertDeleteAccountVerificationSuccessResponse(self.client.verify(confirmation_link))
  455. self.assertUserDoesNotExist(self.email)
  456. def test_view_account(self):
  457. response = self.client.view_account(self.token)
  458. self.assertEqual(response.status_code, 200)
  459. self.assertTrue('created' in response.data)
  460. self.assertEqual(response.data['email'], self.email)
  461. self.assertEqual(response.data['id'], User.objects.get(email=self.email).pk)
  462. self.assertEqual(response.data['limit_domains'], settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT)
  463. def test_view_account_read_only(self):
  464. # Should this test ever be removed (to allow writeable fields), make sure to
  465. # add new tests for each read-only field individually (such as limit_domains)!
  466. for method in [self.client.put, self.client.post, self.client.delete]:
  467. response = method(
  468. reverse('v1:account'),
  469. {'limit_domains': 99},
  470. HTTP_AUTHORIZATION='Token {}'.format(self.token)
  471. )
  472. self.assertResponse(response, status.HTTP_405_METHOD_NOT_ALLOWED)
  473. def test_reset_password(self):
  474. self._test_reset_password(self.email)
  475. def test_reset_password_inactive_user(self):
  476. user = User.objects.get(email=self.email)
  477. user.is_active = False
  478. user.save()
  479. self.assertResetPasswordSuccessResponse(self.reset_password(self.email))
  480. self.assertNoEmailSent()
  481. def test_reset_password_multiple_times(self):
  482. for _ in range(3):
  483. self._test_reset_password(self.email)
  484. mail.outbox = []
  485. def test_reset_password_during_change_email_interleaved(self):
  486. reset_password_verification_code = self._start_reset_password()
  487. change_email_verification_code, new_email = self._start_change_email()
  488. new_password = self._finish_reset_password(reset_password_verification_code)
  489. self._finish_change_email(change_email_verification_code, expect_success=False)
  490. self.assertUserExists(self.email)
  491. self.assertUserDoesNotExist(new_email)
  492. self.assertPassword(self.email, new_password)
  493. def test_reset_password_during_change_email_nested(self):
  494. change_email_verification_code, new_email = self._start_change_email()
  495. reset_password_verification_code = self._start_reset_password()
  496. new_password = self._finish_reset_password(reset_password_verification_code)
  497. self._finish_change_email(change_email_verification_code, expect_success=False)
  498. self.assertUserExists(self.email)
  499. self.assertUserDoesNotExist(new_email)
  500. self.assertPassword(self.email, new_password)
  501. def test_reset_password_validation_unknown_user(self):
  502. confirmation_link = self._start_reset_password()
  503. self._test_delete_account(self.email, self.password)
  504. self.assertVerificationFailureUnknownUserResponse(
  505. response=self.client.verify(confirmation_link)
  506. )
  507. self.assertNoEmailSent()
  508. def test_change_email(self):
  509. self._test_change_email()
  510. def test_change_email_requires_password(self):
  511. # Make sure that the account's email address cannot be changed with a token (password required)
  512. new_email = self.random_username()
  513. response = self.client.change_email_token_auth(self.token, new_email=new_email)
  514. self.assertContains(response, 'You do not have permission', status_code=status.HTTP_403_FORBIDDEN)
  515. self.assertNoEmailSent()
  516. def test_change_email_multiple_times(self):
  517. for _ in range(3):
  518. self._test_change_email()
  519. def test_change_email_user_exists(self):
  520. known_email, _ = self._test_registration()
  521. # We send a verification link to the new email and check account existence only later, upon verification
  522. self.assertChangeEmailSuccessResponse(
  523. response=self.change_email(known_email)
  524. )
  525. def test_change_email_verification_user_exists(self):
  526. new_email = self.random_username()
  527. self.assertChangeEmailSuccessResponse(self.change_email(new_email))
  528. confirmation_link = self.assertChangeEmailVerificationEmail(new_email)
  529. new_email, new_password = self._test_registration(new_email)
  530. self.assertChangeEmailFailureAddressTakenResponse(
  531. response=self.client.verify(confirmation_link)
  532. )
  533. self.assertUserExists(self.email)
  534. self.assertPassword(self.email, self.password)
  535. self.assertUserExists(new_email)
  536. self.assertPassword(new_email, new_password)
  537. def test_change_email_verification_change_password(self):
  538. new_email = self.random_username()
  539. self.assertChangeEmailSuccessResponse(self.change_email(new_email))
  540. confirmation_link = self.assertChangeEmailVerificationEmail(new_email)
  541. response = self.client.verify(confirmation_link, new_password=self.random_password())
  542. self.assertStatus(response, status.HTTP_405_METHOD_NOT_ALLOWED)
  543. def test_change_email_same_email(self):
  544. self.assertChangeEmailFailureSameAddressResponse(
  545. response=self.change_email(self.email)
  546. )
  547. self.assertUserExists(self.email)
  548. def test_change_email_during_reset_password_interleaved(self):
  549. change_email_verification_code, new_email = self._start_change_email()
  550. reset_password_verification_code = self._start_reset_password()
  551. self._finish_change_email(change_email_verification_code)
  552. self._finish_reset_password(reset_password_verification_code, expect_success=False)
  553. self.assertUserExists(new_email)
  554. self.assertUserDoesNotExist(self.email)
  555. self.assertPassword(new_email, self.password)
  556. def test_change_email_during_reset_password_nested(self):
  557. reset_password_verification_code = self._start_reset_password()
  558. change_email_verification_code, new_email = self._start_change_email()
  559. self._finish_change_email(change_email_verification_code)
  560. self._finish_reset_password(reset_password_verification_code, expect_success=False)
  561. self.assertUserExists(new_email)
  562. self.assertUserDoesNotExist(self.email)
  563. self.assertPassword(new_email, self.password)
  564. def test_change_email_nested(self):
  565. verification_code_1, new_email_1 = self._start_change_email()
  566. verification_code_2, new_email_2 = self._start_change_email()
  567. self._finish_change_email(verification_code_2)
  568. self.assertUserDoesNotExist(self.email)
  569. self.assertUserDoesNotExist(new_email_1)
  570. self.assertUserExists(new_email_2)
  571. self._finish_change_email(verification_code_1, expect_success=False)
  572. self.assertUserDoesNotExist(self.email)
  573. self.assertUserDoesNotExist(new_email_1)
  574. self.assertUserExists(new_email_2)
  575. def test_change_email_interleaved(self):
  576. verification_code_1, new_email_1 = self._start_change_email()
  577. verification_code_2, new_email_2 = self._start_change_email()
  578. self._finish_change_email(verification_code_1)
  579. self.assertUserDoesNotExist(self.email)
  580. self.assertUserExists(new_email_1)
  581. self.assertUserDoesNotExist(new_email_2)
  582. self._finish_change_email(verification_code_2, expect_success=False)
  583. self.assertUserDoesNotExist(self.email)
  584. self.assertUserExists(new_email_1)
  585. self.assertUserDoesNotExist(new_email_2)
  586. def test_change_email_validation_unknown_user(self):
  587. confirmation_link, new_email = self._start_change_email()
  588. self._test_delete_account(self.email, self.password)
  589. self.assertVerificationFailureUnknownUserResponse(
  590. response=self.client.verify(confirmation_link)
  591. )
  592. self.assertNoEmailSent()
  593. def test_delete_account_validation_unknown_user(self):
  594. confirmation_link = self._start_delete_account()
  595. self._test_delete_account(self.email, self.password)
  596. self.assertVerificationFailureUnknownUserResponse(
  597. response=self.client.verify(confirmation_link)
  598. )
  599. self.assertNoEmailSent()
  600. def test_reset_password_password_strip(self):
  601. password = ' %s ' % self.random_password()
  602. self._test_reset_password(self.email, password)
  603. self.assertPassword(self.email, password.strip())
  604. self.assertPassword(self.email, password)
  605. def test_reset_password_no_code_override(self):
  606. password = self.random_password()
  607. self._test_reset_password(self.email, password, code='foobar')
  608. self.assertPassword(self.email, password)