test_pagure_flask_ui_login.py 42 KB

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