Decent clean up. Get rid of AuthProvider class in favor of keeping it simple

This commit is contained in:
Matt Wright
2012-08-21 17:04:41 -04:00
parent 25e9d02a8a
commit 58685f2bb4
6 changed files with 29 additions and 143 deletions
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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):
+1 -53
View File
@@ -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")
+1 -66
View File
@@ -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):
+4 -3
View File
@@ -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")
+21 -18
View File
@@ -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',