예제 #1
1
def unauthorized_redirect(app):
    lm = LoginManager()
    lm.login_view = "login"
    lm.setup_app(app)
    res = lm.unauthorized()
    assert res.headers["Location"] == "/login?next=%2F"
    assert LOGIN_MESSAGE in get_flashed_messages()
예제 #2
0
def login_message(app):
    lm = LoginManager()
    lm.login_view = "login"
    lm.login_message = u"Log in or the owl will eat you."
    lm.setup_app(app)
    lm.unauthorized()
    assert u"Log in or the owl will eat you." in get_flashed_messages()
예제 #3
0
def login_message(app):
    lm = LoginManager()
    lm.login_view = "login"
    lm.login_message = u"Log in or the owl will eat you."
    lm.setup_app(app)
    lm.unauthorized()
    assert u"Log in or the owl will eat you." in get_flashed_messages()
예제 #4
0
def unauthorized_redirect(app):
    lm = LoginManager()
    lm.login_view = "login"
    lm.setup_app(app)
    res = lm.unauthorized()
    assert res.headers["Location"] == "/login?next=%2F"
    assert LOGIN_MESSAGE in get_flashed_messages()
예제 #5
0
class AuthenticationModule(SystemModule):
    def __init__(self, *args, **kwargs):
        super(AuthenticationModule, self).__init__(*args, **kwargs)
        self.login_manager = LoginManager()
        self.login_manager.login_message_category = "warning"
        self.login_manager.user_loader(self.load_user)
        self.logged_in_menu = DropdownMenu()
        self.login_text = mydomain.lazy_gettext('Login...')
        self.logged_in_user_text = mydomain.lazy_gettext('User: '******'.' + view.__name__
        return view

    def register(self, app, *args, **kwargs):
        super(AuthenticationModule, self).register(app, *args, **kwargs)
        self.login_manager.init_app(app)

    def login_url(self):
        return login_url(self.login_manager.login_view,
                         request.url)

    def logged_in_text(self):
        return self.logged_in_user_text + ' ' + self.get_current_user_name()

    def get_current_user_name(self):
        return current_user.get_name()

    def user_menu(self, text, group='', values={}):
        def wrap(view):
            name = self.name + '.' + view.__name__
            entry = self.menu_entry_for_view(view, text, values=values)
            self.logged_in_menu.add_entry(name, entry, group)
            return view
        return wrap

    def get_anonymous_system_menu(self):
        return MenuItem(text=self.login_text,
                        url=self.login_url())
    

    def get_system_menu_item(self):
        if current_user.is_anonymous():
            return self.get_anonymous_system_menu()
        else:
            return MenuItem(text=self.logged_in_text(),
                            items=self.logged_in_menu.build_real_menu())

    def handle_forbidden_endpoint(self):
        if current_user.is_anonymous():
            return self.login_manager.unauthorized()
        else:
            return self.forbidden()

    def forbidden(self):
        return abort(403)
예제 #6
0
def unauthorized_callback(app):
    lm = LoginManager()
    lm.login_view = "login"
    @lm.unauthorized_handler
    def unauth():
        return "UNAUTHORIZED!"
    lm.setup_app(app)
    assert lm.unauthorized() == "UNAUTHORIZED!"
    assert len(get_flashed_messages()) == 0
예제 #7
0
class AuthenticationModule(SystemModule):
    def __init__(self, *args, **kwargs):
        super(AuthenticationModule, self).__init__(*args, **kwargs)
        self.login_manager = LoginManager()
        self.login_manager.login_message_category = "warning"
        self.login_manager.user_loader(self.load_user)
        self.logged_in_menu = DropdownMenu()
        self.login_text = mydomain.lazy_gettext('Login...')
        self.logged_in_user_text = mydomain.lazy_gettext('User: '******'.' + view.__name__
        return view

    def register(self, app, *args, **kwargs):
        super(AuthenticationModule, self).register(app, *args, **kwargs)
        self.login_manager.init_app(app)

    def login_url(self):
        return login_url(self.login_manager.login_view, request.url)

    def logged_in_text(self):
        return self.logged_in_user_text + ' ' + self.get_current_user_name()

    def get_current_user_name(self):
        return current_user.get_name()

    def user_menu(self, text, group='', values={}):
        def wrap(view):
            name = self.name + '.' + view.__name__
            entry = self.menu_entry_for_view(view, text, values=values)
            self.logged_in_menu.add_entry(name, entry, group)
            return view

        return wrap

    def get_anonymous_system_menu(self):
        return MenuItem(text=self.login_text, url=self.login_url())

    def get_system_menu_item(self):
        if current_user.is_anonymous():
            return self.get_anonymous_system_menu()
        else:
            return MenuItem(text=self.logged_in_text(),
                            items=self.logged_in_menu.build_real_menu())

    def handle_forbidden_endpoint(self):
        if current_user.is_anonymous():
            return self.login_manager.unauthorized()
        else:
            return self.forbidden()

    def forbidden(self):
        return abort(403)
예제 #8
0
def unauthorized_callback(app):
    lm = LoginManager()
    lm.login_view = "login"

    @lm.unauthorized_handler
    def unauth():
        return "UNAUTHORIZED!"

    lm.setup_app(app)
    assert lm.unauthorized() == "UNAUTHORIZED!"
    assert len(get_flashed_messages()) == 0
예제 #9
0
class LoginTestCase(unittest.TestCase):
    """ Tests for results of the login_user function """

    def setUp(self):
        self.app = Flask(__name__)
        self.app.config["SECRET_KEY"] = "deterministic"
        self.app.config["SESSION_PROTECTION"] = None
        self.app.config["TESTING"] = True
        self.remember_cookie_name = "remember"
        self.app.config["REMEMBER_COOKIE_NAME"] = self.remember_cookie_name
        self.login_manager = LoginManager()
        self.login_manager.init_app(self.app)
        self.login_manager._login_disabled = False

        @self.app.route("/")
        def index():
            return u"Welcome!"

        @self.app.route("/secret")
        def secret():
            return self.login_manager.unauthorized()

        @self.app.route("/login-notch")
        def login_notch():
            return unicode(login_user(notch))

        @self.app.route("/login-notch-remember")
        def login_notch_remember():
            return unicode(login_user(notch, remember=True))

        @self.app.route("/login-notch-permanent")
        def login_notch_permanent():
            session.permanent = True
            return unicode(login_user(notch))

        @self.app.route("/needs-refresh")
        def needs_refresh():
            return self.login_manager.needs_refresh()

        @self.app.route("/confirm-login")
        def _confirm_login():
            confirm_login()
            return u""

        @self.app.route("/username")
        def username():
            if current_user.is_authenticated():
                return current_user.name
            return u"Anonymous"

        @self.app.route("/is-fresh")
        def is_fresh():
            return unicode(login_fresh())

        @self.app.route("/logout")
        def logout():
            return unicode(logout_user())

        @self.login_manager.user_loader
        def load_user(user_id):
            return USERS[int(user_id)]

        @self.login_manager.header_loader
        def load_user_from_header(header_value):
            if header_value.startswith("Basic "):
                header_value = header_value.replace("Basic ", "", 1)
            try:
                user_id = base64.b64decode(header_value)
            except TypeError:
                pass
            return USERS.get(int(user_id))

        @self.login_manager.request_loader
        def load_user_from_request(request):
            user_id = request.args.get("user_id")
            try:
                user_id = int(float(user_id))
            except TypeError:
                pass
            return USERS.get(user_id)

        @self.app.route("/empty_session")
        def empty_session():
            return unicode(u"modified=%s" % session.modified)

        # This will help us with the possibility of typoes in the tests. Now
        # we shouldn't have to check each response to help us set up state
        # (such as login pages) to make sure it worked: we will always
        # get an exception raised (rather than return a 404 response)
        @self.app.errorhandler(404)
        def handle_404(e):
            raise e

        unittest.TestCase.setUp(self)

    def _get_remember_cookie(self, test_client):
        our_cookies = test_client.cookie_jar._cookies["localhost.local"]["/"]
        return our_cookies[self.remember_cookie_name]

    def _delete_session(self, c):
        # Helper method to cause the session to be deleted
        # as if the browser was closed. This will remove
        # the session regardless of the permament flag
        # on the session!
        with c.session_transaction() as sess:
            sess.clear()

    #
    # Login
    #
    def test_test_request_context_users_are_anonymous(self):
        with self.app.test_request_context():
            self.assertTrue(current_user.is_anonymous())

    def test_defaults_anonymous(self):
        with self.app.test_client() as c:
            result = c.get("/username")
            self.assertEqual(u"Anonymous", result.data.decode("utf-8"))

    def test_login_user(self):
        with self.app.test_request_context():
            result = login_user(notch)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u"Notch")

    def test_login_user_emits_signal(self):
        with self.app.test_request_context():
            with listen_to(user_logged_in) as listener:
                login_user(notch)
                listener.assert_heard_one(self.app, user=notch)

    def test_login_inactive_user(self):
        with self.app.test_request_context():
            result = login_user(creeper)
            self.assertTrue(current_user.is_anonymous())
            self.assertFalse(result)

    def test_login_inactive_user_forced(self):
        with self.app.test_request_context():
            login_user(creeper, force=True)
            self.assertEqual(current_user.name, u"Creeper")

    def test_login_user_with_header(self):
        user_id = 2
        user_name = USERS[user_id].name
        with self.app.test_client() as c:
            basic_fmt = "Basic {0}"
            decoded = bytes.decode(base64.b64encode(str.encode(str(user_id))))
            headers = [("Authorization", basic_fmt.format(decoded))]
            result = c.get("/username", headers=headers)
            self.assertEqual(user_name, result.data.decode("utf-8"))

    def test_login_invalid_user_with_header(self):
        user_id = 4
        user_name = u"Anonymous"
        with self.app.test_client() as c:
            basic_fmt = "Basic {0}"
            decoded = bytes.decode(base64.b64encode(str.encode(str(user_id))))
            headers = [("Authorization", basic_fmt.format(decoded))]
            result = c.get("/username", headers=headers)
            self.assertEqual(user_name, result.data.decode("utf-8"))

    def test_login_user_with_request(self):
        user_id = 2
        user_name = USERS[user_id].name
        with self.app.test_client() as c:
            url = "/username?user_id={user_id}".format(user_id=user_id)
            result = c.get(url)
            self.assertEqual(user_name, result.data.decode("utf-8"))

    def test_login_invalid_user_with_request(self):
        user_id = 4
        user_name = u"Anonymous"
        with self.app.test_client() as c:
            url = "/username?user_id={user_id}".format(user_id=user_id)
            result = c.get(url)
            self.assertEqual(user_name, result.data.decode("utf-8"))

    #
    # Logout
    #
    def test_logout_logs_out_current_user(self):
        with self.app.test_request_context():
            login_user(notch)
            logout_user()
            self.assertTrue(current_user.is_anonymous())

    def test_logout_emits_signal(self):
        with self.app.test_request_context():
            login_user(notch)
            with listen_to(user_logged_out) as listener:
                logout_user()
                listener.assert_heard_one(self.app, user=notch)

    #
    # Unauthorized
    #
    def test_unauthorized_fires_unauthorized_signal(self):
        with self.app.test_client() as c:
            with listen_to(user_unauthorized) as listener:
                c.get("/secret")
                listener.assert_heard_one(self.app)

    def test_unauthorized_flashes_message_with_login_view(self):
        self.login_manager.login_view = "/login"

        expected_message = self.login_manager.login_message = u"Log in!"
        expected_category = self.login_manager.login_message_category = "login"

        with self.app.test_client() as c:
            c.get("/secret")
            msgs = get_flashed_messages(category_filter=[expected_category])
            self.assertEqual([expected_message], msgs)

    def test_unauthorized_flash_message_localized(self):
        def _gettext(msg):
            if msg == u"Log in!":
                return u"Einloggen"

        self.login_manager.login_view = "/login"
        self.login_manager.localize_callback = _gettext
        self.login_manager.login_message = u"Log in!"

        expected_message = u"Einloggen"
        expected_category = self.login_manager.login_message_category = "login"

        with self.app.test_client() as c:
            c.get("/secret")
            msgs = get_flashed_messages(category_filter=[expected_category])
            self.assertEqual([expected_message], msgs)
        self.login_manager.localize_callback = None

    def test_unauthorized_uses_authorized_handler(self):
        @self.login_manager.unauthorized_handler
        def _callback():
            return Response("This is secret!", 401)

        with self.app.test_client() as c:
            result = c.get("/secret")
            self.assertEqual(result.status_code, 401)
            self.assertEqual(u"This is secret!", result.data.decode("utf-8"))

    def test_unauthorized_aborts_with_401(self):
        with self.app.test_client() as c:
            result = c.get("/secret")
            self.assertEqual(result.status_code, 401)

    def test_unauthorized_redirects_to_login_view(self):
        self.login_manager.login_view = "login"

        @self.app.route("/login")
        def login():
            return "Login Form Goes Here!"

        with self.app.test_client() as c:
            result = c.get("/secret")
            self.assertEqual(result.status_code, 302)
            self.assertEqual(result.location, "http://localhost/login?next=%2Fsecret")

    #
    # Session Persistence/Freshness
    #
    def test_login_persists(self):
        with self.app.test_client() as c:
            c.get("/login-notch")
            result = c.get("/username")

            self.assertEqual(u"Notch", result.data.decode("utf-8"))

    def test_logout_persists(self):
        with self.app.test_client() as c:
            c.get("/login-notch")
            c.get("/logout")
            result = c.get("/username")
            self.assertEqual(result.data.decode("utf-8"), u"Anonymous")

    def test_incorrect_id_logs_out(self):
        # Ensure that any attempt to reload the user by the ID
        # will seem as if the user is no longer valid
        @self.login_manager.user_loader
        def new_user_loader(user_id):
            return

        with self.app.test_client() as c:
            # Successfully logs in
            c.get("/login-notch")
            result = c.get("/username")

            self.assertEqual(u"Anonymous", result.data.decode("utf-8"))

    def test_authentication_is_fresh(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            result = c.get("/is-fresh")
            self.assertEqual(u"True", result.data.decode("utf-8"))

    def test_remember_me(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)
            result = c.get("/username")
            self.assertEqual(u"Notch", result.data.decode("utf-8"))

    def test_remember_me_uses_custom_cookie_parameters(self):
        name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname"
        duration = self.app.config["REMEMBER_COOKIE_DURATION"] = timedelta(days=2)
        domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local"

        with self.app.test_client() as c:
            c.get("/login-notch-remember")

            # TODO: Is there a better way to test this?
            self.assertTrue(domain in c.cookie_jar._cookies, "Custom domain not found as cookie domain")
            domain_cookie = c.cookie_jar._cookies[domain]
            self.assertTrue(name in domain_cookie["/"], "Custom name not found as cookie name")
            cookie = domain_cookie["/"][name]

            expiration_date = datetime.fromtimestamp(cookie.expires)
            expected_date = datetime.now() + duration
            difference = expected_date - expiration_date

            fail_msg = "The expiration date {0} was far from the expected {1}"
            fail_msg = fail_msg.format(expiration_date, expected_date)
            self.assertLess(difference, timedelta(seconds=10), fail_msg)
            self.assertGreater(difference, timedelta(seconds=-10), fail_msg)

    def test_remember_me_is_unfresh(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)
            self.assertEqual(u"False", c.get("/is-fresh").data.decode("utf-8"))

    def test_user_loaded_from_cookie_fired(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)
            with listen_to(user_loaded_from_cookie) as listener:
                c.get("/username")
                listener.assert_heard_one(self.app, user=notch)

    def test_user_loaded_from_header_fired(self):
        user_id = 1
        user_name = USERS[user_id].name
        with self.app.test_client() as c:
            with listen_to(user_loaded_from_header) as listener:
                headers = [("Authorization", "Basic %s" % (bytes.decode(base64.b64encode(str.encode(str(user_id))))))]
                result = c.get("/username", headers=headers)
                self.assertEqual(user_name, result.data.decode("utf-8"))
                listener.assert_heard_one(self.app, user=USERS[user_id])

    def test_user_loaded_from_request_fired(self):
        user_id = 1
        user_name = USERS[user_id].name
        with self.app.test_client() as c:
            with listen_to(user_loaded_from_request) as listener:
                url = "/username?user_id={user_id}".format(user_id=user_id)
                result = c.get(url)
                self.assertEqual(user_name, result.data.decode("utf-8"))
                listener.assert_heard_one(self.app, user=USERS[user_id])

    def test_logout_stays_logged_out_with_remember_me(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            c.get("/logout")
            result = c.get("/username")
            self.assertEqual(result.data.decode("utf-8"), u"Anonymous")

    def test_needs_refresh_uses_handler(self):
        @self.login_manager.needs_refresh_handler
        def _on_refresh():
            return u"Needs Refresh!"

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            result = c.get("/needs-refresh")
            self.assertEqual(u"Needs Refresh!", result.data.decode("utf-8"))

    def test_needs_refresh_fires_needs_refresh_signal(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            with listen_to(user_needs_refresh) as listener:
                c.get("/needs-refresh")
                listener.assert_heard_one(self.app)

    def test_needs_refresh_fires_flash_when_redirect_to_refresh_view(self):
        self.login_manager.refresh_view = "/refresh_view"

        self.login_manager.needs_refresh_message = u"Refresh"
        self.login_manager.needs_refresh_message_category = "refresh"
        category_filter = [self.login_manager.needs_refresh_message_category]

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            c.get("/needs-refresh")
            msgs = get_flashed_messages(category_filter=category_filter)
            self.assertIn(self.login_manager.needs_refresh_message, msgs)

    def test_needs_refresh_flash_message_localized(self):
        def _gettext(msg):
            if msg == u"Refresh":
                return u"Aktualisieren"

        self.login_manager.refresh_view = "/refresh_view"
        self.login_manager.localize_callback = _gettext

        self.login_manager.needs_refresh_message = u"Refresh"
        self.login_manager.needs_refresh_message_category = "refresh"
        category_filter = [self.login_manager.needs_refresh_message_category]

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            c.get("/needs-refresh")
            msgs = get_flashed_messages(category_filter=category_filter)
            self.assertIn(u"Aktualisieren", msgs)
        self.login_manager.localize_callback = None

    def test_needs_refresh_aborts_403(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            result = c.get("/needs-refresh")
            self.assertEqual(result.status_code, 403)

    def test_redirects_to_refresh_view(self):
        @self.app.route("/refresh-view")
        def refresh_view():
            return ""

        self.login_manager.refresh_view = "refresh_view"
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            result = c.get("/needs-refresh")
            self.assertEqual(result.status_code, 302)
            expected = "http://localhost/refresh-view?next=%2Fneeds-refresh"
            self.assertEqual(result.location, expected)

    def test_confirm_login(self):
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)
            self.assertEqual(u"False", c.get("/is-fresh").data.decode("utf-8"))
            c.get("/confirm-login")
            self.assertEqual(u"True", c.get("/is-fresh").data.decode("utf-8"))

    def test_user_login_confirmed_signal_fired(self):
        with self.app.test_client() as c:
            with listen_to(user_login_confirmed) as listener:
                c.get("/confirm-login")
                listener.assert_heard_one(self.app)

    def test_session_not_modified(self):
        with self.app.test_client() as c:
            # Within the request we think we didn't modify the session.
            self.assertEquals(u"modified=False", c.get("/empty_session").data.decode("utf-8"))
            # But after the request, the session could be modified by the
            # "after_request" handlers that call _update_remember_cookie.
            # Ensure that if nothing changed the session is not modified.
            self.assertFalse(session.modified)

    #
    # Session Protection
    #
    def test_session_protection_basic_passes_successive_requests(self):
        self.app.config["SESSION_PROTECTION"] = "basic"
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            username_result = c.get("/username")
            self.assertEqual(u"Notch", username_result.data.decode("utf-8"))
            fresh_result = c.get("/is-fresh")
            self.assertEqual(u"True", fresh_result.data.decode("utf-8"))

    def test_session_protection_strong_passes_successive_requests(self):
        self.app.config["SESSION_PROTECTION"] = "strong"
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            username_result = c.get("/username")
            self.assertEqual(u"Notch", username_result.data.decode("utf-8"))
            fresh_result = c.get("/is-fresh")
            self.assertEqual(u"True", fresh_result.data.decode("utf-8"))

    def test_session_protection_basic_marks_session_unfresh(self):
        self.app.config["SESSION_PROTECTION"] = "basic"
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            username_result = c.get("/username", headers=[("User-Agent", "different")])
            self.assertEqual(u"Notch", username_result.data.decode("utf-8"))
            fresh_result = c.get("/is-fresh")
            self.assertEqual(u"False", fresh_result.data.decode("utf-8"))

    def test_session_protection_basic_fires_signal(self):
        self.app.config["SESSION_PROTECTION"] = "basic"

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            with listen_to(session_protected) as listener:
                c.get("/username", headers=[("User-Agent", "different")])
                listener.assert_heard_one(self.app)

    def test_session_protection_basic_skips_when_remember_me(self):
        self.app.config["SESSION_PROTECTION"] = "basic"

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            # clear session to force remember me (and remove old session id)
            self._delete_session(c)
            # should not trigger protection because "sess" is empty
            with listen_to(session_protected) as listener:
                c.get("/username")
                listener.assert_heard_none(self.app)

    def test_session_protection_strong_skips_when_remember_me(self):
        self.app.config["SESSION_PROTECTION"] = "strong"

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            # clear session to force remember me (and remove old session id)
            self._delete_session(c)
            # should not trigger protection because "sess" is empty
            with listen_to(session_protected) as listener:
                c.get("/username")
                listener.assert_heard_none(self.app)

    def test_permanent_strong_session_protection_marks_session_unfresh(self):
        self.app.config["SESSION_PROTECTION"] = "strong"
        with self.app.test_client() as c:
            c.get("/login-notch-permanent")
            username_result = c.get("/username", headers=[("User-Agent", "different")])
            self.assertEqual(u"Notch", username_result.data.decode("utf-8"))
            fresh_result = c.get("/is-fresh")
            self.assertEqual(u"False", fresh_result.data.decode("utf-8"))

    def test_permanent_strong_session_protection_fires_signal(self):
        self.app.config["SESSION_PROTECTION"] = "strong"

        with self.app.test_client() as c:
            c.get("/login-notch-permanent")
            with listen_to(session_protected) as listener:
                c.get("/username", headers=[("User-Agent", "different")])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_deletes_session(self):
        self.app.config["SESSION_PROTECTION"] = "strong"
        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            username_result = c.get("/username", headers=[("User-Agent", "different")])
            self.assertEqual(u"Anonymous", username_result.data.decode("utf-8"))

    def test_session_protection_strong_fires_signal_user_agent(self):
        self.app.config["SESSION_PROTECTION"] = "strong"

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            with listen_to(session_protected) as listener:
                c.get("/username", headers=[("User-Agent", "different")])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_fires_signal_x_forwarded_for(self):
        self.app.config["SESSION_PROTECTION"] = "strong"

        with self.app.test_client() as c:
            c.get("/login-notch-remember", headers=[("X-Forwarded-For", "10.1.1.1")])
            with listen_to(session_protected) as listener:
                c.get("/username", headers=[("X-Forwarded-For", "10.1.1.2")])
                listener.assert_heard_one(self.app)

    def test_session_protection_skip_when_off_and_anonymous(self):
        with self.app.test_client() as c:
            # no user access
            with listen_to(user_accessed) as user_listener:
                results = c.get("/")
                user_listener.assert_heard_none(self.app)

            # access user with no session data
            with listen_to(session_protected) as session_listener:
                results = c.get("/username")
                self.assertEqual(results.data.decode("utf-8"), u"Anonymous")
                session_listener.assert_heard_none(self.app)

            # verify no session data has been set
            self.assertFalse(session)

    def test_session_protection_skip_when_basic_and_anonymous(self):
        self.app.config["SESSION_PROTECTION"] = "basic"

        with self.app.test_client() as c:
            # no user access
            with listen_to(user_accessed) as user_listener:
                results = c.get("/")
                user_listener.assert_heard_none(self.app)

            # access user with no session data
            with listen_to(session_protected) as session_listener:
                results = c.get("/username")
                self.assertEqual(results.data.decode("utf-8"), u"Anonymous")
                session_listener.assert_heard_none(self.app)

            # verify no session data has been set other than '_id'
            self.assertIsNotNone(session.get("_id"))
            self.assertTrue(len(session) == 1)

    #
    # Custom Token Loader
    #
    def test_custom_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)

            # Test that remember me functionality still works
            self.assertEqual(u"Notch", c.get("/username").data.decode("utf-8"))

            # Test that we used the custom authentication token
            remember_cookie = self._get_remember_cookie(c)
            expected_value = make_secure_token(u"Notch", key="deterministic")
            self.assertEqual(expected_value, remember_cookie.value)

    def test_change_api_key_with_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)
            self.app.config["SECRET_KEY"] = "ima change this now"

            result = c.get("/username")
            self.assertEqual(result.data.decode("utf-8"), u"Notch")

    def test_custom_token_loader_with_no_user(self):
        @self.login_manager.token_loader
        def load_token(token):
            return

        with self.app.test_client() as c:
            c.get("/login-notch-remember")
            self._delete_session(c)

            result = c.get("/username")
            self.assertEqual(result.data.decode("utf-8"), u"Anonymous")

    #
    # Lazy Access User
    #
    def test_requests_without_accessing_session(self):
        with self.app.test_client() as c:
            c.get("/login-notch")

            # no session access
            with listen_to(user_accessed) as listener:
                c.get("/")
                listener.assert_heard_none(self.app)

            # should have a session access
            with listen_to(user_accessed) as listener:
                result = c.get("/username")
                listener.assert_heard_one(self.app)
                self.assertEqual(result.data.decode("utf-8"), u"Notch")

    #
    # View Decorators
    #
    def test_login_required_decorator(self):
        @self.app.route("/protected")
        @login_required
        def protected():
            return u"Access Granted"

        with self.app.test_client() as c:
            result = c.get("/protected")
            self.assertEqual(result.status_code, 401)

            c.get("/login-notch")
            result2 = c.get("/protected")
            self.assertIn(u"Access Granted", result2.data.decode("utf-8"))

    def test_decorators_are_disabled(self):
        @self.app.route("/protected")
        @login_required
        @fresh_login_required
        def protected():
            return u"Access Granted"

        self.app.login_manager._login_disabled = True

        with self.app.test_client() as c:
            result = c.get("/protected")
            self.assertIn(u"Access Granted", result.data.decode("utf-8"))

    def test_fresh_login_required_decorator(self):
        @self.app.route("/very-protected")
        @fresh_login_required
        def very_protected():
            return "Access Granted"

        with self.app.test_client() as c:
            result = c.get("/very-protected")
            self.assertEqual(result.status_code, 401)

            c.get("/login-notch-remember")
            logged_in_result = c.get("/very-protected")
            self.assertEqual(u"Access Granted", logged_in_result.data.decode("utf-8"))

            self._delete_session(c)
            stale_result = c.get("/very-protected")
            self.assertEqual(stale_result.status_code, 403)

            c.get("/confirm-login")
            refreshed_result = c.get("/very-protected")
            self.assertEqual(u"Access Granted", refreshed_result.data.decode("utf-8"))

    #
    # Misc
    #
    @unittest.skipIf(werkzeug_version.startswith("0.9"), "wait for upstream implementing RFC 5987")
    def test_chinese_user_agent(self):
        with self.app.test_client() as c:
            result = c.get("/", headers=[("User-Agent", u"中文")])
            self.assertEqual(u"Welcome!", result.data.decode("utf-8"))

    @unittest.skipIf(werkzeug_version.startswith("0.9"), "wait for upstream implementing RFC 5987")
    def test_russian_cp1251_user_agent(self):
        with self.app.test_client() as c:
            headers = [("User-Agent", u"ЯЙЮя".encode("cp1251"))]
            response = c.get("/", headers=headers)
            self.assertEqual(response.data.decode("utf-8"), u"Welcome!")

    def test_make_secure_token_default_key(self):
        with self.app.test_request_context():
            self.assertEqual(make_secure_token("foo"), "0f05743a2b617b2625362ab667c0dbdf4c9ec13a")

    def test_user_context_processor(self):
        with self.app.test_request_context():
            _ucp = self.app.context_processor(_user_context_processor)
            self.assertIsInstance(_ucp()["current_user"], AnonymousUserMixin)
예제 #10
0
def unauthorized_401(app):
    lm = LoginManager()
    lm.setup_app(app)
    with raises(Unauthorized):
        with assert_fired(user_unauthorized):
            res = lm.unauthorized()
예제 #11
0
class Auth:
    GHOST = 0
    USER = 1
    ADMIN = 2
    SUPER_ADMIN = 3

    class Anonymous(AnonymousUserMixin):
        def __init__(self):
            self._id = None

        def get_id(self):
            return unicode('')

    def __init__(self):
        self.__login_manager = LoginManager()
        self.config = {}

    def init_app(self, app, config):
        print 'Attaching LoginManager to app.'
        self.__login_manager.init_app(app)
        self.__login_manager.anonymous_user = Auth.Anonymous
        self.config = config

        def load_user(user_id):
            if user_id == '':
                return None

            Users = Database['Users']
            try:
                return Users.User.find_one({'_id': ObjectId(user_id)})
            except:
                return None

        def unauthorized():
            return jsonify(error='Unauthorized'), HTTP_401_UNAUTHORIZED

        self.__login_manager.user_loader(load_user)
        self.__login_manager.unauthorized_handler(unauthorized)

        self.__bcrypt = Bcrypt(app)

    def login_manager(self):
        return self.__login_manager

    def require(self, user_level):
        def login_required(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                if current_user.is_authenticated(
                ) and current_user.get_access_level() >= user_level:
                    return f(*args, **kwargs)
                return self.__login_manager.unauthorized()

            return decorated_function

        return login_required

    def login(self, user_object, password):
        if user_object is not None:
            if self.__bcrypt.check_password_hash(user_object['password'],
                                                 password):
                if not login_user(user_object):
                    return resource_strings["USER_NOT_ACTIVATED"]
            else:
                return 'Invalid password'
        else:
            return 'Invalid email'
        return None

    def login_social(self, login_type, token):
        user_object = None

        try:
            user_object = SocialSignin.verify(login_type, token)
        except SocialSignin.Invalid as e:
            return str(e)

        login_user(user_object)

    def logout(self):
        logout_user()

    def hash_password(self, password):
        return self.__bcrypt.generate_password_hash(password)

    # decorator that protects other users from PUT/POST/DELETE on you stuff
    # user_id _must_ be passed in as 'user_id'
    def only_me(self, function):
        @wraps(function)
        def inner(*args, **kwargs):
            if kwargs['user_id'] != 'me':
                return '{}', HTTP_401_UNAUTHORIZED
            return function(*args, **kwargs)

        return inner
예제 #12
0
def unauthorized_401(app):
    lm = LoginManager()
    lm.setup_app(app)
    with raises(Unauthorized):
        with assert_fired(user_unauthorized):
            res = lm.unauthorized()
예제 #13
0
class LoginTestCase(unittest.TestCase):
    ''' Tests for results of the login_user function '''

    def setUp(self):
        self.app = Flask(__name__)
        self.app.config['SECRET_KEY'] = 'deterministic'
        self.app.config['SESSION_PROTECTION'] = None
        self.app.config['TESTING'] = True
        self.remember_cookie_name = 'remember'
        self.app.config['REMEMBER_COOKIE_NAME'] = self.remember_cookie_name
        self.login_manager = LoginManager()
        self.login_manager.init_app(self.app)
        self.login_manager._login_disabled = False

        @self.app.route('/')
        def index():
            return u'Welcome!'

        @self.app.route('/secret')
        def secret():
            return self.login_manager.unauthorized()

        @self.app.route('/login-notch')
        def login_notch():
            return unicode(login_user(notch))

        @self.app.route('/login-notch-remember')
        def login_notch_remember():
            return unicode(login_user(notch, remember=True))

        @self.app.route('/login-notch-permanent')
        def login_notch_permanent():
            session.permanent = True
            return unicode(login_user(notch))

        @self.app.route('/needs-refresh')
        def needs_refresh():
            return self.login_manager.needs_refresh()

        @self.app.route('/confirm-login')
        def _confirm_login():
            confirm_login()
            return u''

        @self.app.route('/username')
        def username():
            if current_user.is_authenticated:
                return current_user.name
            return u'Anonymous'

        @self.app.route('/is-fresh')
        def is_fresh():
            return unicode(login_fresh())

        @self.app.route('/logout')
        def logout():
            return unicode(logout_user())

        @self.login_manager.user_loader
        def load_user(user_id):
            return USERS[int(user_id)]

        @self.login_manager.header_loader
        def load_user_from_header(header_value):
            if header_value.startswith('Basic '):
                header_value = header_value.replace('Basic ', '', 1)
            try:
                user_id = base64.b64decode(header_value)
            except TypeError:
                pass
            return USERS.get(int(user_id))

        @self.login_manager.request_loader
        def load_user_from_request(request):
            user_id = request.args.get('user_id')
            try:
                user_id = int(float(user_id))
            except TypeError:
                pass
            return USERS.get(user_id)

        @self.app.route('/empty_session')
        def empty_session():
            return unicode(u'modified=%s' % session.modified)

        # This will help us with the possibility of typoes in the tests. Now
        # we shouldn't have to check each response to help us set up state
        # (such as login pages) to make sure it worked: we will always
        # get an exception raised (rather than return a 404 response)
        @self.app.errorhandler(404)
        def handle_404(e):
            raise e

        unittest.TestCase.setUp(self)

    def _get_remember_cookie(self, test_client):
        our_cookies = test_client.cookie_jar._cookies['localhost.local']['/']
        return our_cookies[self.remember_cookie_name]

    def _delete_session(self, c):
        # Helper method to cause the session to be deleted
        # as if the browser was closed. This will remove
        # the session regardless of the permament flag
        # on the session!
        with c.session_transaction() as sess:
            sess.clear()

    def test_login_user(self):
        with self.app.test_request_context():
            result = login_user(notch)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u'Notch')
예제 #14
0
class Auth:
    GHOST = 0
    USER = 1
    ADMIN = 2
    SUPER_ADMIN = 3

    class Anonymous(AnonymousUserMixin):
        def __init__(self):
            self._id = None

        def get_id(self):
            return unicode('')

    def __init__(self):
        self.__login_manager = LoginManager()
        self.config = {}

    def init_app(self, app, config):
        print 'Attaching LoginManager to app.'
        self.__login_manager.init_app(app)
        self.__login_manager.anonymous_user = Auth.Anonymous
        self.config = config

        def load_user(user_id):
            if user_id == '':
                return None

            Users = Database['Users']
            try:
                return Users.User.find_one({'_id': ObjectId(user_id)})
            except:
                return None

        def unauthorized():
            return jsonify(error='Unauthorized'), HTTP_401_UNAUTHORIZED

        self.__login_manager.user_loader(load_user)
        self.__login_manager.unauthorized_handler(unauthorized)

        self.__bcrypt = Bcrypt(app)

    def login_manager(self):
        return self.__login_manager

    def require(self, user_level):
        def login_required(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                if current_user.is_authenticated() and current_user.get_access_level() >= user_level:
                    return f(*args, **kwargs)
                return self.__login_manager.unauthorized()
            return decorated_function
        return login_required

    def login(self, user_object, password):
        if user_object is not None:
            if self.__bcrypt.check_password_hash(user_object['password'], password):
                if not login_user(user_object):
                    return resource_strings["USER_NOT_ACTIVATED"]
            else:
                return 'Invalid password'
        else:
            return 'Invalid email'
        return None

    def login_social(self, login_type, token):
        user_object = None

        try:
            user_object = SocialSignin.verify(login_type, token)
        except SocialSignin.Invalid as e:
            return str(e)

        login_user(user_object)

    def logout(self):
        logout_user()

    def hash_password(self, password):
        return self.__bcrypt.generate_password_hash(password)

    # decorator that protects other users from PUT/POST/DELETE on you stuff
    # user_id _must_ be passed in as 'user_id'
    def only_me(self, function):
        @wraps(function)
        def inner(*args, **kwargs):
            if kwargs['user_id'] != 'me':
                return '{}', HTTP_401_UNAUTHORIZED
            return function(*args, **kwargs)
        return inner
예제 #15
0
# Set this up before touching handlers, since app is used in decorators.
# Simiarly db for models.
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = config.database
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)

# Now we can import the rest
from admin import *
from handlers import *
from models import *

login_manager.user_loader(User.get_by_username)
login_manager.unauthorized = lambda: redirect('/login')

if __name__ == '__main__':
  db.create_all()
  Organisation.query.delete()
  User.query.delete()
  db.session.commit()

  o1 = Organisation(name='The Big Issue')
  o2 = Organisation(name='Helping Hands')
  u1 = User(username='******', organisation=o1)
  u1.set_password('potato')
  u2 = User(username='******', organisation=o2)
  u2.set_password('helloworld')

  for obj in [o1, o2, u1, u2]:
예제 #16
0
class LoginTestCase(unittest.TestCase):
    ''' Tests for results of the login_user function '''

    def setUp(self):
        self.app = Flask(__name__)
        self.app.config['SECRET_KEY'] = 'deterministic'
        self.app.config['TESTING'] = True
        self.login_manager = LoginManager()
        self.login_manager.init_app(self.app)
        self.login_manager._login_disabled = False

        @self.app.route('/')
        def index():
            return u'Welcome!'

        @self.app.route('/secret')
        def secret():
            return self.login_manager.unauthorized()

        @self.app.route('/login-notch')
        def login_notch():
            return unicode(login_user(notch))

        @self.app.route('/login-notch-remember')
        def login_notch_remember():
            return unicode(login_user(notch))

        @self.app.route('/login-notch-permanent')
        def login_notch_permanent():
            session.permanent = True
            return unicode(login_user(notch))

        @self.app.route('/username')
        def username():
            if current_user.is_authenticated():
                return current_user.name
            return u'Anonymous'

        @self.app.route('/logout')
        def logout():
            return unicode(logout_user())

        @self.login_manager.user_loader
        def load_user(user_id):
            return USERS[int(user_id)]

        @self.app.route('/empty_session')
        def empty_session():
            return unicode(u'modified=%s' % session.modified)

        # This will help us with the possibility of typoes in the tests. Now
        # we shouldn't have to check each response to help us set up state
        # (such as login pages) to make sure it worked: we will always
        # get an exception raised (rather than return a 404 response)
        # @self.app.errorhandler(404)
        # def handle_404(e):
        #     raise e

        unittest.TestCase.setUp(self)

    def _delete_session(self, c):
        # Helper method to cause the session to be deleted
        # as if the browser was closed. This will remove
        # the session regardless of the permament flag
        # on the session!
        with c.session_transaction() as sess:
            sess.clear()

    #
    # Login
    #
    def test_test_request_context_users_are_anonymous(self):
        with self.app.test_request_context():
            self.assertTrue(current_user.is_anonymous())

    def test_defaults_anonymous(self):
        with self.app.test_client() as c:
            result = c.get('/username')
            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_login_user(self):
        with self.app.test_request_context():
            result = login_user(notch)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u'Notch')

    def test_login_user_emits_signal(self):
        with self.app.test_request_context():
            with listen_to(user_logged_in) as listener:
                login_user(notch)
                listener.assert_heard_one(self.app, user=notch)

    def test_login_inactive_user(self):
        with self.app.test_request_context():
            result = login_user(creeper)
            self.assertTrue(current_user.is_anonymous())
            self.assertFalse(result)

    def test_login_inactive_user_forced(self):
        with self.app.test_request_context():
            login_user(creeper, force=True)
            self.assertEqual(current_user.name, u'Creeper')

    #
    # Logout
    #
    def test_logout_logs_out_current_user(self):
        with self.app.test_request_context():
            login_user(notch)
            logout_user()
            self.assertTrue(current_user.is_anonymous())

    def test_logout_emits_signal(self):
        with self.app.test_request_context():
            login_user(notch)
            with listen_to(user_logged_out) as listener:
                logout_user()
                listener.assert_heard_one(self.app, user=notch)

    #
    # Unauthorized
    #
    def test_unauthorized_fires_unauthorized_signal(self):
        with self.app.test_client() as c:
            with listen_to(user_unauthorized) as listener:
                c.get('/secret')
                listener.assert_heard_one(self.app)

    def test_unauthorized_uses_authorized_handler(self):
        @self.login_manager.unauthorized_handler
        def _callback():
            return Response('This is secret!', 401)

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)
            self.assertEqual(u'This is secret!', result.data.decode('utf-8'))

    def test_unauthorized_aborts_with_401(self):
        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)

    #
    # Session Persistence/Freshness
    #
    def test_login_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_logout_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_incorrect_id_logs_out(self):
        # Ensure that any attempt to reload the user by the ID
        # will seem as if the user is no longer valid
        @self.login_manager.user_loader
        def new_user_loader(user_id):
            return

        with self.app.test_client() as c:
            # Successfully logs in
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_session_not_modified(self):
        with self.app.test_client() as c:
            # Within the request we think we didn't modify the session.
            self.assertEquals(
                u'modified=False',
                c.get('/empty_session').data.decode('utf-8'))
            # But after the request, the session could be modified by the
            # "after_request" handlers that call _update_remember_cookie.
            # Ensure that if nothing changed the session is not modified.
            self.assertFalse(session.modified)

    #
    # Lazy Access User
    #
    def test_requests_without_accessing_session(self):
        with self.app.test_client() as c:
            c.get('/login-notch')

            #no session access
            with listen_to(user_accessed) as listener:
                c.get('/')
                listener.assert_heard_none(self.app)

            #should have a session access
            with listen_to(user_accessed) as listener:
                result = c.get('/username')
                listener.assert_heard_one(self.app)
                self.assertEqual(result.data.decode('utf-8'), u'Notch')

    #
    # View Decorators
    #
    def test_login_required_decorator(self):
        @self.app.route('/protected')
        @login_required
        def protected():
            return u'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch')
            result2 = c.get('/protected')
            self.assertIn(u'Access Granted', result2.data.decode('utf-8'))

    def test_decorators_are_disabled(self):
        @self.app.route('/protected')
        @login_required
        def protected():
            return u'Access Granted'

        self.app.login_manager._login_disabled = True

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertIn(u'Access Granted', result.data.decode('utf-8'))
예제 #17
0
class LoginTestCase(unittest.TestCase):
    ''' Tests for results of the login_user function '''

    def setUp(self):
        self.app = Flask(__name__)
        self.app.config['SECRET_KEY'] = 'deterministic'
        self.app.config['SESSION_PROTECTION'] = None
        self.app.config['TESTING'] = True
        self.remember_cookie_name = 'remember'
        self.app.config['REMEMBER_COOKIE_NAME'] = self.remember_cookie_name
        self.login_manager = LoginManager()
        self.login_manager.init_app(self.app)
        self.login_manager._login_disabled = False

        @self.app.route('/')
        def index():
            return u'Welcome!'

        @self.app.route('/secret')
        def secret():
            return self.login_manager.unauthorized()

        @self.app.route('/login-notch')
        def login_notch():
            return unicode(login_user(notch))

        @self.app.route('/login-notch-remember')
        def login_notch_remember():
            return unicode(login_user(notch, remember=True))

        @self.app.route('/login-notch-permanent')
        def login_notch_permanent():
            session.permanent = True
            return unicode(login_user(notch))

        @self.app.route('/needs-refresh')
        def needs_refresh():
            return self.login_manager.needs_refresh()

        @self.app.route('/confirm-login')
        def _confirm_login():
            confirm_login()
            return u''

        @self.app.route('/username')
        def username():
            if current_user.is_authenticated():
                return current_user.name
            return u'Anonymous'

        @self.app.route('/is-fresh')
        def is_fresh():
            return unicode(login_fresh())

        @self.app.route('/logout')
        def logout():
            return unicode(logout_user())

        @self.login_manager.user_loader
        def load_user(user_id):
            return USERS[int(user_id)]

        @self.app.route('/empty_session')
        def empty_session():
            return unicode(u'modified=%s' % session.modified)

        # This will help us with the possibility of typoes in the tests. Now
        # we shouldn't have to check each response to help us set up state
        # (such as login pages) to make sure it worked: we will always
        # get an exception raised (rather than return a 404 response)
        @self.app.errorhandler(404)
        def handle_404(e):
            raise e

        unittest.TestCase.setUp(self)

    def _get_remember_cookie(self, test_client):
        our_cookies = test_client.cookie_jar._cookies['localhost.local']['/']
        return our_cookies[self.remember_cookie_name]

    def _delete_session(self, c):
        # Helper method to cause the session to be deleted
        # as if the browser was closed. This will remove
        # the session regardless of the permament flag
        # on the session!
        with c.session_transaction() as sess:
            sess.clear()

    #
    # Login
    #
    def test_test_request_context_users_are_anonymous(self):
        with self.app.test_request_context():
            self.assertTrue(current_user.is_anonymous())

    def test_defaults_anonymous(self):
        with self.app.test_client() as c:
            result = c.get('/username')
            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_login_user(self):
        with self.app.test_request_context():
            result = login_user(notch)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u'Notch')

    def test_login_user_emits_signal(self):
        with self.app.test_request_context():
            with listen_to(user_logged_in) as listener:
                login_user(notch)
                listener.assert_heard_one(self.app, user=notch)

    def test_login_inactive_user(self):
        with self.app.test_request_context():
            result = login_user(creeper)
            self.assertTrue(current_user.is_anonymous())
            self.assertFalse(result)

    def test_login_inactive_user_forced(self):
        with self.app.test_request_context():
            login_user(creeper, force=True)
            self.assertEqual(current_user.name, u'Creeper')

    #
    # Logout
    #
    def test_logout_logs_out_current_user(self):
        with self.app.test_request_context():
            login_user(notch)
            logout_user()
            self.assertTrue(current_user.is_anonymous())

    def test_logout_emits_signal(self):
        with self.app.test_request_context():
            login_user(notch)
            with listen_to(user_logged_out) as listener:
                logout_user()
                listener.assert_heard_one(self.app, user=notch)

    #
    # Unauthorized
    #
    def test_unauthorized_fires_unauthorized_signal(self):
        with self.app.test_client() as c:
            with listen_to(user_unauthorized) as listener:
                c.get('/secret')
                listener.assert_heard_one(self.app)

    def test_unauthorized_flashes_message_with_login_view(self):
        self.login_manager.login_view = '/login'

        expected_message = self.login_manager.login_message = u'Log in!'
        expected_category = self.login_manager.login_message_category = 'login'

        with self.app.test_client() as c:
            c.get('/secret')
            msgs = get_flashed_messages(category_filter=[expected_category])
            self.assertEqual([expected_message], msgs)

    def test_unauthorized_uses_authorized_handler(self):
        @self.login_manager.unauthorized_handler
        def _callback():
            return Response('This is secret!', 401)

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)
            self.assertEqual(u'This is secret!', result.data.decode('utf-8'))

    def test_unauthorized_aborts_with_401(self):
        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)

    def test_unauthorized_redirects_to_login_view(self):
        self.login_manager.login_view = 'login'

        @self.app.route('/login')
        def login():
            return 'Login Form Goes Here!'

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 302)
            self.assertEqual(result.location,
                             'http://localhost/login?next=%2Fsecret')

    #
    # Session Persistence/Freshness
    #
    def test_login_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_logout_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_incorrect_id_logs_out(self):
        # Ensure that any attempt to reload the user by the ID
        # will seem as if the user is no longer valid
        @self.login_manager.user_loader
        def new_user_loader(user_id):
            return

        with self.app.test_client() as c:
            # Successfully logs in
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_authentication_is_fresh(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/is-fresh')
            self.assertEqual(u'True', result.data.decode('utf-8'))

    def test_remember_me(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            result = c.get('/username')
            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_remember_me_uses_custom_cookie_parameters(self):
        name = self.app.config['REMEMBER_COOKIE_NAME'] = 'myname'
        duration = self.app.config['REMEMBER_COOKIE_DURATION'] = \
            timedelta(days=2)
        domain = self.app.config['REMEMBER_COOKIE_DOMAIN'] = '.localhost.local'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')

            # TODO: Is there a better way to test this?
            self.assertTrue(domain in c.cookie_jar._cookies,
                            'Custom domain not found as cookie domain')
            domain_cookie = c.cookie_jar._cookies[domain]
            self.assertTrue(name in domain_cookie['/'],
                            'Custom name not found as cookie name')
            cookie = domain_cookie['/'][name]

            expiration_date = datetime.fromtimestamp(cookie.expires)
            expected_date = datetime.now() + duration
            difference = expected_date - expiration_date

            fail_msg = 'The expiration date {0} was far from the expected {1}'
            fail_msg = fail_msg.format(expiration_date, expected_date)
            self.assertLess(difference, timedelta(seconds=10), fail_msg)
            self.assertGreater(difference, timedelta(seconds=-10), fail_msg)

    def test_remember_me_is_unfresh(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.assertEqual(u'False', c.get('/is-fresh').data.decode('utf-8'))

    def test_user_loaded_from_cookie_fired(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            with listen_to(user_loaded_from_cookie) as listener:
                c.get('/username')
                listener.assert_heard_one(self.app, user=notch)

    def test_logout_stays_logged_out_with_remember_me(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_needs_refresh_uses_handler(self):
        @self.login_manager.needs_refresh_handler
        def _on_refresh():
            return u'Needs Refresh!'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(u'Needs Refresh!', result.data.decode('utf-8'))

    def test_needs_refresh_fires_needs_refresh_signal(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(user_needs_refresh) as listener:
                c.get('/needs-refresh')
                listener.assert_heard_one(self.app)

    def test_needs_refresh_fires_flash_when_redirect_to_refresh_view(self):
        self.login_manager.refresh_view = '/refresh_view'

        self.login_manager.needs_refresh_message = u'Refresh'
        self.login_manager.needs_refresh_message_category = 'refresh'
        category_filter = [self.login_manager.needs_refresh_message_category]

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/needs-refresh')
            msgs = get_flashed_messages(category_filter=category_filter)
            self.assertIn(self.login_manager.needs_refresh_message, msgs)

    def test_needs_refresh_aborts_403(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(result.status_code, 403)

    def test_redirects_to_refresh_view(self):
        @self.app.route('/refresh-view')
        def refresh_view():
            return ''

        self.login_manager.refresh_view = 'refresh_view'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(result.status_code, 302)
            expected = 'http://localhost/refresh-view?next=%2Fneeds-refresh'
            self.assertEqual(result.location, expected)

    def test_confirm_login(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.assertEqual(u'False', c.get('/is-fresh').data.decode('utf-8'))
            c.get('/confirm-login')
            self.assertEqual(u'True', c.get('/is-fresh').data.decode('utf-8'))

    def test_user_login_confirmed_signal_fired(self):
        with self.app.test_client() as c:
            with listen_to(user_login_confirmed) as listener:
                c.get('/confirm-login')
                listener.assert_heard_one(self.app)

    def test_session_not_modified(self):
        with self.app.test_client() as c:
            # Within the request we think we didn't modify the session.
            self.assertEquals(
                u'modified=False',
                c.get('/empty_session').data.decode('utf-8'))
            # But after the request, the session could be modified by the
            # "after_request" handlers that call _update_remember_cookie.
            # Ensure that if nothing changed the session is not modified.
            self.assertFalse(session.modified)

    #
    # Session Protection
    #
    def test_session_protection_basic_passes_successive_requests(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username')
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'True', fresh_result.data.decode('utf-8'))

    def test_session_protection_strong_passes_successive_requests(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username')
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'True', fresh_result.data.decode('utf-8'))

    def test_session_protection_basic_marks_session_unfresh(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username',
                                    headers=[('User-Agent', 'different')])
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'False', fresh_result.data.decode('utf-8'))

    def test_session_protection_basic_fires_signal(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_basic_skips_when_remember_me(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            # clear session to force remember me (and remove old session id)
            self._delete_session(c)
            # should not trigger protection because "sess" is empty
            with listen_to(session_protected) as listener:
                c.get('/username')
                listener.assert_heard_none(self.app)

    def test_session_protection_strong_triggers_when_remember_me(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            # clear session to force remember me (and remove old session id)
            self._delete_session(c)
            # should not trigger protection because "sess" is empty
            with listen_to(session_protected) as listener:
                c.get('/username')
                listener.assert_heard_one(self.app)

    def test_permanent_strong_session_protection_marks_session_unfresh(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-permanent')
            username_result = c.get('/username', headers=[('User-Agent',
                                                           'different')])
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'False', fresh_result.data.decode('utf-8'))

    def test_permanent_strong_session_protection_fires_signal(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-permanent')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_deletes_session(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username', headers=[('User-Agent',
                                                           'different')])
            self.assertEqual(u'Anonymous',
                             username_result.data.decode('utf-8'))

    def test_session_protection_strong_fires_signal_user_agent(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_fires_signal_x_forwarded_for(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember',
                  headers=[('X-Forwarded-For', '10.1.1.1')])
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('X-Forwarded-For', '10.1.1.2')])
                listener.assert_heard_one(self.app)

    def test_session_protection_skip_when_off_and_anonymous(self):
        with self.app.test_client() as c:
            # no user access
            with listen_to(user_accessed) as user_listener:
                results = c.get('/')
                user_listener.assert_heard_none(self.app)

            # access user with no session data
            with listen_to(session_protected) as session_listener:
                results = c.get('/username')
                self.assertEqual(results.data.decode('utf-8'), u'Anonymous')
                session_listener.assert_heard_none(self.app)

            # verify no session data has been set
            self.assertFalse(session)

    def test_session_protection_skip_when_basic_and_anonymous(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            # no user access
            with listen_to(user_accessed) as user_listener:
                results = c.get('/')
                user_listener.assert_heard_none(self.app)

            # access user with no session data
            with listen_to(session_protected) as session_listener:
                results = c.get('/username')
                self.assertEqual(results.data.decode('utf-8'), u'Anonymous')
                session_listener.assert_heard_none(self.app)

            # verify no session data has been set
            self.assertFalse(session)

    #
    # Custom Token Loader
    #
    def test_custom_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)

            # Test that remember me functionality still works
            self.assertEqual(u'Notch', c.get('/username').data.decode('utf-8'))

            # Test that we used the custom authentication token
            remember_cookie = self._get_remember_cookie(c)
            expected_value = make_secure_token(u'Notch', key='deterministic')
            self.assertEqual(expected_value, remember_cookie.value)

    def test_change_api_key_with_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.app.config['SECRET_KEY'] = 'ima change this now'

            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Notch')

    def test_custom_token_loader_with_no_user(self):
        @self.login_manager.token_loader
        def load_token(token):
            return

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)

            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    #
    # Lazy Access User
    #
    def test_requests_without_accessing_session(self):
        with self.app.test_client() as c:
            c.get('/login-notch')

            #no session access
            with listen_to(user_accessed) as listener:
                c.get('/')
                listener.assert_heard_none(self.app)

            #should have a session access
            with listen_to(user_accessed) as listener:
                result = c.get('/username')
                listener.assert_heard_one(self.app)
                self.assertEqual(result.data.decode('utf-8'), u'Notch')

    #
    # View Decorators
    #
    def test_login_required_decorator(self):
        @self.app.route('/protected')
        @login_required
        def protected():
            return u'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch')
            result2 = c.get('/protected')
            self.assertIn(u'Access Granted', result2.data.decode('utf-8'))

    def test_decorators_are_disabled(self):
        @self.app.route('/protected')
        @login_required
        @fresh_login_required
        def protected():
            return u'Access Granted'

        self.app.login_manager._login_disabled = True

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertIn(u'Access Granted', result.data.decode('utf-8'))

    def test_fresh_login_required_decorator(self):
        @self.app.route('/very-protected')
        @fresh_login_required
        def very_protected():
            return 'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/very-protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch-remember')
            logged_in_result = c.get('/very-protected')
            self.assertEqual(u'Access Granted',
                             logged_in_result.data.decode('utf-8'))

            self._delete_session(c)
            stale_result = c.get('/very-protected')
            self.assertEqual(stale_result.status_code, 403)

            c.get('/confirm-login')
            refreshed_result = c.get('/very-protected')
            self.assertEqual(u'Access Granted',
                             refreshed_result.data.decode('utf-8'))

    #
    # Misc
    #
    @unittest.skipIf(werkzeug_version.startswith("0.9"),
                     "wait for upstream implementing RFC 5987")
    def test_chinese_user_agent(self):
        with self.app.test_client() as c:
            result = c.get('/', headers=[('User-Agent', u'中文')])
            self.assertEqual(u'Welcome!', result.data.decode('utf-8'))

    @unittest.skipIf(werkzeug_version.startswith("0.9"),
                     "wait for upstream implementing RFC 5987")
    def test_russian_cp1251_user_agent(self):
        with self.app.test_client() as c:
            headers = [('User-Agent', u'ЯЙЮя'.encode('cp1251'))]
            response = c.get('/', headers=headers)
            self.assertEqual(response.data.decode('utf-8'), u'Welcome!')

    def test_make_secure_token_default_key(self):
        with self.app.test_request_context():
            self.assertEqual(make_secure_token('foo'),
                             '0f05743a2b617b2625362ab667c0dbdf4c9ec13a')

    def test_user_context_processor(self):
        with self.app.test_request_context():
            _ucp = self.app.context_processor(_user_context_processor)
            self.assertEqual(_ucp(), {'current_user': None})
예제 #18
0
class LoginTestCase(unittest.TestCase):
    ''' Tests for results of the login_user function '''

    def setUp(self):
        self.app = Flask(__name__)
        self.app.config['SECRET_KEY'] = 'deterministic'
        self.app.config['SESSION_PROTECTION'] = None
        self.remember_cookie_name = 'remember'
        self.app.config['REMEMBER_COOKIE_NAME'] = self.remember_cookie_name
        self.login_manager = LoginManager()
        self.login_manager.init_app(self.app)
        self.login_manager._login_disabled = False

        @self.app.route('/')
        def index():
            return u'Welcome!'

        @self.app.route('/secret')
        def secret():
            return self.login_manager.unauthorized()

        @self.app.route('/login-notch')
        def login_notch():
            return unicode(login_user(notch))

        @self.app.route('/login-notch-remember')
        def login_notch_remember():
            return unicode(login_user(notch, remember=True))

        @self.app.route('/login-notch-permanent')
        def login_notch_permanent():
            session.permanent = True
            return unicode(login_user(notch))

        @self.app.route('/needs-refresh')
        def needs_refresh():
            return self.login_manager.needs_refresh()

        @self.app.route('/confirm-login')
        def _confirm_login():
            confirm_login()
            return u''

        @self.app.route('/username')
        def username():
            if current_user.is_authenticated:
                return current_user.name
            return u'Anonymous'

        @self.app.route('/is-fresh')
        def is_fresh():
            return unicode(login_fresh())

        @self.app.route('/logout')
        def logout():
            return unicode(logout_user())

        @self.login_manager.user_loader
        def load_user(user_id):
            return USERS[int(user_id)]

        @self.login_manager.header_loader
        def load_user_from_header(header_value):
            if header_value.startswith('Basic '):
                header_value = header_value.replace('Basic ', '', 1)
            try:
                user_id = base64.b64decode(header_value)
            except TypeError:
                pass
            return USERS.get(int(user_id))

        @self.login_manager.request_loader
        def load_user_from_request(request):
            user_id = request.args.get('user_id')
            try:
                user_id = int(float(user_id))
            except TypeError:
                pass
            return USERS.get(user_id)

        @self.app.route('/empty_session')
        def empty_session():
            return unicode(u'modified=%s' % session.modified)

        # This will help us with the possibility of typoes in the tests. Now
        # we shouldn't have to check each response to help us set up state
        # (such as login pages) to make sure it worked: we will always
        # get an exception raised (rather than return a 404 response)
        @self.app.errorhandler(404)
        def handle_404(e):
            raise e

        unittest.TestCase.setUp(self)

    def _get_remember_cookie(self, test_client):
        our_cookies = test_client.cookie_jar._cookies['localhost.local']['/']
        return our_cookies[self.remember_cookie_name]

    def _delete_session(self, c):
        # Helper method to cause the session to be deleted
        # as if the browser was closed. This will remove
        # the session regardless of the permament flag
        # on the session!
        with c.session_transaction() as sess:
            sess.clear()

    #
    # Login
    #
    def test_test_request_context_users_are_anonymous(self):
        with self.app.test_request_context():
            self.assertTrue(current_user.is_anonymous)

    def test_defaults_anonymous(self):
        with self.app.test_client() as c:
            result = c.get('/username')
            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_login_user(self):
        with self.app.test_request_context():
            result = login_user(notch)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u'Notch')

    def test_login_user_not_fresh(self):
        with self.app.test_request_context():
            result = login_user(notch, fresh=False)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u'Notch')
            self.assertIs(login_fresh(), False)

    def test_login_user_emits_signal(self):
        with self.app.test_request_context():
            with listen_to(user_logged_in) as listener:
                login_user(notch)
                listener.assert_heard_one(self.app, user=notch)

    def test_login_inactive_user(self):
        with self.app.test_request_context():
            result = login_user(creeper)
            self.assertTrue(current_user.is_anonymous)
            self.assertFalse(result)

    def test_login_inactive_user_forced(self):
        with self.app.test_request_context():
            login_user(creeper, force=True)
            self.assertEqual(current_user.name, u'Creeper')

    def test_login_user_with_header(self):
        user_id = 2
        user_name = USERS[user_id].name
        self.login_manager.request_callback = None
        with self.app.test_client() as c:
            basic_fmt = 'Basic {0}'
            decoded = bytes.decode(base64.b64encode(str.encode(str(user_id))))
            headers = [('Authorization', basic_fmt.format(decoded))]
            result = c.get('/username', headers=headers)
            self.assertEqual(user_name, result.data.decode('utf-8'))

    def test_login_invalid_user_with_header(self):
        user_id = 9000
        user_name = u'Anonymous'
        self.login_manager.request_callback = None
        with self.app.test_client() as c:
            basic_fmt = 'Basic {0}'
            decoded = bytes.decode(base64.b64encode(str.encode(str(user_id))))
            headers = [('Authorization', basic_fmt.format(decoded))]
            result = c.get('/username', headers=headers)
            self.assertEqual(user_name, result.data.decode('utf-8'))

    def test_login_user_with_request(self):
        user_id = 2
        user_name = USERS[user_id].name
        with self.app.test_client() as c:
            url = '/username?user_id={user_id}'.format(user_id=user_id)
            result = c.get(url)
            self.assertEqual(user_name, result.data.decode('utf-8'))

    def test_login_invalid_user_with_request(self):
        user_id = 9000
        user_name = u'Anonymous'
        with self.app.test_client() as c:
            url = '/username?user_id={user_id}'.format(user_id=user_id)
            result = c.get(url)
            self.assertEqual(user_name, result.data.decode('utf-8'))

    #
    # Logout
    #
    def test_logout_logs_out_current_user(self):
        with self.app.test_request_context():
            login_user(notch)
            logout_user()
            self.assertTrue(current_user.is_anonymous)

    def test_logout_emits_signal(self):
        with self.app.test_request_context():
            login_user(notch)
            with listen_to(user_logged_out) as listener:
                logout_user()
                listener.assert_heard_one(self.app, user=notch)

    def test_logout_without_current_user(self):
        with self.app.test_request_context():
            login_user(notch)
            del session['user_id']
            with listen_to(user_logged_out) as listener:
                logout_user()
                listener.assert_heard_one(self.app, user=ANY)

    #
    # Unauthorized
    #
    def test_unauthorized_fires_unauthorized_signal(self):
        with self.app.test_client() as c:
            with listen_to(user_unauthorized) as listener:
                c.get('/secret')
                listener.assert_heard_one(self.app)

    def test_unauthorized_flashes_message_with_login_view(self):
        self.login_manager.login_view = '/login'

        expected_message = self.login_manager.login_message = u'Log in!'
        expected_category = self.login_manager.login_message_category = 'login'

        with self.app.test_client() as c:
            c.get('/secret')
            msgs = get_flashed_messages(category_filter=[expected_category])
            self.assertEqual([expected_message], msgs)

    def test_unauthorized_flash_message_localized(self):
        def _gettext(msg):
            if msg == u'Log in!':
                return u'Einloggen'

        self.login_manager.login_view = '/login'
        self.login_manager.localize_callback = _gettext
        self.login_manager.login_message = u'Log in!'

        expected_message = u'Einloggen'
        expected_category = self.login_manager.login_message_category = 'login'

        with self.app.test_client() as c:
            c.get('/secret')
            msgs = get_flashed_messages(category_filter=[expected_category])
            self.assertEqual([expected_message], msgs)
        self.login_manager.localize_callback = None

    def test_unauthorized_uses_authorized_handler(self):
        @self.login_manager.unauthorized_handler
        def _callback():
            return Response('This is secret!', 401)

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)
            self.assertEqual(u'This is secret!', result.data.decode('utf-8'))

    def test_unauthorized_aborts_with_401(self):
        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)

    def test_unauthorized_redirects_to_login_view(self):
        self.login_manager.login_view = 'login'

        @self.app.route('/login')
        def login():
            return 'Login Form Goes Here!'

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 302)
            self.assertEqual(result.location,
                             'http://localhost/login?next=%2Fsecret')

    def test_unauthorized_uses_blueprint_login_view(self):
        with self.app.app_context():

            first = Blueprint('first', 'first')
            second = Blueprint('second', 'second')

            @self.app.route('/app_login')
            def app_login():
                return 'Login Form Goes Here!'

            @self.app.route('/first_login')
            def first_login():
                return 'Login Form Goes Here!'

            @self.app.route('/second_login')
            def second_login():
                return 'Login Form Goes Here!'

            @self.app.route('/protected')
            @login_required
            def protected():
                return u'Access Granted'

            @first.route('/protected')
            @login_required
            def first_protected():
                return u'Access Granted'

            @second.route('/protected')
            @login_required
            def second_protected():
                return u'Access Granted'

            self.app.register_blueprint(first, url_prefix='/first')
            self.app.register_blueprint(second, url_prefix='/second')

            set_login_view('app_login')
            set_login_view('first_login', blueprint=first)
            set_login_view('second_login', blueprint=second)

            with self.app.test_client() as c:

                result = c.get('/protected')
                self.assertEqual(result.status_code, 302)
                expected = ('http://localhost/'
                            'app_login?next=%2Fprotected')
                self.assertEqual(result.location, expected)

                result = c.get('/first/protected')
                self.assertEqual(result.status_code, 302)
                expected = ('http://localhost/'
                            'first_login?next=%2Ffirst%2Fprotected')
                self.assertEqual(result.location, expected)

                result = c.get('/second/protected')
                self.assertEqual(result.status_code, 302)
                expected = ('http://localhost/'
                            'second_login?next=%2Fsecond%2Fprotected')
                self.assertEqual(result.location, expected)

    def test_set_login_view_without_blueprints(self):
        with self.app.app_context():

            @self.app.route('/app_login')
            def app_login():
                return 'Login Form Goes Here!'

            @self.app.route('/protected')
            @login_required
            def protected():
                return u'Access Granted'

            set_login_view('app_login')

            with self.app.test_client() as c:

                result = c.get('/protected')
                self.assertEqual(result.status_code, 302)
                expected = 'http://localhost/app_login?next=%2Fprotected'
                self.assertEqual(result.location, expected)

    #
    # Session Persistence/Freshness
    #
    def test_login_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_logout_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_incorrect_id_logs_out(self):
        # Ensure that any attempt to reload the user by the ID
        # will seem as if the user is no longer valid
        @self.login_manager.user_loader
        def new_user_loader(user_id):
            return

        with self.app.test_client() as c:
            # Successfully logs in
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_authentication_is_fresh(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/is-fresh')
            self.assertEqual(u'True', result.data.decode('utf-8'))

    def test_remember_me(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            result = c.get('/username')
            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_remember_me_uses_custom_cookie_parameters(self):
        name = self.app.config['REMEMBER_COOKIE_NAME'] = 'myname'
        duration = self.app.config['REMEMBER_COOKIE_DURATION'] = \
            timedelta(days=2)
        path = self.app.config['REMEMBER_COOKIE_PATH'] = '/mypath'
        domain = self.app.config['REMEMBER_COOKIE_DOMAIN'] = '.localhost.local'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')

            # TODO: Is there a better way to test this?
            self.assertIn(domain, c.cookie_jar._cookies,
                          'Custom domain not found as cookie domain')
            domain_cookie = c.cookie_jar._cookies[domain]
            self.assertIn(path, domain_cookie,
                          'Custom path not found as cookie path')
            path_cookie = domain_cookie[path]
            self.assertIn(name, path_cookie,
                          'Custom name not found as cookie name')
            cookie = path_cookie[name]

            expiration_date = datetime.utcfromtimestamp(cookie.expires)
            expected_date = datetime.utcnow() + duration
            difference = expected_date - expiration_date

            fail_msg = 'The expiration date {0} was far from the expected {1}'
            fail_msg = fail_msg.format(expiration_date, expected_date)
            self.assertLess(difference, timedelta(seconds=10), fail_msg)
            self.assertGreater(difference, timedelta(seconds=-10), fail_msg)

    def test_remember_me_is_unfresh(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.assertEqual(u'False', c.get('/is-fresh').data.decode('utf-8'))

    def test_login_persists_with_signle_x_forwarded_for(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch', headers=[('X-Forwarded-For', '10.1.1.1')])
            result = c.get('/username',
                           headers=[('X-Forwarded-For', '10.1.1.1')])
            self.assertEqual(u'Notch', result.data.decode('utf-8'))
            result = c.get('/username',
                           headers=[('X-Forwarded-For', '10.1.1.1')])
            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_login_persists_with_many_x_forwarded_for(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch',
                  headers=[('X-Forwarded-For', '10.1.1.1')])
            result = c.get('/username',
                           headers=[('X-Forwarded-For', '10.1.1.1')])
            self.assertEqual(u'Notch', result.data.decode('utf-8'))
            result = c.get('/username',
                           headers=[('X-Forwarded-For', '10.1.1.1, 10.1.1.2')])
            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_user_loaded_from_cookie_fired(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            with listen_to(user_loaded_from_cookie) as listener:
                c.get('/username')
                listener.assert_heard_one(self.app, user=notch)

    def test_user_loaded_from_header_fired(self):
        user_id = 1
        user_name = USERS[user_id].name
        self.login_manager.request_callback = None
        with self.app.test_client() as c:
            with listen_to(user_loaded_from_header) as listener:
                headers = [
                    (
                        'Authorization',
                        'Basic %s' % (
                            bytes.decode(
                                base64.b64encode(str.encode(str(user_id))))
                        ),
                    )
                ]
                result = c.get('/username', headers=headers)
                self.assertEqual(user_name, result.data.decode('utf-8'))
                listener.assert_heard_one(self.app, user=USERS[user_id])

    def test_user_loaded_from_request_fired(self):
        user_id = 1
        user_name = USERS[user_id].name
        with self.app.test_client() as c:
            with listen_to(user_loaded_from_request) as listener:
                url = '/username?user_id={user_id}'.format(user_id=user_id)
                result = c.get(url)
                self.assertEqual(user_name, result.data.decode('utf-8'))
                listener.assert_heard_one(self.app, user=USERS[user_id])

    def test_logout_stays_logged_out_with_remember_me(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_needs_refresh_uses_handler(self):
        @self.login_manager.needs_refresh_handler
        def _on_refresh():
            return u'Needs Refresh!'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(u'Needs Refresh!', result.data.decode('utf-8'))

    def test_needs_refresh_fires_needs_refresh_signal(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(user_needs_refresh) as listener:
                c.get('/needs-refresh')
                listener.assert_heard_one(self.app)

    def test_needs_refresh_fires_flash_when_redirect_to_refresh_view(self):
        self.login_manager.refresh_view = '/refresh_view'

        self.login_manager.needs_refresh_message = u'Refresh'
        self.login_manager.needs_refresh_message_category = 'refresh'
        category_filter = [self.login_manager.needs_refresh_message_category]

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/needs-refresh')
            msgs = get_flashed_messages(category_filter=category_filter)
            self.assertIn(self.login_manager.needs_refresh_message, msgs)

    def test_needs_refresh_flash_message_localized(self):
        def _gettext(msg):
            if msg == u'Refresh':
                return u'Aktualisieren'

        self.login_manager.refresh_view = '/refresh_view'
        self.login_manager.localize_callback = _gettext

        self.login_manager.needs_refresh_message = u'Refresh'
        self.login_manager.needs_refresh_message_category = 'refresh'
        category_filter = [self.login_manager.needs_refresh_message_category]

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/needs-refresh')
            msgs = get_flashed_messages(category_filter=category_filter)
            self.assertIn(u'Aktualisieren', msgs)
        self.login_manager.localize_callback = None

    def test_needs_refresh_aborts_401(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(result.status_code, 401)

    def test_redirects_to_refresh_view(self):
        @self.app.route('/refresh-view')
        def refresh_view():
            return ''

        self.login_manager.refresh_view = 'refresh_view'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(result.status_code, 302)
            expected = 'http://localhost/refresh-view?next=%2Fneeds-refresh'
            self.assertEqual(result.location, expected)

    def test_confirm_login(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.assertEqual(u'False', c.get('/is-fresh').data.decode('utf-8'))
            c.get('/confirm-login')
            self.assertEqual(u'True', c.get('/is-fresh').data.decode('utf-8'))

    def test_user_login_confirmed_signal_fired(self):
        with self.app.test_client() as c:
            with listen_to(user_login_confirmed) as listener:
                c.get('/confirm-login')
                listener.assert_heard_one(self.app)

    def test_session_not_modified(self):
        with self.app.test_client() as c:
            # Within the request we think we didn't modify the session.
            self.assertEquals(
                u'modified=False',
                c.get('/empty_session').data.decode('utf-8'))
            # But after the request, the session could be modified by the
            # "after_request" handlers that call _update_remember_cookie.
            # Ensure that if nothing changed the session is not modified.
            self.assertFalse(session.modified)

    #
    # Session Protection
    #
    def test_session_protection_basic_passes_successive_requests(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username')
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'True', fresh_result.data.decode('utf-8'))

    def test_session_protection_strong_passes_successive_requests(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username')
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'True', fresh_result.data.decode('utf-8'))

    def test_session_protection_basic_marks_session_unfresh(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username',
                                    headers=[('User-Agent', 'different')])
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'False', fresh_result.data.decode('utf-8'))

    def test_session_protection_basic_fires_signal(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_basic_skips_when_remember_me(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            # clear session to force remember me (and remove old session id)
            self._delete_session(c)
            # should not trigger protection because "sess" is empty
            with listen_to(session_protected) as listener:
                c.get('/username')
                listener.assert_heard_none(self.app)

    def test_session_protection_strong_skips_when_remember_me(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            # clear session to force remember me (and remove old session id)
            self._delete_session(c)
            # should not trigger protection because "sess" is empty
            with listen_to(session_protected) as listener:
                c.get('/username')
                listener.assert_heard_none(self.app)

    def test_permanent_strong_session_protection_marks_session_unfresh(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-permanent')
            username_result = c.get('/username', headers=[('User-Agent',
                                                           'different')])
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'False', fresh_result.data.decode('utf-8'))

    def test_permanent_strong_session_protection_fires_signal(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-permanent')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_deletes_session(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            # write some unrelated data in the session, to ensure it does not
            # get destroyed
            with c.session_transaction() as sess:
                sess['foo'] = 'bar'
            c.get('/login-notch-remember')
            username_result = c.get('/username', headers=[('User-Agent',
                                                           'different')])
            self.assertEqual(u'Anonymous',
                             username_result.data.decode('utf-8'))
            with c.session_transaction() as sess:
                self.assertIn('foo', sess)
                self.assertEqual('bar', sess['foo'])

    def test_session_protection_strong_fires_signal_user_agent(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_fires_signal_x_forwarded_for(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember',
                  headers=[('X-Forwarded-For', '10.1.1.1')])
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('X-Forwarded-For', '10.1.1.2')])
                listener.assert_heard_one(self.app)

    def test_session_protection_skip_when_off_and_anonymous(self):
        with self.app.test_client() as c:
            # no user access
            with listen_to(user_accessed) as user_listener:
                results = c.get('/')
                user_listener.assert_heard_none(self.app)

            # access user with no session data
            with listen_to(session_protected) as session_listener:
                results = c.get('/username')
                self.assertEqual(results.data.decode('utf-8'), u'Anonymous')
                session_listener.assert_heard_none(self.app)

            # verify no session data has been set
            self.assertFalse(session)

    def test_session_protection_skip_when_basic_and_anonymous(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            # no user access
            with listen_to(user_accessed) as user_listener:
                results = c.get('/')
                user_listener.assert_heard_none(self.app)

            # access user with no session data
            with listen_to(session_protected) as session_listener:
                results = c.get('/username')
                self.assertEqual(results.data.decode('utf-8'), u'Anonymous')
                session_listener.assert_heard_none(self.app)

            # verify no session data has been set
            self.assertFalse(session)

    #
    # Custom Token Loader
    #
    def test_custom_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)

            # Test that remember me functionality still works
            self.assertEqual(u'Notch', c.get('/username').data.decode('utf-8'))

            # Test that we used the custom authentication token
            remember_cookie = self._get_remember_cookie(c)
            expected_value = make_secure_token(u'Notch', key='deterministic')
            self.assertEqual(expected_value, remember_cookie.value)

    def test_change_api_key_with_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.app.config['SECRET_KEY'] = 'ima change this now'

            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Notch')

    def test_custom_token_loader_with_no_user(self):
        @self.login_manager.token_loader
        def load_token(token):
            return

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)

            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    #
    # Lazy Access User
    #
    def test_requests_without_accessing_session(self):
        with self.app.test_client() as c:
            c.get('/login-notch')

            # no session access
            with listen_to(user_accessed) as listener:
                c.get('/')
                listener.assert_heard_none(self.app)

            # should have a session access
            with listen_to(user_accessed) as listener:
                result = c.get('/username')
                listener.assert_heard_one(self.app)
                self.assertEqual(result.data.decode('utf-8'), u'Notch')

    #
    # View Decorators
    #
    def test_login_required_decorator(self):
        @self.app.route('/protected')
        @login_required
        def protected():
            return u'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch')
            result2 = c.get('/protected')
            self.assertIn(u'Access Granted', result2.data.decode('utf-8'))

    def test_decorators_are_disabled(self):
        @self.app.route('/protected')
        @login_required
        @fresh_login_required
        def protected():
            return u'Access Granted'

        self.app.login_manager._login_disabled = True

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertIn(u'Access Granted', result.data.decode('utf-8'))

    def test_fresh_login_required_decorator(self):
        @self.app.route('/very-protected')
        @fresh_login_required
        def very_protected():
            return 'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/very-protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch-remember')
            logged_in_result = c.get('/very-protected')
            self.assertEqual(u'Access Granted',
                             logged_in_result.data.decode('utf-8'))

            self._delete_session(c)
            stale_result = c.get('/very-protected')
            self.assertEqual(stale_result.status_code, 401)

            c.get('/confirm-login')
            refreshed_result = c.get('/very-protected')
            self.assertEqual(u'Access Granted',
                             refreshed_result.data.decode('utf-8'))

    #
    # Misc
    #
    @unittest.skipIf(werkzeug_version.startswith("0.9"),
                     "wait for upstream implementing RFC 5987")
    def test_chinese_user_agent(self):
        with self.app.test_client() as c:
            result = c.get('/', headers=[('User-Agent', u'中文')])
            self.assertEqual(u'Welcome!', result.data.decode('utf-8'))

    @unittest.skipIf(werkzeug_version.startswith("0.9"),
                     "wait for upstream implementing RFC 5987")
    def test_russian_cp1251_user_agent(self):
        with self.app.test_client() as c:
            headers = [('User-Agent', u'ЯЙЮя'.encode('cp1251'))]
            response = c.get('/', headers=headers)
            self.assertEqual(response.data.decode('utf-8'), u'Welcome!')

    def test_make_secure_token_default_key(self):
        # Old test: 0f05743a2b617b2625362ab667c0dbdf4c9ec13a
        # New test with sha512:
        h1 = "47bec94a46a5d3939ca0671b01bafd5d7d5353941791734ec1e4734de40e5ce0"
        h2 = "a45c05c17cd33b8a18840991bb1cc154fa4ee8ef2f80a572b5a6a24b3a3afc20"
        with self.app.test_request_context():
            self.assertEqual(make_secure_token('foo'), h1 + h2)

    def test_user_context_processor(self):
        with self.app.test_request_context():
            _ucp = self.app.context_processor(_user_context_processor)
            self.assertIsInstance(_ucp()['current_user'], AnonymousUserMixin)
예제 #19
0
class LoginTestCase(unittest.TestCase):
    ''' Tests for results of the login_user function '''
    def setUp(self):
        self.app = Flask(__name__)
        self.app.config['SECRET_KEY'] = 'deterministic'
        self.app.config['SESSION_PROTECTION'] = None
        self.app.config['TESTING'] = True
        self.remember_cookie_name = 'remember'
        self.app.config['REMEMBER_COOKIE_NAME'] = self.remember_cookie_name
        self.login_manager = LoginManager()
        self.login_manager.init_app(self.app)
        self.login_manager._login_disabled = False

        @self.app.route('/')
        def index():
            return u'Welcome!'

        @self.app.route('/secret')
        def secret():
            return self.login_manager.unauthorized()

        @self.app.route('/login-notch')
        def login_notch():
            return unicode(login_user(notch))

        @self.app.route('/login-notch-remember')
        def login_notch_remember():
            return unicode(login_user(notch, remember=True))

        @self.app.route('/login-notch-permanent')
        def login_notch_permanent():
            session.permanent = True
            return unicode(login_user(notch))

        @self.app.route('/needs-refresh')
        def needs_refresh():
            return self.login_manager.needs_refresh()

        @self.app.route('/confirm-login')
        def _confirm_login():
            confirm_login()
            return u''

        @self.app.route('/username')
        def username():
            if current_user.is_authenticated():
                return current_user.name
            return u'Anonymous'

        @self.app.route('/is-fresh')
        def is_fresh():
            return unicode(login_fresh())

        @self.app.route('/logout')
        def logout():
            return unicode(logout_user())

        @self.login_manager.user_loader
        def load_user(user_id):
            return USERS[int(user_id)]

        @self.app.route('/empty_session')
        def empty_session():
            return unicode(u'modified=%s' % session.modified)

        # This will help us with the possibility of typoes in the tests. Now
        # we shouldn't have to check each response to help us set up state
        # (such as login pages) to make sure it worked: we will always
        # get an exception raised (rather than return a 404 response)
        @self.app.errorhandler(404)
        def handle_404(e):
            raise e

        unittest.TestCase.setUp(self)

    def _get_remember_cookie(self, test_client):
        our_cookies = test_client.cookie_jar._cookies['localhost.local']['/']
        return our_cookies[self.remember_cookie_name]

    def _delete_session(self, c):
        # Helper method to cause the session to be deleted
        # as if the browser was closed. This will remove
        # the session regardless of the permament flag
        # on the session!
        with c.session_transaction() as sess:
            sess.clear()

    #
    # Login
    #
    def test_test_request_context_users_are_anonymous(self):
        with self.app.test_request_context():
            self.assertTrue(current_user.is_anonymous())

    def test_defaults_anonymous(self):
        with self.app.test_client() as c:
            result = c.get('/username')
            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_login_user(self):
        with self.app.test_request_context():
            result = login_user(notch)
            self.assertTrue(result)
            self.assertEqual(current_user.name, u'Notch')

    def test_login_user_emits_signal(self):
        with self.app.test_request_context():
            with listen_to(user_logged_in) as listener:
                login_user(notch)
                listener.assert_heard_one(self.app, user=notch)

    def test_login_inactive_user(self):
        with self.app.test_request_context():
            result = login_user(creeper)
            self.assertTrue(current_user.is_anonymous())
            self.assertFalse(result)

    def test_login_inactive_user_forced(self):
        with self.app.test_request_context():
            login_user(creeper, force=True)
            self.assertEqual(current_user.name, u'Creeper')

    #
    # Logout
    #
    def test_logout_logs_out_current_user(self):
        with self.app.test_request_context():
            login_user(notch)
            logout_user()
            self.assertTrue(current_user.is_anonymous())

    def test_logout_emits_signal(self):
        with self.app.test_request_context():
            login_user(notch)
            with listen_to(user_logged_out) as listener:
                logout_user()
                listener.assert_heard_one(self.app, user=notch)

    #
    # Unauthorized
    #
    def test_unauthorized_fires_unauthorized_signal(self):
        with self.app.test_client() as c:
            with listen_to(user_unauthorized) as listener:
                c.get('/secret')
                listener.assert_heard_one(self.app)

    def test_unauthorized_flashes_message_with_login_view(self):
        self.login_manager.login_view = '/login'

        expected_message = self.login_manager.login_message = u'Log in!'
        expected_category = self.login_manager.login_message_category = 'login'

        with self.app.test_client() as c:
            c.get('/secret')
            msgs = get_flashed_messages(category_filter=[expected_category])
            self.assertEqual([expected_message], msgs)

    def test_unauthorized_uses_authorized_handler(self):
        @self.login_manager.unauthorized_handler
        def _callback():
            return Response('This is secret!', 401)

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)
            self.assertEqual(u'This is secret!', result.data.decode('utf-8'))

    def test_unauthorized_aborts_with_401(self):
        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 401)

    def test_unauthorized_redirects_to_login_view(self):
        self.login_manager.login_view = 'login'

        @self.app.route('/login')
        def login():
            return 'Login Form Goes Here!'

        with self.app.test_client() as c:
            result = c.get('/secret')
            self.assertEqual(result.status_code, 302)
            self.assertEqual(result.location,
                             'http://localhost/login?next=%2Fsecret')

    #
    # Session Persistence/Freshness
    #
    def test_login_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_logout_persists(self):
        with self.app.test_client() as c:
            c.get('/login-notch')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_incorrect_id_logs_out(self):
        # Ensure that any attempt to reload the user by the ID
        # will seem as if the user is no longer valid
        @self.login_manager.user_loader
        def new_user_loader(user_id):
            return

        with self.app.test_client() as c:
            # Successfully logs in
            c.get('/login-notch')
            result = c.get('/username')

            self.assertEqual(u'Anonymous', result.data.decode('utf-8'))

    def test_authentication_is_fresh(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/is-fresh')
            self.assertEqual(u'True', result.data.decode('utf-8'))

    def test_remember_me(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            result = c.get('/username')
            self.assertEqual(u'Notch', result.data.decode('utf-8'))

    def test_remember_me_uses_custom_cookie_parameters(self):
        name = self.app.config['REMEMBER_COOKIE_NAME'] = 'myname'
        duration = self.app.config['REMEMBER_COOKIE_DURATION'] = \
            timedelta(days=2)
        domain = self.app.config['REMEMBER_COOKIE_DOMAIN'] = '.localhost.local'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')

            # TODO: Is there a better way to test this?
            self.assertTrue(domain in c.cookie_jar._cookies,
                            'Custom domain not found as cookie domain')
            domain_cookie = c.cookie_jar._cookies[domain]
            self.assertTrue(name in domain_cookie['/'],
                            'Custom name not found as cookie name')
            cookie = domain_cookie['/'][name]

            expiration_date = datetime.fromtimestamp(cookie.expires)
            expected_date = datetime.now() + duration
            difference = expected_date - expiration_date

            fail_msg = 'The expiration date {0} was far from the expected {1}'
            fail_msg = fail_msg.format(expiration_date, expected_date)
            self.assertLess(difference, timedelta(seconds=10), fail_msg)
            self.assertGreater(difference, timedelta(seconds=-10), fail_msg)

    def test_remember_me_is_unfresh(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.assertEqual(u'False', c.get('/is-fresh').data.decode('utf-8'))

    def test_user_loaded_from_cookie_fired(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            with listen_to(user_loaded_from_cookie) as listener:
                c.get('/username')
                listener.assert_heard_one(self.app, user=notch)

    def test_logout_stays_logged_out_with_remember_me(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/logout')
            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    def test_needs_refresh_uses_handler(self):
        @self.login_manager.needs_refresh_handler
        def _on_refresh():
            return u'Needs Refresh!'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(u'Needs Refresh!', result.data.decode('utf-8'))

    def test_needs_refresh_fires_needs_refresh_signal(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(user_needs_refresh) as listener:
                c.get('/needs-refresh')
                listener.assert_heard_one(self.app)

    def test_needs_refresh_fires_flash_when_redirect_to_refresh_view(self):
        self.login_manager.refresh_view = '/refresh_view'

        self.login_manager.needs_refresh_message = u'Refresh'
        self.login_manager.needs_refresh_message_category = 'refresh'
        category_filter = [self.login_manager.needs_refresh_message_category]

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            c.get('/needs-refresh')
            msgs = get_flashed_messages(category_filter=category_filter)
            self.assertIn(self.login_manager.needs_refresh_message, msgs)

    def test_needs_refresh_aborts_403(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(result.status_code, 403)

    def test_redirects_to_refresh_view(self):
        @self.app.route('/refresh-view')
        def refresh_view():
            return ''

        self.login_manager.refresh_view = 'refresh_view'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            result = c.get('/needs-refresh')
            self.assertEqual(result.status_code, 302)
            expected = 'http://localhost/refresh-view?next=%2Fneeds-refresh'
            self.assertEqual(result.location, expected)

    def test_confirm_login(self):
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.assertEqual(u'False', c.get('/is-fresh').data.decode('utf-8'))
            c.get('/confirm-login')
            self.assertEqual(u'True', c.get('/is-fresh').data.decode('utf-8'))

    def test_user_login_confirmed_signal_fired(self):
        with self.app.test_client() as c:
            with listen_to(user_login_confirmed) as listener:
                c.get('/confirm-login')
                listener.assert_heard_one(self.app)

    def test_session_not_modified(self):
        with self.app.test_client() as c:
            # Within the request we think we didn't modify the session.
            self.assertEquals(u'modified=False',
                              c.get('/empty_session').data.decode('utf-8'))
            # But after the request, the session could be modified by the
            # "after_request" handlers that call _update_remember_cookie.
            # Ensure that if nothing changed the session is not modified.
            self.assertFalse(session.modified)

    #
    # Session Protection
    #
    def test_session_protection_basic_marks_session_unfresh(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username',
                                    headers=[('User-Agent', 'different')])
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'False', fresh_result.data.decode('utf-8'))

    def test_session_protection_basic_fires_signal(self):
        self.app.config['SESSION_PROTECTION'] = 'basic'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_permanent_strong_session_protection_marks_session_unfresh(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-permanent')
            username_result = c.get('/username',
                                    headers=[('User-Agent', 'different')])
            self.assertEqual(u'Notch', username_result.data.decode('utf-8'))
            fresh_result = c.get('/is-fresh')
            self.assertEqual(u'False', fresh_result.data.decode('utf-8'))

    def test_permanent_strong_session_protection_fires_signal(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-permanent')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_deletes_session(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'
        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            username_result = c.get('/username',
                                    headers=[('User-Agent', 'different')])
            self.assertEqual(u'Anonymous',
                             username_result.data.decode('utf-8'))

    def test_session_protection_strong_fires_signal_user_agent(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('User-Agent', 'different')])
                listener.assert_heard_one(self.app)

    def test_session_protection_strong_fires_signal_x_forwarded_for(self):
        self.app.config['SESSION_PROTECTION'] = 'strong'

        with self.app.test_client() as c:
            c.get('/login-notch-remember',
                  headers=[('X-Forwarded-For', '10.1.1.1')])
            with listen_to(session_protected) as listener:
                c.get('/username', headers=[('X-Forwarded-For', '10.1.1.2')])
                listener.assert_heard_one(self.app)

    #
    # Custom Token Loader
    #
    def test_custom_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)

            # Test that remember me functionality still works
            self.assertEqual(u'Notch', c.get('/username').data.decode('utf-8'))

            # Test that we used the custom authentication token
            remember_cookie = self._get_remember_cookie(c)
            expected_value = make_secure_token(u'Notch', key='deterministic')
            self.assertEqual(expected_value, remember_cookie.value)

    def test_change_api_key_with_token_loader(self):
        @self.login_manager.token_loader
        def load_token(token):
            return USER_TOKENS.get(token)

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)
            self.app.config['SECRET_KEY'] = 'ima change this now'

            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Notch')

    def test_custom_token_loader_with_no_user(self):
        @self.login_manager.token_loader
        def load_token(token):
            return

        with self.app.test_client() as c:
            c.get('/login-notch-remember')
            self._delete_session(c)

            result = c.get('/username')
            self.assertEqual(result.data.decode('utf-8'), u'Anonymous')

    #
    # View Decorators
    #
    def test_login_required_decorator(self):
        @self.app.route('/protected')
        @login_required
        def protected():
            return u'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch')
            result2 = c.get('/protected')
            self.assertIn(u'Access Granted', result2.data.decode('utf-8'))

    def test_decorators_are_disabled(self):
        @self.app.route('/protected')
        @login_required
        @fresh_login_required
        def protected():
            return u'Access Granted'

        self.app.login_manager._login_disabled = True

        with self.app.test_client() as c:
            result = c.get('/protected')
            self.assertIn(u'Access Granted', result.data.decode('utf-8'))

    def test_fresh_login_required_decorator(self):
        @self.app.route('/very-protected')
        @fresh_login_required
        def very_protected():
            return 'Access Granted'

        with self.app.test_client() as c:
            result = c.get('/very-protected')
            self.assertEqual(result.status_code, 401)

            c.get('/login-notch-remember')
            logged_in_result = c.get('/very-protected')
            self.assertEqual(u'Access Granted',
                             logged_in_result.data.decode('utf-8'))

            self._delete_session(c)
            stale_result = c.get('/very-protected')
            self.assertEqual(stale_result.status_code, 403)

            c.get('/confirm-login')
            refreshed_result = c.get('/very-protected')
            self.assertEqual(u'Access Granted',
                             refreshed_result.data.decode('utf-8'))

    #
    # Misc
    #
    @unittest.skipIf(werkzeug_version.startswith("0.9"),
                     "wait for upstream implementing RFC 5987")
    def test_chinese_user_agent(self):
        with self.app.test_client() as c:
            result = c.get('/', headers=[('User-Agent', u'中文')])
            self.assertEqual(u'Welcome!', result.data.decode('utf-8'))

    @unittest.skipIf(werkzeug_version.startswith("0.9"),
                     "wait for upstream implementing RFC 5987")
    def test_russian_cp1251_user_agent(self):
        with self.app.test_client() as c:
            headers = [('User-Agent', u'ЯЙЮя'.encode('cp1251'))]
            response = c.get('/', headers=headers)
            self.assertEqual(response.data.decode('utf-8'), u'Welcome!')

    def test_make_secure_token_default_key(self):
        with self.app.test_request_context():
            self.assertEqual(make_secure_token('foo'),
                             '0f05743a2b617b2625362ab667c0dbdf4c9ec13a')

    def test_user_context_processor(self):
        self.assertEqual(_user_context_processor(), {'current_user': None})