diff --git a/flask_security/__init__.py b/flask_security/__init__.py index f42cc2c..9c3b22d 100644 --- a/flask_security/__init__.py +++ b/flask_security/__init__.py @@ -11,18 +11,16 @@ """ from functools import wraps -from importlib import import_module -from flask import current_app, Blueprint, flash, redirect, request, \ - session, url_for +from flask import current_app, Blueprint, redirect, request from flask.ext.login import AnonymousUser as AnonymousUserBase, \ - UserMixin as BaseUserMixin, LoginManager, login_required, \ - current_user, login_url + UserMixin as BaseUserMixin, LoginManager, login_required, \ + current_user, login_url from flask.ext.principal import Principal, RoleNeed, UserNeed, \ - Permission, identity_loaded + Permission, identity_loaded from flask.ext.wtf import Form, TextField, PasswordField, SubmitField, \ - HiddenField, Required, BooleanField -from flask.ext.security import views + HiddenField, Required, BooleanField +from flask.ext.security import views, exceptions, utils from passlib.context import CryptContext from werkzeug.datastructures import ImmutableList @@ -45,51 +43,6 @@ _default_config = { } -class BadCredentialsError(Exception): - """Raised when an authentication attempt fails due to an error with the - provided credentials. - """ - - -class AuthenticationError(Exception): - """Raised when an authentication attempt fails due to invalid configuration - or an unknown reason. - """ - - -class UserNotFoundError(Exception): - """Raised by a user datastore when there is an attempt to find a user by - their identifier, often username or email, and the user is not found. - """ - - -class RoleNotFoundError(Exception): - """Raised by a user datastore when there is an attempt to find a role and - the role cannot be found. - """ - - -class UserIdNotFoundError(Exception): - """Raised by a user datastore when there is an attempt to find a user by - ID and the user is not found. - """ - - -class UserDatastoreError(Exception): - """Raised when a user datastore experiences an unexpected error - """ - - -class UserCreationError(Exception): - """Raised when an error occurs when creating a user - """ - - -class RoleCreationError(Exception): - """Raised when an error occurs when creating a role - """ - - def roles_required(*roles): """View decorator which specifies that a user must have all the specified roles. Example:: @@ -154,7 +107,7 @@ def roles_accepted(*roles): 'role. Accepted: %s Provided: %s' % ([r for r in roles], [r.name for r in current_user.roles])) - _do_flash('You do not have permission to view this resource', + utils.do_flash('You do not have permission to view this resource', 'error') return redirect(request.referrer or '/') return decorated_view @@ -247,12 +200,12 @@ class Security(object): login_manager = LoginManager() login_manager.anonymous_user = AnonymousUser - login_manager.login_view = _config_value(app, 'LOGIN_VIEW') + login_manager.login_view = utils.config_value(app, 'LOGIN_VIEW') login_manager.setup_app(app) - Provider = _get_class_from_string(app, 'AUTH_PROVIDER') - Form = _get_class_from_string(app, 'LOGIN_FORM') - pw_hash = _config_value(app, 'PASSWORD_HASH') + Provider = utils.get_class_from_string(app, 'AUTH_PROVIDER') + Form = utils.get_class_from_string(app, 'LOGIN_FORM') + pw_hash = utils.config_value(app, 'PASSWORD_HASH') self.login_manager = login_manager self.pwd_context = CryptContext(schemes=[pw_hash], default=pw_hash) @@ -260,12 +213,12 @@ class Security(object): self.principal = Principal(app) self.datastore = datastore self.form_class = Form - self.auth_url = _config_value(app, 'AUTH_URL') - self.logout_url = _config_value(app, 'LOGOUT_URL') - self.reset_url = _config_value(app, 'RESET_URL') - self.post_login_view = _config_value(app, 'POST_LOGIN_VIEW') - self.post_logout_view = _config_value(app, 'POST_LOGOUT_VIEW') - self.reset_password_within = _config_value(app, 'RESET_PASSWORD_WITHIN') + self.auth_url = utils.config_value(app, 'AUTH_URL') + self.logout_url = utils.config_value(app, 'LOGOUT_URL') + self.reset_url = utils.config_value(app, 'RESET_URL') + self.post_login_view = utils.config_value(app, 'POST_LOGIN_VIEW') + self.post_logout_view = utils.config_value(app, 'POST_LOGOUT_VIEW') + self.reset_password_within = utils.config_value(app, 'RESET_PASSWORD_WITHIN') identity_loaded.connect_via(app)(on_identity_loaded) @@ -285,7 +238,8 @@ class Security(object): methods=['POST'], endpoint='reset')(views.reset) - app.register_blueprint(bp, url_prefix=_config_value(app, 'URL_PREFIX')) + app.register_blueprint(bp, + url_prefix=utils.config_value(app, 'URL_PREFIX')) app.security = self @@ -329,9 +283,9 @@ class AuthenticationProvider(object): """ if not form.validate(): if form.username.errors: - raise BadCredentialsError(form.username.errors[0]) + raise exceptions.BadCredentialsError(form.username.errors[0]) if form.password.errors: - raise BadCredentialsError(form.password.errors[0]) + raise exceptions.BadCredentialsError(form.password.errors[0]) return self.do_authenticate(form.username.data, form.password.data) @@ -347,8 +301,8 @@ class AuthenticationProvider(object): user = current_app.security.datastore.find_user(user_identifier) except AttributeError, e: self.auth_error("Could not find user datastore: %s" % e) - except UserNotFoundError, e: - raise BadCredentialsError("Specified user does not exist") + except exceptions.UserNotFoundError, e: + raise exceptions.BadCredentialsError("Specified user does not exist") except Exception, e: self.auth_error('Unexpected authentication error: %s' % e) @@ -357,55 +311,11 @@ class AuthenticationProvider(object): return user # bad match - raise BadCredentialsError("Password does not match") + raise exceptions.BadCredentialsError("Password does not match") def auth_error(self, msg): """Sends an error log message and raises an authentication error. :param msg: An authentication error message""" current_app.logger.error(msg) - raise AuthenticationError(msg) - - -def _do_flash(message, category): - if _config_value(current_app, 'FLASH_MESSAGES'): - flash(message, category) - - -def _get_class_from_string(app, key): - """Get a reference to a class by its configuration key name.""" - cv = _config_value(app, key).split('::') - cm = import_module(cv[0]) - return getattr(cm, cv[1]) - - -def get_url(endpoint_or_url): - """Returns a URL if a valid endpoint is found. Otherwise, returns the - provided value.""" - try: - return url_for(endpoint_or_url) - except: - return endpoint_or_url - - -def _get_post_login_redirect(): - """Returns the URL to redirect to after a user logs in successfully""" - return (get_url(request.args.get('next')) or - get_url(request.form.get('next')) or - _find_redirect('SECURITY_POST_LOGIN_VIEW')) - - -def _find_redirect(key): - """Returns the URL to redirect to after a user logs in successfully""" - result = (get_url(session.pop(key.lower(), None)) or - get_url(current_app.config[key.upper()] or None) or '/') - - try: - del session[key.lower()] - except: - pass - return result - - -def _config_value(app, key, default=None): - return app.config.get('SECURITY_' + key.upper(), default) + raise exceptions.AuthenticationError(msg) diff --git a/flask_security/datastore.py b/flask_security/datastore.py index 4b1a17b..d80ac32 100644 --- a/flask_security/datastore.py +++ b/flask_security/datastore.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ flask.ext.security.datastore - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This module contains an user datastore classes. @@ -10,7 +10,7 @@ """ from flask import current_app -from flask.ext import security +from flask.ext.security import exceptions class UserDatastore(object): @@ -84,7 +84,7 @@ class UserDatastore(object): kwargs[key] = kwargs.get(key, None) if kwargs['name'] is None: - raise security.RoleCreationError("Missing name argument") + raise exceptions.RoleCreationError("Missing name argument") return kwargs @@ -95,11 +95,11 @@ class UserDatastore(object): kwargs.setdefault('active', True) if username is None and email is None: - raise security.UserCreationError( + raise exceptions.UserCreationError( 'Missing username and/or email arguments') if password is None: - raise security.UserCreationError('Missing password argument') + raise exceptions.UserCreationError('Missing password argument') roles = kwargs.get('roles', []) @@ -124,7 +124,7 @@ class UserDatastore(object): user = self._do_with_id(id) if user: return user - raise security.UserIdNotFoundError() + raise exceptions.UserIdNotFoundError() def find_user(self, user): """Returns a user based on the specified identifier. @@ -134,7 +134,7 @@ class UserDatastore(object): user = self._do_find_user(user) if user: return user - raise security.UserNotFoundError() + raise exceptions.UserNotFoundError() def find_role(self, role): """Returns a role based on its name. @@ -144,7 +144,7 @@ class UserDatastore(object): role = self._do_find_role(role) if role: return role - raise security.RoleNotFoundError() + raise exceptions.RoleNotFoundError() def create_role(self, **kwargs): """Creates and returns a new role. diff --git a/flask_security/exceptions.py b/flask_security/exceptions.py new file mode 100644 index 0000000..f15d4ea --- /dev/null +++ b/flask_security/exceptions.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" + flask.ext.security.exceptions + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Flask-Security exceptions module + + :copyright: (c) 2012 by Matt Wright. + :license: MIT, see LICENSE for more details. +""" + + +class BadCredentialsError(Exception): + """Raised when an authentication attempt fails due to an error with the + provided credentials. + """ + + +class AuthenticationError(Exception): + """Raised when an authentication attempt fails due to invalid configuration + or an unknown reason. + """ + + +class UserNotFoundError(Exception): + """Raised by a user datastore when there is an attempt to find a user by + their identifier, often username or email, and the user is not found. + """ + + +class RoleNotFoundError(Exception): + """Raised by a user datastore when there is an attempt to find a role and + the role cannot be found. + """ + + +class UserIdNotFoundError(Exception): + """Raised by a user datastore when there is an attempt to find a user by + ID and the user is not found. + """ + + +class UserDatastoreError(Exception): + """Raised when a user datastore experiences an unexpected error + """ + + +class UserCreationError(Exception): + """Raised when an error occurs when creating a user + """ + + +class RoleCreationError(Exception): + """Raised when an error occurs when creating a role + """ diff --git a/flask_security/utils.py b/flask_security/utils.py new file mode 100644 index 0000000..dc754bb --- /dev/null +++ b/flask_security/utils.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" + flask.ext.security.utils + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Flask-Security utils module + + :copyright: (c) 2012 by Matt Wright. + :license: MIT, see LICENSE for more details. +""" + +from importlib import import_module + +from flask import url_for, flash, current_app, request, session + + +def do_flash(message, category): + if config_value(current_app, 'FLASH_MESSAGES'): + flash(message, category) + + +def get_class_from_string(app, key): + """Get a reference to a class by its configuration key name.""" + cv = config_value(app, key).split('::') + cm = import_module(cv[0]) + return getattr(cm, cv[1]) + + +def get_url(endpoint_or_url): + """Returns a URL if a valid endpoint is found. Otherwise, returns the + provided value.""" + try: + return url_for(endpoint_or_url) + except: + return endpoint_or_url + + +def get_post_login_redirect(): + """Returns the URL to redirect to after a user logs in successfully""" + return (get_url(request.args.get('next')) or + get_url(request.form.get('next')) or + find_redirect('SECURITY_POST_LOGIN_VIEW')) + + +def find_redirect(key): + """Returns the URL to redirect to after a user logs in successfully""" + result = (get_url(session.pop(key.lower(), None)) or + get_url(current_app.config[key.upper()] or None) or '/') + + try: + del session[key.lower()] + except: + pass + return result + + +def config_value(app, key, default=None): + return app.config.get('SECURITY_' + key.upper(), default) diff --git a/flask_security/views.py b/flask_security/views.py index 74c7ffb..1c6632d 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -1,10 +1,18 @@ +# -*- coding: utf-8 -*- +""" + flask.ext.security.views + ~~~~~~~~~~~~~~~~~~~~~~~~ -from __future__ import absolute_import + Flask-Security views module + + :copyright: (c) 2012 by Matt Wright. + :license: MIT, see LICENSE for more details. +""" from flask import current_app, redirect, request, session from flask.ext.login import login_user, logout_user from flask.ext.principal import Identity, AnonymousIdentity, identity_changed -from flask.ext import security +from flask.ext.security import exceptions, utils def authenticate(): @@ -13,18 +21,18 @@ def authenticate(): user = current_app.security.auth_provider.authenticate(form) if login_user(user, remember=form.remember.data): - redirect_url = security._get_post_login_redirect() + redirect_url = utils.get_post_login_redirect() identity_changed.send(current_app._get_current_object(), identity=Identity(user.id)) current_app.logger.debug('User %s logged in. Redirecting to: ' '%s' % (user, redirect_url)) return redirect(redirect_url) - raise security.BadCredentialsError('Inactive user') + raise exceptions.BadCredentialsError('Inactive user') - except security.BadCredentialsError, e: + except exceptions.BadCredentialsError, e: message = '%s' % e - security._do_flash(message, 'error') + utils.do_flash(message, 'error') redirect_url = request.referrer or \ current_app.security.login_manager.login_view current_app.logger.error('Unsuccessful authentication attempt: %s. ' @@ -40,7 +48,7 @@ def logout(): identity=AnonymousIdentity()) logout_user() - redirect_url = security._find_redirect('SECURITY_POST_LOGOUT_VIEW') + redirect_url = utils.find_redirect('SECURITY_POST_LOGOUT_VIEW') current_app.logger.debug('User logged out. Redirect to: %s' % redirect_url) return redirect(redirect_url)