test_auth_api.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. from django.contrib.auth import get_user_model
  2. from django.core import mail
  3. from django.test import TestCase
  4. from misago.users.models import Ban
  5. from misago.users.tokens import make_password_change_token
  6. UserModel = get_user_model()
  7. class GatewayTests(TestCase):
  8. def test_api_invalid_credentials(self):
  9. """login api returns 400 on invalid POST"""
  10. response = self.client.post(
  11. '/api/auth/', data={
  12. 'username': 'nope',
  13. 'password': 'nope',
  14. }
  15. )
  16. self.assertEqual(response.status_code, 400)
  17. self.assertEqual(response.json(), {
  18. 'non_field_errors': ["Login or password is incorrect."],
  19. })
  20. response = self.client.get('/api/auth/')
  21. self.assertEqual(response.status_code, 200)
  22. user_json = response.json()
  23. self.assertIsNone(user_json['id'])
  24. def test_login(self):
  25. """api signs user in"""
  26. user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
  27. response = self.client.post(
  28. '/api/auth/',
  29. data={
  30. 'username': 'Bob',
  31. 'password': 'Pass.123',
  32. },
  33. )
  34. self.assertEqual(response.status_code, 200)
  35. response = self.client.get('/api/auth/')
  36. self.assertEqual(response.status_code, 200)
  37. user_json = response.json()
  38. self.assertEqual(user_json['id'], user.id)
  39. self.assertEqual(user_json['username'], user.username)
  40. def test_login_whitespaces_password(self):
  41. """api signs user in with password left untouched"""
  42. user = UserModel.objects.create_user('Bob', 'bob@test.com', ' Pass.123 ')
  43. response = self.client.post(
  44. '/api/auth/',
  45. data={
  46. 'username': 'Bob',
  47. 'password': 'Pass.123',
  48. },
  49. )
  50. self.assertEqual(response.status_code, 400)
  51. self.assertEqual(response.json(), {
  52. 'non_field_errors': ["Login or password is incorrect."],
  53. })
  54. response = self.client.post(
  55. '/api/auth/',
  56. data={
  57. 'username': 'Bob',
  58. 'password': ' Pass.123 ',
  59. },
  60. )
  61. self.assertEqual(response.status_code, 200)
  62. response = self.client.get('/api/auth/')
  63. self.assertEqual(response.status_code, 200)
  64. user_json = response.json()
  65. self.assertEqual(user_json['id'], user.id)
  66. self.assertEqual(user_json['username'], user.username)
  67. def test_submit_no_data(self):
  68. """login api errors for no body"""
  69. response = self.client.post('/api/auth/')
  70. self.assertEqual(response.status_code, 400)
  71. self.assertEqual(response.json(), {
  72. 'username': ['This field is required.'],
  73. 'password': ['This field is required.'],
  74. })
  75. def test_submit_empty(self):
  76. """login api errors for empty fields"""
  77. response = self.client.post('/api/auth/', data={
  78. 'username': '',
  79. 'password': '',
  80. })
  81. self.assertEqual(response.status_code, 400)
  82. self.assertEqual(response.json(), {
  83. 'username': ['This field may not be blank.'],
  84. 'password': ['This field may not be blank.'],
  85. })
  86. def test_submit_invalid(self):
  87. """login api errors for invalid data"""
  88. response = self.client.post(
  89. '/api/auth/',
  90. 'false',
  91. content_type="application/json",
  92. )
  93. self.assertEqual(response.status_code, 400)
  94. self.assertEqual(response.json(), {
  95. 'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
  96. })
  97. def test_login_banned(self):
  98. """login api fails to sign banned user in"""
  99. UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
  100. ban = Ban.objects.create(
  101. check_type=Ban.USERNAME,
  102. banned_value='bob',
  103. user_message='You are tragically banned.',
  104. )
  105. response = self.client.post(
  106. '/api/auth/',
  107. data={
  108. 'username': 'Bob',
  109. 'password': 'Pass.123',
  110. },
  111. )
  112. self.assertEqual(response.status_code, 403)
  113. self.assertEqual(response.json(), {
  114. 'detail': {
  115. 'html': '<p>%s</p>' % ban.user_message,
  116. 'plain': ban.user_message,
  117. },
  118. 'expires_on': None,
  119. })
  120. response = self.client.get('/api/auth/')
  121. self.assertEqual(response.status_code, 200)
  122. user_json = response.json()
  123. self.assertIsNone(user_json['id'])
  124. def test_login_banned_staff(self):
  125. """login api signs banned staff member in"""
  126. user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
  127. user.is_staff = True
  128. user.save()
  129. Ban.objects.create(
  130. check_type=Ban.USERNAME,
  131. banned_value='bob',
  132. user_message='You are tragically banned.',
  133. )
  134. response = self.client.post(
  135. '/api/auth/',
  136. data={
  137. 'username': 'Bob',
  138. 'password': 'Pass.123',
  139. },
  140. )
  141. self.assertEqual(response.status_code, 200)
  142. response = self.client.get('/api/auth/')
  143. self.assertEqual(response.status_code, 200)
  144. user_json = response.json()
  145. self.assertEqual(user_json['id'], user.id)
  146. self.assertEqual(user_json['username'], user.username)
  147. def test_login_inactive_admin(self):
  148. """login api fails to sign admin-activated user in"""
  149. UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123', requires_activation=2)
  150. response = self.client.post(
  151. '/api/auth/',
  152. data={
  153. 'username': 'Bob',
  154. 'password': 'Pass.123',
  155. },
  156. )
  157. self.assertEqual(response.status_code, 400)
  158. self.assertEqual(response.json(), {
  159. 'non_field_errors': [
  160. "Your account has to be activated by Administrator before you will be able to sign in.",
  161. ],
  162. })
  163. response = self.client.get('/api/auth/')
  164. self.assertEqual(response.status_code, 200)
  165. user_json = response.json()
  166. self.assertIsNone(user_json['id'])
  167. def test_login_inactive_user(self):
  168. """login api fails to sign user-activated user in"""
  169. UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123', requires_activation=1)
  170. response = self.client.post(
  171. '/api/auth/',
  172. data={
  173. 'username': 'Bob',
  174. 'password': 'Pass.123',
  175. },
  176. )
  177. self.assertEqual(response.status_code, 400)
  178. self.assertEqual(response.json(), {
  179. 'non_field_errors': [
  180. "You have to activate your account before you will be able to sign in.",
  181. ],
  182. })
  183. response = self.client.get('/api/auth/')
  184. self.assertEqual(response.status_code, 200)
  185. user_json = response.json()
  186. self.assertIsNone(user_json['id'])
  187. def test_login_disabled_user(self):
  188. """its impossible to sign in to disabled account"""
  189. user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123', is_active=False)
  190. user.is_staff = True
  191. user.save()
  192. response = self.client.post(
  193. '/api/auth/',
  194. data={
  195. 'username': 'Bob',
  196. 'password': 'Pass.123',
  197. },
  198. )
  199. self.assertEqual(response.status_code, 400)
  200. self.assertEqual(response.json(), {
  201. 'non_field_errors': ["Login or password is incorrect."],
  202. })
  203. response = self.client.get('/api/auth/')
  204. self.assertEqual(response.status_code, 200)
  205. user_json = response.json()
  206. self.assertIsNone(user_json['id'])
  207. class UserRequirementsTests(TestCase):
  208. def test_edge_returns_response(self):
  209. """api edge has no showstoppers"""
  210. response = self.client.get('/api/auth/requirements/')
  211. self.assertEqual(response.status_code, 200)
  212. self.assertEqual(response.json(), {
  213. 'username': {'max_length': 14, 'min_length': 3},
  214. 'password': [
  215. {
  216. 'name': 'UserAttributeSimilarityValidator',
  217. 'user_attributes': ['username', 'email'],
  218. },
  219. {
  220. 'name': 'MinimumLengthValidator',
  221. 'min_length': 7,
  222. },
  223. {'name': 'CommonPasswordValidator'},
  224. {'name': 'NumericPasswordValidator'},
  225. ],
  226. })
  227. class SendActivationAPITests(TestCase):
  228. def setUp(self):
  229. self.user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
  230. self.user.requires_activation = 1
  231. self.user.save()
  232. self.link = '/api/auth/send-activation/'
  233. def test_submit_valid(self):
  234. """request activation link api sends reset link mail"""
  235. response = self.client.post(
  236. self.link,
  237. data={
  238. 'email': self.user.email,
  239. },
  240. )
  241. self.assertEqual(response.status_code, 200)
  242. self.assertIn('Activate Bob', mail.outbox[0].subject)
  243. def test_submit_banned(self):
  244. """request activation link api errors for banned users"""
  245. ban = Ban.objects.create(
  246. check_type=Ban.USERNAME,
  247. banned_value=self.user.username,
  248. user_message='Nope!',
  249. )
  250. response = self.client.post(
  251. self.link,
  252. data={
  253. 'email': self.user.email,
  254. },
  255. )
  256. self.assertEqual(response.status_code, 403)
  257. self.assertEqual(response.json(), {
  258. 'detail': {
  259. 'html': '<p>%s</p>' % ban.user_message,
  260. 'plain': ban.user_message,
  261. },
  262. 'expires_on': None,
  263. })
  264. self.assertTrue(not mail.outbox)
  265. def test_submit_disabled(self):
  266. """request activation link api fails disabled users"""
  267. self.user.is_active = False
  268. self.user.save()
  269. response = self.client.post(
  270. self.link,
  271. data={
  272. 'email': self.user.email,
  273. },
  274. )
  275. self.assertEqual(response.status_code, 400)
  276. # fixme: don't leak out the info that email is invalid in auth forms
  277. # instead, message that if email was valid you'll get an email
  278. self.assertEqual(response.json(), {
  279. 'non_field_errors': ["No user with this e-mail exists."],
  280. })
  281. self.assertTrue(not mail.outbox)
  282. def test_submit_empty(self):
  283. """request activation link api errors for no body"""
  284. response = self.client.post(self.link)
  285. self.assertEqual(response.status_code, 400)
  286. self.assertEqual(response.json(), {
  287. 'email': ["This field is required."],
  288. })
  289. self.assertTrue(not mail.outbox)
  290. def test_submit_invalid_data(self):
  291. """login api errors for invalid data"""
  292. response = self.client.post(
  293. self.link,
  294. 'false',
  295. content_type="application/json",
  296. )
  297. self.assertEqual(response.status_code, 400)
  298. self.assertEqual(response.json(), {
  299. 'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
  300. })
  301. def test_submit_invalid_email(self):
  302. """request activation link api errors for invalid email"""
  303. response = self.client.post(
  304. self.link,
  305. data={
  306. 'email': 'fake@mail.com',
  307. },
  308. )
  309. self.assertEqual(response.status_code, 400)
  310. # fixme: don't leak out the info that email is invalid in auth forms
  311. # instead, message that if email was valid you'll get an email
  312. self.assertEqual(response.json(), {
  313. 'non_field_errors': ["No user with this e-mail exists."],
  314. })
  315. self.assertTrue(not mail.outbox)
  316. def test_submit_active_user(self):
  317. """request activation link api errors for active user"""
  318. self.user.requires_activation = 0
  319. self.user.save()
  320. response = self.client.post(
  321. self.link,
  322. data={
  323. 'email': self.user.email,
  324. },
  325. )
  326. self.assertEqual(response.status_code, 400)
  327. self.assertEqual(response.json(), {
  328. 'non_field_errors': ["Bob, your account is already active."],
  329. })
  330. def test_submit_inactive_user(self):
  331. """request activation link api errors for admin-activated users"""
  332. self.user.requires_activation = 2
  333. self.user.save()
  334. response = self.client.post(
  335. self.link,
  336. data={
  337. 'email': self.user.email,
  338. },
  339. )
  340. self.assertEqual(response.status_code, 400)
  341. self.assertEqual(response.json(), {
  342. 'non_field_errors': ["Bob, only administrator may activate your account."],
  343. })
  344. self.assertTrue(not mail.outbox)
  345. # but succeed for user-activated
  346. self.user.requires_activation = 1
  347. self.user.save()
  348. response = self.client.post(
  349. self.link, data={
  350. 'email': self.user.email,
  351. }
  352. )
  353. self.assertEqual(response.json(), {
  354. 'username': self.user.username,
  355. 'email': self.user.email,
  356. })
  357. self.assertTrue(mail.outbox)
  358. class SendPasswordFormAPITests(TestCase):
  359. def setUp(self):
  360. self.user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
  361. self.link = '/api/auth/send-password-form/'
  362. def test_submit_valid(self):
  363. """request change password form link api sends reset link mail"""
  364. response = self.client.post(
  365. self.link,
  366. data={
  367. 'email': self.user.email,
  368. },
  369. )
  370. self.assertEqual(response.status_code, 200)
  371. self.assertIn('Change Bob password', mail.outbox[0].subject)
  372. def test_submit_banned(self):
  373. """request change password form link api errors for banned users"""
  374. ban = Ban.objects.create(
  375. check_type=Ban.USERNAME,
  376. banned_value=self.user.username,
  377. user_message='Nope!',
  378. )
  379. response = self.client.post(
  380. self.link,
  381. data={
  382. 'email': self.user.email,
  383. },
  384. )
  385. self.assertEqual(response.status_code, 403)
  386. self.assertEqual(response.json(), {
  387. 'detail': {
  388. 'html': '<p>%s</p>' % ban.user_message,
  389. 'plain': ban.user_message,
  390. },
  391. 'expires_on': None,
  392. })
  393. self.assertTrue(not mail.outbox)
  394. def test_submit_disabled(self):
  395. """request change password form api fails disabled users"""
  396. self.user.is_active = False
  397. self.user.save()
  398. response = self.client.post(
  399. self.link,
  400. data={
  401. 'email': self.user.email,
  402. },
  403. )
  404. self.assertEqual(response.status_code, 400)
  405. self.assertEqual(response.json(), {
  406. 'non_field_errors': ["No user with this e-mail exists."],
  407. })
  408. self.assertTrue(not mail.outbox)
  409. def test_submit_empty(self):
  410. """request change password form link api errors for no body"""
  411. response = self.client.post(self.link)
  412. self.assertEqual(response.status_code, 400)
  413. self.assertEqual(response.json(), {
  414. 'email': ["This field is required."],
  415. })
  416. self.assertTrue(not mail.outbox)
  417. def test_submit_invalid(self):
  418. """request change password form link api errors for invalid email"""
  419. response = self.client.post(
  420. self.link,
  421. data={
  422. 'email': 'fake@mail.com',
  423. },
  424. )
  425. self.assertEqual(response.status_code, 400)
  426. self.assertEqual(response.json(), {
  427. 'non_field_errors': ["No user with this e-mail exists."],
  428. })
  429. self.assertTrue(not mail.outbox)
  430. def test_submit_invalid_data(self):
  431. """login api errors for invalid data"""
  432. response = self.client.post(
  433. self.link,
  434. 'false',
  435. content_type="application/json",
  436. )
  437. self.assertEqual(response.status_code, 400)
  438. self.assertEqual(response.json(), {
  439. 'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
  440. })
  441. def test_submit_inactive_user(self):
  442. """request change password form link api errors for inactive users"""
  443. self.user.requires_activation = 1
  444. self.user.save()
  445. response = self.client.post(
  446. self.link,
  447. data={
  448. 'email': self.user.email,
  449. },
  450. )
  451. self.assertEqual(response.status_code, 400)
  452. self.assertEqual(response.json(), {
  453. 'non_field_errors': [
  454. "You have to activate your account before you will "
  455. "be able to request new password.",
  456. ],
  457. })
  458. self.assertTrue(not mail.outbox)
  459. self.user.requires_activation = 2
  460. self.user.save()
  461. response = self.client.post(
  462. self.link,
  463. data={
  464. 'email': self.user.email,
  465. },
  466. )
  467. self.assertEqual(response.status_code, 400)
  468. self.assertEqual(response.json(), {
  469. 'non_field_errors': [
  470. "Administrator has to activate your account before you "
  471. "will be able to request new password.",
  472. ],
  473. })
  474. self.assertTrue(not mail.outbox)
  475. class ChangePasswordAPITests(TestCase):
  476. def setUp(self):
  477. self.user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
  478. self.link = '/api/auth/change-password/%s/'
  479. def test_submit_valid(self):
  480. """submit change password form api changes password"""
  481. response = self.client.post(
  482. self.link % self.user.pk,
  483. data={
  484. 'password': 'n3wp4ss!',
  485. 'token': make_password_change_token(self.user),
  486. },
  487. )
  488. self.assertEqual(response.status_code, 200)
  489. user = UserModel.objects.get(id=self.user.pk)
  490. self.assertTrue(user.check_password('n3wp4ss!'))
  491. def test_submit_with_whitespaces(self):
  492. """submit change password form api changes password with whitespaces"""
  493. response = self.client.post(
  494. self.link % self.user.pk,
  495. data={
  496. 'password': ' n3wp4ss! ',
  497. 'token': make_password_change_token(self.user),
  498. },
  499. )
  500. self.assertEqual(response.status_code, 200)
  501. user = UserModel.objects.get(id=self.user.pk)
  502. self.assertTrue(user.check_password(' n3wp4ss! '))
  503. def test_submit_invalid_data(self):
  504. """login api errors for invalid data"""
  505. response = self.client.post(
  506. self.link % self.user.pk,
  507. 'false',
  508. content_type="application/json",
  509. )
  510. self.assertEqual(response.status_code, 400)
  511. self.assertEqual(response.json(), {
  512. 'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
  513. })
  514. def test_invalid_token(self):
  515. """api errors on invalid user id link"""
  516. response = self.client.post(
  517. self.link % self.user.pk,
  518. data={
  519. 'password': 'n3wp4ss!',
  520. 'token': 'invalid!',
  521. },
  522. )
  523. self.assertEqual(response.status_code, 400)
  524. self.assertEqual(response.json(), {
  525. 'token': ["Form link is invalid or expired. Please try again."],
  526. })
  527. def test_banned_user_link(self):
  528. """request errors because user is banned"""
  529. ban = Ban.objects.create(
  530. check_type=Ban.USERNAME,
  531. banned_value=self.user.username,
  532. user_message='Nope!',
  533. )
  534. response = self.client.post(
  535. self.link % self.user.pk,
  536. data={
  537. 'password': 'n3wp4ss!',
  538. 'token': make_password_change_token(self.user),
  539. },
  540. )
  541. self.assertEqual(response.status_code, 403)
  542. self.assertEqual(response.json(), {
  543. 'detail': {
  544. 'html': '<p>%s</p>' % ban.user_message,
  545. 'plain': ban.user_message,
  546. },
  547. 'expires_on': None,
  548. })
  549. def test_inactive_user(self):
  550. """change password api errors for inactive users"""
  551. self.user.requires_activation = 1
  552. self.user.save()
  553. response = self.client.post(
  554. self.link % self.user.pk,
  555. data={
  556. 'password': 'n3wp4ss!',
  557. 'token': make_password_change_token(self.user),
  558. },
  559. )
  560. self.assertEqual(response.status_code, 400)
  561. self.assertEqual(response.json(), {
  562. 'non_field_errors': [
  563. "You have to activate your account before you will "
  564. "be able to change your password.",
  565. ],
  566. })
  567. self.user.requires_activation = 2
  568. self.user.save()
  569. response = self.client.post(
  570. self.link % self.user.pk,
  571. data={
  572. 'password': 'n3wp4ss!',
  573. 'token': make_password_change_token(self.user),
  574. },
  575. )
  576. self.assertEqual(response.status_code, 400)
  577. self.assertEqual(response.json(), {
  578. 'non_field_errors': [
  579. "Administrator has to activate your account before you "
  580. "will be able to change your password.",
  581. ],
  582. })
  583. def test_disabled_user(self):
  584. """change password api errors for disabled users"""
  585. self.user.is_active = False
  586. self.user.save()
  587. response = self.client.post(self.link % self.user.pk)
  588. self.assertEqual(response.status_code, 404)
  589. self.assertEqual(response.json(), {
  590. 'detail': "No User matches the given query.",
  591. })
  592. def test_submit_empty(self):
  593. """change password api errors for empty body"""
  594. response = self.client.post(self.link % self.user.pk)
  595. self.assertEqual(response.status_code, 400)
  596. self.assertEqual(response.json(), {
  597. 'password': ["This field is required."],
  598. 'token': ["This field is required."],
  599. })