From b1ae7b43528536e397a024dde94310e33f1444ae Mon Sep 17 00:00:00 2001 From: Charles Young Date: Wed, 20 Nov 2013 10:58:32 -0800 Subject: [PATCH 01/24] Update features.rst Changed HTTP header name to reflect reality in code --- docs/features.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features.rst b/docs/features.rst index 2db9d42..4828661 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -55,7 +55,7 @@ authentication endpoint. A successful call to this endpoint will return the user's ID and their authentication token. This token can be used in subsequent requests to protected resources. The auth token is supplied in the request through an HTTP header or query string parameter. By default the HTTP header -name is `X-Auth-Token` and the default query string parameter name is +name is `Authentication-Token` and the default query string parameter name is `auth_token`. Authentication tokens are generated using the user's password. Thus if the user changes his or her password their existing authentication token will become invalid. A new token will need to be retrieved using the user's new From c302fb8e303b4bdbc982d079a7eee1e6bc795570 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Thu, 19 Dec 2013 14:45:53 -0500 Subject: [PATCH 02/24] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fcf114d..d91c806 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ env/ #Editor temporaries *~ + +*.db From 06ce1c68fd5e6b7f578e106331c7c686a9e2ed48 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Thu, 19 Dec 2013 15:10:20 -0500 Subject: [PATCH 03/24] Use tox for running tests --- .travis.yml | 7 ++----- tox.ini | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6fae2be..c6eccd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,12 @@ python: install: - pip install . --quiet - "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install importlib --quiet --use-mirrors; fi" - - pip install nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee py-bcrypt MySQL-python --quiet - -before_script: - - mysql -e 'create database flask_security_test;' + - pip install tox simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee bcrypt --quiet services: - mongodb -script: nosetests +script: tox branches: only: diff --git a/tox.ini b/tox.ini index 411086c..02e47ae 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,6 @@ deps = Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee - py-bcrypt + bcrypt commands = nosetests [] From d95a8c93642face68f1756dd623b067fd5eb1327 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Thu, 19 Dec 2013 15:11:50 -0500 Subject: [PATCH 04/24] Nope, nevermind. Dont use tox for tests. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6eccd0..29c3f67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ python: install: - pip install . --quiet - "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install importlib --quiet --use-mirrors; fi" - - pip install tox simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee bcrypt --quiet + - pip install nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee bcrypt --quiet services: - mongodb -script: tox +script: nosetests branches: only: From f1447b2adcbc19c2172060f797ecda0f51674cac Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Thu, 19 Dec 2013 16:12:29 -0500 Subject: [PATCH 05/24] Work in progress --- flask_security/core.py | 5 +- flask_security/datastore.py | 11 +++-- flask_security/forms.py | 13 ++--- flask_security/utils.py | 32 ++++++++----- tests/__init__.py | 2 +- tests/configured_tests.py | 94 ++++++++++++++++++------------------- tests/functional_tests.py | 88 ++++++++++++++++++---------------- tests/test_app/__init__.py | 10 ++-- tox.ini | 4 +- 9 files changed, 141 insertions(+), 118 deletions(-) diff --git a/flask_security/core.py b/flask_security/core.py index fbccc15..fd77673 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -19,7 +19,7 @@ from passlib.context import CryptContext from werkzeug.datastructures import ImmutableList from werkzeug.local import LocalProxy -from .utils import config_value as cv, get_config, md5, url_for_security +from .utils import config_value as cv, get_config, md5, url_for_security, string_types from .views import create_blueprint from .forms import LoginForm, ConfirmRegisterForm, RegisterForm, \ ForgotPasswordForm, ChangePasswordForm, ResetPasswordForm, \ @@ -249,6 +249,7 @@ def _context_processor(): class RoleMixin(object): """Mixin for `Role` model definitions""" + def __eq__(self, other): return (self.name == other or self.name == getattr(other, 'name', None)) @@ -273,7 +274,7 @@ class UserMixin(BaseUserMixin): """Returns `True` if the user identifies with the specified role. :param role: A role name or `Role` instance""" - if isinstance(role, basestring): + if isinstance(role, string_types): return role in (role.name for role in self.roles) else: return role in self.roles diff --git a/flask_security/datastore.py b/flask_security/datastore.py index 7342d41..8aa4adc 100644 --- a/flask_security/datastore.py +++ b/flask_security/datastore.py @@ -9,7 +9,7 @@ :license: MIT, see LICENSE for more details. """ -from .utils import get_identity_attributes +from .utils import get_identity_attributes, string_types class Datastore(object): @@ -68,9 +68,9 @@ class UserDatastore(object): self.role_model = role_model def _prepare_role_modify_args(self, user, role): - if isinstance(user, basestring): + if isinstance(user, string_types): user = self.find_user(email=user) - if isinstance(role, basestring): + if isinstance(role, string_types): role = self.find_role(role) return user, role @@ -105,6 +105,7 @@ class UserDatastore(object): user, role = self._prepare_role_modify_args(user, role) if role not in user.roles: user.roles.append(role) + self.put(user) return True return False @@ -161,8 +162,8 @@ class UserDatastore(object): def create_user(self, **kwargs): """Creates and returns a new user from the given parameters.""" - - user = self.user_model(**self._prepare_create_user_args(**kwargs)) + kwargs = self._prepare_create_user_args(**kwargs) + user = self.user_model(**kwargs) return self.put(user) def delete_user(self, user): diff --git a/flask_security/forms.py b/flask_security/forms.py index 2b62ab2..54876ae 100644 --- a/flask_security/forms.py +++ b/flask_security/forms.py @@ -10,9 +10,10 @@ """ import inspect -import urlparse - -import flask_wtf as wtf +try: + from urlparse import urlsplit +except ImportError: + from urllib.parse import urlsplit from flask import request, current_app from flask_wtf import Form as BaseForm @@ -22,7 +23,7 @@ from flask_login import current_user from werkzeug.local import LocalProxy from .confirmable import requires_confirmation -from .utils import verify_and_update_password, get_message, encrypt_password, config_value +from .utils import verify_and_update_password, get_message, config_value # Convenient reference _datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) @@ -137,8 +138,8 @@ class NextFormMixin(): def validate_next(self, field): if field.data: - url_next = urlparse.urlsplit(field.data) - url_base = urlparse.urlsplit(request.host_url) + url_next = urlsplit(field.data) + url_base = urlsplit(request.host_url) if url_next.netloc and url_next.netloc != url_base.netloc: field.data = '' raise ValidationError(get_message('INVALID_REDIRECT')[0]) diff --git a/flask_security/utils.py b/flask_security/utils.py index dc85fee..bc59890 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -14,6 +14,7 @@ import blinker import functools import hashlib import hmac +import sys from contextlib import contextmanager from datetime import datetime, timedelta @@ -37,6 +38,15 @@ _datastore = LocalProxy(lambda: _security.datastore) _pwd_context = LocalProxy(lambda: _security.pwd_context) +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + text_type = str +else: + string_types = basestring, + text_type = unicode + def login_user(user, remember=None): """Performs the login routine. @@ -85,16 +95,13 @@ def get_hmac(password): :param password: The password to sign """ - if _security.password_hash == 'plaintext': - return password - if _security.password_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, password.encode('utf-8'), hashlib.sha512) + h = hmac.new(_security.password_salt.encode('utf-8'), password.encode('utf-8'), hashlib.sha512) return base64.b64encode(h.digest()) @@ -104,7 +111,7 @@ def verify_password(password, password_hash): :param password: A plaintext password to verify :param password_hash: The expected hash value of the password (usually form your database) """ - return _pwd_context.verify(get_hmac(password), password_hash) + return _pwd_context.verify(encrypt_password(password), password_hash) def verify_and_update_password(password, user): @@ -114,7 +121,7 @@ def verify_and_update_password(password, user): :param password: A plaintext password to verify :param user: The user to verify against """ - verified, new_password = _pwd_context.verify_and_update(get_hmac(password), user.password) + verified, new_password = _pwd_context.verify_and_update(encrypt_password(password), user.password) if verified and new_password: user.password = new_password _datastore.put(user) @@ -126,11 +133,14 @@ def encrypt_password(password): :param password: The plaintext passwrod to encrypt """ - return _pwd_context.encrypt(get_hmac(password)) + if _security.password_hash == 'plaintext': + return password + signed = get_hmac(password) + return _pwd_context.encrypt(signed.decode('ascii')) def md5(data): - return hashlib.md5(data).hexdigest() + return hashlib.md5(data.encode('ascii')).hexdigest() def do_flash(message, category=None): @@ -408,19 +418,19 @@ class CaptureSignals(object): self._records[signal].append((args, kwargs)) def __enter__(self): - for signal, receiver in self._receivers.iteritems(): + for signal, receiver in self._receivers.items(): signal.connect(receiver) return self def __exit__(self, type, value, traceback): - for signal, receiver in self._receivers.iteritems(): + for signal, receiver in self._receivers.items(): signal.disconnect(receiver) def signals_sent(self): """Return a set of the signals sent. :rtype: list of blinker `NamedSignals`. """ - return set([signal for signal, _ in self._records.iteritems() if self._records[signal]]) + return set([signal for signal, _ in self._records.items() if self._records[signal]]) def capture_signals(): diff --git a/tests/__init__.py b/tests/__init__.py index 92df3f2..0e7d036 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -61,7 +61,7 @@ class SecurityTest(TestCase): return self._get(endpoint or '/logout', follow_redirects=True) def assertIsHomePage(self, data): - self.assertIn('Home Page', data) + self.assertIn(b'Home Page', data) def assertIn(self, member, container, msg=None): if hasattr(TestCase, 'assertIn'): diff --git a/tests/configured_tests.py b/tests/configured_tests.py index 0a5955c..71ca1bb 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -27,7 +27,7 @@ class ConfiguredPasswordHashSecurityTests(SecurityTest): def test_authenticate(self): r = self.authenticate(endpoint="/login") - self.assertIn('Home Page', r.data) + self.assertIn(b'Home Page', r.data) class ConfiguredSecurityTests(SecurityTest): @@ -49,16 +49,16 @@ class ConfiguredSecurityTests(SecurityTest): def test_authenticate(self): r = self.authenticate(endpoint="/custom_login") - self.assertIn('Post Login', r.data) + self.assertIn(b'Post Login', r.data) def test_logout(self): self.authenticate(endpoint="/custom_login") r = self.logout(endpoint="/custom_logout") - self.assertIn('Post Logout', r.data) + self.assertIn(b'Post Logout', r.data) def test_register_view(self): r = self._get('/register') - self.assertIn('

