Compare commits

..
17 Commits
Author SHA1 Message Date
Matt Wright 514de64303 Bump version number to 1.6.6 2013-06-28 17:24:50 -04:00
Matt Wright bad63265f8 Update CHANGES 2013-06-28 17:24:40 -04:00
Matt Wright 66a9dcd2e6 Merge branch 'develop' of github.com:mattupstate/flask-security into develop 2013-06-28 17:23:36 -04:00
Matt Wright df1647f1f9 Fix install_requires in setup.py 2013-06-28 17:23:16 -04:00
Matt Wright 89ecded480 Merge pull request #128 from vkotovv/develop
Fix issue tracker link
2013-06-26 08:04:10 -07:00
Vadim Kotov 2b35b37a66 Update sidebarintro.html
Fixed link to the issue tracker
2013-06-26 18:30:44 +04:00
Matt Wright 4d8a813004 Bump version number to 1.6.5 2013-06-20 16:01:36 -04:00
Matt Wright 06312ef50f Fix typo with _external parameter in confirmable.py. Fixes #126 2013-06-20 16:01:22 -04:00
Matt Wright c3ad5b2fa6 Take --use-mirrors out of pip calls in .travis.yml 2013-06-18 15:53:32 -04:00
Matt Wright 637fc913cd Take Flask-Mail out of .travis.yml 2013-06-18 15:32:32 -04:00
Matt Wright e00522f331 Add SECURITY_CHANGEABLE to docs. Fixes #115 2013-06-18 15:05:32 -04:00
Matt Wright 3d34d87a97 Bump version number to 1.6.4 2013-06-18 15:00:41 -04:00
Matt Wright 461ace9303 Update docs 2013-06-18 15:00:34 -04:00
Matt Wright d19bb98abd Version 1.6.4 changes. Refer to CHANGES for updates. Fixes #123 #121 #120 $119 2013-06-18 14:56:12 -04:00
Matt Wright c24af5ca6e Whitespace! 2013-05-28 11:11:37 -04:00
Matt Wright 26045fc4dc Use the _external parameter when generating links for emails 2013-05-28 11:11:19 -04:00
Matt Wright bf260d4b7e Add optional next parameter to registration endpoint. Fixes #117. 2013-05-28 11:01:42 -04:00
20 changed files with 112 additions and 58 deletions
+2 -2
View File
@@ -6,9 +6,9 @@ python:
- "pypy" - "pypy"
install: install:
- pip install . --quiet --use-mirrors - pip install . --quiet
- "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install importlib --quiet --use-mirrors; fi" - "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install importlib --quiet --use-mirrors; fi"
- pip install nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee Flask-Mail py-bcrypt MySQL-python --quiet --use-mirrors - pip install nose simplejson Flask-SQLAlchemy Flask-MongoEngine Flask-Peewee py-bcrypt MySQL-python --quiet
before_script: before_script:
- mysql -e 'create database flask_security_test;' - mysql -e 'create database flask_security_test;'
+25
View File
@@ -3,6 +3,31 @@ Flask-Security Changelog
Here you can see the full list of changes between each Flask-Security release. Here you can see the full list of changes between each Flask-Security release.
Version 1.6.6
-------------
Released June 28th 2013
- Fixed dependency versions
Version 1.6.5
-------------
Released June 20th 2013
- Fixed bug in `flask.ext.security.confirmable.generate_confirmation_link`
Version 1.6.4
-------------
Released June 18th 2013
- Added `SECURITY_DEFAULT_REMEMBER_ME` configuration value to unify behavior between endpoints
- Fixed Flask-Login dependency problem
- Added optional `next` parameter to registration endpoint, similar to that of login
Version 1.6.3 Version 1.6.3
------------- -------------
+1 -1
View File
@@ -9,7 +9,7 @@
<ul> <ul>
<li><a href="http://pypi.python.org/pypi/Flask-Security">Flask-Security @ PyPI</a></li> <li><a href="http://pypi.python.org/pypi/Flask-Security">Flask-Security @ PyPI</a></li>
<li><a href="http://github.com/mattupstate/flask-security">Flask-Security @ github</a></li> <li><a href="http://github.com/mattupstate/flask-security">Flask-Security @ github</a></li>
<li><a href="http://github.com/jfinkels/flask-security/issues">Issue Tracker</a></li> <li><a href="http://github.com/mattupstate/flask-security/issues">Issue Tracker</a></li>
</ul> </ul>
<ul> <ul>
<li><a href="http://pypi.python.org/pypi/Flask-Social">Flask-Social</a></li> <li><a href="http://pypi.python.org/pypi/Flask-Social">Flask-Social</a></li>
+1 -1
View File
@@ -49,7 +49,7 @@ copyright = u'2012, Matt Wright'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '1.6.3' version = '1.6.6'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version release = version
+7
View File
@@ -156,6 +156,10 @@ Feature Flags
sent an email with a login link. This feature is sent an email with a login link. This feature is
experimental and should be used with caution. Defaults experimental and should be used with caution. Defaults
to ``False``. to ``False``.
``SECURITY_CHANGEABLE`` Specifies if Flask-Security should enable the
change password endpoint. The URL for this endpoint is
specified by the ``SECURITY_CHANGE_URL`` configuration
option. Defaults to ``False``.
========================= ====================================================== ========================= ======================================================
Email Email
@@ -220,4 +224,7 @@ Miscellaneous
remember tokens. Remember tokens are remember tokens. Remember tokens are
used instead of user ID's as it is more used instead of user ID's as it is more
secure. Defaults to ``remember-salt``. secure. Defaults to ``remember-salt``.
``SECURITY_DEFAULT_REMEMBER_ME`` Specifies the default "remember me"
value used when logging in a user.
Defaults to ``False``.
======================================= ======================================== ======================================= ========================================
+1 -1
View File
@@ -10,7 +10,7 @@
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
__version__ = '1.6.3' __version__ = '1.6.6'
from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
+1 -2
View File
@@ -14,8 +14,7 @@ from flask import current_app as app, request
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from .signals import password_changed from .signals import password_changed
from .utils import send_mail, encrypt_password, url_for_security, \ from .utils import send_mail, encrypt_password, config_value
config_value
# Convenient references # Convenient references
+2 -3
View File
@@ -15,7 +15,7 @@ from flask import current_app as app, request
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from .utils import send_mail, md5, url_for_security, get_token_status,\ from .utils import send_mail, md5, url_for_security, get_token_status,\
config_value config_value
from .signals import user_confirmed, confirm_instructions_sent from .signals import user_confirmed, confirm_instructions_sent
@@ -27,8 +27,7 @@ _datastore = LocalProxy(lambda: _security.datastore)
def generate_confirmation_link(user): def generate_confirmation_link(user):
token = generate_confirmation_token(user) token = generate_confirmation_token(user)
url = url_for_security('confirm_email', token=token) return url_for_security('confirm_email', token=token, _external=True), token
return request.url_root[:-1] + url, token
def send_confirmation_instructions(user): def send_confirmation_instructions(user):
+8 -9
View File
@@ -10,10 +10,10 @@
""" """
from flask import current_app from flask import current_app
from flask.ext.login import AnonymousUser as AnonymousUserBase, \ from flask.ext.login import AnonymousUserMixin, UserMixin as BaseUserMixin, \
UserMixin as BaseUserMixin, LoginManager, current_user LoginManager, current_user
from flask.ext.principal import Principal, RoleNeed, UserNeed, Identity, \ from flask.ext.principal import Principal, RoleNeed, UserNeed, Identity, \
identity_loaded identity_loaded
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
from passlib.context import CryptContext from passlib.context import CryptContext
from werkzeug.datastructures import ImmutableList from werkzeug.datastructures import ImmutableList
@@ -22,8 +22,8 @@ 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
from .views import create_blueprint from .views import create_blueprint
from .forms import LoginForm, ConfirmRegisterForm, RegisterForm, \ from .forms import LoginForm, ConfirmRegisterForm, RegisterForm, \
ForgotPasswordForm, ChangePasswordForm, ResetPasswordForm, \ ForgotPasswordForm, ChangePasswordForm, ResetPasswordForm, \
SendConfirmationForm, PasswordlessLoginForm SendConfirmationForm, PasswordlessLoginForm
# Convenient references # Convenient references
_security = LocalProxy(lambda: current_app.extensions['security']) _security = LocalProxy(lambda: current_app.extensions['security'])
@@ -76,6 +76,7 @@ _default_config = {
'LOGIN_SALT': 'login-salt', 'LOGIN_SALT': 'login-salt',
'CHANGE_SALT': 'change-salt', 'CHANGE_SALT': 'change-salt',
'REMEMBER_SALT': 'remember-salt', 'REMEMBER_SALT': 'remember-salt',
'DEFAULT_REMEMBER_ME': False,
'DEFAULT_HTTP_AUTH_REALM': 'Login Required', 'DEFAULT_HTTP_AUTH_REALM': 'Login Required',
'EMAIL_SUBJECT_REGISTER': 'Welcome', 'EMAIL_SUBJECT_REGISTER': 'Welcome',
'EMAIL_SUBJECT_CONFIRM': 'Please confirm your email', 'EMAIL_SUBJECT_CONFIRM': 'Please confirm your email',
@@ -153,8 +154,7 @@ def _token_loader(token):
return user return user
except: except:
pass pass
return AnonymousUser()
return None
def _identity_loader(): def _identity_loader():
@@ -272,11 +272,10 @@ class UserMixin(BaseUserMixin):
return role in self.roles return role in self.roles
class AnonymousUser(AnonymousUserBase): class AnonymousUser(AnonymousUserMixin):
"""AnonymousUser definition""" """AnonymousUser definition"""
def __init__(self): def __init__(self):
super(AnonymousUser, self).__init__()
self.roles = ImmutableList() self.roles = ImmutableList()
def has_role(self, *args): def has_role(self, *args):
+1 -1
View File
@@ -16,7 +16,7 @@ import flask_wtf as wtf
from flask import request, current_app from flask import request, current_app
from flask_wtf import Form as BaseForm, TextField, PasswordField, \ from flask_wtf import Form as BaseForm, TextField, PasswordField, \
SubmitField, HiddenField, BooleanField, ValidationError, Field SubmitField, HiddenField, BooleanField, ValidationError, Field
from flask_login import current_user from flask_login import current_user
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
+2 -3
View File
@@ -14,7 +14,7 @@ from werkzeug.local import LocalProxy
from .signals import login_instructions_sent from .signals import login_instructions_sent
from .utils import send_mail, url_for_security, get_token_status, \ from .utils import send_mail, url_for_security, get_token_status, \
config_value config_value
# Convenient references # Convenient references
@@ -30,8 +30,7 @@ def send_login_instructions(user):
:param token: The login token :param token: The login token
""" """
token = generate_login_token(user) token = generate_login_token(user)
url = url_for_security('token_login', token=token) login_link = url_for_security('token_login', token=token, _external=True)
login_link = request.url_root[:-1] + url
send_mail(config_value('EMAIL_SUBJECT_PASSWORDLESS'), user.email, send_mail(config_value('EMAIL_SUBJECT_PASSWORDLESS'), user.email,
'login_instructions', user=user, login_link=login_link) 'login_instructions', user=user, login_link=login_link)
+2 -3
View File
@@ -14,7 +14,7 @@ from werkzeug.local import LocalProxy
from .signals import password_reset, reset_password_instructions_sent from .signals import password_reset, reset_password_instructions_sent
from .utils import send_mail, md5, encrypt_password, url_for_security, \ from .utils import send_mail, md5, encrypt_password, url_for_security, \
get_token_status, config_value get_token_status, config_value
# Convenient references # Convenient references
@@ -29,8 +29,7 @@ def send_reset_password_instructions(user):
:param user: The user to send the instructions to :param user: The user to send the instructions to
""" """
token = generate_reset_password_token(user) token = generate_reset_password_token(user)
url = url_for_security('reset_password', token=token) reset_link = url_for_security('reset_password', token=token, _external=True)
reset_link = request.url_root[:-1] + url
send_mail(config_value('EMAIL_SUBJECT_PASSWORD_RESET'), user.email, send_mail(config_value('EMAIL_SUBJECT_PASSWORD_RESET'), user.email,
'reset_instructions', 'reset_instructions',
+1 -1
View File
@@ -15,7 +15,7 @@ from werkzeug.local import LocalProxy
from .confirmable import generate_confirmation_link from .confirmable import generate_confirmation_link
from .signals import user_registered from .signals import user_registered
from .utils import do_flash, get_message, send_mail, encrypt_password, \ from .utils import do_flash, get_message, send_mail, encrypt_password, \
config_value config_value
# Convenient references # Convenient references
_security = LocalProxy(lambda: app.extensions['security']) _security = LocalProxy(lambda: app.extensions['security'])
+16 -5
View File
@@ -14,12 +14,13 @@ import blinker
import functools import functools
import hashlib import hashlib
import hmac import hmac
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import url_for, flash, current_app, request, session, render_template from flask import url_for, flash, current_app, request, session, render_template
from flask.ext.login import login_user as _login_user, \ from flask.ext.login import login_user as _login_user, \
logout_user as _logout_user logout_user as _logout_user
from flask.ext.mail import Message from flask.ext.mail import Message
from flask.ext.principal import Identity, AnonymousIdentity, identity_changed from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
from itsdangerous import BadSignature, SignatureExpired from itsdangerous import BadSignature, SignatureExpired
@@ -37,9 +38,12 @@ _datastore = LocalProxy(lambda: _security.datastore)
_pwd_context = LocalProxy(lambda: _security.pwd_context) _pwd_context = LocalProxy(lambda: _security.pwd_context)
def login_user(user, remember=True): def login_user(user, remember=None):
"""Performs the login and sends the appropriate signal.""" """Performs the login and sends the appropriate signal."""
if remember is None:
remember = config_value('DEFAULT_REMEMBER_ME')
if not _login_user(user, remember): if not _login_user(user, remember):
return False return False
@@ -144,11 +148,18 @@ def url_for_security(endpoint, **values):
return url_for(endpoint, **values) return url_for(endpoint, **values)
def get_post_login_redirect(): def get_post_action_redirect(config_key):
"""Returns the URL to redirect to after a user logs in successfully."""
return (get_url(request.args.get('next')) or return (get_url(request.args.get('next')) or
get_url(request.form.get('next')) or get_url(request.form.get('next')) or
find_redirect('SECURITY_POST_LOGIN_VIEW')) find_redirect(config_key))
def get_post_login_redirect():
return get_post_action_redirect('SECURITY_POST_LOGIN_VIEW')
def get_post_register_redirect():
return get_post_action_redirect('SECURITY_POST_REGISTER_VIEW')
def find_redirect(key): def find_redirect(key):
+11 -14
View File
@@ -10,24 +10,23 @@
""" """
from flask import current_app, redirect, request, render_template, jsonify, \ from flask import current_app, redirect, request, render_template, jsonify, \
after_this_request, Blueprint after_this_request, Blueprint
from flask_login import current_user from flask_login import current_user
from werkzeug.datastructures import MultiDict from werkzeug.datastructures import MultiDict
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from .confirmable import send_confirmation_instructions, \ from .confirmable import send_confirmation_instructions, \
confirm_user, confirm_email_token_status confirm_user, confirm_email_token_status
from .decorators import login_required, anonymous_user_required from .decorators import login_required, anonymous_user_required
from .passwordless import send_login_instructions, \ from .passwordless import send_login_instructions, \
login_token_status login_token_status
from .recoverable import reset_password_token_status, \ from .recoverable import reset_password_token_status, \
send_reset_password_instructions, update_password send_reset_password_instructions, update_password
from .changeable import change_user_password from .changeable import change_user_password
from .registerable import register_user from .registerable import register_user
from .utils import get_url, get_post_login_redirect, do_flash, \ from .utils import config_value, do_flash, get_url, get_post_login_redirect, \
get_message, login_user, logout_user, url_for_security as url_for, \ get_post_register_redirect, get_message, login_user, logout_user, \
config_value url_for_security as url_for
# Convenient references # Convenient references
_security = LocalProxy(lambda: current_app.extensions['security']) _security = LocalProxy(lambda: current_app.extensions['security'])
@@ -123,9 +122,7 @@ def register():
login_user(user) login_user(user)
if not request.json: if not request.json:
post_register_url = get_url(_security.post_register_view) return redirect(get_post_register_redirect())
post_login_url = get_url(_security.post_login_view)
return redirect(post_register_url or post_login_url)
if request.json: if request.json:
return _render_json(form) return _render_json(form)
@@ -173,7 +170,7 @@ def token_login(token):
if invalid or expired: if invalid or expired:
return redirect(url_for('login')) return redirect(url_for('login'))
login_user(user, True) login_user(user)
after_this_request(_commit) after_this_request(_commit)
do_flash(*get_message('PASSWORDLESS_LOGIN_SUCCESSFUL')) do_flash(*get_message('PASSWORDLESS_LOGIN_SUCCESSFUL'))
@@ -221,7 +218,7 @@ def confirm_email(token):
url_for('send_confirmation')) url_for('send_confirmation'))
confirm_user(user) confirm_user(user)
login_user(user, True) login_user(user)
after_this_request(_commit) after_this_request(_commit)
do_flash(*get_message('EMAIL_CONFIRMED')) do_flash(*get_message('EMAIL_CONFIRMED'))
@@ -272,7 +269,7 @@ def reset_password(token):
after_this_request(_commit) after_this_request(_commit)
update_password(user, form.password.data) update_password(user, form.password.data)
do_flash(*get_message('PASSWORD_RESET')) do_flash(*get_message('PASSWORD_RESET'))
login_user(user, True) login_user(user)
return redirect(get_url(_security.post_reset_view) or return redirect(get_url(_security.post_reset_view) or
get_url(_security.post_login_view)) get_url(_security.post_login_view))
+2 -2
View File
@@ -20,7 +20,7 @@ from setuptools import setup
setup( setup(
name='Flask-Security', name='Flask-Security',
version='1.6.3', version='1.6.6',
url='https://github.com/mattupstate/flask-security', url='https://github.com/mattupstate/flask-security',
license='MIT', license='MIT',
author='Matt Wright', author='Matt Wright',
@@ -35,7 +35,7 @@ setup(
platforms='any', platforms='any',
install_requires=[ install_requires=[
'Flask>=0.9', 'Flask>=0.9',
'Flask-Login>=0.1.3', 'Flask-Login>=0.2.3',
'Flask-Mail>=0.7.3', 'Flask-Mail>=0.7.3',
'Flask-Principal>=0.3.3', 'Flask-Principal>=0.3.3',
'Flask-WTF>=0.8', 'Flask-WTF>=0.8',
+8
View File
@@ -68,6 +68,14 @@ class ConfiguredSecurityTests(SecurityTest):
r = self._post('/register', data=data, follow_redirects=True) r = self._post('/register', data=data, follow_redirects=True)
self.assertIn('Post Register', r.data) self.assertIn('Post Register', r.data)
def test_register_with_next_querystring_argument(self):
data = dict(email='dude@lp.com',
password='password',
password_confirm='password')
r = self._post('/register?next=/page1', data=data, follow_redirects=True)
self.assertIn('Page 1', r.data)
def test_register_json(self): def test_register_json(self):
data = '{ "email": "dude@lp.com", "password": "password", "csrf_token":"%s" }' % self.csrf_token data = '{ "email": "dude@lp.com", "password": "password", "csrf_token":"%s" }' % self.csrf_token
r = self._post('/register', data=data, content_type='application/json') r = self._post('/register', data=data, content_type='application/json')
+1 -1
View File
@@ -60,7 +60,7 @@ class DefaultSecurityTests(SecurityTest):
def test_unauthorized_access(self): def test_unauthorized_access(self):
r = self._get('/profile', follow_redirects=True) r = self._get('/profile', follow_redirects=True)
self.assertIn('<li class="message">Please log in to access this page.</li>', r.data) self.assertIn('<li class="info">Please log in to access this page.</li>', r.data)
def test_authorized_access(self): def test_authorized_access(self):
self.authenticate() self.authenticate()
+12 -5
View File
@@ -15,7 +15,14 @@ def compare_user(a, b):
return a.id == b.id and a.email == b.email and a.password == b.password return a.id == b.id and a.email == b.email and a.password == b.password
class RegisterableSignalsTests(SecurityTest): class SignalTest(SecurityTest):
def _create_app(self, auth_config, **kwargs):
from tests.test_app.mongoengine import create_app
return create_app(auth_config, **kwargs)
class RegisterableSignalsTests(SignalTest):
AUTH_CONFIG = { AUTH_CONFIG = {
'SECURITY_CONFIRMABLE': True, 'SECURITY_CONFIRMABLE': True,
@@ -42,7 +49,7 @@ class RegisterableSignalsTests(SecurityTest):
self.assertEqual(mocks.signals_sent(), set()) self.assertEqual(mocks.signals_sent(), set())
class ConfirmableSignalsTests(SecurityTest): class ConfirmableSignalsTests(SignalTest):
AUTH_CONFIG = { AUTH_CONFIG = {
'SECURITY_CONFIRMABLE': True, 'SECURITY_CONFIRMABLE': True,
@@ -103,7 +110,7 @@ class ConfirmableSignalsTests(SecurityTest):
self.assertEqual(mocks.signals_sent(), set()) self.assertEqual(mocks.signals_sent(), set())
class RecoverableSignalsTests(SecurityTest): class RecoverableSignalsTests(SignalTest):
AUTH_CONFIG = { AUTH_CONFIG = {
'SECURITY_RECOVERABLE': True, 'SECURITY_RECOVERABLE': True,
@@ -153,7 +160,7 @@ class RecoverableSignalsTests(SecurityTest):
self.assertEqual(mocks.signals_sent(), set()) self.assertEqual(mocks.signals_sent(), set())
class ChangeableSignalsTests(SecurityTest): class ChangeableSignalsTests(SignalTest):
AUTH_CONFIG = { AUTH_CONFIG = {
'SECURITY_CHANGEABLE': True, 'SECURITY_CHANGEABLE': True,
@@ -204,7 +211,7 @@ class ChangeableSignalsTests(SecurityTest):
self.assertEqual(mocks.signals_sent(), set()) self.assertEqual(mocks.signals_sent(), set())
class PasswordlessTests(SecurityTest): class PasswordlessTests(SignalTest):
AUTH_CONFIG = { AUTH_CONFIG = {
'SECURITY_PASSWORDLESS': True 'SECURITY_PASSWORDLESS': True
+4
View File
@@ -113,6 +113,10 @@ def create_app(config):
def invalid_role(): def invalid_role():
return 'success' if ds.find_role('bogus') is None else 'failure' return 'success' if ds.find_role('bogus') is None else 'failure'
@app.route('/page1')
def page_1():
return 'Page 1'
return app return app