From 58685f2bb4a56bcb1068c4bd1b8e0e199971f096 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 21 Aug 2012 17:04:41 -0400 Subject: [PATCH] Decent clean up. Get rid of AuthProvider class in favor of keeping it simple --- flask_security/__init__.py | 3 +- flask_security/confirmable.py | 2 +- flask_security/core.py | 54 +--------------------------- flask_security/datastore.py | 67 +---------------------------------- flask_security/forms.py | 7 ++-- flask_security/views.py | 39 ++++++++++---------- 6 files changed, 29 insertions(+), 143 deletions(-) diff --git a/flask_security/__init__.py b/flask_security/__init__.py index e8364fc..56429a9 100644 --- a/flask_security/__init__.py +++ b/flask_security/__init__.py @@ -12,8 +12,7 @@ __version__ = '1.3.0-dev' -from .core import Security, RoleMixin, UserMixin, AnonymousUser, \ - AuthenticationProvider, current_user +from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore from .decorators import auth_token_required, http_auth_required, \ login_required, roles_accepted, roles_required diff --git a/flask_security/confirmable.py b/flask_security/confirmable.py index e8e9bec..692c99c 100644 --- a/flask_security/confirmable.py +++ b/flask_security/confirmable.py @@ -55,7 +55,7 @@ def generate_confirmation_token(user): def requires_confirmation(user): """Returns `True` if the user requires confirmation.""" - return user.confirmed_at == None if _security.confirmable else False + return user.confirmed_at == None and _security.confirmable def confirm_by_token(token): diff --git a/flask_security/core.py b/flask_security/core.py index d850f17..cd3ddcc 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -76,6 +76,7 @@ _default_messages = { 'ALREADY_CONFIRMED': ('Your email has already been confirmed.', 'info'), 'INVALID_CONFIRMATION_TOKEN': ('Invalid confirmation token.', 'error'), 'ALREADY_CONFIRMED': ('This email has already been confirmed', 'info'), + 'PASSWORD_MISMATCH': ('Password does not match', 'error'), 'PASSWORD_RESET_REQUEST': ('Instructions to reset your password have been sent to %(email)s.', 'info'), 'PASSWORD_RESET_EXPIRED': ('You did not reset your password within %(within)s. New instructions have been sent to %(email)s.', 'error'), 'INVALID_RESET_PASSWORD_TOKEN': ('Invalid reset password token.', 'error'), @@ -313,7 +314,6 @@ class Security(object): for key, value in [ ('app', app), ('datastore', datastore), - ('auth_provider', AuthenticationProvider()), ('login_manager', _get_login_manager(app)), ('principal', _get_principal(app)), ('pwd_context', _get_pwd_context(app)), @@ -331,55 +331,3 @@ class Security(object): def __getattr__(self, name): return getattr(self._state, name, None) - - -class AuthenticationProvider(object): - """The default authentication provider implementation.""" - def _get_user(self, username_or_email): - datastore = _security.datastore - - try: - return datastore.find_user(email=username_or_email) - except exceptions.UserNotFoundError: - try: - return datastore.find_user(username=username_or_email) - except: - raise exceptions.UserNotFoundError() - - def authenticate(self, form): - """Processes an authentication request and returns a user instance if - authentication is successful. - - :param form: A populated WTForm instance that contains `email` and - `password` form fields - """ - if not form.validate(): - if form.email.errors: - raise exceptions.BadCredentialsError(form.email.errors[0]) - if form.password.errors: - raise exceptions.BadCredentialsError(form.password.errors[0]) - - return self.do_authenticate(form.email.data, form.password.data) - - def do_authenticate(self, username_or_email, password): - """Returns the authenticated user if authentication is successfull. If - authentication fails an appropriate `AuthenticationError` is raised - - :param username_or_email: The username or email address of the user - :param password: The password supplied by the authentication request - """ - - try: - user = self._get_user(username_or_email) - except exceptions.UserNotFoundError: - raise exceptions.BadCredentialsError('Specified user does not exist.') - - if requires_confirmation(user): - raise exceptions.ConfirmationError('Email requires confirmation.', user) - - # compare passwords - if verify_password(password, user.password): - return user - - # bad match - raise exceptions.BadCredentialsError("Password does not match") diff --git a/flask_security/datastore.py b/flask_security/datastore.py index aae7367..41f2156 100644 --- a/flask_security/datastore.py +++ b/flask_security/datastore.py @@ -9,14 +9,8 @@ :license: MIT, see LICENSE for more details. """ -from flask import current_app -from werkzeug.local import LocalProxy - from . import exceptions -# Convenient references -_security = LocalProxy(lambda: current_app.extensions['security']) - class UserDatastore(object): """Abstracted user datastore. Always extend this class and implement the @@ -177,42 +171,8 @@ class UserDatastore(object): class SQLAlchemyUserDatastore(UserDatastore): """A SQLAlchemy datastore implementation for Flask-Security that assumes the use of the Flask-SQLAlchemy extension. - - Example usage:: - - from flask import Flask - from flask.ext.security import Security, SQLAlchemyUserDatastore - from flask.ext.sqlalchemy import SQLAlchemy - - app = Flask(__name__) - app.config['SECRET_KEY'] = 'secret' - app.config['SQLALCHEMY_DATABASE_URI'] = \ - 'sqlite:////tmp/flask_security_example.sqlite' - - db = SQLAlchemy(app) - - roles_users = db.Table('roles_users', - db.Column('user_id', db.Integer(), db.ForeignKey('role.id')), - db.Column('role_id', db.Integer(), db.ForeignKey('user.id'))) - - class Role(db.Model, RoleMixin): - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(80), unique=True) - - class User(db.Model, UserMixin): - id = db.Column(db.Integer, primary_key=True) - email = db.Column(db.String(255), unique=True) - password = db.Column(db.String(120)) - first_name = db.Column(db.String(120)) - last_name = db.Column(db.String(120)) - active = db.Column(db.Boolean()) - created_at = db.Column(db.DateTime()) - modified_at = db.Column(db.DateTime()) - roles = db.relationship('Role', secondary=roles_users, - backref=db.backref('users', lazy='dynamic')) - - Security(app, SQLAlchemyUserDatastore(db, User, Role)) """ + def _commit(self, *args, **kwargs): self.db.session.commit() @@ -233,31 +193,6 @@ class SQLAlchemyUserDatastore(UserDatastore): class MongoEngineUserDatastore(UserDatastore): """A MongoEngine datastore implementation for Flask-Security that assumes the use of the Flask-MongoEngine extension. - - Example usage:: - - from flask import Flask - from flask.ext.mongoengine import MongoEngine - from flask.ext.security import Security, MongoEngineUserDatastore - - app = Flask(__name__) - app.config['SECRET_KEY'] = 'secret' - app.config['MONGODB_DB'] = 'flask_security_example' - app.config['MONGODB_HOST'] = 'localhost' - app.config['MONGODB_PORT'] = 27017 - - db = MongoEngine(app) - - class Role(db.Document, RoleMixin): - name = db.StringField(required=True, unique=True, max_length=80) - - class User(db.Document, UserMixin): - email = db.StringField(unique=True, max_length=255) - password = db.StringField(required=True, max_length=120) - active = db.BooleanField(default=True) - roles = db.ListField(db.ReferenceField(Role), default=[]) - - Security(app, MongoEngineUserDatastore(db, User, Role)) """ def _save_model(self, model): diff --git a/flask_security/forms.py b/flask_security/forms.py index 0784d13..607645f 100644 --- a/flask_security/forms.py +++ b/flask_security/forms.py @@ -35,9 +35,9 @@ def unique_user_email(form, field): def valid_user_email(form, field): try: - _datastore.find_user(email=field.data) + form.user = _datastore.find_user(email=field.data) except UserNotFoundError: - raise ValidationError('Invalid email address') + raise ValidationError('Specified user does not exist') class EmailFormMixin(): @@ -47,6 +47,7 @@ class EmailFormMixin(): class UserEmailFormMixin(): + user = None email = TextField("Email Address", validators=[email_required, email_validator, @@ -108,7 +109,7 @@ class PasswordlessLoginForm(Form, UserEmailFormMixin): return dict(email=self.email.data) -class LoginForm(Form, EmailFormMixin, PasswordFormMixin): +class LoginForm(Form, UserEmailFormMixin, PasswordFormMixin): """The default login form""" remember = BooleanField("Remember Me") diff --git a/flask_security/views.py b/flask_security/views.py index 9aa6270..83f8a58 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -15,10 +15,10 @@ from werkzeug.datastructures import MultiDict from werkzeug.local import LocalProxy from flask_security.confirmable import confirm_by_token, \ - send_confirmation_instructions + send_confirmation_instructions, requires_confirmation from flask_security.decorators import login_required -from flask_security.exceptions import ConfirmationError, BadCredentialsError, \ - ResetPasswordError, PasswordlessLoginError +from flask_security.exceptions import ConfirmationError, ResetPasswordError, \ + PasswordlessLoginError from flask_security.forms import LoginForm, RegisterForm, ForgotPasswordForm, \ ResetPasswordForm, SendConfirmationForm, PasswordlessLoginForm from flask_security.passwordless import send_login_instructions, login_by_token @@ -27,7 +27,7 @@ from flask_security.recoverable import reset_by_token, \ from flask_security.signals import user_registered from flask_security.utils import get_url, get_post_login_redirect, do_flash, \ get_message, config_value, login_user, logout_user, \ - anonymous_user_required, url_for_security as url_for + anonymous_user_required, url_for_security as url_for, verify_password # Convenient references @@ -77,37 +77,40 @@ def _ctx(endpoint): @anonymous_user_required def login(): """View function for login view""" - form = LoginForm(request.form, csrf_enabled=not app.testing) + user, msg, confirm_url = None, None, None + form = LoginForm(request.form, csrf_enabled=not app.testing) if request.json: form = LoginForm(MultiDict(request.json), csrf_enabled=not app.testing) if form.validate_on_submit(): - try: - user = _security.auth_provider.authenticate(form) - except ConfirmationError, e: - msg = str(e) - confirm_url = url_for('send_confirmation', email=e.user.email) - except BadCredentialsError, e: - msg = str(e) - form.password.errors.append(msg) + user = form.user - if user: + if requires_confirmation(user): + msg = get_message('CONFIRMATION_REQUIRED') + confirm_url = url_for('send_confirmation', email=user.email) + form.email.errors.append(msg[0]) + + elif verify_password(form.password.data, user.password): if login_user(user, remember=form.remember.data): after_this_request(_commit) if request.json: return _json_auth_ok(user) return redirect(get_post_login_redirect()) - form.email.errors.append(get_message('DISABLED_ACCOUNT')[0]) + msg = get_message('DISABLED_ACCOUNT') + form.email.errors.append(msg[0]) + else: + msg = get_message('PASSWORD_MISMATCH') + form.password.errors.append(msg[0]) - _logger.debug('Unsuccessful authentication attempt: %s' % msg) + _logger.debug('Unsuccessful authentication attempt: %s' % msg[0]) if request.json: - return _json_auth_error(msg) + return _json_auth_error(msg[0]) if confirm_url: - do_flash(msg, 'error') + do_flash(*msg) return redirect(confirm_url) return render_template('security/login.html',