diff --git a/.gitignore b/.gitignore index fcf114d..d91c806 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ env/ #Editor temporaries *~ + +*.db diff --git a/.travis.yml b/.travis.yml index 6fae2be..e7dde2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,13 @@ language: python python: - "2.6" - "2.7" + - "3.3" + - "pypy" 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 nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee bcrypt --quiet services: - mongodb diff --git a/CHANGES b/CHANGES index 07a6652..c3aeb7e 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,41 @@ 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 +------------- + +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` +- 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 ------------- diff --git a/docs/conf.py b/docs/conf.py index 1ca2c6e..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.6.9' +version = '1.7.1' # The full version, including alpha/beta/rc tags. release = version 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 diff --git a/flask_security/__init__.py b/flask_security/__init__.py index 95485b0..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.6.9' +__version__ = '1.7.1' from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore diff --git a/flask_security/core.py b/flask_security/core.py index f48d390..c9574a5 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/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) 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/flask_security/utils.py b/flask_security/utils.py index dc85fee..57d7e28 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,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(get_hmac(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) @@ -126,11 +136,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).decode('ascii') + return _pwd_context.encrypt(signed) def md5(data): - return hashlib.md5(data).hexdigest() + return hashlib.md5(data.encode('ascii')).hexdigest() def do_flash(message, category=None): @@ -305,6 +318,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]) @@ -408,19 +425,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/flask_security/views.py b/flask_security/views.py index 72e5b99..981ea8a 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/setup.py b/setup.py index 805af3f..6cbad06 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.1', url='https://github.com/mattupstate/flask-security', license='MIT', author='Matt Wright', @@ -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=[ @@ -48,7 +47,7 @@ setup( 'Flask-SQLAlchemy', 'Flask-MongoEngine', 'Flask-Peewee', - 'py-bcrypt', + 'bcrypt', 'simplejson' ], classifiers=[ 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..32d7b90 100644 --- a/tests/configured_tests.py +++ b/tests/configured_tests.py @@ -1,17 +1,20 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement +# from __future__ import with_statement import base64 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_security.signals import user_registered from tests import SecurityTest @@ -27,7 +30,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): @@ -45,20 +48,20 @@ 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") - 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 +69,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 +77,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"}' @@ -87,20 +90,20 @@ 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): 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 +128,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 +140,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 +153,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 +164,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 +176,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 +188,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 +203,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,19 +218,20 @@ 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' 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) self.logout() r = self._post('/confirm', data=dict(email=e)) - self.assertIn(self.get_message('ALREADY_CONFIRMED'), 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' @@ -240,19 +244,22 @@ class ConfirmableTests(SecurityTest): def test_confirm_email(self): e = 'dude@lp.com' - with capture_registrations() as registrations: - 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) + 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"}', @@ -262,14 +269,14 @@ 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' 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): @@ -287,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): @@ -316,7 +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): @@ -332,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' @@ -340,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'] @@ -348,19 +356,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('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): @@ -377,7 +385,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(): @@ -393,7 +401,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: @@ -408,15 +416,25 @@ 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={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) + m = self.get_message('INVALID_RESET_PASSWORD_TOKEN') + self.assertIn(m.encode('utf-8'), r.data) - self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN'), 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) + + m = self.get_message('INVALID_RESET_PASSWORD_TOKEN') + self.assertIn(m.encode('utf-8'), r.data) class ExpiredResetPasswordTest(SecurityTest): @@ -439,7 +457,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 +470,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 +479,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 +489,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 +499,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 +509,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 +523,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) @@ -531,8 +549,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) @@ -552,7 +569,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): @@ -599,18 +616,18 @@ 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"}' 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' @@ -632,19 +649,19 @@ class PasswordlessTests(SecurityTest): 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') + 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] + 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: @@ -654,15 +671,15 @@ class PasswordlessTests(SecurityTest): r = self.client.get('/login/' + token, follow_redirects=True) msg = self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') - 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') - self.assertNotIn(msg, r.data) + 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')) - self.assertIn('Specified user does not exist', r.data) + self.assertIn(b'Specified user does not exist', r.data) class ExpiredLoginTokenTests(SecurityTest): @@ -688,8 +705,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) @@ -730,9 +746,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") - r = self._get('/http', headers={'Authorization': auth}) - self.assertIn('HTTP Authentication', r.data) + 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) class ExtendFormsTest(SecurityTest): @@ -755,11 +771,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 +798,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 +806,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 +824,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 +847,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): @@ -846,4 +862,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..d366c0e 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,61 @@ 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").decode('utf-8') }) - 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").decode('utf-8') }) - 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").decode('utf-8') }) - 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").decode('utf-8') }) - 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") + 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password").decode('utf-8') }) - self.assertIn('Basic', r.data) + 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 +212,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 +230,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 +251,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 411086c..731b8ae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27 +envlist = py26, py27, py33, pypy [testenv] deps = @@ -8,6 +8,6 @@ deps = Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee - py-bcrypt + bcrypt -commands = nosetests [] +commands = nosetests -xs []