test_pagure_flask_ui_login.py 43 KB

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