diff --git a/example/app.py b/example/app.py index 38a8f0d..69e0b6e 100644 --- a/example/app.py +++ b/example/app.py @@ -149,8 +149,8 @@ def create_sqlalchemy_app(auth_config=None, register_blueprint=True): roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) - Security(app, SQLAlchemyUserDatastore(db, User, Role), - register_blueprint=register_blueprint) + app.security = Security(app, SQLAlchemyUserDatastore(db, User, Role), + register_blueprint=register_blueprint) if not register_blueprint: from example import security @@ -192,7 +192,7 @@ def create_mongoengine_app(auth_config=None): authentication_token = db.StringField(max_length=255) roles = db.ListField(db.ReferenceField(Role), default=[]) - Security(app, MongoEngineUserDatastore(db, User, Role)) + app.security = Security(app, MongoEngineUserDatastore(db, User, Role)) @app.before_first_request def before_first_request(): diff --git a/flask_security/confirmable.py b/flask_security/confirmable.py index 58ceff7..9c703a8 100644 --- a/flask_security/confirmable.py +++ b/flask_security/confirmable.py @@ -21,9 +21,9 @@ from .signals import user_confirmed, confirm_instructions_sent # Convenient references -_security = LocalProxy(lambda: app.security) +_security = LocalProxy(lambda: app.extensions['security']) -_datastore = LocalProxy(lambda: app.security.datastore) +_datastore = LocalProxy(lambda: _security.datastore) def send_confirmation_instructions(user, token): diff --git a/flask_security/core.py b/flask_security/core.py index 8050cc1..f4d8219 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -17,11 +17,15 @@ from flask.ext.principal import Principal, RoleNeed, UserNeed, Identity, \ identity_loaded from passlib.context import CryptContext from werkzeug.datastructures import ImmutableList +from werkzeug.local import LocalProxy from . import views, exceptions from .confirmable import requires_confirmation from .utils import config_value as cv, get_config +# Convenient references +_security = LocalProxy(lambda: current_app.extensions['security']) + #: Default Flask-Security configuration _default_config = { @@ -75,7 +79,7 @@ _default_flash_messages = { def _user_loader(user_id): try: - return current_app.security.datastore.with_id(user_id) + return _security.datastore.with_id(user_id) except Exception, e: current_app.logger.error('Error getting user: %s' % e) return None @@ -83,7 +87,7 @@ def _user_loader(user_id): def _token_loader(token): try: - return current_app.security.datastore.find_user(remember_token=token) + return _security.datastore.find_user(remember_token=token) except Exception, e: current_app.logger.error('Error getting user: %s' % e) return None @@ -189,6 +193,13 @@ class AnonymousUser(AnonymousUserBase): return False +class _SecurityState(object): + + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key.lower(), value) + + class Security(object): """The :class:`Security` class initializes the Flask-Security extension. @@ -196,7 +207,11 @@ class Security(object): :param datastore: An instance of a user datastore. """ def __init__(self, app=None, datastore=None, **kwargs): - self.init_app(app, datastore, **kwargs) + self.app = app + self.datastore = datastore + + if app is not None and datastore is not None: + self.init_app(app, datastore, **kwargs) def init_app(self, app, datastore, register_blueprint=True): """Initializes the Flask-Security extension for the specified @@ -205,8 +220,6 @@ class Security(object): :param app: The application. :param datastore: An instance of a user datastore. """ - if app is None or datastore is None: - return for key, value in _default_config.items(): app.config.setdefault('SECURITY_' + key, value) @@ -214,18 +227,6 @@ class Security(object): for key, value in _default_flash_messages.items(): app.config.setdefault('SECURITY_MSG_' + key, value) - self.datastore = datastore - self.auth_provider = AuthenticationProvider() - self.login_manager = _get_login_manager(app) - self.principal = _get_principal(app) - self.pwd_context = _get_pwd_context(app) - self.reset_serializer = _get_reset_serializer(app) - self.confirm_serializer = _get_confirm_serializer(app) - self.token_auth_serializer = _get_token_auth_serializer(app) - - for key, value in get_config(app).items(): - setattr(self, key.lower(), value) - identity_loaded.connect_via(app)(_on_identity_loaded) if register_blueprint: @@ -234,13 +235,30 @@ class Security(object): url_prefix=cv('URL_PREFIX', app=app)) app.register_blueprint(bp) - app.security = self + if not hasattr(app, 'extensions'): + app.extensions = {} + + kwargs = {} + for key, value in get_config(app).items(): + kwargs[key.lower()] = value + + app.extensions['security'] = _SecurityState( + app=app, + datastore=datastore, + auth_provider=AuthenticationProvider(), + login_manager=_get_login_manager(app), + principal=_get_principal(app), + pwd_context=_get_pwd_context(app), + reset_serializer=_get_reset_serializer(app), + confirm_serializer=_get_confirm_serializer(app), + token_auth_serializer=_get_token_auth_serializer(app), + **kwargs) class AuthenticationProvider(object): """The default authentication provider implementation.""" def _get_user(self, username_or_email): - datastore = current_app.security.datastore + datastore = _security.datastore try: return datastore.find_user(email=username_or_email) @@ -286,7 +304,7 @@ class AuthenticationProvider(object): raise exceptions.BadCredentialsError('Account requires confirmation') # compare passwords - if current_app.security.pwd_context.verify(password, user.password): + if _security.pwd_context.verify(password, user.password): return user # bad match diff --git a/flask_security/datastore.py b/flask_security/datastore.py index bd076f5..98d5213 100644 --- a/flask_security/datastore.py +++ b/flask_security/datastore.py @@ -10,9 +10,13 @@ """ from flask import current_app +from werkzeug.local import LocalProxy from . import exceptions, utils +# Convenient references +_security = LocalProxy(lambda: current_app.extensions['security']) + class UserDatastore(object): """Abstracted user datastore. Always extend this class and implement the @@ -88,7 +92,7 @@ class UserDatastore(object): password = kwargs.get('password', None) kwargs.setdefault('active', True) - kwargs.setdefault('roles', current_app.security.default_roles) + kwargs.setdefault('roles', _security.default_roles) if email is None: raise exceptions.UserCreationError('Missing email argument') @@ -105,8 +109,9 @@ class UserDatastore(object): kwargs['roles'] = roles - pwd_context = current_app.security.pwd_context + pwd_context = _security.pwd_context pw = kwargs['password'] + if not pwd_context.identify(pw): kwargs['password'] = pwd_context.encrypt(pw) diff --git a/flask_security/decorators.py b/flask_security/decorators.py index 5406e4f..93bfe8a 100644 --- a/flask_security/decorators.py +++ b/flask_security/decorators.py @@ -21,7 +21,7 @@ from .exceptions import UserNotFoundError # Convenient references -_security = LocalProxy(lambda: current_app.security) +_security = LocalProxy(lambda: current_app.extensions['security']) _logger = LocalProxy(lambda: current_app.logger) diff --git a/flask_security/forms.py b/flask_security/forms.py index 4be9bbe..9fab6f4 100644 --- a/flask_security/forms.py +++ b/flask_security/forms.py @@ -18,7 +18,7 @@ from .exceptions import UserNotFoundError # Convenient reference -_datastore = LocalProxy(lambda: app.security.datastore) +_datastore = LocalProxy(lambda: app.extensions['security'].datastore) def valid_user_email(form, field): diff --git a/flask_security/recoverable.py b/flask_security/recoverable.py index cebd57e..48fd1b5 100644 --- a/flask_security/recoverable.py +++ b/flask_security/recoverable.py @@ -20,9 +20,9 @@ from .utils import send_mail, get_max_age, md5, get_message # Convenient references -_security = LocalProxy(lambda: app.security) +_security = LocalProxy(lambda: app.extensions['security']) -_datastore = LocalProxy(lambda: app.security.datastore) +_datastore = LocalProxy(lambda: _security.datastore) def send_reset_password_instructions(user, reset_token): diff --git a/flask_security/script.py b/flask_security/script.py index 85b61f8..ca01c93 100644 --- a/flask_security/script.py +++ b/flask_security/script.py @@ -15,7 +15,7 @@ from werkzeug.local import LocalProxy from flask_security import views -_datastore = LocalProxy(lambda: current_app.security.datastore) +_datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) def pprint(obj): diff --git a/flask_security/tokens.py b/flask_security/tokens.py index f6665f0..f896469 100644 --- a/flask_security/tokens.py +++ b/flask_security/tokens.py @@ -16,9 +16,9 @@ from .utils import md5 # Convenient references -_security = LocalProxy(lambda: app.security) +_security = LocalProxy(lambda: app.extensions['security']) -_datastore = LocalProxy(lambda: app.security.datastore) +_datastore = LocalProxy(lambda: _security.datastore) def generate_authentication_token(user): diff --git a/flask_security/utils.py b/flask_security/utils.py index 62274ba..fdbcf39 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -17,10 +17,15 @@ from datetime import datetime, timedelta from flask import url_for, flash, current_app, request, session, render_template from flask.ext.login import make_secure_token +from werkzeug.local import LocalProxy from .signals import user_registered, password_reset_requested +# Convenient references +_security = LocalProxy(lambda: current_app.extensions['security']) + + def md5(data): return hashlib.md5(data).hexdigest() @@ -150,7 +155,7 @@ def send_mail(subject, recipient, template, context=None): context = context or {} msg = Message(subject, - sender=current_app.security.email_sender, + sender=_security.email_sender, recipients=[recipient]) base = 'security/email' diff --git a/flask_security/views.py b/flask_security/views.py index e9eb2ed..f1d534c 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -33,9 +33,9 @@ from flask_security.utils import get_url, get_post_login_redirect, do_flash, \ # Convenient references -_security = LocalProxy(lambda: app.security) +_security = LocalProxy(lambda: app.extensions['security']) -_datastore = LocalProxy(lambda: app.security.datastore) +_datastore = LocalProxy(lambda: _security.datastore) _logger = LocalProxy(lambda: app.logger) diff --git a/tests/functional_tests.py b/tests/functional_tests.py index 7fa4fb1..87a51be 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -279,7 +279,7 @@ class ExpiredConfirmationTest(SecurityTest): self.assertIn(e, outbox[0].html) self.assertNotIn(token, outbox[0].html) - expire_text = self.app.security.confirm_email_within + expire_text = self.AUTH_CONFIG['SECURITY_CONFIRM_EMAIL_WITHIN'] text = 'You did not confirm your account within %s' % expire_text self.assertIn(text, r.data)