From 1d1a3a894602671382c07c3dba0abad6ffb956db Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Wed, 5 Feb 2020 21:10:05 -0800 Subject: [PATCH] prefix internal session keys with underscore (#470) --- CHANGES | 3 ++- flask_login/config.py | 9 ++++++++- flask_login/login_manager.py | 24 ++++++++++++------------ flask_login/test_client.py | 2 +- flask_login/utils.py | 22 +++++++++++----------- test_login.py | 10 +++++----- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index 03d2c7f5..434db2fe 100644 --- a/CHANGES +++ b/CHANGES @@ -5,7 +5,8 @@ Here you can see the full list of changes between each Flask-Login release. Unreleased ---------- -nothing yet +- Prefix authenticated user_id, remember, and remember_seconds in Flask Session + with underscores to prevent accidental usage in application code. #465 Version 1.0.0 ------------- diff --git a/flask_login/config.py b/flask_login/config.py index 724198b6..c6516fc2 100644 --- a/flask_login/config.py +++ b/flask_login/config.py @@ -42,7 +42,14 @@ #: A set of session keys that are populated by Flask-Login. Use this set to #: purge keys safely and accurately. -SESSION_KEYS = set(['user_id', 'remember', '_id', '_fresh', 'next']) +SESSION_KEYS = set([ + '_user_id', + '_remember', + '_remember_seconds', + '_id', + '_fresh', + 'next', +]) #: A set of HTTP methods which are exempt from `login_required` and #: `fresh_login_required`. By default, this is just ``OPTIONS``. diff --git a/flask_login/login_manager.py b/flask_login/login_manager.py index 1919bae1..6c8c38e9 100644 --- a/flask_login/login_manager.py +++ b/flask_login/login_manager.py @@ -313,7 +313,7 @@ def _load_user(self): user = None # Load user from Flask Session - user_id = session.get('user_id') + user_id = session.get('_user_id') if user_id is not None and self._user_callback is not None: user = self._user_callback(user_id) @@ -323,7 +323,7 @@ def _load_user(self): cookie_name = config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME) header_name = config.get('AUTH_HEADER_NAME', AUTH_HEADER_NAME) has_cookie = (cookie_name in request.cookies and - session.get('remember') != 'clear') + session.get('_remember') != 'clear') if has_cookie: cookie = request.cookies[cookie_name] user = self._load_user_from_remember_cookie(cookie) @@ -356,7 +356,7 @@ def _session_protection_failed(self): for k in SESSION_KEYS: sess.pop(k, None) - sess['remember'] = 'clear' + sess['_remember'] = 'clear' session_protected.send(app) return True @@ -365,7 +365,7 @@ def _session_protection_failed(self): def _load_user_from_remember_cookie(self, cookie): user_id = decode_cookie(cookie) if user_id is not None: - session['user_id'] = user_id + session['_user_id'] = user_id session['_fresh'] = False user = None if self._user_callback: @@ -396,14 +396,14 @@ def _load_user_from_request(self, request): def _update_remember_cookie(self, response): # Don't modify the session unless there's something to do. - if 'remember' not in session and \ + if '_remember' not in session and \ current_app.config.get('REMEMBER_COOKIE_REFRESH_EACH_REQUEST'): - session['remember'] = 'set' + session['_remember'] = 'set' - if 'remember' in session: - operation = session.pop('remember', None) + if '_remember' in session: + operation = session.pop('_remember', None) - if operation == 'set' and 'user_id' in session: + if operation == 'set' and '_user_id' in session: self._set_cookie(response) elif operation == 'clear': self._clear_cookie(response) @@ -420,13 +420,13 @@ def _set_cookie(self, response): secure = config.get('REMEMBER_COOKIE_SECURE', COOKIE_SECURE) httponly = config.get('REMEMBER_COOKIE_HTTPONLY', COOKIE_HTTPONLY) - if 'remember_seconds' in session: - duration = timedelta(seconds=session['remember_seconds']) + if '_remember_seconds' in session: + duration = timedelta(seconds=session['_remember_seconds']) else: duration = config.get('REMEMBER_COOKIE_DURATION', COOKIE_DURATION) # prepare data - data = encode_cookie(text_type(session['user_id'])) + data = encode_cookie(text_type(session['_user_id'])) if isinstance(duration, int): duration = timedelta(seconds=duration) diff --git a/flask_login/test_client.py b/flask_login/test_client.py index fe44f937..54401f40 100644 --- a/flask_login/test_client.py +++ b/flask_login/test_client.py @@ -15,5 +15,5 @@ def __init__(self, *args, **kwargs): if user: with self.session_transaction() as sess: - sess["user_id"] = user.get_id() + sess["_user_id"] = user.get_id() sess["_fresh"] = fresh diff --git a/flask_login/utils.py b/flask_login/utils.py index e6e3391f..1376944f 100644 --- a/flask_login/utils.py +++ b/flask_login/utils.py @@ -167,19 +167,19 @@ def login_user(user, remember=False, duration=None, force=False, fresh=True): return False user_id = getattr(user, current_app.login_manager.id_attribute)() - session['user_id'] = user_id + session['_user_id'] = user_id session['_fresh'] = fresh session['_id'] = current_app.login_manager._session_identifier_generator() if remember: - session['remember'] = 'set' + session['_remember'] = 'set' if duration is not None: try: # equal to timedelta.total_seconds() but works with Python 2.6 - session['remember_seconds'] = (duration.microseconds + - (duration.seconds + - duration.days * 24 * 3600) * - 10**6) / 10.0**6 + session['_remember_seconds'] = (duration.microseconds + + (duration.seconds + + duration.days * 24 * 3600) * + 10**6) / 10.0**6 except AttributeError: raise Exception('duration must be a datetime.timedelta, ' 'instead got: {0}'.format(duration)) @@ -197,8 +197,8 @@ def logout_user(): user = _get_user() - if 'user_id' in session: - session.pop('user_id') + if '_user_id' in session: + session.pop('_user_id') if '_fresh' in session: session.pop('_fresh') @@ -208,9 +208,9 @@ def logout_user(): cookie_name = current_app.config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME) if cookie_name in request.cookies: - session['remember'] = 'clear' - if 'remember_seconds' in session: - session.pop('remember_seconds') + session['_remember'] = 'clear' + if '_remember_seconds' in session: + session.pop('_remember_seconds') user_logged_out.send(current_app._get_current_object(), user=user) diff --git a/test_login.py b/test_login.py index c31fe87e..8c6830e9 100644 --- a/test_login.py +++ b/test_login.py @@ -202,7 +202,7 @@ def test_login_disabled_is_set(self): def test_no_user_loader_raises(self): login_manager = LoginManager(self.app, add_context_processor=True) with self.app.test_request_context(): - session['user_id'] = '2' + session['_user_id'] = '2' with self.assertRaises(Exception) as cm: login_manager._load_user() expected_message = 'Missing user_loader or request_loader' @@ -443,7 +443,7 @@ def test_logout_emits_signal(self): def test_logout_without_current_user(self): with self.app.test_request_context(): login_user(notch) - del session['user_id'] + del session['_user_id'] with listen_to(user_logged_out) as listener: logout_user() listener.assert_heard_one(self.app, user=ANY) @@ -783,7 +783,7 @@ def test_set_cookie_with_invalid_duration_raises_exception(self): with self.assertRaises(Exception) as cm: with self.app.test_request_context(): - session['user_id'] = 2 + session['_user_id'] = 2 self.login_manager._set_cookie(None) expected_exception_message = 'REMEMBER_COOKIE_DURATION must be a ' \ @@ -1036,7 +1036,7 @@ def test_invalid_remember_cookie(self): with self.app.test_client() as c: c.get('/login-notch-remember') with c.session_transaction() as sess: - sess['user_id'] = None + sess['_user_id'] = None c.set_cookie(domain, self.remember_cookie_name, 'foo') result = c.get('/username') self.assertEqual(u'Anonymous', result.data.decode('utf-8')) @@ -1322,7 +1322,7 @@ def logout(): @self.login_manager.request_loader def load_user_from_request(request): - user_id = request.args.get('user_id') or session.get('user_id') + user_id = request.args.get('user_id') or session.get('_user_id') try: user_id = int(float(user_id)) except TypeError: