From 9cadf855a4506e29009a910206c6ce213279aafe Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 10:42:28 -0400 Subject: [PATCH 1/8] Adjust POST_LOGIN_VIEW and POST_LOGOUT_VIEW test --- tests/test_configuration.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 9a75ae0..0c9519d 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -23,11 +23,13 @@ def test_view_configuration(client): response = client.get('/custom_login') assert b"

Login

" in response.data - response = authenticate(client, endpoint='/custom_login', follow_redirects=True) - assert b'Post Login' in response.data + response = authenticate(client, endpoint='/custom_login') + assert b'location' in response.headers + assert response.headers['Location'] == 'http://localhost/post_login' - response = logout(client, endpoint='/custom_logout', follow_redirects=True) - assert b'Post Logout' in response.data + response = logout(client, endpoint='/custom_logout') + assert b'location' in response.headers + assert response.headers['Location'] == 'http://localhost/post_logout' response = client.get('/http', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") From 0facdaacd96430d71b2be82975a47a5ebcb2900a Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 10:48:56 -0400 Subject: [PATCH 2/8] Make `validate_redirect_url` smarter. Fixes #261. --- flask_security/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_security/utils.py b/flask_security/utils.py index 5b20352..ddca6be 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -196,7 +196,7 @@ def url_for_security(endpoint, **values): def validate_redirect_url(url): - if url is None: + if url is None or url.strip() == '': return False url_next = urlsplit(url) url_base = urlsplit(request.host_url) From 96f1b3e0d113677da742ec05ccd7f539705ec682 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 11:11:53 -0400 Subject: [PATCH 3/8] Fix tests to pass python 3 --- flask_security/utils.py | 2 +- tests/test_configuration.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask_security/utils.py b/flask_security/utils.py index ddca6be..b9837c4 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -150,7 +150,7 @@ def encrypt_password(password): def md5(data): - return hashlib.md5(data.encode('ascii')).hexdigest() + return hashlib.md5(data.encode('utf-8')).hexdigest() def do_flash(message, category=None): diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 0c9519d..6bc3c52 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -24,11 +24,11 @@ def test_view_configuration(client): assert b"

Login

" in response.data response = authenticate(client, endpoint='/custom_login') - assert b'location' in response.headers + assert 'location' in response.headers assert response.headers['Location'] == 'http://localhost/post_login' response = logout(client, endpoint='/custom_logout') - assert b'location' in response.headers + assert 'location' in response.headers assert response.headers['Location'] == 'http://localhost/post_logout' response = client.get('/http', headers={ From 0a48997fdd32b4027e07361dbd4b2238fbdae62e Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 11:47:35 -0400 Subject: [PATCH 4/8] Improve encoding of strings. Addresses #231 and #253 --- flask_security/utils.py | 18 +++++++++++++++--- tests/test_misc.py | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/flask_security/utils.py b/flask_security/utils.py index b9837c4..f519b3d 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -99,13 +99,15 @@ def get_hmac(password): :param password: The password to sign """ - if _security.password_salt is None: + salt = _security.password_salt + + if salt is None: raise RuntimeError( 'The configuration value `SECURITY_PASSWORD_SALT` must ' 'not be None when the value of `SECURITY_PASSWORD_HASH` is ' 'set to "%s"' % _security.password_hash) - h = hmac.new(_security.password_salt.encode('utf-8'), password.encode('utf-8'), hashlib.sha512) + h = hmac.new(encode_string(salt), encode_string(password), hashlib.sha512) return base64.b64encode(h.digest()) @@ -149,8 +151,18 @@ def encrypt_password(password): return _pwd_context.encrypt(signed) +def encode_string(string): + """Encodes a string to bytes, if it isn't already. + + :param string: The string to encode""" + + if isinstance(string, text_type): + string = string.encode('utf-8') + return string + + def md5(data): - return hashlib.md5(data.encode('utf-8')).hexdigest() + return hashlib.md5(encode_string(data)).hexdigest() def do_flash(message, category=None): diff --git a/tests/test_misc.py b/tests/test_misc.py index 8be2a7d..9b76457 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -12,7 +12,7 @@ from flask_security import Security from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \ SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \ ChangePasswordForm, TextField, PasswordField, email_required, email_validator, valid_user_email -from flask_security.utils import capture_reset_password_requests +from flask_security.utils import capture_reset_password_requests, md5, string_types from utils import authenticate, init_app_with_options, populate_data @@ -170,3 +170,19 @@ def test_change_hash_type(app, sqlalchemy_datastore): response = client.post('/login', data=dict(email='matt@lp.com', password='password')) assert response.status_code == 302 + + +def test_md5(): + data = md5(b'hello') + assert isinstance(data, string_types) + data = md5(u'hellö') + assert isinstance(data, string_types) + + +@pytest.mark.settings(password_salt=u'öööööööööööööööööööööööööööööööööö', + password_hash='bcrypt') +def test_password_unicode_password_salt(client): + response = authenticate(client) + assert response.status_code == 302 + response = authenticate(client, follow_redirects=True) + assert b'Hello matt@lp.com' in response.data From a6b5d3053cca64f05c9741272844a3832cc3b2f4 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 12:14:52 -0400 Subject: [PATCH 5/8] Use `safe_str_cmp` when evaluating tokens. Fixes #252 --- flask_security/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask_security/core.py b/flask_security/core.py index e6341e2..7e922b4 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -18,6 +18,7 @@ from itsdangerous import URLSafeTimedSerializer from passlib.context import CryptContext from werkzeug.datastructures import ImmutableList from werkzeug.local import LocalProxy +from werkzeug.security import safe_str_cmp from .utils import config_value as cv, get_config, md5, url_for_security, string_types from .views import create_blueprint @@ -193,7 +194,7 @@ def _token_loader(token): try: data = _security.remember_token_serializer.loads(token) user = _security.datastore.find_user(id=data[0]) - if user and md5(user.password) == data[1]: + if user and safe_str_cmp(md5(user.password), data[1]): return user except: pass From 76cf3eaf6a2f55ff74e5cc227cb06ca33ca6d593 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 12:24:19 -0400 Subject: [PATCH 6/8] Do not expose user info in `/reset` responses. Fixes #249 --- flask_security/views.py | 12 +++++++----- tests/test_recoverable.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/flask_security/views.py b/flask_security/views.py index 847c980..be80372 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -34,7 +34,7 @@ _security = LocalProxy(lambda: current_app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) -def _render_json(form, include_auth_token=False): +def _render_json(form, include_user=True, include_auth_token=False): has_errors = len(form.errors) > 0 if has_errors: @@ -42,7 +42,9 @@ def _render_json(form, include_auth_token=False): response = dict(errors=form.errors) else: code = 200 - response = dict(user=dict(id=str(form.user.id))) + response = dict() + if include_user: + response['user'] = dict(id=str(form.user.id)) if include_auth_token: token = form.user.get_auth_token() response['user']['authentication_token'] = token @@ -78,7 +80,7 @@ def login(): return redirect(get_post_login_redirect(form.next.data)) if request.json: - return _render_json(form, True) + return _render_json(form, include_auth_token=True) return _security.render_template(config_value('LOGIN_USER_TEMPLATE'), login_user_form=form, @@ -121,7 +123,7 @@ def register(): if not request.json: return redirect(get_post_register_redirect()) - return _render_json(form, True) + return _render_json(form, include_auth_token=True) if request.json: return _render_json(form) @@ -247,7 +249,7 @@ def forgot_password(): do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email)) if request.json: - return _render_json(form) + return _render_json(form, include_user=False) return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'), forgot_password_form=form, diff --git a/tests/test_recoverable.py b/tests/test_recoverable.py index ade31a0..de278e1 100644 --- a/tests/test_recoverable.py +++ b/tests/test_recoverable.py @@ -71,7 +71,7 @@ def test_recoverable_flag(app, client, get_message): 'Content-Type': 'application/json' }) assert response.headers['Content-Type'] == 'application/json' - assert 'user' in response.jdata['response'] + assert 'user' not in response.jdata['response'] logout(client) From a140c01b57896df0d232e4967cc7803581389eb7 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 12:40:12 -0400 Subject: [PATCH 7/8] Add python3.4 to travis and tox --- .travis.yml | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 558e09b..beba9be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - "2.6" - "2.7" - "3.3" + - "3.4" - "pypy" install: diff --git a/tox.ini b/tox.ini index 341e949..15fa2e1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27, py33, pypy +envlist = py26, py27, py33, py34, pypy [testenv] deps = From 999f882f610f546421f74abd3fb339a70f20bce4 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 10 Jun 2014 13:05:06 -0400 Subject: [PATCH 8/8] Update CHANGES --- CHANGES | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES b/CHANGES index 1973ecd..2e4b8e8 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,18 @@ Flask-Security Changelog Here you can see the full list of changes between each Flask-Security release. +Version 1.7.3 +------------- + +Released June 10th 2014 + +- Fixed a bug where redirection to `SECURITY_POST_LOGIN_VIEW` was not respected +- Fixed string encoding in various places to be friendly to unicode +- Now using `werkzeug.security.safe_str_cmp` to check tokens +- Removed user information from JSON output on `/reset` responses +- Added Python 3.4 support + + Version 1.7.2 -------------