test_pagure_flask_ui_login.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2016 - Copyright Red Hat Inc
  4. Authors:
  5. Pierre-Yves Chibon <pingou@pingoured.fr>
  6. Farhaan Bukhsh <farhaan.bukhsh@gmail.com>
  7. """
  8. from __future__ import unicode_literals, absolute_import
  9. import datetime
  10. import hashlib
  11. import json
  12. import unittest
  13. import shutil
  14. import sys
  15. import tempfile
  16. import os
  17. import flask
  18. import pygit2
  19. import six
  20. from mock import patch, MagicMock
  21. sys.path.insert(
  22. 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
  23. )
  24. import pagure.lib.query
  25. import tests
  26. from pagure.lib.repo import PagureRepo
  27. import pagure.ui.login
  28. class PagureFlaskLogintests(tests.SimplePagureTest):
  29. """ Tests for flask app controller of pagure """
  30. def setUp(self):
  31. """ Create the application with PAGURE_AUTH being local. """
  32. super(PagureFlaskLogintests, self).setUp()
  33. app = pagure.flask_app.create_app(
  34. {"DB_URL": self.dbpath, "PAGURE_AUTH": "local"}
  35. )
  36. # Remove the log handlers for the tests
  37. app.logger.handlers = []
  38. self.app = app.test_client()
  39. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  40. def test_front_page(self):
  41. """ Test the front page. """
  42. # First access the front page
  43. output = self.app.get("/")
  44. self.assertEqual(output.status_code, 200)
  45. self.assertIn(
  46. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  47. )
  48. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  49. @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
  50. def test_new_user(self):
  51. """ Test the new_user endpoint. """
  52. # Check before:
  53. items = pagure.lib.query.search_user(self.session)
  54. self.assertEqual(2, len(items))
  55. # First access the new user page
  56. output = self.app.get("/user/new")
  57. self.assertEqual(output.status_code, 200)
  58. self.assertIn(
  59. "<title>New user - Pagure</title>", output.get_data(as_text=True)
  60. )
  61. self.assertIn(
  62. '<form action="/user/new" method="post">',
  63. output.get_data(as_text=True),
  64. )
  65. # Create the form to send there
  66. # This has all the data needed
  67. data = {
  68. "user": "foo",
  69. "fullname": "user foo",
  70. "email_address": "foo@bar.com",
  71. "password": "barpass",
  72. "confirm_password": "barpass",
  73. }
  74. # Submit this form - Doesn't work since there is no csrf token
  75. output = self.app.post("/user/new", data=data)
  76. self.assertEqual(output.status_code, 200)
  77. self.assertIn(
  78. "<title>New user - Pagure</title>", output.get_data(as_text=True)
  79. )
  80. self.assertIn(
  81. '<form action="/user/new" method="post">',
  82. output.get_data(as_text=True),
  83. )
  84. csrf_token = (
  85. output.get_data(as_text=True)
  86. .split('name="csrf_token" type="hidden" value="')[1]
  87. .split('">')[0]
  88. )
  89. # Submit the form with the csrf token
  90. data["csrf_token"] = csrf_token
  91. output = self.app.post("/user/new", data=data, follow_redirects=True)
  92. self.assertEqual(output.status_code, 200)
  93. self.assertIn(
  94. "<title>New user - Pagure</title>", output.get_data(as_text=True)
  95. )
  96. self.assertIn(
  97. '<form action="/user/new" method="post">',
  98. output.get_data(as_text=True),
  99. )
  100. self.assertIn("Username already taken.", output.get_data(as_text=True))
  101. # Submit the form with another username
  102. data["user"] = "foouser"
  103. output = self.app.post("/user/new", data=data, follow_redirects=True)
  104. self.assertEqual(output.status_code, 200)
  105. self.assertIn(
  106. "<title>New user - Pagure</title>", output.get_data(as_text=True)
  107. )
  108. self.assertIn(
  109. "Email address already taken.", output.get_data(as_text=True)
  110. )
  111. # Submit the form with proper data
  112. data["email_address"] = "foo@example.com"
  113. output = self.app.post("/user/new", data=data, follow_redirects=True)
  114. self.assertEqual(output.status_code, 200)
  115. self.assertIn(
  116. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  117. )
  118. self.assertIn(
  119. "User created, please check your email to activate the account",
  120. output.get_data(as_text=True),
  121. )
  122. # Check after:
  123. items = pagure.lib.query.search_user(self.session)
  124. self.assertEqual(3, len(items))
  125. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  126. @patch.dict("pagure.config.config", {"ALLOW_USER_REGISTRATION": False})
  127. @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
  128. def test_new_user_disabled(self):
  129. """ Test the disabling of the new_user endpoint. """
  130. # Check before:
  131. items = pagure.lib.query.search_user(self.session)
  132. self.assertEqual(2, len(items))
  133. # Attempt to access the new user page
  134. output = self.app.get("/user/new", follow_redirects=True)
  135. self.assertEqual(output.status_code, 200)
  136. self.assertIn(
  137. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  138. )
  139. self.assertIn(
  140. "User registration is disabled.", output.get_data(as_text=True)
  141. )
  142. # Check after:
  143. items = pagure.lib.query.search_user(self.session)
  144. self.assertEqual(2, len(items))
  145. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  146. @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
  147. def test_do_login(self):
  148. """ Test the do_login endpoint. """
  149. output = self.app.get("/login/")
  150. self.assertEqual(output.status_code, 200)
  151. self.assertIn(
  152. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  153. )
  154. self.assertIn(
  155. '<form action="/dologin" method="post">',
  156. output.get_data(as_text=True),
  157. )
  158. # This has all the data needed
  159. data = {"username": "foouser", "password": "barpass"}
  160. # Submit this form - Doesn't work since there is no csrf token
  161. output = self.app.post("/dologin", data=data, follow_redirects=True)
  162. self.assertEqual(output.status_code, 200)
  163. self.assertIn(
  164. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  165. )
  166. self.assertIn(
  167. '<form action="/dologin" method="post">',
  168. output.get_data(as_text=True),
  169. )
  170. self.assertIn(
  171. "Insufficient information provided", output.get_data(as_text=True)
  172. )
  173. csrf_token = (
  174. output.get_data(as_text=True)
  175. .split('name="csrf_token" type="hidden" value="')[1]
  176. .split('">')[0]
  177. )
  178. # Submit the form with the csrf token - but invalid user
  179. data["csrf_token"] = csrf_token
  180. output = self.app.post("/dologin", data=data, follow_redirects=True)
  181. self.assertEqual(output.status_code, 200)
  182. self.assertIn(
  183. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  184. )
  185. self.assertIn(
  186. '<form action="/dologin" method="post">',
  187. output.get_data(as_text=True),
  188. )
  189. self.assertIn(
  190. "Username or password invalid.", output.get_data(as_text=True)
  191. )
  192. # Create a local user
  193. self.test_new_user()
  194. items = pagure.lib.query.search_user(self.session)
  195. self.assertEqual(3, len(items))
  196. # Submit the form with the csrf token - but user not confirmed
  197. data["csrf_token"] = csrf_token
  198. output = self.app.post("/dologin", data=data, follow_redirects=True)
  199. self.assertEqual(output.status_code, 200)
  200. self.assertIn(
  201. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  202. )
  203. self.assertIn(
  204. '<form action="/dologin" method="post">',
  205. output.get_data(as_text=True),
  206. )
  207. self.assertIn(
  208. "Invalid user, did you confirm the creation with the url "
  209. "provided by email?",
  210. output.get_data(as_text=True),
  211. )
  212. # User in the DB, csrf provided - but wrong password submitted
  213. data["password"] = "password"
  214. output = self.app.post("/dologin", data=data, follow_redirects=True)
  215. self.assertEqual(output.status_code, 200)
  216. self.assertIn(
  217. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  218. )
  219. self.assertIn(
  220. '<form action="/dologin" method="post">',
  221. output.get_data(as_text=True),
  222. )
  223. self.assertIn(
  224. "Username or password invalid.", output.get_data(as_text=True)
  225. )
  226. # When account is not confirmed i.e user_obj != None
  227. data["password"] = "barpass"
  228. output = self.app.post("/dologin", data=data, follow_redirects=True)
  229. self.assertEqual(output.status_code, 200)
  230. self.assertIn(
  231. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  232. )
  233. self.assertIn(
  234. '<form action="/dologin" method="post">',
  235. output.get_data(as_text=True),
  236. )
  237. self.assertIn(
  238. "Invalid user, did you confirm the creation with the url "
  239. "provided by email?",
  240. output.get_data(as_text=True),
  241. )
  242. # Confirm the user so that we can log in
  243. self.session.commit()
  244. item = pagure.lib.query.search_user(self.session, username="foouser")
  245. self.assertEqual(item.user, "foouser")
  246. self.assertNotEqual(item.token, None)
  247. # Remove the token
  248. item.token = None
  249. self.session.add(item)
  250. self.session.commit()
  251. # Check the user
  252. item = pagure.lib.query.search_user(self.session, username="foouser")
  253. self.assertEqual(item.user, "foouser")
  254. self.assertEqual(item.token, None)
  255. # Login but cannot save the session to the DB due to the missing IP
  256. # address in the flask request
  257. data["password"] = "barpass"
  258. output = self.app.post("/dologin", data=data, follow_redirects=True)
  259. self.assertEqual(output.status_code, 200)
  260. output_text = output.get_data(as_text=True)
  261. self.assertIn("<title>Home - Pagure</title>", output_text)
  262. # I'm not sure if the change was in flask or werkzeug, but in older
  263. # version flask.request.remote_addr was returning None, while it
  264. # now returns 127.0.0.1 making our logic pass where it used to
  265. # partly fail
  266. if hasattr(flask, "__version__"):
  267. flask_v = tuple(int(el) for el in flask.__version__.split("."))
  268. if flask_v < (0, 12, 0):
  269. self.assertIn(
  270. '<a class="btn btn-primary" '
  271. 'href="/login/?next=http://localhost/">',
  272. output_text,
  273. )
  274. self.assertIn(
  275. "Could not set the session in the db, please report "
  276. "this error to an admin",
  277. output_text,
  278. )
  279. else:
  280. self.assertIn(
  281. '<a class="dropdown-item" '
  282. 'href="/logout/?next=http://localhost/dashboard/projects">',
  283. output_text,
  284. )
  285. # Make the password invalid
  286. self.session.commit()
  287. item = pagure.lib.query.search_user(self.session, username="foouser")
  288. self.assertEqual(item.user, "foouser")
  289. self.assertTrue(item.password.startswith("$2$"))
  290. # Remove the $2$
  291. item.password = item.password[3:]
  292. self.session.add(item)
  293. self.session.commit()
  294. # Check the password
  295. self.session.commit()
  296. item = pagure.lib.query.search_user(self.session, username="foouser")
  297. self.assertEqual(item.user, "foouser")
  298. self.assertFalse(item.password.startswith("$2$"))
  299. # Try login again
  300. output = self.app.post(
  301. "/dologin",
  302. data=data,
  303. follow_redirects=True,
  304. environ_base={"REMOTE_ADDR": "127.0.0.1"},
  305. )
  306. self.assertEqual(output.status_code, 200)
  307. self.assertIn(
  308. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  309. )
  310. self.assertIn(
  311. '<form action="/dologin" method="post">',
  312. output.get_data(as_text=True),
  313. )
  314. self.assertIn(
  315. "Username or password invalid.", output.get_data(as_text=True),
  316. )
  317. # Check the password is still not of a known version
  318. self.session.commit()
  319. item = pagure.lib.query.search_user(self.session, username="foouser")
  320. self.assertEqual(item.user, "foouser")
  321. self.assertFalse(item.password.startswith("$1$"))
  322. self.assertFalse(item.password.startswith("$2$"))
  323. # V1 password
  324. password = "%s%s" % ("barpass", None)
  325. if isinstance(password, six.text_type):
  326. password = password.encode("utf-8")
  327. password = hashlib.sha512(password).hexdigest().encode("utf-8")
  328. item.token = None
  329. item.password = b"$1$" + password
  330. self.session.add(item)
  331. self.session.commit()
  332. # Check the password
  333. self.session.commit()
  334. item = pagure.lib.query.search_user(self.session, username="foouser")
  335. self.assertEqual(item.user, "foouser")
  336. self.assertTrue(item.password.startswith(b"$1$"))
  337. # Log in with a v1 password
  338. output = self.app.post(
  339. "/dologin",
  340. data=data,
  341. follow_redirects=True,
  342. environ_base={"REMOTE_ADDR": "127.0.0.1"},
  343. )
  344. self.assertEqual(output.status_code, 200)
  345. output_text = output.get_data(as_text=True)
  346. self.assertIn("<title>Home - Pagure</title>", output_text)
  347. self.assertIn("Welcome foouser", output_text)
  348. self.assertIn("Activity", output_text)
  349. # Check the password got upgraded to version 2
  350. self.session.commit()
  351. item = pagure.lib.query.search_user(self.session, username="foouser")
  352. self.assertEqual(item.user, "foouser")
  353. self.assertTrue(item.password.startswith("$2$"))
  354. # We have set the REMOTE_ADDR in the request, so this works with all
  355. # versions of Flask.
  356. self.assertIn(
  357. '<a class="dropdown-item" '
  358. 'href="/logout/?next=http://localhost/dashboard/projects">',
  359. output_text,
  360. )
  361. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  362. @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
  363. def test_do_login_and_redirect(self):
  364. """ Test the do_login endpoint with a non-default redirect. """
  365. # This has all the data needed
  366. data = {
  367. "username": "foouser",
  368. "password": "barpass",
  369. "csrf_token": self.get_csrf(url="/login/"),
  370. "next_url": "http://localhost/test/",
  371. }
  372. # Create a local user
  373. self.test_new_user()
  374. self.session.commit()
  375. # Confirm the user so that we can log in
  376. item = pagure.lib.query.search_user(self.session, username="foouser")
  377. self.assertEqual(item.user, "foouser")
  378. self.assertNotEqual(item.token, None)
  379. # Remove the token
  380. item.token = None
  381. self.session.add(item)
  382. self.session.commit()
  383. # Check the user
  384. item = pagure.lib.query.search_user(self.session, username="foouser")
  385. self.assertEqual(item.user, "foouser")
  386. self.assertEqual(item.token, None)
  387. # Add a test project to the user
  388. tests.create_projects(self.session, user_id=3)
  389. tests.create_projects_git(os.path.join(self.path, "repos"))
  390. output = self.app.get("/test")
  391. output_text = output.get_data(as_text=True)
  392. self.assertEqual(output.status_code, 200)
  393. self.assertIn("<title>Overview - test - Pagure</title>", output_text)
  394. # Login and redirect to the test project
  395. output = self.app.post(
  396. "/dologin",
  397. data=data,
  398. follow_redirects=True,
  399. environ_base={"REMOTE_ADDR": "127.0.0.1"},
  400. )
  401. self.assertEqual(output.status_code, 200)
  402. output_text = output.get_data(as_text=True)
  403. self.assertIn("<title>Overview - test - Pagure</title>", output_text)
  404. self.assertIn(
  405. '<a class="dropdown-item" '
  406. 'href="/logout/?next=http://localhost/test/">',
  407. output_text,
  408. )
  409. self.assertIn(
  410. '<span class="d-none d-md-inline">Settings</span>', output_text
  411. )
  412. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  413. @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
  414. def test_has_settings(self):
  415. """ Test that user can see the Settings button when they are logged
  416. in. """
  417. # Create a local user
  418. self.test_new_user()
  419. self.session.commit()
  420. # Remove the token
  421. item = pagure.lib.query.search_user(self.session, username="foouser")
  422. item.token = None
  423. self.session.add(item)
  424. self.session.commit()
  425. # Check the user
  426. item = pagure.lib.query.search_user(self.session, username="foouser")
  427. self.assertEqual(item.user, "foouser")
  428. self.assertEqual(item.token, None)
  429. # Add a test project to the user
  430. tests.create_projects(self.session)
  431. tests.create_projects_git(os.path.join(self.path, "repos"))
  432. output = self.app.get("/test")
  433. output_text = output.get_data(as_text=True)
  434. self.assertEqual(output.status_code, 200)
  435. self.assertIn("<title>Overview - test - Pagure</title>", output_text)
  436. # Login and redirect to the test project
  437. user = tests.FakeUser(username="pingou")
  438. with tests.user_set(self.app.application, user):
  439. output = self.app.get("/test")
  440. self.assertEqual(output.status_code, 200)
  441. output_text = output.get_data(as_text=True)
  442. self.assertIn(
  443. "<title>Overview - test - Pagure</title>", output_text
  444. )
  445. self.assertIn(
  446. '<span class="d-none d-md-inline">Settings</span>', output_text
  447. )
  448. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  449. @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
  450. def test_non_ascii_password(self):
  451. """ Test login and create user functionality when the password is
  452. non-ascii.
  453. """
  454. # Check before:
  455. items = pagure.lib.query.search_user(self.session)
  456. self.assertEqual(2, len(items))
  457. # First access the new user page
  458. output = self.app.get("/user/new")
  459. self.assertEqual(output.status_code, 200)
  460. output_text = output.get_data(as_text=True)
  461. self.assertIn("<title>New user - Pagure</title>", output_text)
  462. self.assertIn('<form action="/user/new" method="post">', output_text)
  463. # Create the form to send there
  464. # This has all the data needed
  465. data = {
  466. "user": "foo",
  467. "fullname": "user foo",
  468. "email_address": "foo@bar.com",
  469. "password": "ö",
  470. "confirm_password": "ö",
  471. }
  472. # Submit this form - Doesn't work since there is no csrf token
  473. output = self.app.post("/user/new", data=data)
  474. self.assertEqual(output.status_code, 200)
  475. output_text = output.get_data(as_text=True)
  476. self.assertIn("<title>New user - Pagure</title>", output_text)
  477. self.assertIn('<form action="/user/new" method="post">', output_text)
  478. csrf_token = output_text.split(
  479. 'name="csrf_token" type="hidden" value="'
  480. )[1].split('">')[0]
  481. # Submit the form with the csrf token
  482. data["csrf_token"] = csrf_token
  483. output = self.app.post("/user/new", data=data, follow_redirects=True)
  484. self.assertEqual(output.status_code, 200)
  485. output_text = output.get_data(as_text=True)
  486. self.assertIn("<title>New user - Pagure</title>", output_text)
  487. self.assertIn('<form action="/user/new" method="post">', output_text)
  488. self.assertIn("Username already taken.", output_text)
  489. # Submit the form with another username
  490. data["user"] = "foobar"
  491. output = self.app.post("/user/new", data=data, follow_redirects=True)
  492. self.assertEqual(output.status_code, 200)
  493. output_text = output.get_data(as_text=True)
  494. self.assertIn("<title>New user - Pagure</title>", output_text)
  495. self.assertIn("Email address already taken.", output_text)
  496. # Submit the form with proper data
  497. data["email_address"] = "foobar@foobar.com"
  498. output = self.app.post("/user/new", data=data, follow_redirects=True)
  499. self.assertEqual(output.status_code, 200)
  500. output_text = output.get_data(as_text=True)
  501. self.assertIn("<title>Login - Pagure</title>", output_text)
  502. self.assertIn(
  503. "User created, please check your email to activate the account",
  504. output_text,
  505. )
  506. # Check after:
  507. items = pagure.lib.query.search_user(self.session)
  508. self.assertEqual(3, len(items))
  509. # Checking for the /login page
  510. output = self.app.get("/login/")
  511. self.assertEqual(output.status_code, 200)
  512. output_text = output.get_data(as_text=True)
  513. self.assertIn("<title>Login - Pagure</title>", output_text)
  514. self.assertIn('<form action="/dologin" method="post">', output_text)
  515. # This has all the data needed
  516. data = {"username": "foob_bar", "password": "ö"}
  517. # Submit this form - Doesn't work since there is no csrf token
  518. output = self.app.post("/dologin", data=data, follow_redirects=True)
  519. self.assertEqual(output.status_code, 200)
  520. output_text = output.get_data(as_text=True)
  521. self.assertIn("<title>Login - Pagure</title>", output_text)
  522. self.assertIn('<form action="/dologin" method="post">', output_text)
  523. self.assertIn("Insufficient information provided", output_text)
  524. # Submit the form with the csrf token - but invalid user
  525. data["csrf_token"] = csrf_token
  526. output = self.app.post("/dologin", data=data, follow_redirects=True)
  527. self.assertEqual(output.status_code, 200)
  528. output_text = output.get_data(as_text=True)
  529. self.assertIn("<title>Login - Pagure</title>", output_text)
  530. self.assertIn('<form action="/dologin" method="post">', output_text)
  531. self.assertIn("Username or password invalid.", output_text)
  532. # Submit the form with the csrf token - but user not confirmed
  533. data["username"] = "foobar"
  534. output = self.app.post("/dologin", data=data, follow_redirects=True)
  535. self.assertEqual(output.status_code, 200)
  536. output_text = output.get_data(as_text=True)
  537. self.assertIn("<title>Login - Pagure</title>", output_text)
  538. self.assertIn('<form action="/dologin" method="post">', output_text)
  539. self.assertIn(
  540. "Invalid user, did you confirm the creation with the url "
  541. "provided by email?",
  542. output_text,
  543. )
  544. # User in the DB, csrf provided - but wrong password submitted
  545. data["password"] = "öö"
  546. output = self.app.post("/dologin", data=data, follow_redirects=True)
  547. self.assertEqual(output.status_code, 200)
  548. output_text = output.get_data(as_text=True)
  549. self.assertIn("<title>Login - Pagure</title>", output_text)
  550. self.assertIn('<form action="/dologin" method="post">', output_text)
  551. self.assertIn("Username or password invalid.", output_text)
  552. # When account is not confirmed i.e user_obj != None
  553. data["password"] = "ö"
  554. output = self.app.post("/dologin", data=data, follow_redirects=True)
  555. self.assertEqual(output.status_code, 200)
  556. output_text = output.get_data(as_text=True)
  557. self.assertIn("<title>Login - Pagure</title>", output_text)
  558. self.assertIn('<form action="/dologin" method="post">', output_text)
  559. self.assertIn(
  560. "Invalid user, did you confirm the creation with the url "
  561. "provided by email?",
  562. output_text,
  563. )
  564. # Confirm the user so that we can log in
  565. item = pagure.lib.query.search_user(self.session, username="foobar")
  566. self.assertEqual(item.user, "foobar")
  567. self.assertNotEqual(item.token, None)
  568. # Remove the token
  569. item.token = None
  570. self.session.add(item)
  571. self.session.commit()
  572. # Login but cannot save the session to the DB due to the missing IP
  573. # address in the flask request
  574. data["password"] = "ö"
  575. output = self.app.post("/dologin", data=data, follow_redirects=True)
  576. self.assertEqual(output.status_code, 200)
  577. output_text = output.get_data(as_text=True)
  578. self.assertIn("<title>Home - Pagure</title>", output_text)
  579. # I'm not sure if the change was in flask or werkzeug, but in older
  580. # version flask.request.remote_addr was returning None, while it
  581. # now returns 127.0.0.1 making our logic pass where it used to
  582. # partly fail
  583. if hasattr(flask, "__version__"):
  584. flask_v = tuple(int(el) for el in flask.__version__.split("."))
  585. if flask_v <= (0, 12, 0):
  586. self.assertIn(
  587. '<a class="btn btn-primary" '
  588. 'href="/login/?next=http://localhost/">',
  589. output_text,
  590. )
  591. self.assertIn(
  592. "Could not set the session in the db, please report "
  593. "this error to an admin",
  594. output_text,
  595. )
  596. else:
  597. self.assertIn(
  598. '<a class="dropdown-item" '
  599. 'href="/logout/?next=http://localhost/dashboard/projects">',
  600. output_text,
  601. )
  602. # Check the user
  603. item = pagure.lib.query.search_user(self.session, username="foobar")
  604. self.assertEqual(item.user, "foobar")
  605. self.assertEqual(item.token, None)
  606. def test_confirm_user(self):
  607. """ Test the confirm_user endpoint. """
  608. output = self.app.get("/confirm/foo", follow_redirects=True)
  609. self.assertEqual(output.status_code, 200)
  610. self.assertIn(
  611. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  612. )
  613. self.assertIn(
  614. "No user associated with this token.",
  615. output.get_data(as_text=True),
  616. )
  617. # Create a local user
  618. self.test_new_user()
  619. items = pagure.lib.query.search_user(self.session)
  620. self.assertEqual(3, len(items))
  621. item = pagure.lib.query.search_user(self.session, username="foouser")
  622. self.assertEqual(item.user, "foouser")
  623. self.assertTrue(item.password.startswith("$2$"))
  624. self.assertNotEqual(item.token, None)
  625. output = self.app.get(
  626. "/confirm/%s" % item.token, follow_redirects=True
  627. )
  628. self.assertEqual(output.status_code, 200)
  629. self.assertIn(
  630. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  631. )
  632. self.assertIn(
  633. "Email confirmed, account activated", output.get_data(as_text=True)
  634. )
  635. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  636. @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
  637. def test_lost_password(self):
  638. """ Test the lost_password endpoint. """
  639. output = self.app.get("/password/lost")
  640. self.assertEqual(output.status_code, 200)
  641. self.assertIn(
  642. "<title>Lost password - Pagure</title>",
  643. output.get_data(as_text=True),
  644. )
  645. self.assertIn(
  646. '<form action="/password/lost" method="post">',
  647. output.get_data(as_text=True),
  648. )
  649. # Prepare the data to send
  650. data = {"username": "foouser"}
  651. # Missing CSRF
  652. output = self.app.post("/password/lost", data=data)
  653. self.assertEqual(output.status_code, 200)
  654. self.assertIn(
  655. "<title>Lost password - Pagure</title>",
  656. output.get_data(as_text=True),
  657. )
  658. self.assertIn(
  659. '<form action="/password/lost" method="post">',
  660. output.get_data(as_text=True),
  661. )
  662. csrf_token = (
  663. output.get_data(as_text=True)
  664. .split('name="csrf_token" type="hidden" value="')[1]
  665. .split('">')[0]
  666. )
  667. # With the CSRF - But invalid user
  668. data["csrf_token"] = csrf_token
  669. output = self.app.post(
  670. "/password/lost", data=data, follow_redirects=True
  671. )
  672. self.assertEqual(output.status_code, 200)
  673. self.assertIn(
  674. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  675. )
  676. self.assertIn("Username invalid.", output.get_data(as_text=True))
  677. # With the CSRF and a valid user
  678. data["username"] = "foo"
  679. output = self.app.post(
  680. "/password/lost", data=data, follow_redirects=True
  681. )
  682. self.assertEqual(output.status_code, 200)
  683. self.assertIn(
  684. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  685. )
  686. self.assertIn(
  687. "Check your email to finish changing your password",
  688. output.get_data(as_text=True),
  689. )
  690. # With the CSRF and a valid user - but too quick after the last one
  691. data["username"] = "foo"
  692. output = self.app.post(
  693. "/password/lost", data=data, follow_redirects=True
  694. )
  695. self.assertEqual(output.status_code, 200)
  696. self.assertIn(
  697. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  698. )
  699. self.assertIn(
  700. "An email was sent to you less than 3 minutes ago, did you "
  701. "check your spam folder? Otherwise, try again after some time.",
  702. output.get_data(as_text=True),
  703. )
  704. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  705. @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
  706. def test_reset_password(self):
  707. """ Test the reset_password endpoint. """
  708. output = self.app.get("/password/reset/foo", follow_redirects=True)
  709. self.assertEqual(output.status_code, 200)
  710. self.assertIn(
  711. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  712. )
  713. self.assertIn(
  714. "No user associated with this token.",
  715. output.get_data(as_text=True),
  716. )
  717. self.assertIn(
  718. '<form action="/dologin" method="post">',
  719. output.get_data(as_text=True),
  720. )
  721. self.test_lost_password()
  722. self.test_new_user()
  723. # Check the password
  724. item = pagure.lib.query.search_user(self.session, username="foouser")
  725. self.assertEqual(item.user, "foouser")
  726. self.assertNotEqual(item.token, None)
  727. self.assertTrue(item.password.startswith("$2$"))
  728. old_password = item.password
  729. token = item.token
  730. output = self.app.get(
  731. "/password/reset/%s" % token, follow_redirects=True
  732. )
  733. self.assertEqual(output.status_code, 200)
  734. self.assertIn(
  735. "<title>Change password - Pagure</title>",
  736. output.get_data(as_text=True),
  737. )
  738. self.assertIn(
  739. '<form action="/password/reset/', output.get_data(as_text=True)
  740. )
  741. data = {"password": "passwd", "confirm_password": "passwd"}
  742. # Missing CSRF
  743. output = self.app.post(
  744. "/password/reset/%s" % token, data=data, follow_redirects=True
  745. )
  746. self.assertEqual(output.status_code, 200)
  747. self.assertIn(
  748. "<title>Change password - Pagure</title>",
  749. output.get_data(as_text=True),
  750. )
  751. self.assertIn(
  752. '<form action="/password/reset/', output.get_data(as_text=True)
  753. )
  754. csrf_token = (
  755. output.get_data(as_text=True)
  756. .split('name="csrf_token" type="hidden" value="')[1]
  757. .split('">')[0]
  758. )
  759. # With CSRF
  760. data["csrf_token"] = csrf_token
  761. output = self.app.post(
  762. "/password/reset/%s" % token, data=data, follow_redirects=True
  763. )
  764. self.assertEqual(output.status_code, 200)
  765. self.assertIn(
  766. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  767. )
  768. self.assertIn("Password changed", output.get_data(as_text=True))
  769. @patch(
  770. "pagure.ui.login._check_session_cookie", MagicMock(return_value=True)
  771. )
  772. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  773. def test_change_password(self):
  774. """ Test the change_password endpoint. """
  775. # Not logged in, redirects
  776. output = self.app.get("/password/change", follow_redirects=True)
  777. self.assertEqual(output.status_code, 200)
  778. self.assertIn(
  779. "<title>Login - Pagure</title>", output.get_data(as_text=True)
  780. )
  781. self.assertIn(
  782. '<form action="/dologin" method="post">',
  783. output.get_data(as_text=True),
  784. )
  785. user = tests.FakeUser()
  786. with tests.user_set(self.app.application, user):
  787. output = self.app.get("/password/change")
  788. self.assertEqual(output.status_code, 404)
  789. self.assertIn("User not found", output.get_data(as_text=True))
  790. user = tests.FakeUser(username="foo")
  791. with tests.user_set(self.app.application, user):
  792. output = self.app.get("/password/change")
  793. self.assertEqual(output.status_code, 200)
  794. self.assertIn(
  795. "<title>Change password - Pagure</title>",
  796. output.get_data(as_text=True),
  797. )
  798. self.assertIn(
  799. '<form action="/password/change" method="post">',
  800. output.get_data(as_text=True),
  801. )
  802. data = {
  803. "old_password": "bfoo",
  804. "password": "foo",
  805. "confirm_password": "foo",
  806. }
  807. # No CSRF token
  808. output = self.app.post("/password/change", data=data)
  809. self.assertEqual(output.status_code, 200)
  810. self.assertIn(
  811. "<title>Change password - Pagure</title>",
  812. output.get_data(as_text=True),
  813. )
  814. self.assertIn(
  815. '<form action="/password/change" method="post">',
  816. output.get_data(as_text=True),
  817. )
  818. csrf_token = (
  819. output.get_data(as_text=True)
  820. .split('name="csrf_token" type="hidden" value="')[1]
  821. .split('">')[0]
  822. )
  823. # With CSRF - Invalid password format
  824. data["csrf_token"] = csrf_token
  825. output = self.app.post(
  826. "/password/change", data=data, follow_redirects=True
  827. )
  828. self.assertEqual(output.status_code, 200)
  829. self.assertIn(
  830. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  831. )
  832. self.assertIn(
  833. "Could not update your password, either user or password "
  834. "could not be checked",
  835. output.get_data(as_text=True),
  836. )
  837. self.test_new_user()
  838. # Remove token of foouser
  839. item = pagure.lib.query.search_user(self.session, username="foouser")
  840. self.assertEqual(item.user, "foouser")
  841. self.assertNotEqual(item.token, None)
  842. self.assertTrue(item.password.startswith("$2$"))
  843. item.token = None
  844. self.session.add(item)
  845. self.session.commit()
  846. user = tests.FakeUser(username="foouser")
  847. with tests.user_set(self.app.application, user):
  848. output = self.app.get("/password/change")
  849. self.assertEqual(output.status_code, 200)
  850. self.assertIn(
  851. "<title>Change password - Pagure</title>",
  852. output.get_data(as_text=True),
  853. )
  854. self.assertIn(
  855. '<form action="/password/change" method="post">',
  856. output.get_data(as_text=True),
  857. )
  858. data = {
  859. "old_password": "bfoo",
  860. "password": "foo",
  861. "confirm_password": "foo",
  862. }
  863. # No CSRF token
  864. output = self.app.post("/password/change", data=data)
  865. self.assertEqual(output.status_code, 200)
  866. self.assertIn(
  867. "<title>Change password - Pagure</title>",
  868. output.get_data(as_text=True),
  869. )
  870. self.assertIn(
  871. '<form action="/password/change" method="post">',
  872. output.get_data(as_text=True),
  873. )
  874. csrf_token = (
  875. output.get_data(as_text=True)
  876. .split('name="csrf_token" type="hidden" value="')[1]
  877. .split('">')[0]
  878. )
  879. # With CSRF - Incorrect password
  880. data["csrf_token"] = csrf_token
  881. output = self.app.post(
  882. "/password/change", data=data, follow_redirects=True
  883. )
  884. self.assertEqual(output.status_code, 200)
  885. self.assertIn(
  886. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  887. )
  888. self.assertIn(
  889. "Could not update your password, either user or password "
  890. "could not be checked",
  891. output.get_data(as_text=True),
  892. )
  893. # With CSRF - Correct password
  894. data["old_password"] = "barpass"
  895. output = self.app.post(
  896. "/password/change", data=data, follow_redirects=True
  897. )
  898. self.assertEqual(output.status_code, 200)
  899. self.assertIn(
  900. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  901. )
  902. self.assertIn("Password changed", output.get_data(as_text=True))
  903. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  904. def test_logout(self):
  905. """ Test the auth_logout endpoint for local login. """
  906. output = self.app.get("/logout/", follow_redirects=True)
  907. self.assertEqual(output.status_code, 200)
  908. self.assertIn(
  909. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  910. )
  911. self.assertNotIn(
  912. "You have been logged out", output.get_data(as_text=True)
  913. )
  914. self.assertIn(
  915. '<a class="btn btn-primary" '
  916. 'href="/login/?next=http://localhost/">',
  917. output.get_data(as_text=True),
  918. )
  919. user = tests.FakeUser(username="foo")
  920. with tests.user_set(self.app.application, user):
  921. output = self.app.get("/logout/", follow_redirects=True)
  922. self.assertEqual(output.status_code, 200)
  923. self.assertIn(
  924. "<title>Home - Pagure</title>", output.get_data(as_text=True)
  925. )
  926. self.assertIn(
  927. "You have been logged out", output.get_data(as_text=True)
  928. )
  929. # Due to the way the tests are running we do not actually
  930. # log out
  931. self.assertIn(
  932. '<a class="dropdown-item" href="/logout/?next='
  933. 'http://localhost/dashboard/projects">Log Out</a>',
  934. output.get_data(as_text=True),
  935. )
  936. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  937. def test_settings_admin_session_timedout(self):
  938. """ Test the admin_session_timedout with settings endpoint. """
  939. lifetime = pagure.config.config.get(
  940. "ADMIN_SESSION_LIFETIME", datetime.timedelta(minutes=15)
  941. )
  942. td1 = datetime.timedelta(minutes=1)
  943. # session already expired
  944. user = tests.FakeUser(username="foo")
  945. user.login_time = datetime.datetime.utcnow() - lifetime - td1
  946. with tests.user_set(self.app.application, user):
  947. # not following the redirect because user_set contextmanager
  948. # will run again for the login page and set back the user
  949. # which results in a loop, since admin_session_timedout will
  950. # redirect again for the login page
  951. output = self.app.get("/settings/")
  952. self.assertEqual(output.status_code, 302)
  953. self.assertIn("http://localhost/login/", output.location)
  954. # session did not expire
  955. user.login_time = datetime.datetime.utcnow() - lifetime + td1
  956. with tests.user_set(self.app.application, user):
  957. output = self.app.get("/settings/")
  958. self.assertEqual(output.status_code, 200)
  959. @patch("flask.flash")
  960. def test_admin_session_timedout(self, flash):
  961. """ Test the call to admin_session_timedout. """
  962. lifetime = pagure.config.config.get(
  963. "ADMIN_SESSION_LIFETIME", datetime.timedelta(minutes=15)
  964. )
  965. td1 = datetime.timedelta(minutes=1)
  966. # session already expired
  967. user = tests.FakeUser(username="foo")
  968. user.login_time = datetime.datetime.utcnow() - lifetime - td1
  969. with self.app.application.app_context() as ctx:
  970. ctx.g.session = self.session
  971. ctx.g.fas_user = user
  972. self.assertTrue(pagure.flask_app.admin_session_timedout())
  973. # session did not expire
  974. user.login_time = datetime.datetime.utcnow() - lifetime + td1
  975. with self.app.application.app_context() as ctx:
  976. ctx.g.session = self.session
  977. ctx.g.fas_user = user
  978. self.assertFalse(pagure.flask_app.admin_session_timedout())
  979. @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
  980. def test_force_logout(self):
  981. """ Test forcing logout. """
  982. user = tests.FakeUser(username="foo")
  983. with tests.user_set(self.app.application, user, keep_get_user=True):
  984. # Test that accessing settings works
  985. output = self.app.get("/settings")
  986. self.assertEqual(output.status_code, 200)
  987. # Now logout everywhere
  988. data = {"csrf_token": self.get_csrf()}
  989. output = self.app.post("/settings/forcelogout/", data=data)
  990. self.assertEqual(output.status_code, 302)
  991. self.assertEqual(
  992. output.headers["Location"], "http://localhost/settings"
  993. )
  994. # We should now get redirected to index, because our session became
  995. # invalid
  996. output = self.app.get("/settings")
  997. self.assertEqual(output.headers["Location"], "http://localhost/")
  998. # After changing the login_time to now, the session should again be
  999. # valid
  1000. user.login_time = datetime.datetime.utcnow()
  1001. output = self.app.get("/")
  1002. self.assertEqual(output.status_code, 302)
  1003. if __name__ == "__main__":
  1004. unittest.main(verbosity=2)