From 5fdfa544db656775f093275f627ea58948fd3e99 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Fri, 15 Jun 2012 12:13:49 -0400 Subject: [PATCH] Polish up some code and add some signals --- flask_security/confirmable.py | 6 +++- flask_security/core.py | 10 +++++- flask_security/recoverable.py | 8 +++-- flask_security/signals.py | 8 +++++ flask_security/views.py | 58 ++++++++++++++++++++--------------- tests/functional_tests.py | 4 +-- 6 files changed, 63 insertions(+), 31 deletions(-) diff --git a/flask_security/confirmable.py b/flask_security/confirmable.py index 992c8dc..c78ae75 100644 --- a/flask_security/confirmable.py +++ b/flask_security/confirmable.py @@ -5,10 +5,11 @@ from flask import current_app, request, url_for from flask.ext.security.exceptions import UserNotFoundError, \ ConfirmationError, TokenExpiredError from flask.ext.security.utils import generate_token, send_mail +from flask.ext.security.signals import user_confirmed, \ + confirm_instructions_sent from werkzeug.local import LocalProxy security = LocalProxy(lambda: current_app.security) - logger = LocalProxy(lambda: current_app.logger) @@ -28,6 +29,8 @@ def send_confirmation_instructions(user): 'confirmation_instructions', dict(user=user, confirmation_link=confirmation_link)) + confirm_instructions_sent.send(user, app=current_app._get_current_object()) + return True @@ -89,6 +92,7 @@ def confirm_by_token(token): security.datastore._save_model(user) + user_confirmed.send(user, app=current_app._get_current_object()) return user diff --git a/flask_security/core.py b/flask_security/core.py index f73c764..7bee606 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -302,6 +302,9 @@ class ForgotPasswordForm(Form): email = TextField("Email Address", validators=[Required(message="Email not provided")]) + def to_dict(self): + return dict(email=self.email.data) + class LoginForm(Form): """The default login form""" @@ -334,13 +337,18 @@ class RegisterForm(Form): class ResetPasswordForm(Form): - reset_password_token = HiddenField(validators=[Required()]) + token = HiddenField(validators=[Required()]) email = HiddenField(validators=[Required()]) password = PasswordField("Password", validators=[Required(message="Password not provided")]) password_confirm = PasswordField("Retype Password", validators=[EqualTo('password', message="Passwords do not match")]) + def to_dict(self): + return dict(token=self.token.data, + email=self.email.data, + password=self.password.data) + class AuthenticationProvider(object): """The default authentication provider implementation. diff --git a/flask_security/recoverable.py b/flask_security/recoverable.py index 86cc424..0ec377b 100644 --- a/flask_security/recoverable.py +++ b/flask_security/recoverable.py @@ -4,12 +4,12 @@ from datetime import datetime from flask import current_app, request, url_for from flask.ext.security.exceptions import ResetPasswordError, \ UserNotFoundError, TokenExpiredError -from flask.ext.security.signals import password_reset_requested +from flask.ext.security.signals import password_reset, \ + password_reset_requested, confirm_instructions_sent from flask.ext.security.utils import generate_token, send_mail from werkzeug.local import LocalProxy security = LocalProxy(lambda: current_app.security) - logger = LocalProxy(lambda: current_app.logger) @@ -30,6 +30,8 @@ def send_reset_password_instructions(user): 'reset_instructions', dict(user=user, reset_link=reset_link)) + confirm_instructions_sent.send(user, app=current_app._get_current_object()) + return True @@ -75,6 +77,8 @@ def reset_by_token(token, email, password): send_mail('Your password has been reset', user.email, 'reset_notice') + password_reset.send(user, app=current_app._get_current_object()) + return user diff --git a/flask_security/signals.py b/flask_security/signals.py index 3990126..bc77265 100644 --- a/flask_security/signals.py +++ b/flask_security/signals.py @@ -4,4 +4,12 @@ signals = blinker.Namespace() user_registered = signals.signal("user-registered") +user_confirmed = signals.signal("user-confirmed") + +confirm_instructions_sent = signals.signal("confirm-instructions-sent") + +password_reset = signals.signal("password-reset") + password_reset_requested = signals.signal("password-reset-requested") + +reset_instructions_sent = signals.signal("reset-instructions-sent") diff --git a/flask_security/views.py b/flask_security/views.py index 5a02c83..9e51c35 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -24,7 +24,7 @@ from werkzeug.local import LocalProxy security = LocalProxy(lambda: current_app.security) - +datastore = LocalProxy(lambda: current_app.security.datastore) logger = LocalProxy(lambda: current_app.logger) @@ -63,7 +63,6 @@ def authenticate(): msg = 'Unknown authentication error' do_flash(msg, 'error') - logger.debug('Unsuccessful authentication attempt: %s' % msg) return redirect(request.referrer or security.login_manager.login_view) @@ -81,10 +80,9 @@ def logout(): identity=AnonymousIdentity()) logout_user() - logger.debug('User logged out') - return redirect(request.args.get('next', None) or \ + return redirect(request.args.get('next', None) or security.post_logout_view) @@ -99,24 +97,26 @@ def register(): form = security.RegisterForm(csrf_enabled=not current_app.testing) # Exit early if the form doesn't validate - if not form.validate_on_submit(): - return redirect(request.referrer or security.register_url) + if form.validate_on_submit(): + # Create user and send signal + user = datastore.create_user(**form.to_dict()) + app = current_app._get_current_object() - # Create user and send signal - user = security.datastore.create_user(**form.to_dict()) - user_registered.send(user, app=current_app._get_current_object()) + user_registered.send(user, app=app) - # Send confirmation instructions if necessary - if security.confirm_email: - send_confirmation_instructions(user) + # Send confirmation instructions if necessary + if security.confirm_email: + send_confirmation_instructions(user) - logger.debug('User %s registered' % user) + logger.debug('User %s registered' % user) - # Login the user if allowed - if (not security.confirm_email) or security.login_without_confirmation: - _do_login(user) + # Login the user if allowed + if not security.confirm_email or security.login_without_confirmation: + _do_login(user) - return redirect(security.post_register_view or security.post_login_view) + return redirect(security.post_register_view or security.post_login_view) + + return redirect(request.referrer or security.register_url) def confirm(): @@ -140,7 +140,6 @@ def confirm(): security.confirm_email_within_text, e.user.email) do_flash(msg, 'error') - return redirect('/') # TODO: Don't redirect to root logger.debug('User %s confirmed' % user) @@ -150,35 +149,44 @@ def confirm(): def forgot(): + """View function that handles the generation of a password reset token. + """ + form = security.ForgotPasswordForm(csrf_enabled=not current_app.testing) if form.validate_on_submit(): try: - user = security.datastore.find_user(email=form.email.data) + user = datastore.find_user(**form.to_dict()) + reset_password_reset_token(user) - do_flash('Instructions to reset your password have been sent to %s' % user.email, 'success') + + do_flash('Instructions to reset your password have been ' + 'sent to %s' % user.email, 'success') except UserNotFoundError: do_flash('The email you provided could not be found', 'error') return redirect(security.post_forgot_view) - return render_template('security/passwords/new.html', forgot_password_form=form) + return render_template('security/passwords/new.html', + forgot_password_form=form) def reset(): + """View function that handles the reset of a user's password. + """ + form = security.ResetPasswordForm(csrf_enabled=not current_app.testing) if form.validate_on_submit(): try: - reset_by_token(token=form.reset_password_token.data, - email=form.email.data, - password=form.password.data) + reset_by_token(**form.to_dict()) except ResetPasswordError, e: do_flash(str(e), 'error') except TokenExpiredError, e: - do_flash('You did not reset your password within %s.' % security.reset_password_within_text) + do_flash('You did not reset your password within' + '%s.' % security.reset_password_within_text) return redirect(request.referrer or security.reset_password_error_view) diff --git a/tests/functional_tests.py b/tests/functional_tests.py index 8ce1934..3a728d6 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -236,7 +236,7 @@ class RecoverableTests(SecurityTest): r = self.client.post('/reset', data={ 'email': u.email, - 'reset_password_token': u.reset_password_token, + 'token': u.reset_password_token, 'password': 'newpassword', 'password_confirm': 'newpassword' }) @@ -251,7 +251,7 @@ class RecoverableTests(SecurityTest): data = { 'email': u.email, - 'reset_password_token': u.reset_password_token, + 'token': u.reset_password_token, 'password': 'newpassword', 'password_confirm': 'newpassword' }