Register

', r.data) + self.assertIn(b'

Register

', r.data) def test_register(self): data = dict(email='dude@lp.com', @@ -66,7 +66,7 @@ class ConfiguredSecurityTests(SecurityTest): password_confirm='password') r = self._post('/register', data=data, follow_redirects=True) - self.assertIn('Post Register', r.data) + self.assertIn(b'Post Register', r.data) def test_register_with_next_querystring_argument(self): data = dict(email='dude@lp.com', @@ -74,7 +74,7 @@ class ConfiguredSecurityTests(SecurityTest): password_confirm='password') r = self._post('/register?next=/page1', data=data, follow_redirects=True) - self.assertIn('Page 1', r.data) + self.assertIn(b'Page 1', r.data) def test_register_json(self): data = '{ "email": "dude@lp.com", "password": "password"}' @@ -98,9 +98,9 @@ class ConfiguredSecurityTests(SecurityTest): def test_default_http_auth_realm(self): r = self._get('/http', headers={ - 'Authorization': 'Basic ' + base64.b64encode("joe@lp.com:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") }) - self.assertIn('

Unauthorized

', r.data) + self.assertIn(b'

Unauthorized

', r.data) self.assertIn('WWW-Authenticate', r.headers) self.assertEquals('Basic realm="Custom Realm"', r.headers['WWW-Authenticate']) @@ -125,7 +125,7 @@ class DefaultTemplatePathTests(SecurityTest): def test_login_user_template(self): r = self._get('/login') - self.assertIn('CUSTOM LOGIN USER', r.data) + self.assertIn(b'CUSTOM LOGIN USER', r.data) class RegisterableTemplatePathTests(SecurityTest): @@ -137,7 +137,7 @@ class RegisterableTemplatePathTests(SecurityTest): def test_register_user_template(self): r = self._get('/register') - self.assertIn('CUSTOM REGISTER USER', r.data) + self.assertIn(b'CUSTOM REGISTER USER', r.data) class RecoverableTemplatePathTests(SecurityTest): @@ -150,7 +150,7 @@ class RecoverableTemplatePathTests(SecurityTest): def test_forgot_password_template(self): r = self._get('/reset') - self.assertIn('CUSTOM FORGOT PASSWORD', r.data) + self.assertIn(b'CUSTOM FORGOT PASSWORD', r.data) def test_reset_password_template(self): with capture_reset_password_requests() as requests: @@ -161,7 +161,7 @@ class RecoverableTemplatePathTests(SecurityTest): r = self._get('/reset/' + t) - self.assertIn('CUSTOM RESET PASSWORD', r.data) + self.assertIn(b'CUSTOM RESET PASSWORD', r.data) class ConfirmableTemplatePathTests(SecurityTest): @@ -173,7 +173,7 @@ class ConfirmableTemplatePathTests(SecurityTest): def test_send_confirmation_template(self): r = self._get('/confirm') - self.assertIn('CUSTOM SEND CONFIRMATION', r.data) + self.assertIn(b'CUSTOM SEND CONFIRMATION', r.data) class PasswordlessTemplatePathTests(SecurityTest): @@ -185,7 +185,7 @@ class PasswordlessTemplatePathTests(SecurityTest): def test_send_login_template(self): r = self._get('/login') - self.assertIn('CUSTOM SEND LOGIN', r.data) + self.assertIn(b'CUSTOM SEND LOGIN', r.data) class RegisterableTests(SecurityTest): @@ -200,7 +200,7 @@ class RegisterableTests(SecurityTest): password_confirm='password') self._post('/register', data=data, follow_redirects=True) r = self.authenticate('dude@lp.com') - self.assertIn('Hello dude@lp.com', r.data) + self.assertIn(b'Hello dude@lp.com', r.data) class ConfirmableTests(SecurityTest): @@ -215,7 +215,7 @@ class ConfirmableTests(SecurityTest): e = 'dude@lp.com' self.register(e) r = self.authenticate(email=e) - self.assertIn(self.get_message('CONFIRMATION_REQUIRED'), r.data) + self.assertIn(self.get_message('CONFIRMATION_REQUIRED').encode('utf-8'), r.data) def test_send_confirmation_of_already_confirmed_account(self): e = 'dude@lp.com' @@ -227,7 +227,7 @@ class ConfirmableTests(SecurityTest): self.client.get('/confirm/' + token, follow_redirects=True) self.logout() r = self._post('/confirm', data=dict(email=e)) - self.assertIn(self.get_message('ALREADY_CONFIRMED'), r.data) + self.assertIn(self.get_message('ALREADY_CONFIRMED').encode('utf-8'), r.data) def test_register_sends_confirmation_email(self): e = 'dude@lp.com' @@ -269,7 +269,7 @@ class ConfirmableTests(SecurityTest): self.register(e) r = self._post('/confirm', data={'email': e}) - msg = self.get_message('CONFIRMATION_REQUEST', email=e) + msg = self.get_message('CONFIRMATION_REQUEST', email=e).encode('utf-8') self.assertIn(msg, r.data) def test_user_deleted_before_confirmation(self): @@ -350,7 +350,7 @@ class LoginWithoutImmediateConfirmTests(SecurityTest): r = self.client.get('/confirm/' + token2, follow_redirects=True) msg = self.app.config['SECURITY_MSG_EMAIL_CONFIRMED'][0] self.assertIn(msg, r.data) - self.assertIn('Hello %s' % e2, r.data) + self.assertIn(b'Hello %s' % e2, r.data) def test_login_unconfirmed_user_when_login_without_confirmation_is_true(self): e = 'dude@lp.com' @@ -377,7 +377,7 @@ class RecoverableTests(SecurityTest): follow_redirects=True) t = requests[0]['token'] r = self._get('/reset/' + t) - self.assertIn('

Reset password

', r.data) + self.assertIn(b'

Reset password

', r.data) def test_forgot_post_sends_email(self): with capture_reset_password_requests(): @@ -408,7 +408,7 @@ class RecoverableTests(SecurityTest): r = self.logout() r = self.authenticate('joe@lp.com', 'newpassword') - self.assertIn('Hello joe@lp.com', r.data) + self.assertIn(b'Hello joe@lp.com', r.data) def test_reset_password_with_invalid_token(self): r = self._post('/reset/bogus', data={ @@ -416,7 +416,7 @@ class RecoverableTests(SecurityTest): 'password_confirm': 'newpassword' }, follow_redirects=True) - self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN'), r.data) + self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN').encode('utf-8'), r.data) class ExpiredResetPasswordTest(SecurityTest): @@ -439,7 +439,7 @@ class ExpiredResetPasswordTest(SecurityTest): 'password_confirm': 'newpassword' }, follow_redirects=True) - self.assertIn('You did not reset your password within', r.data) + self.assertIn(b'You did not reset your password within', r.data) class ChangePasswordTest(SecurityTest): @@ -452,7 +452,7 @@ class ChangePasswordTest(SecurityTest): def test_change_password(self): self.authenticate() r = self.client.get('/change', follow_redirects=True) - self.assertIn('Change password', r.data) + self.assertIn(b'Change password', r.data) def test_change_password_invalid(self): self.authenticate() @@ -461,8 +461,8 @@ class ChangePasswordTest(SecurityTest): 'new_password': 'newpassword', 'new_password_confirm': 'newpassword' }, follow_redirects=True) - self.assertNotIn('You successfully changed your password', r.data) - self.assertIn('Invalid password', r.data) + self.assertNotIn(b'You successfully changed your password', r.data) + self.assertIn(b'Invalid password', r.data) def test_change_password_mismatch(self): self.authenticate() @@ -471,8 +471,8 @@ class ChangePasswordTest(SecurityTest): 'new_password': 'newpassword', 'new_password_confirm': 'notnewpassword' }, follow_redirects=True) - self.assertNotIn('You successfully changed your password', r.data) - self.assertIn('Passwords do not match', r.data) + self.assertNotIn(b'You successfully changed your password', r.data) + self.assertIn(b'Passwords do not match', r.data) def test_change_password_bad_password(self): self.authenticate() @@ -481,8 +481,8 @@ class ChangePasswordTest(SecurityTest): 'new_password': 'a', 'new_password_confirm': 'a' }, follow_redirects=True) - self.assertNotIn('You successfully changed your password', r.data) - self.assertIn('Password must be at least 6 characters', r.data) + self.assertNotIn(b'You successfully changed your password', r.data) + self.assertIn(b'Password must be at least 6 characters', r.data) def test_change_password_same_as_previous(self): self.authenticate() @@ -491,8 +491,8 @@ class ChangePasswordTest(SecurityTest): 'new_password': 'password', 'new_password_confirm': 'password' }, follow_redirects=True) - self.assertNotIn('You successfully changed your password', r.data) - self.assertIn('Your new password must be different than your previous password.', r.data) + self.assertNotIn(b'You successfully changed your password', r.data) + self.assertIn(b'Your new password must be different than your previous password.', r.data) def test_change_password_success(self): data = { @@ -505,8 +505,8 @@ class ChangePasswordTest(SecurityTest): with self.app.extensions['mail'].record_messages() as outbox: r = self._post('/change', data=data, follow_redirects=True) - self.assertIn('You successfully changed your password', r.data) - self.assertIn('Home Page', r.data) + self.assertIn(b'You successfully changed your password', r.data) + self.assertIn(b'Home Page', r.data) self.assertEqual(len(outbox), 1) self.assertIn("Your password has been changed", outbox[0].html) @@ -552,7 +552,7 @@ class ChangePasswordPostViewTest(SecurityTest): self.authenticate() r = self._post('/change', data=data, follow_redirects=True) - self.assertIn('Profile Page', r.data) + self.assertIn(b'Profile Page', r.data) class ChangePasswordDisabledTest(SecurityTest): @@ -605,12 +605,12 @@ class PasswordlessTests(SecurityTest): data = '{"email": "matt@lp.com", "password": "password"}' r = self._post('/login', data=data, content_type='application/json') self.assertEquals(r.status_code, 200) - self.assertNotIn('error', r.data) + self.assertNotIn(b'error', r.data) def test_request_login_token_with_json_and_invalid_email(self): data = '{"email": "nobody@lp.com", "password": "password"}' r = self._post('/login', data=data, content_type='application/json') - self.assertIn('errors', r.data) + self.assertIn(b'errors', r.data) def test_request_login_token_sends_email_and_can_login(self): e = 'matt@lp.com' @@ -624,8 +624,8 @@ class PasswordlessTests(SecurityTest): self.assertEqual(len(outbox), 1) self.assertEquals(1, len(requests)) - self.assertIn('user', requests[0]) - self.assertIn('login_token', requests[0]) + self.assertIn(b'user', requests[0]) + self.assertIn(b'login_token', requests[0]) user = requests[0]['user'] token = requests[0]['login_token'] @@ -635,11 +635,11 @@ class PasswordlessTests(SecurityTest): self.assertIn(msg, r.data) r = self.client.get('/login/' + token, follow_redirects=True) - msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') + msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL').encode('utf-8') self.assertIn(msg, r.data) r = self.client.get('/profile') - self.assertIn('Profile Page', r.data) + self.assertIn(b'Profile Page', r.data) def test_invalid_login_token(self): msg = self.app.config['SECURITY_MSG_INVALID_LOGIN_TOKEN'][0] @@ -653,16 +653,16 @@ class PasswordlessTests(SecurityTest): token = requests[0]['login_token'] r = self.client.get('/login/' + token, follow_redirects=True) - msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') + msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL').encode('utf-8') self.assertIn(msg, r.data) r = self.client.get('/login/' + token, follow_redirects=True) - msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') + msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL').encode('utf-8') self.assertNotIn(msg, r.data) def test_send_login_with_invalid_email(self): r = self._post('/login', data=dict(email='bogus@bogus.com')) - self.assertIn('Specified user does not exist', r.data) + self.assertIn(b'Specified user does not exist', r.data) class ExpiredLoginTokenTests(SecurityTest): @@ -730,9 +730,9 @@ class NoBlueprintTests(SecurityTest): self.assertEqual(404, r.status_code) def test_http_auth_without_blueprint(self): - auth = 'Basic ' + base64.b64encode("matt@lp.com:password") + auth = 'Basic %s' % base64.b64encode(b"matt@lp.com:password") r = self._get('/http', headers={'Authorization': auth}) - self.assertIn('HTTP Authentication', r.data) + self.assertIn(b'HTTP Authentication', r.data) class ExtendFormsTest(SecurityTest): @@ -846,4 +846,4 @@ class AdditionalUserIdentityAttributes(SecurityTest): def test_authenticate(self): r = self.authenticate(email='matt') - self.assertIn('Hello matt@lp.com', r.data) + self.assertIn(b'Hello matt@lp.com', r.data) diff --git a/tests/functional_tests.py b/tests/functional_tests.py index 90263e9..c64d6f4 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -4,7 +4,11 @@ from __future__ import with_statement import base64 import simplejson as json -from cookielib import Cookie + +try: + from cookielib import Cookie +except ImportError: + from http.cookiejar import Cookie from werkzeug.utils import parse_cookie @@ -27,35 +31,35 @@ class DefaultSecurityTests(SecurityTest): def test_login_view(self): r = self._get('/login') - self.assertIn('

Login

', r.data) + self.assertIn(b'

Login

