test_user_create_api.py 18 KB

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