test_user_create_api.py 18 KB

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