diff --git a/flask_user/db_manager.py b/flask_user/db_manager.py index 38836fbf..4aebe627 100644 --- a/flask_user/db_manager.py +++ b/flask_user/db_manager.py @@ -7,6 +7,7 @@ from .db_adapters import PynamoDbAdapter, DynamoDbAdapter, MongoDbAdapter, SQLDbAdapter from flask_user import current_user, ConfigError + class DBManager(object): """Manage DB objects.""" @@ -21,23 +22,31 @@ def __init__(self, app, db, UserClass, UserEmailClass=None, UserInvitationClass= UserInvitationClass: Optional UserInvitation class for user-invitation feature. RoleClass: For testing purposes only. """ - self.app = app self.db = db self.UserClass = UserClass self.UserEmailClass = UserEmailClass self.UserInvitationClass = UserInvitationClass self.RoleClass = RoleClass - self.user_manager = app.user_manager + self.app = app + self.user_manager = None self.db_adapter = None + if app: + self.init_app(app) + + def init_app(self, app): + + self.app = app + self.user_manager = app.user_manager + # Check if db is a SQLAlchemy instance if self.db_adapter is None: try: from flask_sqlalchemy import SQLAlchemy - if isinstance(db, SQLAlchemy): - self.db_adapter = SQLDbAdapter(app, db) + if isinstance(self.db, SQLAlchemy): + self.db_adapter = SQLDbAdapter(app, self.db) except ImportError: pass # Ignore ImportErrors @@ -46,18 +55,18 @@ def __init__(self, app, db, UserClass, UserEmailClass=None, UserInvitationClass= try: from flask_mongoengine import MongoEngine - if isinstance(db, MongoEngine): - self.db_adapter = MongoDbAdapter(app, db) + if isinstance(self.db, MongoEngine): + self.db_adapter = MongoDbAdapter(app, self.db) except ImportError: pass # Ignore ImportErrors # Check if db is a Flywheel instance - if self.db_adapter is None: # pragma: no cover + if self.db_adapter is None: # pragma: no cover try: from flask_flywheel import Flywheel - if isinstance(db, Flywheel): - self.db_adapter = DynamoDbAdapter(app, db) + if isinstance(self.db, Flywheel): + self.db_adapter = DynamoDbAdapter(app, self.db) except ImportError: pass # Ignore ImportErrors @@ -66,18 +75,17 @@ def __init__(self, app, db, UserClass, UserEmailClass=None, UserInvitationClass= try: from pynamodb.models import Model - if issubclass(UserClass, Model): + if issubclass(self.UserClass, Model): self.db_adapter = PynamoDbAdapter(app) except ImportError: - pass # Ignore ImportErrors + pass # Ignore ImportErrors # Check self.db_adapter if self.db_adapter is None: raise ConfigError( - 'No Flask-SQLAlchemy, Flask-MongoEngine or Flask-Flywheel installed and no Pynamo Model in use.'\ + 'No Flask-SQLAlchemy, Flask-MongoEngine or Flask-Flywheel installed and no Pynamo Model in use.' \ ' You must install one of these Flask extensions.') - def add_user_role(self, user, role_name): """Associate a role name with a user.""" @@ -256,14 +264,12 @@ def username_is_available(self, new_username): # Return False otherwise. return self.find_user_by_username(new_username) == None - # def delete_role_name(self, role_name): # if isinstance(self.db_adapter, SQLDbAdapter): # role = self.db_adapter.find_first_object(self.user_manager.db_manager.RoleClass, name=role_name) # if role: # self.db_adapter.delete_object(role) - # Database management methods # --------------------------- @@ -277,4 +283,3 @@ def drop_all_tables(self): .. warning:: ALL DATA WILL BE LOST. Use only for automated testing. """ return self.db_adapter.drop_all_tables() - diff --git a/flask_user/password_manager.py b/flask_user/password_manager.py index 193dea22..bdff714e 100644 --- a/flask_user/password_manager.py +++ b/flask_user/password_manager.py @@ -26,14 +26,19 @@ def __init__(self, app): ``password_manager = PasswordManager('bcrypt')`` """ + # Create a passlib CryptContext self.app = app - self.user_manager = app.user_manager + self.user_manager = None + if app: + self.init_app(app) - # Create a passlib CryptContext self.password_crypt_context = CryptContext( schemes=self.user_manager.USER_PASSLIB_CRYPTCONTEXT_SCHEMES, **self.user_manager.USER_PASSLIB_CRYPTCONTEXT_KEYWORDS) + def init_app(self, app): + self.app = app + self.user_manager = app.user_manager def hash_password(self, password): """Hash plaintext ``password`` using the ``password_hash`` specified in the constructor. diff --git a/flask_user/token_manager.py b/flask_user/token_manager.py index fee77e55..09727258 100644 --- a/flask_user/token_manager.py +++ b/flask_user/token_manager.py @@ -26,7 +26,7 @@ class TokenManager(object): # *** Public methods *** - def __init__(self, app): + def __init__(self, app=None): """Check config settings and initialize the Fernet encryption cypher. Fernet is basically AES128 in CBC mode, with a timestamp and a signature. @@ -34,7 +34,11 @@ def __init__(self, app): Args: app(Flask): The Flask application instance. """ + self.app = app + if app: + self.init_app(app) + def init_app(self, app): self.app = app # Use the applications's SECRET_KEY if flask_secret_key is not specified. diff --git a/flask_user/user_manager.py b/flask_user/user_manager.py index b6323997..836cb5ab 100644 --- a/flask_user/user_manager.py +++ b/flask_user/user_manager.py @@ -5,14 +5,15 @@ # Copyright (c) 2013 Ling Thio' import datetime +import string -from flask import abort, Blueprint, current_app, Flask, session +from flask import Blueprint, Flask, abort, current_app, session from flask_login import LoginManager from wtforms import ValidationError -from . import ConfigError -from . import forms +from . import ConfigError, forms from .db_manager import DBManager +from .email_adapters.smtp_email_adapter import SMTPEmailAdapter from .email_manager import EmailManager from .password_manager import PasswordManager from .token_manager import TokenManager @@ -25,10 +26,21 @@ # The UserManager is implemented across several source code files. # Mixins are used to aggregate all member functions into the one UserManager class for ease of customization. class UserManager(UserManager__Settings, UserManager__Utils, UserManager__Views): - """ Customizable User Authentication and Management. - """ - - def __init__(self, app, db, UserClass, **kwargs): + """Customizable User Authentication and Management.""" + + def __init__( + self, + app=None, + db=None, + UserClass=None, + login_manager=None, + token_manager=None, + email_manager=None, + email_adapter=None, + password_manager=None, + db_manager=None, + **kwargs + ): """ Args: app(Flask): The Flask application instance. @@ -50,17 +62,45 @@ def __init__(self, app, db, UserClass, **kwargs): Customizable UserManager methods """ - #see http://flask.pocoo.org/docs/0.12/extensiondev/#the-extension-code """ + # see http://flask.pocoo.org/docs/0.12/extensiondev/#the-extension-code """ + self.app = app + self.db = db + self.login_manager = login_manager + self.babel = None + + # Required managers + self.token_manager = token_manager + self.email_manager = email_manager + self.email_adapter = email_adapter + self.password_manager = password_manager + self.db_manager = db_manager + + # Set default form classes + # ------------------------ + self.AddEmailFormClass = forms.AddEmailForm + self.ChangePasswordFormClass = forms.ChangePasswordForm + self.ChangeUsernameFormClass = forms.ChangeUsernameForm + self.EditUserProfileFormClass = forms.EditUserProfileForm + self.ForgotPasswordFormClass = forms.ForgotPasswordForm + self.InviteUserFormClass = forms.InviteUserForm + self.LoginFormClass = forms.LoginForm + self.RegisterFormClass = forms.RegisterForm + self.ResendEmailConfirmationFormClass = forms.ResendEmailConfirmationForm + self.ResetPasswordFormClass = forms.ResetPasswordForm + if app: self.init_app(app, db, UserClass, **kwargs) def init_app( - self, app, db, UserClass, + self, + app, + db, + UserClass, UserInvitationClass=None, UserEmailClass=None, - RoleClass=None, # Only used for testing - ): + RoleClass=None, # Only used for testing + ): # See http://flask.pocoo.org/docs/0.12/extensiondev/#the-extension-code # Perform Class type checking @@ -120,8 +160,12 @@ def advance_session_timeout(): # Configure Flask-Login # -------------------- - # Setup default LoginManager using Flask-Login - self.login_manager = LoginManager(app) + # Init login manager instance passed during init, + # or setup default LoginManager using Flask-Login + if self.login_manager: + self.login_manager.init_app(app) + else: + self.login_manager = LoginManager(app) self.login_manager.login_view = 'user.login' # Flask-Login calls this function to retrieve a User record by token. @@ -132,8 +176,9 @@ def load_user_by_user_token(user_token): # Configure Flask-BabelEx # ----------------------- - self.babel = app.extensions.get('babel', None) + self.babel = app.extensions.get("babel", None) from .translation_utils import init_translations + init_translations(self.babel) # Configure Jinja2 @@ -163,38 +208,33 @@ def call_or_get(function_or_property): blueprint = Blueprint('flask_user', __name__, template_folder='templates') app.register_blueprint(blueprint) - # Set default form classes - # ------------------------ - self.AddEmailFormClass = forms.AddEmailForm - self.ChangePasswordFormClass = forms.ChangePasswordForm - self.ChangeUsernameFormClass = forms.ChangeUsernameForm - self.EditUserProfileFormClass = forms.EditUserProfileForm - self.ForgotPasswordFormClass = forms.ForgotPasswordForm - self.InviteUserFormClass = forms.InviteUserForm - self.LoginFormClass = forms.LoginForm - self.RegisterFormClass = forms.RegisterForm - self.ResendEmailConfirmationFormClass = forms.ResendEmailConfirmationForm - self.ResetPasswordFormClass = forms.ResetPasswordForm - - # Set default managers + # Setup managers. Init app for managers passed during __init__, + # create default for nonexistent once # -------------------- # Setup DBManager - self.db_manager = DBManager(app, db, UserClass, UserEmailClass, UserInvitationClass, RoleClass) + if self.db_manager: + self.db_manager.init_app(app) + else: + self.db_manager = DBManager( + app, db, UserClass, UserEmailClass, UserInvitationClass, RoleClass + ) # Setup PasswordManager - self.password_manager = PasswordManager(app) + if self.password_manager: + self.password_manager.init_app(app) + else: + self.password_manager = PasswordManager(app) # Set default EmailAdapter if self.USER_ENABLE_EMAIL: - from .email_adapters.smtp_email_adapter import SMTPEmailAdapter self.email_adapter = SMTPEmailAdapter(app) - - # Setup EmailManager - if self.USER_ENABLE_EMAIL: self.email_manager = EmailManager(app) # Setup TokenManager - self.token_manager = TokenManager(app) + if self.token_manager: + self.token_manager.init_app(app) + else: + self.token_manager = TokenManager(app) # Allow developers to customize UserManager self.customize(app) @@ -205,9 +245,8 @@ def call_or_get(function_or_property): # Configure a list of URLs to route to their corresponding view method. self._add_url_routes(app) - def customize(self, app): - """ Override this method to customize properties. + """Override this method to customize properties. Example::