Compare commits

..
10 Commits
12 changed files with 66 additions and 20 deletions
+1
View File
@@ -4,6 +4,7 @@ python:
- "2.6" - "2.6"
- "2.7" - "2.7"
- "3.3" - "3.3"
- "3.4"
- "pypy" - "pypy"
install: install:
+12
View File
@@ -3,6 +3,18 @@ Flask-Security Changelog
Here you can see the full list of changes between each Flask-Security release. 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 Version 1.7.2
------------- -------------
+1 -1
View File
@@ -49,7 +49,7 @@ copyright = u'2012, Matt Wright'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '1.7.2' version = '1.7.3'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version release = version
+1 -1
View File
@@ -10,7 +10,7 @@
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
__version__ = '1.7.2' __version__ = '1.7.3'
from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
+2 -1
View File
@@ -18,6 +18,7 @@ from itsdangerous import URLSafeTimedSerializer
from passlib.context import CryptContext from passlib.context import CryptContext
from werkzeug.datastructures import ImmutableList from werkzeug.datastructures import ImmutableList
from werkzeug.local import LocalProxy 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 .utils import config_value as cv, get_config, md5, url_for_security, string_types
from .views import create_blueprint from .views import create_blueprint
@@ -193,7 +194,7 @@ def _token_loader(token):
try: try:
data = _security.remember_token_serializer.loads(token) data = _security.remember_token_serializer.loads(token)
user = _security.datastore.find_user(id=data[0]) 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 return user
except: except:
pass pass
+16 -4
View File
@@ -99,13 +99,15 @@ def get_hmac(password):
:param password: The password to sign :param password: The password to sign
""" """
if _security.password_salt is None: salt = _security.password_salt
if salt is None:
raise RuntimeError( raise RuntimeError(
'The configuration value `SECURITY_PASSWORD_SALT` must ' 'The configuration value `SECURITY_PASSWORD_SALT` must '
'not be None when the value of `SECURITY_PASSWORD_HASH` is ' 'not be None when the value of `SECURITY_PASSWORD_HASH` is '
'set to "%s"' % _security.password_hash) '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()) return base64.b64encode(h.digest())
@@ -149,8 +151,18 @@ def encrypt_password(password):
return _pwd_context.encrypt(signed) 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): def md5(data):
return hashlib.md5(data.encode('ascii')).hexdigest() return hashlib.md5(encode_string(data)).hexdigest()
def do_flash(message, category=None): def do_flash(message, category=None):
@@ -196,7 +208,7 @@ def url_for_security(endpoint, **values):
def validate_redirect_url(url): def validate_redirect_url(url):
if url is None: if url is None or url.strip() == '':
return False return False
url_next = urlsplit(url) url_next = urlsplit(url)
url_base = urlsplit(request.host_url) url_base = urlsplit(request.host_url)
+7 -5
View File
@@ -34,7 +34,7 @@ _security = LocalProxy(lambda: current_app.extensions['security'])
_datastore = LocalProxy(lambda: _security.datastore) _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 has_errors = len(form.errors) > 0
if has_errors: if has_errors:
@@ -42,7 +42,9 @@ def _render_json(form, include_auth_token=False):
response = dict(errors=form.errors) response = dict(errors=form.errors)
else: else:
code = 200 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: if include_auth_token:
token = form.user.get_auth_token() token = form.user.get_auth_token()
response['user']['authentication_token'] = token response['user']['authentication_token'] = token
@@ -78,7 +80,7 @@ def login():
return redirect(get_post_login_redirect(form.next.data)) return redirect(get_post_login_redirect(form.next.data))
if request.json: 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'), return _security.render_template(config_value('LOGIN_USER_TEMPLATE'),
login_user_form=form, login_user_form=form,
@@ -121,7 +123,7 @@ def register():
if not request.json: if not request.json:
return redirect(get_post_register_redirect()) return redirect(get_post_register_redirect())
return _render_json(form, True) return _render_json(form, include_auth_token=True)
if request.json: if request.json:
return _render_json(form) return _render_json(form)
@@ -247,7 +249,7 @@ def forgot_password():
do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email)) do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email))
if request.json: if request.json:
return _render_json(form) return _render_json(form, include_user=False)
return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'), return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
forgot_password_form=form, forgot_password_form=form,
+1 -1
View File
@@ -44,7 +44,7 @@ class PyTest(TestCommand):
setup( setup(
name='Flask-Security', name='Flask-Security',
version='1.7.2', version='1.7.3',
url='https://github.com/mattupstate/flask-security', url='https://github.com/mattupstate/flask-security',
license='MIT', license='MIT',
author='Matt Wright', author='Matt Wright',
+6 -4
View File
@@ -23,11 +23,13 @@ def test_view_configuration(client):
response = client.get('/custom_login') response = client.get('/custom_login')
assert b"<h1>Login</h1>" in response.data assert b"<h1>Login</h1>" in response.data
response = authenticate(client, endpoint='/custom_login', follow_redirects=True) response = authenticate(client, endpoint='/custom_login')
assert b'Post Login' in response.data assert 'location' in response.headers
assert response.headers['Location'] == 'http://localhost/post_login'
response = logout(client, endpoint='/custom_logout', follow_redirects=True) response = logout(client, endpoint='/custom_logout')
assert b'Post Logout' in response.data assert 'location' in response.headers
assert response.headers['Location'] == 'http://localhost/post_logout'
response = client.get('/http', headers={ response = client.get('/http', headers={
'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus")
+17 -1
View File
@@ -12,7 +12,7 @@ from flask_security import Security
from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \ from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \
SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \ SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \
ChangePasswordForm, TextField, PasswordField, email_required, email_validator, valid_user_email 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 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')) response = client.post('/login', data=dict(email='matt@lp.com', password='password'))
assert response.status_code == 302 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
+1 -1
View File
@@ -71,7 +71,7 @@ def test_recoverable_flag(app, client, get_message):
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}) })
assert response.headers['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) logout(client)
+1 -1
View File
@@ -1,5 +1,5 @@
[tox] [tox]
envlist = py26, py27, py33, pypy envlist = py26, py27, py33, py34, pypy
[testenv] [testenv]
deps = deps =