Commit f12bc17e authored by Matt Wright's avatar Matt Wright
Browse files

Merge branch 'develop'

parents f387759c 999f882f
......@@ -4,6 +4,7 @@ python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "pypy"
install:
......
......@@ -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
-------------
......
......@@ -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
......
......@@ -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('ascii')).hexdigest()
return hashlib.md5(encode_string(data)).hexdigest()
def do_flash(message, category=None):
......@@ -196,7 +208,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)
......
......@@ -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,
......
......@@ -23,11 +23,13 @@ def test_view_configuration(client):
response = client.get('/custom_login')
assert b"<h1>Login</h1>" 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 '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 '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")
......
......@@ -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
......@@ -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)
......
[tox]
envlist = py26, py27, py33, pypy
envlist = py26, py27, py33, py34, pypy
[testenv]
deps =
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment