test_pagure_flask_ui_login.py 39 KB

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