', r.data) def test_authenticate(self): r = self.authenticate() - self.assertIn('Hello matt@lp.com', r.data) + self.assertIn(b'Hello matt@lp.com', r.data) def test_authenticate_case_insensitive_email(self): r = self.authenticate(email='MATT@lp.com') - self.assertIn('Hello matt@lp.com', r.data) + self.assertIn(b'Hello matt@lp.com', r.data) def test_unprovided_username(self): r = self.authenticate("") - self.assertIn(self.get_message('EMAIL_NOT_PROVIDED'), r.data) + self.assertIn(self.get_message('EMAIL_NOT_PROVIDED').encode('utf-8'), r.data) def test_unprovided_password(self): r = self.authenticate(password="") - self.assertIn(self.get_message('PASSWORD_NOT_PROVIDED'), r.data) + self.assertIn(self.get_message('PASSWORD_NOT_PROVIDED').encode('utf-8'), r.data) def test_invalid_user(self): r = self.authenticate(email="bogus@bogus.com") - self.assertIn(self.get_message('USER_DOES_NOT_EXIST'), r.data) + self.assertIn(self.get_message('USER_DOES_NOT_EXIST').encode('utf-8'), r.data) def test_bad_password(self): r = self.authenticate(password="bogus") - self.assertIn(self.get_message('INVALID_PASSWORD'), r.data) + self.assertIn(self.get_message('INVALID_PASSWORD').encode('utf-8'), r.data) def test_inactive_user(self): r = self.authenticate("tiya@lp.com", "password") - self.assertIn(self.get_message('DISABLED_ACCOUNT'), r.data) + self.assertIn(self.get_message('DISABLED_ACCOUNT').encode('utf-8'), r.data) def test_logout(self): self.authenticate() @@ -65,17 +69,17 @@ class DefaultSecurityTests(SecurityTest): def test_unauthorized_access(self): self.logout() r = self._get('/profile', follow_redirects=True) - self.assertIn('
  • Please log in to access this page.
  • ', r.data) + self.assertIn(b'
  • Please log in to access this page.
  • ', r.data) def test_authorized_access(self): self.authenticate() r = self._get("/profile") - self.assertIn('profile', r.data) + self.assertIn(b'profile', r.data) def test_valid_admin_role(self): self.authenticate() r = self._get("/admin") - self.assertIn('Admin Page', r.data) + self.assertIn(b'Admin Page', r.data) def test_invalid_admin_role(self): self.authenticate("joe@lp.com") @@ -86,7 +90,7 @@ class DefaultSecurityTests(SecurityTest): for user in ("matt@lp.com", "joe@lp.com"): self.authenticate(user) r = self._get("/admin_or_editor") - self.assertIn('Admin or Editor Page', r.data) + self.assertIn(b'Admin or Editor Page', r.data) self.logout() self.authenticate("jill@lp.com") @@ -95,7 +99,7 @@ class DefaultSecurityTests(SecurityTest): def test_unauthenticated_role_required(self): r = self._get('/admin', follow_redirects=True) - self.assertIn(self.get_message('UNAUTHORIZED'), r.data) + self.assertIn(self.get_message('UNAUTHORIZED').encode('utf-8'), r.data) def test_multiple_role_required(self): for user in ("matt@lp.com", "joe@lp.com"): @@ -106,7 +110,7 @@ class DefaultSecurityTests(SecurityTest): self.authenticate('dave@lp.com') r = self._get("/admin_and_editor", follow_redirects=True) - self.assertIn('Admin and Editor Page', r.data) + self.assertIn(b'Admin and Editor Page', r.data) def test_ok_json_auth(self): r = self.json_authenticate() @@ -116,14 +120,14 @@ class DefaultSecurityTests(SecurityTest): def test_invalid_json_auth(self): r = self.json_authenticate(password='junk') - self.assertIn('"code": 400', r.data) + self.assertIn(b'"code": 400', r.data) def test_token_auth_via_querystring_valid_token(self): r = self.json_authenticate() data = json.loads(r.data) token = data['response']['user']['authentication_token'] r = self._get('/token?auth_token=' + token) - self.assertIn('Token Authentication', r.data) + self.assertIn(b'Token Authentication', r.data) def test_token_auth_via_header_valid_token(self): r = self.json_authenticate() @@ -131,7 +135,7 @@ class DefaultSecurityTests(SecurityTest): token = data['response']['user']['authentication_token'] headers = {"Authentication-Token": token} r = self._get('/token', headers=headers) - self.assertIn('Token Authentication', r.data) + self.assertIn(b'Token Authentication', r.data) def test_token_auth_via_querystring_invalid_token(self): r = self._get('/token?auth_token=X') @@ -143,61 +147,63 @@ class DefaultSecurityTests(SecurityTest): def test_http_auth(self): r = self._get('/http', headers={ - 'Authorization': 'Basic ' + base64.b64encode("joe@lp.com:password") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password") }) - self.assertIn('HTTP Authentication', r.data) + self.assertIn(b'HTTP Authentication', r.data) def test_http_auth_no_authorization(self): r = self._get('/http', headers={}) - self.assertIn('

    Unauthorized

    ', r.data) + self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) self.assertEquals('Basic realm="Login Required"', r.headers['WWW-Authenticate']) def test_invalid_http_auth_invalid_username(self): r = self._get('/http', headers={ - 'Authorization': 'Basic ' + base64.b64encode("bogus:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"bogus:bogus") }) - self.assertIn('

    Unauthorized

    ', r.data) + self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) self.assertEquals('Basic realm="Login Required"', r.headers['WWW-Authenticate']) def test_invalid_http_auth_bad_password(self): r = self._get('/http', headers={ - 'Authorization': 'Basic ' + base64.b64encode("joe@lp.com:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") }) - self.assertIn('

    Unauthorized

    ', r.data) + self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) self.assertEquals('Basic realm="Login Required"', r.headers['WWW-Authenticate']) def test_custom_http_auth_realm(self): r = self._get('/http_custom_realm', headers={ - 'Authorization': 'Basic ' + base64.b64encode("joe@lp.com:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") }) - self.assertIn('

    Unauthorized

    ', r.data) + self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) self.assertEquals('Basic realm="My Realm"', r.headers['WWW-Authenticate']) def test_multi_auth_basic(self): - r = self._get('/multi_auth', headers={ - 'Authorization': 'Basic ' + base64.b64encode("joe@lp.com:password") - }) - self.assertIn('Basic', r.data) + h = { + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password") + } + print(h) + r = self._get('/multi_auth', headers=h) + self.assertIn(b'Basic', r.data) def test_multi_auth_token(self): r = self.json_authenticate() data = json.loads(r.data) token = data['response']['user']['authentication_token'] r = self._get('/multi_auth?auth_token=' + token) - self.assertIn('Token', r.data) + self.assertIn(b'Token', r.data) def test_multi_auth_session(self): self.authenticate() r = self._get('/multi_auth') - self.assertIn('Session', r.data) + self.assertIn(b'Session', r.data) def test_user_deleted_during_session_reverts_to_anonymous_user(self): self.authenticate() @@ -208,13 +214,13 @@ class DefaultSecurityTests(SecurityTest): self.app.security.datastore.commit() r = self._get('/') - self.assertNotIn('Hello matt@lp.com', r.data) + self.assertNotIn(b'Hello matt@lp.com', r.data) def test_remember_token(self): r = self.authenticate(follow_redirects=False) self.client.cookie_jar.clear_session_cookies() r = self._get('/profile') - self.assertIn('profile', r.data) + self.assertIn(b'profile', r.data) def test_token_loader_does_not_fail_with_invalid_token(self): c = Cookie(version=0, name='remember_token', value='None', port=None, @@ -226,7 +232,7 @@ class DefaultSecurityTests(SecurityTest): self.client.cookie_jar.set_cookie(c) r = self._get('/') - self.assertNotIn('BadSignature', r.data) + self.assertNotIn(b'BadSignature', r.data) class MongoEngineSecurityTests(DefaultSecurityTests): @@ -247,23 +253,23 @@ class DefaultDatastoreTests(SecurityTest): def test_add_role_to_user(self): r = self._get('/coverage/add_role_to_user') - self.assertIn('success', r.data) + self.assertIn(b'success', r.data) def test_remove_role_from_user(self): r = self._get('/coverage/remove_role_from_user') - self.assertIn('success', r.data) + self.assertIn(b'success', r.data) def test_activate_user(self): r = self._get('/coverage/activate_user') - self.assertIn('success', r.data) + self.assertIn(b'success', r.data) def test_deactivate_user(self): r = self._get('/coverage/deactivate_user') - self.assertIn('success', r.data) + self.assertIn(b'success', r.data) def test_invalid_role(self): r = self._get('/coverage/invalid_role') - self.assertIn('success', r.data) + self.assertIn(b'success', r.data) class MongoEngineDatastoreTests(DefaultDatastoreTests): diff --git a/tests/test_app/__init__.py b/tests/test_app/__init__.py index 3d0c798..32dfd70 100644 --- a/tests/test_app/__init__.py +++ b/tests/test_app/__init__.py @@ -137,9 +137,13 @@ def create_users(count=None): for u in users[:count]: pw = encrypt_password(u[2]) - ds.create_user(email=u[0], username=u[1], password=pw, - roles=u[3], active=u[4]) - ds.commit() + roles = [ds.find_or_create_role(rn) for rn in u[3]] + ds.commit() + user = ds.create_user(email=u[0], username=u[1], password=pw, active=u[4]) + ds.commit() + for role in roles: + ds.add_role_to_user(user, role) + ds.commit() def populate_data(user_count=None): diff --git a/tox.ini b/tox.ini index 02e47ae..058f9c1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27 +envlist = py26, py27, py33 [testenv] deps = @@ -10,4 +10,4 @@ deps = Flask-Peewee bcrypt -commands = nosetests [] +commands = nosetests -xs [] From afaf6c7d6245a0f4f2e6bbe51e59b2178d4fd78c Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 20 Dec 2013 13:38:44 -0500 Subject: [PATCH 06/24] Polish --- tests/functional_tests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/functional_tests.py b/tests/functional_tests.py index c64d6f4..36d623a 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -186,11 +186,9 @@ class DefaultSecurityTests(SecurityTest): r.headers['WWW-Authenticate']) def test_multi_auth_basic(self): - h = { + r = self._get('/multi_auth', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password") - } - print(h) - r = self._get('/multi_auth', headers=h) + }) self.assertIn(b'Basic', r.data) def test_multi_auth_token(self): From f7b645005e8db5529baed40dfb149400e5334c1c Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 20 Dec 2013 13:39:41 -0500 Subject: [PATCH 07/24] work in progress --- tests/configured_tests.py | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/configured_tests.py b/tests/configured_tests.py index 71ca1bb..6fe76c6 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement +# from __future__ import with_statement import base64 import time @@ -16,18 +16,18 @@ from flask.ext.security.forms import TextField, SubmitField, valid_user_email from tests import SecurityTest +# TODO: Wait for passlib + bcrypt python3 compatibility to be fixed +# class ConfiguredPasswordHashSecurityTests(SecurityTest): -class ConfiguredPasswordHashSecurityTests(SecurityTest): +# AUTH_CONFIG = { +# 'SECURITY_PASSWORD_HASH': 'bcrypt', +# 'SECURITY_PASSWORD_SALT': 'so-salty', +# 'USER_COUNT': 1 +# } - AUTH_CONFIG = { - 'SECURITY_PASSWORD_HASH': 'bcrypt', - 'SECURITY_PASSWORD_SALT': 'so-salty', - 'USER_COUNT': 1 - } - - def test_authenticate(self): - r = self.authenticate(endpoint="/login") - self.assertIn(b'Home Page', r.data) +# def test_authenticate(self): +# r = self.authenticate(endpoint="/login") +# self.assertIn(b'Home Page', r.data) class ConfiguredSecurityTests(SecurityTest): @@ -45,7 +45,7 @@ class ConfiguredSecurityTests(SecurityTest): def test_login_view(self): r = self._get('/custom_login') - self.assertIn("

    Login

    ", r.data) + self.assertIn(b"

    Login

    ", r.data) def test_authenticate(self): r = self.authenticate(endpoint="/custom_login") @@ -87,13 +87,13 @@ class ConfiguredSecurityTests(SecurityTest): password='password', password_confirm='password') r = self._post('/register', data=data, follow_redirects=True) - msg = 'matt@lp.com is already associated with an account' + msg = b'matt@lp.com is already associated with an account' self.assertIn(msg, r.data) def test_unauthorized(self): self.authenticate("joe@lp.com", endpoint="/custom_auth") r = self._get("/admin", follow_redirects=True) - msg = 'You are not allowed to access the requested resouce' + msg = b'You are not allowed to access the requested resouce' self.assertIn(msg, r.data) def test_default_http_auth_realm(self): @@ -221,7 +221,7 @@ class ConfirmableTests(SecurityTest): e = 'dude@lp.com' with capture_registrations() as registrations: - self.register(e) + r = self.register(e) token = registrations[0]['confirm_token'] self.client.get('/confirm/' + token, follow_redirects=True) @@ -241,7 +241,7 @@ class ConfirmableTests(SecurityTest): e = 'dude@lp.com' with capture_registrations() as registrations: - self.register(e) + r = self.register(e) token = registrations[0]['confirm_token'] r = self.client.get('/confirm/' + token, follow_redirects=True) @@ -393,7 +393,7 @@ class RecoverableTests(SecurityTest): def test_forgot_password_invalid_email(self): r = self._post('/reset', data=dict(email='larry@lp.com'), follow_redirects=True) - self.assertIn("Specified user does not exist", r.data) + self.assertIn(b"Specified user does not exist", r.data) def test_reset_password_with_valid_token(self): with capture_reset_password_requests() as requests: @@ -755,11 +755,11 @@ class ExtendFormsTest(SecurityTest): def test_login_view(self): r = self._get('/login', follow_redirects=True) - self.assertIn("My Login Email Address Field", r.data) + self.assertIn(b"My Login Email Address Field", r.data) def test_register(self): r = self._get('/register', follow_redirects=True) - self.assertIn("My Register Email Address Field", r.data) + self.assertIn(b"My Register Email Address Field", r.data) class RecoverableExtendFormsTest(SecurityTest): @@ -782,7 +782,7 @@ class RecoverableExtendFormsTest(SecurityTest): def test_forgot_password(self): r = self._get('/reset', follow_redirects=True) - self.assertIn("My Forgot Password Email Address Field", r.data) + self.assertIn(b"My Forgot Password Email Address Field", r.data) def test_reset_password(self): with capture_reset_password_requests() as requests: @@ -790,7 +790,7 @@ class RecoverableExtendFormsTest(SecurityTest): follow_redirects=True) token = requests[0]['token'] r = self._get('/reset/' + token) - self.assertIn("My Reset Password Submit Field", r.data) + self.assertIn(b"My Reset Password Submit Field", r.data) class PasswordlessExtendFormsTest(SecurityTest): @@ -808,7 +808,7 @@ class PasswordlessExtendFormsTest(SecurityTest): def test_passwordless_login(self): r = self._get('/login', follow_redirects=True) - self.assertIn("My Passwordless Login Email Address Field", r.data) + self.assertIn(b"My Passwordless Login Email Address Field", r.data) class ConfirmableExtendFormsTest(SecurityTest): @@ -831,11 +831,11 @@ class ConfirmableExtendFormsTest(SecurityTest): def test_register(self): r = self._get('/register', follow_redirects=True) - self.assertIn("My Confirm Register Email Address Field", r.data) + self.assertIn(b"My Confirm Register Email Address Field", r.data) def test_send_confirmation(self): r = self._get('/confirm', follow_redirects=True) - self.assertIn("My Send Confirmation Email Address Field", r.data) + self.assertIn(b"My Send Confirmation Email Address Field", r.data) class AdditionalUserIdentityAttributes(SecurityTest): From 8d2815798f407c9455d46d72a1dcff648f40c0ab Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 24 Dec 2013 12:38:05 -0500 Subject: [PATCH 08/24] WIP --- flask_security/signals.py | 1 - tests/configured_tests.py | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/flask_security/signals.py b/flask_security/signals.py index e1c2954..532bba9 100644 --- a/flask_security/signals.py +++ b/flask_security/signals.py @@ -11,7 +11,6 @@ import blinker - signals = blinker.Namespace() user_registered = signals.signal("user-registered") diff --git a/tests/configured_tests.py b/tests/configured_tests.py index 6fe76c6..a9bf7b6 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -5,6 +5,7 @@ import base64 import time import simplejson as json +import flask from flask.ext.security.utils import capture_registrations, \ capture_reset_password_requests, capture_passwordless_login_requests @@ -13,6 +14,8 @@ from flask.ext.security.forms import LoginForm, ConfirmRegisterForm, RegisterFor PasswordlessLoginForm from flask.ext.security.forms import TextField, SubmitField, valid_user_email +from flask.ext.security import signals + from tests import SecurityTest @@ -240,12 +243,15 @@ class ConfirmableTests(SecurityTest): def test_confirm_email(self): e = 'dude@lp.com' - with capture_registrations() as registrations: - r = self.register(e) - token = registrations[0]['confirm_token'] + tokens = [] + def on_registered(sender, **kwargs): + tokens.append(kwargs['confirm_token']) - r = self.client.get('/confirm/' + token, follow_redirects=True) + signals.user_registered.connect(on_registered, self.app) + r = self.register(e) + self.assertEqual(len(tokens), 1) + r = self.client.get('/confirm/' + tokens[0], follow_redirects=True) msg = self.app.config['SECURITY_MSG_EMAIL_CONFIRMED'][0] self.assertIn(msg, r.data) @@ -319,6 +325,10 @@ class ExpiredConfirmationTest(SecurityTest): self.assertIn(msg, r.data) + + + + class LoginWithoutImmediateConfirmTests(SecurityTest): AUTH_CONFIG = { 'SECURITY_CONFIRMABLE': True, From 850b0e714b95c066951412968769f99704bdca41 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 7 Jan 2014 16:31:26 -0500 Subject: [PATCH 09/24] Additional test fixes --- tests/configured_tests.py | 73 ++++++++++++++++++--------------------- tests/functional_tests.py | 10 +++--- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/tests/configured_tests.py b/tests/configured_tests.py index a9bf7b6..5f16191 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -7,14 +7,14 @@ import time import simplejson as json import flask -from flask.ext.security.utils import capture_registrations, \ +from flask_security.utils import capture_registrations, \ capture_reset_password_requests, capture_passwordless_login_requests -from flask.ext.security.forms import LoginForm, ConfirmRegisterForm, RegisterForm, \ +from flask_security.forms import LoginForm, ConfirmRegisterForm, RegisterForm, \ ForgotPasswordForm, ResetPasswordForm, SendConfirmationForm, \ PasswordlessLoginForm -from flask.ext.security.forms import TextField, SubmitField, valid_user_email +from flask_security.forms import TextField, SubmitField, valid_user_email -from flask.ext.security import signals +from flask_security.signals import user_registered from tests import SecurityTest @@ -230,7 +230,8 @@ class ConfirmableTests(SecurityTest): self.client.get('/confirm/' + token, follow_redirects=True) self.logout() r = self._post('/confirm', data=dict(email=e)) - self.assertIn(self.get_message('ALREADY_CONFIRMED').encode('utf-8'), r.data) + m = self.get_message('ALREADY_CONFIRMED') + self.assertIn(m.encode('utf-8'), r.data) def test_register_sends_confirmation_email(self): e = 'dude@lp.com' @@ -247,18 +248,18 @@ class ConfirmableTests(SecurityTest): def on_registered(sender, **kwargs): tokens.append(kwargs['confirm_token']) - signals.user_registered.connect(on_registered, self.app) + user_registered.connect(on_registered, self.app) r = self.register(e) self.assertEqual(len(tokens), 1) r = self.client.get('/confirm/' + tokens[0], follow_redirects=True) msg = self.app.config['SECURITY_MSG_EMAIL_CONFIRMED'][0] - self.assertIn(msg, r.data) + self.assertIn(msg.encode('utf-8'), r.data) def test_invalid_token_when_confirming_email(self): r = self.client.get('/confirm/bogus', follow_redirects=True) msg = self.app.config['SECURITY_MSG_INVALID_CONFIRMATION_TOKEN'][0] - self.assertIn(msg, r.data) + self.assertIn(msg.encode('utf-8'), r.data) def test_send_confirmation_json(self): r = self._post('/confirm', data='{"email": "matt@lp.com"}', @@ -268,7 +269,7 @@ class ConfirmableTests(SecurityTest): def test_send_confirmation_with_invalid_email(self): r = self._post('/confirm', data=dict(email='bogus@bogus.com')) msg = self.app.config['SECURITY_MSG_USER_DOES_NOT_EXIST'][0] - self.assertIn(msg, r.data) + self.assertIn(msg.encode('utf-8'), r.data) def test_resend_confirmation(self): e = 'dude@lp.com' @@ -293,7 +294,7 @@ class ConfirmableTests(SecurityTest): r = self.client.get('/confirm/' + token, follow_redirects=True) msg = self.app.config['SECURITY_MSG_INVALID_CONFIRMATION_TOKEN'][0] - self.assertIn(msg, r.data) + self.assertIn(msg.encode('utf-8'), r.data) class ExpiredConfirmationTest(SecurityTest): @@ -322,11 +323,7 @@ class ExpiredConfirmationTest(SecurityTest): expire_text = self.AUTH_CONFIG['SECURITY_CONFIRM_EMAIL_WITHIN'] msg = self.app.config['SECURITY_MSG_CONFIRMATION_EXPIRED'][0] msg = msg % dict(within=expire_text, email=e) - self.assertIn(msg, r.data) - - - - + self.assertIn(msg.encode('utf-8'), r.data) class LoginWithoutImmediateConfirmTests(SecurityTest): @@ -342,7 +339,7 @@ class LoginWithoutImmediateConfirmTests(SecurityTest): p = 'password' data = dict(email=e, password=p, password_confirm=p) r = self._post('/register', data=data, follow_redirects=True) - self.assertIn(e, r.data) + self.assertIn(e.encode('utf-8'), r.data) def test_confirm_email_of_user_different_than_current_user(self): e1 = 'dude@lp.com' @@ -358,19 +355,19 @@ class LoginWithoutImmediateConfirmTests(SecurityTest): self.client.get('/logout') self.authenticate(email=e1) r = self.client.get('/confirm/' + token2, follow_redirects=True) - msg = self.app.config['SECURITY_MSG_EMAIL_CONFIRMED'][0] - self.assertIn(msg, r.data) - self.assertIn(b'Hello %s' % e2, r.data) + m = self.app.config['SECURITY_MSG_EMAIL_CONFIRMED'][0] + self.assertIn(m.encode('utf-8'), r.data) + self.assertIn(b'Hello lady@lp.com', r.data) def test_login_unconfirmed_user_when_login_without_confirmation_is_true(self): e = 'dude@lp.com' p = 'password' data = dict(email=e, password=p, password_confirm=p) r = self._post('/register', data=data, follow_redirects=True) - self.assertIn(e, r.data) + self.assertIn(e.encode('utf-8'), r.data) self.client.get('/logout') r = self.authenticate(email=e) - self.assertIn(e, r.data) + self.assertIn(e.encode('utf-8'), r.data) class RecoverableTests(SecurityTest): @@ -425,8 +422,8 @@ class RecoverableTests(SecurityTest): 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) - - self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN').encode('utf-8'), r.data) + m = self.get_message('INVALID_RESET_PASSWORD_TOKEN') + self.assertIn(m.encode('utf-8'), r.data) class ExpiredResetPasswordTest(SecurityTest): @@ -541,8 +538,7 @@ class EmailConfigTest(SecurityTest): self.authenticate() with self.app.extensions['mail'].record_messages() as outbox: - r = self._post('/change', data=data, follow_redirects=True) - + self._post('/change', data=data, follow_redirects=True) self.assertEqual(len(outbox), 0) @@ -609,7 +605,7 @@ class PasswordlessTests(SecurityTest): msg = self.app.config['SECURITY_MSG_DISABLED_ACCOUNT'][0] r = self._post('/login', data=dict(email='tiya@lp.com'), follow_redirects=True) - self.assertIn(msg, r.data) + self.assertIn(msg.encode('utf-8'), r.data) def test_request_login_token_with_json_and_valid_email(self): data = '{"email": "matt@lp.com", "password": "password"}' @@ -634,15 +630,15 @@ class PasswordlessTests(SecurityTest): self.assertEqual(len(outbox), 1) self.assertEquals(1, len(requests)) - self.assertIn(b'user', requests[0]) - self.assertIn(b'login_token', requests[0]) + self.assertIn('user', requests[0]) + self.assertIn('login_token', requests[0]) user = requests[0]['user'] token = requests[0]['login_token'] msg = self.app.config['SECURITY_MSG_LOGIN_EMAIL_SENT'][0] msg = msg % dict(email=user.email) - self.assertIn(msg, r.data) + self.assertIn(msg.encode('utf-8'), r.data) r = self.client.get('/login/' + token, follow_redirects=True) msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL').encode('utf-8') @@ -652,9 +648,9 @@ class PasswordlessTests(SecurityTest): self.assertIn(b'Profile Page', r.data) def test_invalid_login_token(self): - msg = self.app.config['SECURITY_MSG_INVALID_LOGIN_TOKEN'][0] + m = self.app.config['SECURITY_MSG_INVALID_LOGIN_TOKEN'][0] r = self._get('/login/bogus', follow_redirects=True) - self.assertIn(msg, r.data) + self.assertIn(m.encode('utf-8'), r.data) def test_token_login_when_already_authenticated(self): with capture_passwordless_login_requests() as requests: @@ -663,12 +659,12 @@ class PasswordlessTests(SecurityTest): token = requests[0]['login_token'] r = self.client.get('/login/' + token, follow_redirects=True) - msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL').encode('utf-8') - self.assertIn(msg, r.data) + msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') + self.assertIn(msg.encode('utf-8'), r.data) r = self.client.get('/login/' + token, follow_redirects=True) - msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL').encode('utf-8') - self.assertNotIn(msg, r.data) + msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') + self.assertNotIn(msg.encode('utf-8'), r.data) def test_send_login_with_invalid_email(self): r = self._post('/login', data=dict(email='bogus@bogus.com')) @@ -698,8 +694,7 @@ class ExpiredLoginTokenTests(SecurityTest): expire_text = self.AUTH_CONFIG['SECURITY_LOGIN_WITHIN'] msg = self.app.config['SECURITY_MSG_LOGIN_EXPIRED'][0] msg = msg % dict(within=expire_text, email=e) - self.assertIn(msg, r.data) - + self.assertIn(msg.encode('utf-8'), r.data) self.assertEqual(len(outbox), 1) self.assertIn(e, outbox[0].html) self.assertNotIn(token, outbox[0].html) @@ -740,8 +735,8 @@ class NoBlueprintTests(SecurityTest): self.assertEqual(404, r.status_code) def test_http_auth_without_blueprint(self): - auth = 'Basic %s' % base64.b64encode(b"matt@lp.com:password") - r = self._get('/http', headers={'Authorization': auth}) + auth = base64.b64encode(b"matt@lp.com:password").decode('utf-8') + r = self._get('/http', headers={'Authorization': 'basic %s' % auth}) self.assertIn(b'HTTP Authentication', r.data) diff --git a/tests/functional_tests.py b/tests/functional_tests.py index 36d623a..d366c0e 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -147,7 +147,7 @@ class DefaultSecurityTests(SecurityTest): def test_http_auth(self): r = self._get('/http', headers={ - 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password").decode('utf-8') }) self.assertIn(b'HTTP Authentication', r.data) @@ -160,7 +160,7 @@ class DefaultSecurityTests(SecurityTest): def test_invalid_http_auth_invalid_username(self): r = self._get('/http', headers={ - 'Authorization': 'Basic %s' % base64.b64encode(b"bogus:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"bogus:bogus").decode('utf-8') }) self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) @@ -169,7 +169,7 @@ class DefaultSecurityTests(SecurityTest): def test_invalid_http_auth_bad_password(self): r = self._get('/http', headers={ - 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus").decode('utf-8') }) self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) @@ -178,7 +178,7 @@ class DefaultSecurityTests(SecurityTest): def test_custom_http_auth_realm(self): r = self._get('/http_custom_realm', headers={ - 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus").decode('utf-8') }) self.assertIn(b'

    Unauthorized

    ', r.data) self.assertIn('WWW-Authenticate', r.headers) @@ -187,7 +187,7 @@ class DefaultSecurityTests(SecurityTest): def test_multi_auth_basic(self): r = self._get('/multi_auth', headers={ - 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password").decode('utf-8') }) self.assertIn(b'Basic', r.data) From 730b86fbc7c49cd0f07c5afab0229e73197a8970 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 7 Jan 2014 16:37:07 -0500 Subject: [PATCH 10/24] Add more python envs --- .travis.yml | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 29c3f67..e7dde2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: python python: - "2.6" - "2.7" + - "3.3" + - "pypy" install: - pip install . --quiet diff --git a/tox.ini b/tox.ini index 058f9c1..731b8ae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27, py33 +envlist = py26, py27, py33, pypy [testenv] deps = From ac2dfe964be87da0810440faea1fcfb3a724aaaa Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 7 Jan 2014 16:40:35 -0500 Subject: [PATCH 11/24] Update dependencies in setup.py --- setup.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 805af3f..3df5d6c 100644 --- a/setup.py +++ b/setup.py @@ -34,13 +34,12 @@ setup( include_package_data=True, platforms='any', install_requires=[ - 'Flask>=0.9', - 'Flask-Login>=0.2.3', - 'Flask-Mail>=0.7.3', - 'Flask-Principal>=0.3.3', - 'Flask-WTF>=0.8', - 'itsdangerous>=0.17', - 'passlib>=1.6.1', + 'Flask>=0.10.1', + 'Flask-Login>=0.2.9', + 'Flask-Mail>=0.9.0', + 'Flask-Principal>=0.4.0', + 'Flask-WTF>=0.9.3', + 'passlib>=1.6.2', ], test_suite='nose.collector', tests_require=[ From 68ecc7bb10338bea066fe804e3973603a69f5b33 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 7 Jan 2014 16:40:59 -0500 Subject: [PATCH 12/24] Update test dependencies in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3df5d6c..d6e0765 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup( 'Flask-SQLAlchemy', 'Flask-MongoEngine', 'Flask-Peewee', - 'py-bcrypt', + 'bcrypt', 'simplejson' ], classifiers=[ From 968f85a39ca2fd57e245a70b0672978780a01308 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 10 Jan 2014 14:03:29 -0500 Subject: [PATCH 13/24] Update CHANGES --- CHANGES | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGES b/CHANGES index 07a6652..d056e8e 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,31 @@ Flask-Security Changelog Here you can see the full list of changes between each Flask-Security release. +Version 1.6.10 +-------------- + +Released ??? + +- Fixed a bug when `SECURITY_LOGIN_WITHOUT_CONFIRMATION = True` did not allow users to log in +- Added `SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL` configuraiton option to optionally send password reset notice emails +- Add documentation for `@security.send_mail_task` +- Move to `request.get_json` as `request.json` is now deprecated in Flask +- Fixed a bug when using AJAX to change a user's password +- Added documentation for select functions in the `flask_security.utils` module +- Fixed a bug in `flask_security.forms.NextFormMixin` +- Added `CHANGE_PASSWORD_TEMPLATE` configuration option to optionally specify a different change password template +- Added the ability to specify addtional fields on the user model to be used for identifying the user via the `USER_IDENTITY_ATTRIBUTES` configuration option +- An error is now shown if a user tries to change their password and the password is the same as before. The message can be customed with the `SECURITY_MSG_PASSWORD_IS_SAME` configuration option +- Fixed a bug in `MongoEngineUserDatastore` where user model would not be updated when using the `add_role_to_user` method +- Added `SECURITY_SEND_PASSWORD_CHANGE_EMAIL` configuration option to optionally disable password change email from being sent +- Fixed a bug in the `find_or_create_role` method of the PeeWee datastore +- Removed pypy tests +- Fixed some tests +- Include CHANGES and LICENSE in MANIFEST.in +- A bit of documentation cleanup +- A bit of code cleanup including removal of unnecessary utcnow call and simplification of get_max_age method + + Version 1.6.9 ------------- From e3b74acc5aa71776d13068e69307c380ff00fa9e Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 10 Jan 2014 14:38:58 -0500 Subject: [PATCH 14/24] Update CHANGES --- CHANGES | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index d056e8e..20b8fc1 100644 --- a/CHANGES +++ b/CHANGES @@ -3,11 +3,13 @@ Flask-Security Changelog Here you can see the full list of changes between each Flask-Security release. -Version 1.6.10 --------------- +Version 1.7.0 +------------- -Released ??? +Released January 10th 2014 +- Python 3.3 support! +- Dependency updates - Fixed a bug when `SECURITY_LOGIN_WITHOUT_CONFIRMATION = True` did not allow users to log in - Added `SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL` configuraiton option to optionally send password reset notice emails - Add documentation for `@security.send_mail_task` From 76fc578cf5a9e254afec41cd2578ae2716a0ea4f Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 10 Jan 2014 14:40:24 -0500 Subject: [PATCH 15/24] Bump version number to 1.7.0 --- docs/conf.py | 2 +- flask_security/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1ca2c6e..1ca606e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ copyright = u'2012, Matt Wright' # built documents. # # The short X.Y version. -version = '1.6.9' +version = '1.7.0' # The full version, including alpha/beta/rc tags. release = version diff --git a/flask_security/__init__.py b/flask_security/__init__.py index 95485b0..3589af6 100644 --- a/flask_security/__init__.py +++ b/flask_security/__init__.py @@ -10,7 +10,7 @@ :license: MIT, see LICENSE for more details. """ -__version__ = '1.6.9' +__version__ = '1.7.0' from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore diff --git a/setup.py b/setup.py index d6e0765..d6c8eb5 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ from setuptools import setup setup( name='Flask-Security', - version='1.6.9', + version='1.7.0', url='https://github.com/mattupstate/flask-security', license='MIT', author='Matt Wright', From 35fd08772b05f0120ed883f780e3299aa008c0be Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 14 Jan 2014 10:34:57 -0500 Subject: [PATCH 16/24] Add configured password hash test back and fix bug with checking passwords --- flask_security/utils.py | 9 ++++++--- tests/configured_tests.py | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/flask_security/utils.py b/flask_security/utils.py index bc59890..6bf83ee 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -121,7 +121,10 @@ def verify_and_update_password(password, user): :param password: A plaintext password to verify :param user: The user to verify against """ - verified, new_password = _pwd_context.verify_and_update(encrypt_password(password), user.password) + + if _security.password_hash != 'plaintext': + password = get_hmac(password) + verified, new_password = _pwd_context.verify_and_update(password, user.password) if verified and new_password: user.password = new_password _datastore.put(user) @@ -135,8 +138,8 @@ def encrypt_password(password): """ if _security.password_hash == 'plaintext': return password - signed = get_hmac(password) - return _pwd_context.encrypt(signed.decode('ascii')) + signed = get_hmac(password).decode('ascii') + return _pwd_context.encrypt(signed) def md5(data): diff --git a/tests/configured_tests.py b/tests/configured_tests.py index 5f16191..cd223c2 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -19,18 +19,18 @@ from flask_security.signals import user_registered from tests import SecurityTest -# TODO: Wait for passlib + bcrypt python3 compatibility to be fixed -# class ConfiguredPasswordHashSecurityTests(SecurityTest): -# AUTH_CONFIG = { -# 'SECURITY_PASSWORD_HASH': 'bcrypt', -# 'SECURITY_PASSWORD_SALT': 'so-salty', -# 'USER_COUNT': 1 -# } +class ConfiguredPasswordHashSecurityTests(SecurityTest): -# def test_authenticate(self): -# r = self.authenticate(endpoint="/login") -# self.assertIn(b'Home Page', r.data) + AUTH_CONFIG = { + 'SECURITY_PASSWORD_HASH': 'bcrypt', + 'SECURITY_PASSWORD_SALT': 'so-salty', + 'USER_COUNT': 1 + } + + def test_authenticate(self): + r = self.authenticate(endpoint="/login") + self.assertIn(b'Home Page', r.data) class ConfiguredSecurityTests(SecurityTest): From 6eb77c06ad59a2fb7aa0ee9379970466bcf6bbb0 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 14 Jan 2014 10:41:38 -0500 Subject: [PATCH 17/24] Update CHANGES --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index 20b8fc1..c3aeb7e 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,14 @@ Flask-Security Changelog Here you can see the full list of changes between each Flask-Security release. +Version 1.7.1 +------------- + +Released January 14th 2014 + +- Fixed a bug where passwords would fail to verify when specifying a password hash algorithm + + Version 1.7.0 ------------- From 316d945d9686656dee7c9bd816f43cd12db44668 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 14 Jan 2014 10:41:53 -0500 Subject: [PATCH 18/24] Bump version number to 1.7.1 --- docs/conf.py | 2 +- flask_security/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1ca606e..93103c4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ copyright = u'2012, Matt Wright' # built documents. # # The short X.Y version. -version = '1.7.0' +version = '1.7.1' # The full version, including alpha/beta/rc tags. release = version diff --git a/flask_security/__init__.py b/flask_security/__init__.py index 3589af6..5328aca 100644 --- a/flask_security/__init__.py +++ b/flask_security/__init__.py @@ -10,7 +10,7 @@ :license: MIT, see LICENSE for more details. """ -__version__ = '1.7.0' +__version__ = '1.7.1' from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore diff --git a/setup.py b/setup.py index d6c8eb5..6cbad06 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ from setuptools import setup setup( name='Flask-Security', - version='1.7.0', + version='1.7.1', url='https://github.com/mattupstate/flask-security', license='MIT', author='Matt Wright', From b2174bf0359262c666d4abd65a442ebb5512cf5b Mon Sep 17 00:00:00 2001 From: Jameson Date: Tue, 7 Jan 2014 12:23:18 -0800 Subject: [PATCH 19/24] catch possible TypeError and ValueError from serializer --- flask_security/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flask_security/utils.py b/flask_security/utils.py index bc59890..ec85ab5 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -315,6 +315,10 @@ def get_token_status(token, serializer, max_age=None): expired = True except BadSignature: invalid = True + except TypeError: + invalid = True + except ValueError: + invalid = True if data: user = _datastore.find_user(id=data[0]) From f47fce9365d714aa633a82308e9bb141193d5426 Mon Sep 17 00:00:00 2001 From: Jameson Date: Thu, 23 Jan 2014 00:20:56 +0000 Subject: [PATCH 20/24] add test to show TypeError can occur --- tests/configured_tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/configured_tests.py b/tests/configured_tests.py index 5f16191..0324ecf 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -425,6 +425,15 @@ class RecoverableTests(SecurityTest): m = self.get_message('INVALID_RESET_PASSWORD_TOKEN') self.assertIn(m.encode('utf-8'), r.data) + def test_reset_password_with_mangled_token(self): + t = "WyIxNjQ2MzYiLCIxMzQ1YzBlZmVhM2VhZjYwODgwMDhhZGU2YzU0MzZjMiJd.BZEw_Q.lQyo3npdPZtcJ_sNHVHP103syjM&url_id=fbb89a8328e58c181ea7d064c2987874bc54a23d" + r = self._post('/reset/' + t, data={ + 'password': 'newpassword', + 'password_confirm': 'newpassword' + }, follow_redirects=True) + + self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN'), r.data) + class ExpiredResetPasswordTest(SecurityTest): From b4d1a7c9218fefa6931a394c771bd44a73b1ceb5 Mon Sep 17 00:00:00 2001 From: Jameson Date: Thu, 23 Jan 2014 23:33:11 +0000 Subject: [PATCH 21/24] update to make test run red --- flask_security/utils.py | 8 ++++---- tests/configured_tests.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/flask_security/utils.py b/flask_security/utils.py index ec85ab5..336bb0d 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -315,10 +315,10 @@ def get_token_status(token, serializer, max_age=None): expired = True except BadSignature: invalid = True - except TypeError: - invalid = True - except ValueError: - invalid = True +# except TypeError: +# invalid = True +# except ValueError: +# invalid = True if data: user = _datastore.find_user(id=data[0]) diff --git a/tests/configured_tests.py b/tests/configured_tests.py index 0324ecf..cc59011 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -432,7 +432,8 @@ class RecoverableTests(SecurityTest): 'password_confirm': 'newpassword' }, follow_redirects=True) - self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN'), r.data) + m = self.get_message('INVALID_RESET_PASSWORD_TOKEN') + self.assertIn(m.encode('utf-8'), r.data) class ExpiredResetPasswordTest(SecurityTest): From 45c8951877d2f2855c77bae5b168b6a1c295cf51 Mon Sep 17 00:00:00 2001 From: Jameson Date: Fri, 24 Jan 2014 04:41:41 +0000 Subject: [PATCH 22/24] passes tests --- flask_security/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask_security/utils.py b/flask_security/utils.py index 336bb0d..ec85ab5 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -315,10 +315,10 @@ def get_token_status(token, serializer, max_age=None): expired = True except BadSignature: invalid = True -# except TypeError: -# invalid = True -# except ValueError: -# invalid = True + except TypeError: + invalid = True + except ValueError: + invalid = True if data: user = _datastore.find_user(id=data[0]) From c658ee4500a94504e47a1c3a70937e087eebf1f7 Mon Sep 17 00:00:00 2001 From: Klaus Klein Date: Fri, 24 Jan 2014 10:21:41 +0100 Subject: [PATCH 23/24] Update script.py for Python 2's print statement vs. Python 3's function. --- flask_security/script.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/flask_security/script.py b/flask_security/script.py index 9c9a246..16a03d2 100644 --- a/flask_security/script.py +++ b/flask_security/script.py @@ -8,6 +8,8 @@ :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ +from __future__ import print_function + try: import simplejson as json except ImportError: @@ -26,7 +28,7 @@ _datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) def pprint(obj): - print json.dumps(obj, sort_keys=True, indent=4) + print(json.dumps(obj, sort_keys=True, indent=4)) def commit(fn): @@ -59,11 +61,11 @@ class CreateUserCommand(Command): if form.validate(): kwargs['password'] = encrypt_password(kwargs['password']) _datastore.create_user(**kwargs) - print 'User created successfully.' + print('User created successfully.') kwargs['password'] = '****' pprint(kwargs) else: - print 'Error creating user' + print('Error creating user') pprint(form.errors) @@ -78,7 +80,7 @@ class CreateRoleCommand(Command): @commit def run(self, **kwargs): _datastore.create_role(**kwargs) - print 'Role "%(name)s" created successfully.' % kwargs + print('Role "%(name)s" created successfully.' % kwargs) class _RoleCommand(Command): @@ -94,7 +96,7 @@ class AddRoleCommand(_RoleCommand): @commit def run(self, user_identifier, role_name): _datastore.add_role_to_user(user_identifier, role_name) - print "Role '%s' added to user '%s' successfully" % (role_name, user_identifier) + print("Role '%s' added to user '%s' successfully" % (role_name, user_identifier)) class RemoveRoleCommand(_RoleCommand): @@ -103,7 +105,7 @@ class RemoveRoleCommand(_RoleCommand): @commit def run(self, user_identifier, role_name): _datastore.remove_role_from_user(user_identifier, role_name) - print "Role '%s' removed from user '%s' successfully" % (role_name, user_identifier) + print("Role '%s' removed from user '%s' successfully" % (role_name, user_identifier)) class _ToggleActiveCommand(Command): @@ -118,7 +120,7 @@ class DeactivateUserCommand(_ToggleActiveCommand): @commit def run(self, user_identifier): _datastore.deactivate_user(user_identifier) - print "User '%s' has been deactivated" % user_identifier + print("User '%s' has been deactivated" % user_identifier) class ActivateUserCommand(_ToggleActiveCommand): @@ -127,4 +129,4 @@ class ActivateUserCommand(_ToggleActiveCommand): @commit def run(self, user_identifier): _datastore.activate_user(user_identifier) - print "User '%s' has been activated" % user_identifier + print("User '%s' has been activated" % user_identifier) From 2e57734d1fe21601895836375fd9e3dd7847ca96 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 24 Jan 2014 10:23:16 -0500 Subject: [PATCH 24/24] Add @anonymous_user_required to register endpoint. Fixes #212 --- flask_security/views.py | 1 + tests/configured_tests.py | 1 + 2 files changed, 2 insertions(+) diff --git a/flask_security/views.py b/flask_security/views.py index 0291f37..f7f2c3f 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -98,6 +98,7 @@ def logout(): get_url(_security.post_logout_view)) +@anonymous_user_required def register(): """View function which handles a registration request.""" diff --git a/tests/configured_tests.py b/tests/configured_tests.py index cd223c2..05b39fc 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -347,6 +347,7 @@ class LoginWithoutImmediateConfirmTests(SecurityTest): with capture_registrations() as registrations: self.register(e1) + self.logout() self.register(e2) token1 = registrations[0]['confirm_token'] token2 = registrations[1]['confirm_token']