Blob Blame Raw
# -*- coding: utf-8 -*-

"""
 (c) 2016 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>
   Farhaan Bukhsh <farhaan.bukhsh@gmail.com>

"""

from __future__ import unicode_literals, absolute_import

import datetime
import hashlib
import json
import unittest
import shutil
import sys
import tempfile
import os

import flask
import pygit2
import six
from mock import patch, MagicMock

sys.path.insert(
    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
)

import pagure.lib.query
import tests
from pagure.lib.repo import PagureRepo

import pagure.ui.login


class PagureFlaskLogintests(tests.SimplePagureTest):
    """ Tests for flask app controller of pagure """

    def setUp(self):
        """ Create the application with PAGURE_AUTH being local. """
        super(PagureFlaskLogintests, self).setUp()

        app = pagure.flask_app.create_app(
            {"DB_URL": self.dbpath, "PAGURE_AUTH": "local"}
        )
        # Remove the log handlers for the tests
        app.logger.handlers = []

        self.app = app.test_client()

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    def test_front_page(self):
        """ Test the front page. """
        # First access the front page
        output = self.app.get("/")
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Home - Pagure</title>", output.get_data(as_text=True)
        )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
    def test_new_user(self):
        """ Test the new_user endpoint. """

        # Check before:
        items = pagure.lib.query.search_user(self.session)
        self.assertEqual(2, len(items))

        # First access the new user page
        output = self.app.get("/user/new")
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>New user - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/user/new" method="post">',
            output.get_data(as_text=True),
        )

        # Create the form to send there

        # This has all the data needed
        data = {
            "user": "foo",
            "fullname": "user foo",
            "email_address": "foo@bar.com",
            "password": "barpass",
            "confirm_password": "barpass",
        }

        # Submit this form  -  Doesn't work since there is no csrf token
        output = self.app.post("/user/new", data=data)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>New user - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/user/new" method="post">',
            output.get_data(as_text=True),
        )

        csrf_token = (
            output.get_data(as_text=True)
            .split('name="csrf_token" type="hidden" value="')[1]
            .split('">')[0]
        )

        # Submit the form with the csrf token
        data["csrf_token"] = csrf_token
        output = self.app.post("/user/new", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>New user - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/user/new" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn("Username already taken.", output.get_data(as_text=True))

        # Submit the form with another username
        data["user"] = "foouser"
        output = self.app.post("/user/new", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>New user - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "Email address already taken.", output.get_data(as_text=True)
        )

        # Submit the form with proper data
        data["email_address"] = "foo@example.com"
        output = self.app.post("/user/new", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "User created, please check your email to activate the account",
            output.get_data(as_text=True),
        )

        # Check after:
        items = pagure.lib.query.search_user(self.session)
        self.assertEqual(3, len(items))

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
    def test_do_login(self):
        """ Test the do_login endpoint. """

        output = self.app.get("/login/")
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )

        # This has all the data needed
        data = {"username": "foouser", "password": "barpass"}

        # Submit this form  -  Doesn't work since there is no csrf token
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn(
            "Insufficient information provided", output.get_data(as_text=True)
        )

        csrf_token = (
            output.get_data(as_text=True)
            .split('name="csrf_token" type="hidden" value="')[1]
            .split('">')[0]
        )

        # Submit the form with the csrf token  -  but invalid user
        data["csrf_token"] = csrf_token
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn(
            "Username or password invalid.", output.get_data(as_text=True)
        )

        # Create a local user
        self.test_new_user()

        items = pagure.lib.query.search_user(self.session)
        self.assertEqual(3, len(items))

        # Submit the form with the csrf token  -  but user not confirmed
        data["csrf_token"] = csrf_token
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn(
            "Invalid user, did you confirm the creation with the url "
            "provided by email?",
            output.get_data(as_text=True),
        )

        # User in the DB, csrf provided  -  but wrong password submitted
        data["password"] = "password"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn(
            "Username or password invalid.", output.get_data(as_text=True)
        )

        # When account is not confirmed i.e user_obj != None
        data["password"] = "barpass"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn(
            "Invalid user, did you confirm the creation with the url "
            "provided by email?",
            output.get_data(as_text=True),
        )

        # Confirm the user so that we can log in
        self.session.commit()
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertNotEqual(item.token, None)

        # Remove the token
        item.token = None
        self.session.add(item)
        self.session.commit()

        # Check the user
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertEqual(item.token, None)

        # Login but cannot save the session to the DB due to the missing IP
        # address in the flask request
        data["password"] = "barpass"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Home - Pagure</title>", output_text)

        # I'm not sure if the change was in flask or werkzeug, but in older
        # version flask.request.remote_addr was returning None, while it
        # now returns 127.0.0.1 making our logic pass where it used to
        # partly fail
        if hasattr(flask, "__version__"):
            flask_v = tuple(int(el) for el in flask.__version__.split("."))
            if flask_v < (0, 12, 0):
                self.assertIn(
                    '<a class="btn btn-primary" '
                    'href="/login/?next=http://localhost/">',
                    output_text,
                )
                self.assertIn(
                    "Could not set the session in the db, please report "
                    "this error to an admin",
                    output_text,
                )
            else:
                self.assertIn(
                    '<a class="dropdown-item" '
                    'href="/logout/?next=http://localhost/dashboard/projects">',
                    output_text,
                )

        # Make the password invalid
        self.session.commit()
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertTrue(item.password.startswith("$2$"))

        # Remove the $2$
        item.password = item.password[3:]
        self.session.add(item)
        self.session.commit()

        # Check the password
        self.session.commit()
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertFalse(item.password.startswith("$2$"))

        # Try login again
        output = self.app.post(
            "/dologin",
            data=data,
            follow_redirects=True,
            environ_base={"REMOTE_ADDR": "127.0.0.1"},
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )
        self.assertIn(
            "Username or password of invalid format.",
            output.get_data(as_text=True),
        )

        # Check the password is still not of a known version
        self.session.commit()
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertFalse(item.password.startswith("$1$"))
        self.assertFalse(item.password.startswith("$2$"))

        # V1 password
        password = "%s%s" % ("barpass", None)
        if isinstance(password, six.text_type):
            password = password.encode("utf-8")
        password = hashlib.sha512(password).hexdigest().encode("utf-8")
        item.token = None
        item.password = b"$1$" + password
        self.session.add(item)
        self.session.commit()

        # Check the password
        self.session.commit()
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertTrue(item.password.startswith(b"$1$"))

        # Log in with a v1 password
        output = self.app.post(
            "/dologin",
            data=data,
            follow_redirects=True,
            environ_base={"REMOTE_ADDR": "127.0.0.1"},
        )
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Home - Pagure</title>", output_text)
        self.assertIn("Welcome foouser", output_text)
        self.assertIn("Activity", output_text)

        # Check the password got upgraded to version 2
        self.session.commit()
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertTrue(item.password.startswith("$2$"))

        # We have set the REMOTE_ADDR in the request, so this works with all
        # versions of Flask.
        self.assertIn(
            '<a class="dropdown-item" '
            'href="/logout/?next=http://localhost/dashboard/projects">',
            output_text,
        )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
    def test_do_login_and_redirect(self):
        """ Test the do_login endpoint with a non-default redirect. """
        # This has all the data needed
        data = {
            "username": "foouser",
            "password": "barpass",
            "csrf_token": self.get_csrf(url="/login/"),
            "next_url": "http://localhost/test/",
        }

        # Create a local user
        self.test_new_user()
        self.session.commit()

        # Confirm the user so that we can log in
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertNotEqual(item.token, None)

        # Remove the token
        item.token = None
        self.session.add(item)
        self.session.commit()

        # Check the user
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertEqual(item.token, None)

        # Add a test project to the user
        tests.create_projects(self.session, user_id=3)
        tests.create_projects_git(os.path.join(self.path, "repos"))
        output = self.app.get("/test")
        output_text = output.get_data(as_text=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn("<title>Overview - test - Pagure</title>", output_text)

        # Login and redirect to the test project
        output = self.app.post(
            "/dologin",
            data=data,
            follow_redirects=True,
            environ_base={"REMOTE_ADDR": "127.0.0.1"},
        )
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Overview - test - Pagure</title>", output_text)
        self.assertIn(
            '<a class="dropdown-item" '
            'href="/logout/?next=http://localhost/test/">',
            output_text,
        )
        self.assertIn(
            '<span class="d-none d-md-inline">Settings</span>', output_text
        )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
    def test_has_settings(self):
        """ Test that user can see the Settings button when they are logged
        in. """
        # Create a local user
        self.test_new_user()
        self.session.commit()

        # Remove the token
        item = pagure.lib.query.search_user(self.session, username="foouser")
        item.token = None
        self.session.add(item)
        self.session.commit()

        # Check the user
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertEqual(item.token, None)

        # Add a test project to the user
        tests.create_projects(self.session)
        tests.create_projects_git(os.path.join(self.path, "repos"))
        output = self.app.get("/test")
        output_text = output.get_data(as_text=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn("<title>Overview - test - Pagure</title>", output_text)

        # Login and redirect to the test project
        user = tests.FakeUser(username="pingou")
        with tests.user_set(self.app.application, user):
            output = self.app.get("/test")
            self.assertEqual(output.status_code, 200)
            output_text = output.get_data(as_text=True)
            self.assertIn(
                "<title>Overview - test - Pagure</title>", output_text
            )
            self.assertIn(
                '<span class="d-none d-md-inline">Settings</span>', output_text
            )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
    def test_non_ascii_password(self):
        """ Test login and create user functionality when the password is
            non-ascii.
        """

        # Check before:
        items = pagure.lib.query.search_user(self.session)
        self.assertEqual(2, len(items))

        # First access the new user page
        output = self.app.get("/user/new")
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>New user - Pagure</title>", output_text)
        self.assertIn('<form action="/user/new" method="post">', output_text)

        # Create the form to send there
        # This has all the data needed

        data = {
            "user": "foo",
            "fullname": "user foo",
            "email_address": "foo@bar.com",
            "password": "ö",
            "confirm_password": "ö",
        }

        # Submit this form  -  Doesn't work since there is no csrf token
        output = self.app.post("/user/new", data=data)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>New user - Pagure</title>", output_text)
        self.assertIn('<form action="/user/new" method="post">', output_text)

        csrf_token = output_text.split(
            'name="csrf_token" type="hidden" value="'
        )[1].split('">')[0]

        # Submit the form with the csrf token
        data["csrf_token"] = csrf_token
        output = self.app.post("/user/new", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>New user - Pagure</title>", output_text)
        self.assertIn('<form action="/user/new" method="post">', output_text)
        self.assertIn("Username already taken.", output_text)

        # Submit the form with another username
        data["user"] = "foobar"
        output = self.app.post("/user/new", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>New user - Pagure</title>", output_text)
        self.assertIn("Email address already taken.", output_text)

        # Submit the form with proper data
        data["email_address"] = "foobar@foobar.com"
        output = self.app.post("/user/new", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn(
            "User created, please check your email to activate the account",
            output_text,
        )

        # Check after:
        items = pagure.lib.query.search_user(self.session)
        self.assertEqual(3, len(items))

        # Checking for the /login page
        output = self.app.get("/login/")
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn('<form action="/dologin" method="post">', output_text)

        # This has all the data needed
        data = {"username": "foob_bar", "password": "ö"}

        # Submit this form  -  Doesn't work since there is no csrf token
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn('<form action="/dologin" method="post">', output_text)
        self.assertIn("Insufficient information provided", output_text)

        # Submit the form with the csrf token  -  but invalid user
        data["csrf_token"] = csrf_token
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn('<form action="/dologin" method="post">', output_text)
        self.assertIn("Username or password invalid.", output_text)

        # Submit the form with the csrf token  -  but user not confirmed
        data["username"] = "foobar"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn('<form action="/dologin" method="post">', output_text)
        self.assertIn(
            "Invalid user, did you confirm the creation with the url "
            "provided by email?",
            output_text,
        )

        # User in the DB, csrf provided  -  but wrong password submitted
        data["password"] = "öö"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn('<form action="/dologin" method="post">', output_text)
        self.assertIn("Username or password invalid.", output_text)

        # When account is not confirmed i.e user_obj != None
        data["password"] = "ö"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Login - Pagure</title>", output_text)
        self.assertIn('<form action="/dologin" method="post">', output_text)
        self.assertIn(
            "Invalid user, did you confirm the creation with the url "
            "provided by email?",
            output_text,
        )

        # Confirm the user so that we can log in
        item = pagure.lib.query.search_user(self.session, username="foobar")
        self.assertEqual(item.user, "foobar")
        self.assertNotEqual(item.token, None)

        # Remove the token
        item.token = None
        self.session.add(item)
        self.session.commit()

        # Login but cannot save the session to the DB due to the missing IP
        # address in the flask request
        data["password"] = "ö"
        output = self.app.post("/dologin", data=data, follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        output_text = output.get_data(as_text=True)
        self.assertIn("<title>Home - Pagure</title>", output_text)

        # I'm not sure if the change was in flask or werkzeug, but in older
        # version flask.request.remote_addr was returning None, while it
        # now returns 127.0.0.1 making our logic pass where it used to
        # partly fail
        if hasattr(flask, "__version__"):
            flask_v = tuple(int(el) for el in flask.__version__.split("."))
            if flask_v <= (0, 12, 0):
                self.assertIn(
                    '<a class="btn btn-primary" '
                    'href="/login/?next=http://localhost/">',
                    output_text,
                )
                self.assertIn(
                    "Could not set the session in the db, please report "
                    "this error to an admin",
                    output_text,
                )
            else:
                self.assertIn(
                    '<a class="dropdown-item" '
                    'href="/logout/?next=http://localhost/dashboard/projects">',
                    output_text,
                )

        # Check the user
        item = pagure.lib.query.search_user(self.session, username="foobar")
        self.assertEqual(item.user, "foobar")
        self.assertEqual(item.token, None)

    def test_confirm_user(self):
        """ Test the confirm_user endpoint. """

        output = self.app.get("/confirm/foo", follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Home - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "No user associated with this token.",
            output.get_data(as_text=True),
        )

        # Create a local user
        self.test_new_user()

        items = pagure.lib.query.search_user(self.session)
        self.assertEqual(3, len(items))
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertTrue(item.password.startswith("$2$"))
        self.assertNotEqual(item.token, None)

        output = self.app.get(
            "/confirm/%s" % item.token, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "Email confirmed, account activated", output.get_data(as_text=True)
        )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
    def test_lost_password(self):
        """ Test the lost_password endpoint. """

        output = self.app.get("/password/lost")
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Lost password - Pagure</title>",
            output.get_data(as_text=True),
        )
        self.assertIn(
            '<form action="/password/lost" method="post">',
            output.get_data(as_text=True),
        )

        # Prepare the data to send
        data = {"username": "foouser"}

        # Missing CSRF
        output = self.app.post("/password/lost", data=data)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Lost password - Pagure</title>",
            output.get_data(as_text=True),
        )
        self.assertIn(
            '<form action="/password/lost" method="post">',
            output.get_data(as_text=True),
        )

        csrf_token = (
            output.get_data(as_text=True)
            .split('name="csrf_token" type="hidden" value="')[1]
            .split('">')[0]
        )

        # With the CSRF  -  But invalid user
        data["csrf_token"] = csrf_token
        output = self.app.post(
            "/password/lost", data=data, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn("Username invalid.", output.get_data(as_text=True))

        # With the CSRF and a valid user
        data["username"] = "foo"
        output = self.app.post(
            "/password/lost", data=data, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "Check your email to finish changing your password",
            output.get_data(as_text=True),
        )

        # With the CSRF and a valid user  -  but too quick after the last one
        data["username"] = "foo"
        output = self.app.post(
            "/password/lost", data=data, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "An email was sent to you less than 3 minutes ago, did you "
            "check your spam folder? Otherwise, try again after some time.",
            output.get_data(as_text=True),
        )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
    def test_reset_password(self):
        """ Test the reset_password endpoint. """

        output = self.app.get("/password/reset/foo", follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            "No user associated with this token.",
            output.get_data(as_text=True),
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )

        self.test_lost_password()
        self.test_new_user()

        # Check the password
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertNotEqual(item.token, None)
        self.assertTrue(item.password.startswith("$2$"))

        old_password = item.password
        token = item.token

        output = self.app.get(
            "/password/reset/%s" % token, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Change password - Pagure</title>",
            output.get_data(as_text=True),
        )
        self.assertIn(
            '<form action="/password/reset/', output.get_data(as_text=True)
        )

        data = {"password": "passwd", "confirm_password": "passwd"}

        # Missing CSRF
        output = self.app.post(
            "/password/reset/%s" % token, data=data, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Change password - Pagure</title>",
            output.get_data(as_text=True),
        )
        self.assertIn(
            '<form action="/password/reset/', output.get_data(as_text=True)
        )

        csrf_token = (
            output.get_data(as_text=True)
            .split('name="csrf_token" type="hidden" value="')[1]
            .split('">')[0]
        )

        # With CSRF
        data["csrf_token"] = csrf_token
        output = self.app.post(
            "/password/reset/%s" % token, data=data, follow_redirects=True
        )
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn("Password changed", output.get_data(as_text=True))

    @patch(
        "pagure.ui.login._check_session_cookie", MagicMock(return_value=True)
    )
    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    def test_change_password(self):
        """ Test the change_password endpoint. """

        # Not logged in, redirects
        output = self.app.get("/password/change", follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Login - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertIn(
            '<form action="/dologin" method="post">',
            output.get_data(as_text=True),
        )

        user = tests.FakeUser()
        with tests.user_set(self.app.application, user):
            output = self.app.get("/password/change")
            self.assertEqual(output.status_code, 404)
            self.assertIn("User not found", output.get_data(as_text=True))

        user = tests.FakeUser(username="foo")
        with tests.user_set(self.app.application, user):
            output = self.app.get("/password/change")
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Change password - Pagure</title>",
                output.get_data(as_text=True),
            )
            self.assertIn(
                '<form action="/password/change" method="post">',
                output.get_data(as_text=True),
            )

            data = {
                "old_password": "foo",
                "password": "foo",
                "confirm_password": "foo",
            }

            # No CSRF token
            output = self.app.post("/password/change", data=data)
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Change password - Pagure</title>",
                output.get_data(as_text=True),
            )
            self.assertIn(
                '<form action="/password/change" method="post">',
                output.get_data(as_text=True),
            )

            csrf_token = (
                output.get_data(as_text=True)
                .split('name="csrf_token" type="hidden" value="')[1]
                .split('">')[0]
            )

            # With CSRF  -  Invalid password format
            data["csrf_token"] = csrf_token
            output = self.app.post(
                "/password/change", data=data, follow_redirects=True
            )
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Home - Pagure</title>", output.get_data(as_text=True)
            )
            self.assertIn(
                "Could not update your password, either user or password "
                "could not be checked",
                output.get_data(as_text=True),
            )

        self.test_new_user()

        # Remove token of foouser
        item = pagure.lib.query.search_user(self.session, username="foouser")
        self.assertEqual(item.user, "foouser")
        self.assertNotEqual(item.token, None)
        self.assertTrue(item.password.startswith("$2$"))
        item.token = None
        self.session.add(item)
        self.session.commit()

        user = tests.FakeUser(username="foouser")
        with tests.user_set(self.app.application, user):
            output = self.app.get("/password/change")
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Change password - Pagure</title>",
                output.get_data(as_text=True),
            )
            self.assertIn(
                '<form action="/password/change" method="post">',
                output.get_data(as_text=True),
            )

            data = {
                "old_password": "foo",
                "password": "foo",
                "confirm_password": "foo",
            }

            # No CSRF token
            output = self.app.post("/password/change", data=data)
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Change password - Pagure</title>",
                output.get_data(as_text=True),
            )
            self.assertIn(
                '<form action="/password/change" method="post">',
                output.get_data(as_text=True),
            )

            csrf_token = (
                output.get_data(as_text=True)
                .split('name="csrf_token" type="hidden" value="')[1]
                .split('">')[0]
            )

            # With CSRF  -  Incorrect password
            data["csrf_token"] = csrf_token
            output = self.app.post(
                "/password/change", data=data, follow_redirects=True
            )
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Home - Pagure</title>", output.get_data(as_text=True)
            )
            self.assertIn(
                "Could not update your password, either user or password "
                "could not be checked",
                output.get_data(as_text=True),
            )

            # With CSRF  -  Correct password
            data["old_password"] = "barpass"
            output = self.app.post(
                "/password/change", data=data, follow_redirects=True
            )
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Home - Pagure</title>", output.get_data(as_text=True)
            )
            self.assertIn("Password changed", output.get_data(as_text=True))

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    def test_logout(self):
        """ Test the auth_logout endpoint for local login. """

        output = self.app.get("/logout/", follow_redirects=True)
        self.assertEqual(output.status_code, 200)
        self.assertIn(
            "<title>Home - Pagure</title>", output.get_data(as_text=True)
        )
        self.assertNotIn(
            "You have been logged out", output.get_data(as_text=True)
        )
        self.assertIn(
            '<a class="btn btn-primary" '
            'href="/login/?next=http://localhost/">',
            output.get_data(as_text=True),
        )

        user = tests.FakeUser(username="foo")
        with tests.user_set(self.app.application, user):
            output = self.app.get("/logout/", follow_redirects=True)
            self.assertEqual(output.status_code, 200)
            self.assertIn(
                "<title>Home - Pagure</title>", output.get_data(as_text=True)
            )
            self.assertIn(
                "You have been logged out", output.get_data(as_text=True)
            )
            # Due to the way the tests are running we do not actually
            # log out
            self.assertIn(
                '<a class="dropdown-item" href="/logout/?next='
                'http://localhost/dashboard/projects">Log Out</a>',
                output.get_data(as_text=True),
            )

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    def test_settings_admin_session_timedout(self):
        """ Test the admin_session_timedout with settings endpoint. """
        lifetime = pagure.config.config.get(
            "ADMIN_SESSION_LIFETIME", datetime.timedelta(minutes=15)
        )
        td1 = datetime.timedelta(minutes=1)
        # session already expired
        user = tests.FakeUser(username="foo")
        user.login_time = datetime.datetime.utcnow() - lifetime - td1
        with tests.user_set(self.app.application, user):
            # not following the redirect because user_set contextmanager
            # will run again for the login page and set back the user
            # which results in a loop, since admin_session_timedout will
            # redirect again for the login page
            output = self.app.get("/settings/")
            self.assertEqual(output.status_code, 302)
            self.assertIn("http://localhost/login/", output.location)
        # session did not expire
        user.login_time = datetime.datetime.utcnow() - lifetime + td1
        with tests.user_set(self.app.application, user):
            output = self.app.get("/settings/")
            self.assertEqual(output.status_code, 200)

    @patch("flask.flash")
    @patch("flask.g")
    @patch("flask.session")
    def test_admin_session_timedout(self, session, g, flash):
        """ Test the call to admin_session_timedout. """
        lifetime = pagure.config.config.get(
            "ADMIN_SESSION_LIFETIME", datetime.timedelta(minutes=15)
        )
        td1 = datetime.timedelta(minutes=1)
        # session already expired
        user = tests.FakeUser(username="foo")
        user.login_time = datetime.datetime.utcnow() - lifetime - td1
        g.fas_user = user
        self.assertTrue(pagure.flask_app.admin_session_timedout())
        # session did not expire
        user.login_time = datetime.datetime.utcnow() - lifetime + td1
        g.fas_user = user
        self.assertFalse(pagure.flask_app.admin_session_timedout())

    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
    def test_force_logout(self):
        """ Test forcing logout. """
        user = tests.FakeUser(username="foo")
        with tests.user_set(self.app.application, user, keep_get_user=True):
            # Test that accessing settings works
            output = self.app.get("/settings")
            self.assertEqual(output.status_code, 200)

            # Now logout everywhere
            data = {"csrf_token": self.get_csrf()}
            output = self.app.post("/settings/forcelogout/", data=data)
            self.assertEqual(output.status_code, 302)
            self.assertEqual(
                output.headers["Location"], "http://localhost/settings"
            )

            # We should now get redirected to index, because our session became
            # invalid
            output = self.app.get("/settings")
            self.assertEqual(output.headers["Location"], "http://localhost/")

            # After changing the login_time to now, the session should again be
            # valid
            user.login_time = datetime.datetime.utcnow()
            output = self.app.get("/")
            self.assertEqual(output.status_code, 302)


if __name__ == "__main__":
    unittest.main(verbosity=2)