test_user_create_api.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. from django.contrib.auth import get_user_model
  2. from django.core import mail
  3. from django.urls import reverse
  4. from misago.conf.test import override_dynamic_settings
  5. from misago.legal.models import Agreement
  6. from misago.users.models import Ban, Online
  7. from misago.users.testutils import UserTestCase
  8. User = get_user_model()
  9. class UserCreateTests(UserTestCase):
  10. """tests for new user registration (POST to /api/users/)"""
  11. def setUp(self):
  12. super().setUp()
  13. Agreement.objects.invalidate_cache()
  14. self.api_link = '/api/users/'
  15. def tearDown(self):
  16. Agreement.objects.invalidate_cache()
  17. def test_empty_request(self):
  18. """empty request errors with code 400"""
  19. response = self.client.post(self.api_link)
  20. self.assertEqual(response.status_code, 400)
  21. self.assertEqual(response.json(), {
  22. 'username': ['This field is required.'],
  23. 'email': ['This field is required.'],
  24. 'password': ['This field is required.'],
  25. })
  26. def test_invalid_data(self):
  27. """invalid request data errors with code 400"""
  28. response = self.client.post(
  29. self.api_link,
  30. 'false',
  31. content_type="application/json",
  32. )
  33. self.assertEqual(response.status_code, 400)
  34. self.assertEqual(response.json(), {
  35. 'username': ['This field is required.'],
  36. 'email': ['This field is required.'],
  37. 'password': ['This field is required.'],
  38. })
  39. def test_authenticated_request(self):
  40. """authentiated user request errors with code 403"""
  41. self.login_user(self.get_authenticated_user())
  42. response = self.client.post(self.api_link)
  43. self.assertEqual(response.status_code, 403)
  44. self.assertEqual(response.json(), {
  45. "detail": "This action is not available to signed in users."
  46. })
  47. @override_dynamic_settings(account_activation="closed")
  48. def test_registration_off_request(self):
  49. """registrations off request errors with code 403"""
  50. response = self.client.post(self.api_link)
  51. self.assertEqual(response.status_code, 403)
  52. self.assertEqual(response.json(), {
  53. "detail": "New users registrations are currently closed."
  54. })
  55. def test_registration_validates_ip_ban(self):
  56. """api validates ip ban"""
  57. Ban.objects.create(
  58. check_type=Ban.IP,
  59. banned_value='127.*',
  60. user_message="You can't register account like this.",
  61. )
  62. response = self.client.post(
  63. self.api_link,
  64. data={
  65. 'username': 'totallyNew',
  66. 'email': 'loremipsum@dolor.met',
  67. 'password': 'LoremP4ssword',
  68. },
  69. )
  70. self.assertEqual(response.status_code, 403)
  71. def test_registration_validates_ip_registration_ban(self):
  72. """api validates ip registration-only ban"""
  73. Ban.objects.create(
  74. check_type=Ban.IP,
  75. banned_value='127.*',
  76. user_message="You can't register account like this.",
  77. registration_only=True,
  78. )
  79. response = self.client.post(
  80. self.api_link,
  81. data={
  82. 'username': 'totallyNew',
  83. 'email': 'loremipsum@dolor.met',
  84. 'password': 'LoremP4ssword',
  85. },
  86. )
  87. self.assertEqual(response.status_code, 400)
  88. self.assertEqual(
  89. response.json(), {
  90. '__all__': ["You can't register account like this."],
  91. }
  92. )
  93. def test_registration_validates_username(self):
  94. """api validates usernames"""
  95. user = self.get_authenticated_user()
  96. response = self.client.post(
  97. self.api_link,
  98. data={
  99. 'username': user.username,
  100. 'email': 'loremipsum@dolor.met',
  101. 'password': 'LoremP4ssword',
  102. },
  103. )
  104. self.assertEqual(response.status_code, 400)
  105. self.assertEqual(response.json(), {
  106. 'username': ["This username is not available."],
  107. })
  108. def test_registration_validates_username_ban(self):
  109. """api validates username ban"""
  110. Ban.objects.create(
  111. banned_value='totally*',
  112. user_message="You can't register account like this.",
  113. )
  114. response = self.client.post(
  115. self.api_link,
  116. data={
  117. 'username': 'totallyNew',
  118. 'email': 'loremipsum@dolor.met',
  119. 'password': 'LoremP4ssword',
  120. },
  121. )
  122. self.assertEqual(response.status_code, 400)
  123. self.assertEqual(
  124. response.json(), {
  125. 'username': ["You can't register account like this."],
  126. }
  127. )
  128. def test_registration_validates_username_registration_ban(self):
  129. """api validates username registration-only ban"""
  130. Ban.objects.create(
  131. banned_value='totally*',
  132. user_message="You can't register account like this.",
  133. registration_only=True,
  134. )
  135. response = self.client.post(
  136. self.api_link,
  137. data={
  138. 'username': 'totallyNew',
  139. 'email': 'loremipsum@dolor.met',
  140. 'password': 'LoremP4ssword',
  141. },
  142. )
  143. self.assertEqual(response.status_code, 400)
  144. self.assertEqual(
  145. response.json(), {
  146. 'username': ["You can't register account like this."],
  147. }
  148. )
  149. def test_registration_validates_email(self):
  150. """api validates usernames"""
  151. user = self.get_authenticated_user()
  152. response = self.client.post(
  153. self.api_link,
  154. data={
  155. 'username': 'totallyNew',
  156. 'email': user.email,
  157. 'password': 'LoremP4ssword',
  158. },
  159. )
  160. self.assertEqual(response.status_code, 400)
  161. self.assertEqual(response.json(), {
  162. 'email': ["This e-mail address is not available."],
  163. })
  164. def test_registration_validates_email_ban(self):
  165. """api validates email ban"""
  166. Ban.objects.create(
  167. check_type=Ban.EMAIL,
  168. banned_value='lorem*',
  169. user_message="You can't register account like this.",
  170. )
  171. response = self.client.post(
  172. self.api_link,
  173. data={
  174. 'username': 'totallyNew',
  175. 'email': 'loremipsum@dolor.met',
  176. 'password': 'LoremP4ssword',
  177. },
  178. )
  179. self.assertEqual(response.status_code, 400)
  180. self.assertEqual(response.json(), {
  181. 'email': ["You can't register account like this."],
  182. })
  183. def test_registration_validates_email_registration_ban(self):
  184. """api validates email registration-only ban"""
  185. Ban.objects.create(
  186. check_type=Ban.EMAIL,
  187. banned_value='lorem*',
  188. user_message="You can't register account like this.",
  189. registration_only=True,
  190. )
  191. response = self.client.post(
  192. self.api_link,
  193. data={
  194. 'username': 'totallyNew',
  195. 'email': 'loremipsum@dolor.met',
  196. 'password': 'LoremP4ssword',
  197. },
  198. )
  199. self.assertEqual(response.status_code, 400)
  200. self.assertEqual(response.json(), {
  201. 'email': ["You can't register account like this."],
  202. })
  203. def test_registration_requires_password(self):
  204. """api uses django's validate_password to validate registrations"""
  205. response = self.client.post(
  206. self.api_link,
  207. data={
  208. 'username': 'Bob',
  209. 'email': 'loremipsum@dolor.met',
  210. 'password': '',
  211. },
  212. )
  213. self.assertEqual(response.status_code, 400)
  214. self.assertEqual(response.json(), {
  215. "password": ["This field is required."],
  216. })
  217. def test_registration_validates_password(self):
  218. """api uses django's validate_password to validate registrations"""
  219. response = self.client.post(
  220. self.api_link,
  221. data={
  222. 'username': 'Bob',
  223. 'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
  224. 'password': '123',
  225. },
  226. )
  227. self.assertEqual(response.status_code, 400)
  228. self.assertEqual(response.json(), {
  229. "email": ["This email is not allowed."],
  230. "password": [
  231. "This password is too short. It must contain at least 7 characters.",
  232. "This password is entirely numeric.",
  233. ],
  234. })
  235. def test_registration_validates_password_similiarity(self):
  236. """api uses validate_password to validate registrations"""
  237. response = self.client.post(
  238. self.api_link,
  239. data={
  240. 'username': 'BobBoberson',
  241. 'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
  242. 'password': 'BobBoberson',
  243. },
  244. )
  245. self.assertEqual(response.status_code, 400)
  246. self.assertEqual(response.json(), {
  247. "email": ["This email is not allowed."],
  248. "password": ["The password is too similar to the username."],
  249. })
  250. @override_dynamic_settings(
  251. captcha_type='qa',
  252. qa_question='Test',
  253. qa_answers='Lorem\nIpsum'
  254. )
  255. def test_registration_validates_captcha(self):
  256. """api validates captcha"""
  257. response = self.client.post(
  258. self.api_link,
  259. data={
  260. 'username': 'totallyNew',
  261. 'email': 'loremipsum@dolor.met',
  262. 'password': 'LoremP4ssword',
  263. 'captcha': 'dolor'
  264. },
  265. )
  266. self.assertEqual(response.status_code, 400)
  267. self.assertEqual(
  268. response.json(), {
  269. 'captcha': ['Entered answer is incorrect.'],
  270. }
  271. )
  272. # valid captcha
  273. response = self.client.post(
  274. self.api_link,
  275. data={
  276. 'username': 'totallyNew',
  277. 'email': 'loremipsum@dolor.met',
  278. 'password': 'LoremP4ssword',
  279. 'captcha': 'ipSUM'
  280. },
  281. )
  282. self.assertEqual(response.status_code, 200)
  283. @override_dynamic_settings(
  284. captcha_type='qa',
  285. qa_question='',
  286. qa_answers='Lorem\n\nIpsum'
  287. )
  288. def test_qacaptcha_handles_empty_answers(self):
  289. """api validates captcha"""
  290. response = self.client.post(
  291. self.api_link,
  292. data={
  293. 'username': 'totallyNew',
  294. 'email': 'loremipsum@dolor.met',
  295. 'password': 'LoremP4ssword',
  296. 'captcha': ''
  297. },
  298. )
  299. self.assertEqual(response.status_code, 400)
  300. self.assertEqual(
  301. response.json(), {
  302. 'captcha': ['Entered answer is incorrect.'],
  303. }
  304. )
  305. def test_registration_check_agreement(self):
  306. """api checks agreement"""
  307. agreement = Agreement.objects.create(
  308. type=Agreement.TYPE_TOS,
  309. text="Lorem ipsum",
  310. is_active=True,
  311. )
  312. response = self.client.post(
  313. self.api_link,
  314. data={
  315. 'username': 'totallyNew',
  316. 'email': 'loremipsum@dolor.met',
  317. 'password': 'LoremP4ssword',
  318. },
  319. )
  320. self.assertEqual(response.status_code, 400)
  321. self.assertEqual(
  322. response.json(), {
  323. 'terms_of_service': ['This agreement is required.'],
  324. }
  325. )
  326. # invalid agreement id
  327. response = self.client.post(
  328. self.api_link,
  329. data={
  330. 'username': 'totallyNew',
  331. 'email': 'loremipsum@dolor.met',
  332. 'password': 'LoremP4ssword',
  333. 'terms_of_service': agreement.id + 1,
  334. },
  335. )
  336. self.assertEqual(response.status_code, 400)
  337. self.assertEqual(
  338. response.json(), {
  339. 'terms_of_service': ['This agreement is required.'],
  340. }
  341. )
  342. # valid agreement id
  343. response = self.client.post(
  344. self.api_link,
  345. data={
  346. 'username': 'totallyNew',
  347. 'email': 'loremipsum@dolor.met',
  348. 'password': 'LoremP4ssword',
  349. 'terms_of_service': agreement.id,
  350. },
  351. )
  352. self.assertEqual(response.status_code, 200)
  353. user = User.objects.get(email='loremipsum@dolor.met')
  354. self.assertEqual(user.agreements, [agreement.id])
  355. self.assertEqual(user.useragreement_set.count(), 1)
  356. def test_registration_ignore_inactive_agreement(self):
  357. """api ignores inactive agreement"""
  358. Agreement.objects.create(
  359. type=Agreement.TYPE_TOS,
  360. text="Lorem ipsum",
  361. is_active=False,
  362. )
  363. response = self.client.post(
  364. self.api_link,
  365. data={
  366. 'username': 'totallyNew',
  367. 'email': 'loremipsum@dolor.met',
  368. 'password': 'LoremP4ssword',
  369. 'terms_of_service': '',
  370. },
  371. )
  372. self.assertEqual(response.status_code, 200)
  373. user = User.objects.get(email='loremipsum@dolor.met')
  374. self.assertEqual(user.agreements, [])
  375. self.assertEqual(user.useragreement_set.count(), 0)
  376. def test_registration_calls_validate_new_registration(self):
  377. """api uses validate_new_registration to validate registrations"""
  378. response = self.client.post(
  379. self.api_link,
  380. data={
  381. 'username': 'Bob',
  382. 'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
  383. 'password': 'pas123',
  384. },
  385. )
  386. self.assertEqual(response.status_code, 400)
  387. self.assertEqual(response.json(), {
  388. "email": ["This email is not allowed."],
  389. "password": ["This password is too short. It must contain at least 7 characters."],
  390. })
  391. @override_dynamic_settings(account_activation="none")
  392. def test_registration_creates_active_user(self):
  393. """api creates active and signed in user on POST"""
  394. response = self.client.post(
  395. self.api_link,
  396. data={
  397. 'username': 'Bob',
  398. 'email': 'bob@bob.com',
  399. 'password': 'pass123',
  400. },
  401. )
  402. self.assertEqual(response.status_code, 200)
  403. self.assertEqual(response.json(), {
  404. 'activation': 'active',
  405. 'username': 'Bob',
  406. 'email': 'bob@bob.com',
  407. })
  408. User.objects.get_by_username('Bob')
  409. test_user = User.objects.get_by_email('bob@bob.com')
  410. self.assertEqual(Online.objects.filter(user=test_user).count(), 1)
  411. self.assertTrue(test_user.check_password('pass123'))
  412. auth_json = self.client.get(reverse('misago:api:auth')).json()
  413. self.assertTrue(auth_json['is_authenticated'])
  414. self.assertEqual(auth_json['username'], 'Bob')
  415. self.assertIn('Welcome', mail.outbox[0].subject)
  416. self.assertEqual(test_user.audittrail_set.count(), 1)
  417. @override_dynamic_settings(account_activation="user")
  418. def test_registration_creates_inactive_user(self):
  419. """api creates inactive user on POST"""
  420. response = self.client.post(
  421. self.api_link,
  422. data={
  423. 'username': 'Bob',
  424. 'email': 'bob@bob.com',
  425. 'password': 'pass123',
  426. },
  427. )
  428. self.assertEqual(response.status_code, 200)
  429. self.assertEqual(response.json(), {
  430. 'activation': 'user',
  431. 'username': 'Bob',
  432. 'email': 'bob@bob.com',
  433. })
  434. auth_json = self.client.get(reverse('misago:api:auth')).json()
  435. self.assertFalse(auth_json['is_authenticated'])
  436. User.objects.get_by_username('Bob')
  437. User.objects.get_by_email('bob@bob.com')
  438. self.assertIn('Welcome', mail.outbox[0].subject)
  439. @override_dynamic_settings(account_activation="admin")
  440. def test_registration_creates_admin_activated_user(self):
  441. """api creates admin activated user on POST"""
  442. response = self.client.post(
  443. self.api_link,
  444. data={
  445. 'username': 'Bob',
  446. 'email': 'bob@bob.com',
  447. 'password': 'pass123',
  448. },
  449. )
  450. self.assertEqual(response.status_code, 200)
  451. self.assertEqual(response.json(), {
  452. 'activation': 'admin',
  453. 'username': 'Bob',
  454. 'email': 'bob@bob.com',
  455. })
  456. auth_json = self.client.get(reverse('misago:api:auth')).json()
  457. self.assertFalse(auth_json['is_authenticated'])
  458. User.objects.get_by_username('Bob')
  459. User.objects.get_by_email('bob@bob.com')
  460. self.assertIn('Welcome', mail.outbox[0].subject)
  461. @override_dynamic_settings(account_activation="none")
  462. def test_registration_creates_user_with_whitespace_password(self):
  463. """api creates user with spaces around password"""
  464. response = self.client.post(
  465. self.api_link,
  466. data={
  467. 'username': 'Bob',
  468. 'email': 'bob@bob.com',
  469. 'password': ' pass123 ',
  470. },
  471. )
  472. self.assertEqual(response.status_code, 200)
  473. self.assertEqual(response.json(), {
  474. 'activation': 'active',
  475. 'username': 'Bob',
  476. 'email': 'bob@bob.com',
  477. })
  478. User.objects.get_by_username('Bob')
  479. test_user = User.objects.get_by_email('bob@bob.com')
  480. self.assertEqual(Online.objects.filter(user=test_user).count(), 1)
  481. self.assertTrue(test_user.check_password(' pass123 '))
  482. self.assertIn('Welcome', mail.outbox[0].subject)