diff --git a/src/flask_login/login_manager.py b/src/flask_login/login_manager.py index 795e7441..e569ff48 100644 --- a/src/flask_login/login_manager.py +++ b/src/flask_login/login_manager.py @@ -402,6 +402,8 @@ def _update_remember_cookie(self, response): self._set_cookie(response) elif operation == "clear": self._clear_cookie(response) + elif operation == "unset": + session["_remember"] = "unset" return response diff --git a/src/flask_login/utils.py b/src/flask_login/utils.py index 57d49f60..3fd4d2fa 100644 --- a/src/flask_login/utils.py +++ b/src/flask_login/utils.py @@ -198,6 +198,8 @@ def login_user(user, remember=False, duration=None, force=False, fresh=True): raise Exception( f"duration must be a datetime.timedelta, instead got: {duration}" ) from e + else: + session["_remember"] = "unset" current_app.login_manager._update_request_context_with_user(user) user_logged_in.send(current_app._get_current_object(), user=_get_user()) diff --git a/tests/test_login.py b/tests/test_login.py index 50f87872..bb4701df 100644 --- a/tests/test_login.py +++ b/tests/test_login.py @@ -1660,3 +1660,102 @@ def test_session_protection_modes(self): self.assertEqual("Anonymous", username.data.decode("utf-8")) is_fresh = c.get("/is-fresh") self.assertEqual("False", is_fresh.data.decode("utf-8")) + + +class CookieRefreshTest(unittest.TestCase): + """ + This class tests the bug from issue #824. It makes sure that if a user is + logged in with their "rememeber" parameter as False, that user doesn't + have any cookies, even if the "REMEMBER_COOKIE_REFRESH_EACH_REQUEST" + config option is True. + """ + + def setUp(self): + self.app = Flask(__name__) + self.login_manager = LoginManager() + self.login_manager.init_app(self.app) + self.app.config["SECRET_KEY"] = "deterministic" + self.app.config["LOGIN_DISABLED"] = False + self.remember_cookie_name = "remember" + self.app.config["REMEMBER_COOKIE_NAME"] = self.remember_cookie_name + self.app.test_client_class = FlaskLoginClient + + @self.app.route("/login-notch0") + def login_notch0(): + self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = True + login_user(notch, remember=True) + return "Hello" + + @self.app.route("/login-notch1") + def login_notch1(): + self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = False + login_user(notch, remember=True) + return "Hello" + + @self.app.route("/login-notch2") + def login_notch2(): + self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = True + login_user(notch, remember=False) + return "Hello" + + @self.app.route("/login-notch3") + def login_notch3(): + self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = False + login_user(notch, remember=False) + return "Hello" + + # This will help us with the possibility of typos 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) + + # each of these function tests one of the ways cookie rememberance + # configuration options can be set. + def test_0(self): + with self.app.test_client() as c: + name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname" + path = self.app.config["REMEMBER_COOKIE_PATH"] = "/mypath" + domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = "localhost.local" + + c.get("/login-notch0") + + cookie = c.get_cookie(name, domain, path) + self.assertIsNotNone(cookie) + + def test_1(self): + with self.app.test_client() as c: + name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname" + path = self.app.config["REMEMBER_COOKIE_PATH"] = "/mypath" + domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = "localhost.local" + + c.get("/login-notch1") + + cookie = c.get_cookie(name, domain, path) + self.assertIsNotNone(cookie) + + def test_2(self): + with self.app.test_client() as c: + name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname" + path = self.app.config["REMEMBER_COOKIE_PATH"] = "/mypath" + domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = "localhost.local" + + c.get("/login-notch2") + + cookie = c.get_cookie(name, domain, path) + self.assertIsNone(cookie) + + def test_3(self): + with self.app.test_client() as c: + name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname" + path = self.app.config["REMEMBER_COOKIE_PATH"] = "/mypath" + domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = "localhost.local" + + c.get("/login-notch3") + cookie = c.get_cookie(name, domain, path) + + self.assertIsNone(cookie)