From 0ddbcdca06435d83188c5f8ba16c0c6f72940671 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Tue, 12 Jun 2012 12:28:59 -0400 Subject: [PATCH] PEP 8 --- flask_security/__init__.py | 84 +++++++++-------- flask_security/datastore/__init__.py | 116 ++++++++++++------------ flask_security/datastore/mongoengine.py | 42 +++++---- flask_security/datastore/sqlalchemy.py | 48 +++++----- flask_security/script.py | 38 ++++---- tests/unit_tests.py | 22 +++-- 6 files changed, 187 insertions(+), 163 deletions(-) diff --git a/flask_security/__init__.py b/flask_security/__init__.py index a38228b..b0be50d 100644 --- a/flask_security/__init__.py +++ b/flask_security/__init__.py @@ -10,46 +10,43 @@ :license: MIT, see LICENSE for more details. """ -import sys - -from datetime import datetime -from types import StringType - -from flask import (current_app, Blueprint, flash, redirect, request, - session, _request_ctx_stack, url_for, abort, g) - -from flask.ext.login import (AnonymousUser as AnonymousUserBase, - UserMixin as BaseUserMixin, LoginManager, login_required, login_user, - logout_user, current_user, user_logged_in, user_logged_out, - login_url) - -from flask.ext.principal import (Identity, Principal, RoleNeed, UserNeed, - Permission, AnonymousIdentity, identity_changed, identity_loaded) - -from flask.ext.wtf import (Form, TextField, PasswordField, SubmitField, - HiddenField, Required, ValidationError, BooleanField, Email) - from functools import wraps + +from flask import current_app, Blueprint, flash, redirect, request, \ + session, url_for + +from flask.ext.login import AnonymousUser as AnonymousUserBase, \ + UserMixin as BaseUserMixin, LoginManager, login_required, login_user, \ + logout_user, current_user, login_url + +from flask.ext.principal import Identity, Principal, RoleNeed, UserNeed, \ + Permission, AnonymousIdentity, identity_changed, identity_loaded + +from flask.ext.wtf import Form, TextField, PasswordField, SubmitField, \ + HiddenField, Required, BooleanField + from passlib.context import CryptContext -from werkzeug.utils import import_string + from werkzeug.local import LocalProxy + class User(object): """User model""" + class Role(object): """Role model""" -URL_PREFIX_KEY = 'SECURITY_URL_PREFIX' -AUTH_PROVIDER_KEY = 'SECURITY_AUTH_PROVIDER' -PASSWORD_HASH_KEY = 'SECURITY_PASSWORD_HASH' +URL_PREFIX_KEY = 'SECURITY_URL_PREFIX' +AUTH_PROVIDER_KEY = 'SECURITY_AUTH_PROVIDER' +PASSWORD_HASH_KEY = 'SECURITY_PASSWORD_HASH' USER_DATASTORE_KEY = 'SECURITY_USER_DATASTORE' -LOGIN_FORM_KEY = 'SECURITY_LOGIN_FORM' -AUTH_URL_KEY = 'SECURITY_AUTH_URL' -LOGOUT_URL_KEY = 'SECURITY_LOGOUT_URL' -LOGIN_VIEW_KEY = 'SECURITY_LOGIN_VIEW' -POST_LOGIN_KEY = 'SECURITY_POST_LOGIN' -POST_LOGOUT_KEY = 'SECURITY_POST_LOGOUT' +LOGIN_FORM_KEY = 'SECURITY_LOGIN_FORM' +AUTH_URL_KEY = 'SECURITY_AUTH_URL' +LOGOUT_URL_KEY = 'SECURITY_LOGOUT_URL' +LOGIN_VIEW_KEY = 'SECURITY_LOGIN_VIEW' +POST_LOGIN_KEY = 'SECURITY_POST_LOGIN' +POST_LOGOUT_KEY = 'SECURITY_POST_LOGOUT' FLASH_MESSAGES_KEY = 'SECURITY_FLASH_MESSAGES' DEBUG_LOGIN = 'User %s logged in. Redirecting to: %s' @@ -79,34 +76,41 @@ class BadCredentialsError(Exception): 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 """ @@ -128,6 +132,7 @@ pwd_context = LocalProxy(lambda: current_app.pwd_context) user_datastore = LocalProxy(lambda: getattr(current_app, current_app.config[USER_DATASTORE_KEY])) + def roles_required(*args): """View decorator which specifies that a user must have all the specified roles. Example:: @@ -144,6 +149,7 @@ def roles_required(*args): """ roles = args perm = Permission(*[RoleNeed(role) for role in roles]) + def wrapper(fn): @wraps(fn) def decorated_view(*args, **kwargs): @@ -179,6 +185,7 @@ def roles_accepted(*args): """ roles = args perms = [Permission(RoleNeed(role)) for role in roles] + def wrapper(fn): @wraps(fn) def decorated_view(*args, **kwargs): @@ -202,10 +209,10 @@ def roles_accepted(*args): class RoleMixin(object): """Mixin for `Role` model definitions""" def __eq__(self, other): - return self.name == getattr(other, 'name', None) + return self.name == other or self.name == getattr(other, 'name', None) def __ne__(self, other): - return self.name != getattr(other, 'name', None) + return self.name != other and self.name != getattr(other, 'name', None) def __str__(self): return '' % (self.name, self.description) @@ -222,8 +229,6 @@ class UserMixin(BaseUserMixin): """Returns `True` if the user identifies with the specified role. :param role: A role name or `Role` instance""" - if not isinstance(role, Role): - role = Role(name=role) return role in self.roles def __str__(self): @@ -234,7 +239,7 @@ class UserMixin(BaseUserMixin): class AnonymousUser(AnonymousUserBase): def __init__(self): super(AnonymousUser, self).__init__() - self.roles = [] # TODO: Make this immutable? + self.roles = [] # TODO: Make this immutable? def has_role(self, *args): """Returns `False`""" @@ -257,7 +262,8 @@ class Security(object): :param app: The application. :param datastore: An instance of a user datastore. """ - if app is None or datastore is None: return + if app is None or datastore is None: + return # TODO: change blueprint name blueprint = Blueprint('auth', __name__) @@ -309,6 +315,7 @@ class Security(object): return None auth_url = config[AUTH_URL_KEY] + @blueprint.route(auth_url, methods=['POST'], endpoint='authenticate') def authenticate(): try: @@ -425,6 +432,7 @@ class AuthenticationProvider(object): logger.error(msg) raise AuthenticationError(msg) + def do_flash(message, category): if current_app.config[FLASH_MESSAGES_KEY]: flash(message, category) @@ -434,11 +442,12 @@ def get_class_by_name(clazz): """Get a reference to a class by its string representation.""" parts = clazz.split('.') module = ".".join(parts[:-1]) - m = __import__( module ) + m = __import__(module) for comp in parts[1:]: m = getattr(m, comp) return m + def get_class_from_config(key, config): """Get a reference to a class by its configuration key name.""" try: @@ -448,6 +457,7 @@ def get_class_from_config(key, config): "Could not get class '%s' for Auth setting '%s' >> %s" % (config[key], key, e)) + def get_url(endpoint_or_url): """Returns a URL if a valid endpoint is found. Otherwise, returns the provided value.""" @@ -456,12 +466,14 @@ def get_url(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(POST_LOGIN_KEY)) + 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 diff --git a/flask_security/datastore/__init__.py b/flask_security/datastore/__init__.py index 9210448..c0383be 100644 --- a/flask_security/datastore/__init__.py +++ b/flask_security/datastore/__init__.py @@ -3,7 +3,7 @@ flask.ext.security.datastore ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - This module contains an abstracted user datastore. + This module contains an abstracted user datastore. :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. @@ -13,12 +13,13 @@ from datetime import datetime from flask.ext import security from flask.ext.security import UserCreationError, RoleCreationError, pwd_context + class UserDatastore(object): - """Abstracted user datastore. Always extend this class and implement the - :attr:`get_models`, :attr:`_save_model`, :attr:`_do_with_id`, + """Abstracted user datastore. Always extend this class and implement the + :attr:`get_models`, :attr:`_save_model`, :attr:`_do_with_id`, :attr:`_do_find_user`, and :attr:`_do_find_role` methods. - - :param db: An instance of a configured databse manager from a Flask + + :param db: An instance of a configured databse manager from a Flask extension such as Flask-SQLAlchemy or Flask-MongoEngine :param user_account_mixin: An optional mixin class that specifies additional fields to be added to the user model @@ -26,41 +27,41 @@ class UserDatastore(object): def __init__(self, db, user_account_mixin=None): self.db = db self.user_account_mixin = user_account_mixin or object - + def get_models(self): - """Returns configured `User` and `Role` models for the datastore + """Returns configured `User` and `Role` models for the datastore implementation""" raise NotImplementedError( "User datastore does not implement get_models method") - + def _save_model(self, model, **kwargs): raise NotImplementedError( "User datastore does not implement _save_model method") - + def _do_with_id(self, id): raise NotImplementedError( "User datastore does not implement _do_with_id method") - + def _do_find_user(self): raise NotImplementedError( "User datastore does not implement _do_find_user method") - + def _do_find_role(self): raise NotImplementedError( "User datastore does not implement _do_find_role method") - + def _do_add_role(self, user, role): user, role = self._prepare_role_modify_args(user, role) if role not in user.roles: user.roles.append(role) return user - + def _do_remove_role(self, user, role): user, role = self._prepare_role_modify_args(user, role) if role in user.roles: user.roles.remove(role) return user - + def _do_toggle_active(self, user, active=None): user = self.find_user(user) if active is None: @@ -68,98 +69,101 @@ class UserDatastore(object): elif active != user.active: user.active = active return user - + def _do_deactive_user(self, user): return self._do_toggle_active(user, False) - + def _do_active_user(self, user): return self._do_toggle_active(user, True) - + def _prepare_role_modify_args(self, user, role): if isinstance(user, security.User): user = user.username or user.email - + if isinstance(role, security.Role): role = role.name - + return self.find_user(user), self.find_role(role) - + def _prepare_create_role_args(self, kwargs): for key in ('name', 'description'): kwargs[key] = kwargs.get(key, None) - + if kwargs['name'] is None: raise RoleCreationError("Missing name argument") - + return kwargs - + def _prepare_create_user_args(self, kwargs): username = kwargs.get('username', None) email = kwargs.get('email', None) password = kwargs.get('password', None) - + if username is None and email is None: raise UserCreationError('Missing username and/or email arguments') - + if password is None: raise UserCreationError('Missing password argument') - + roles = kwargs.get('roles', []) - + for i, role in enumerate(roles): rn = role.name if isinstance(role, security.Role) else role # see if the role exists roles[i] = self.find_role(rn) - + kwargs['roles'] = roles - + now = datetime.utcnow() kwargs['created_at'], kwargs['modified_at'] = now, now - + pw = kwargs['password'] if not pwd_context.identify(pw): kwargs['password'] = pwd_context.encrypt(pw) - + return kwargs - + def with_id(self, id): """Returns a user with the specified ID. - + :param id: User ID""" user = self._do_with_id(id) - if user: return user + if user: + return user raise security.UserIdNotFoundError() - + def find_user(self, user): - """Returns a user based on the specified identifier. - + """Returns a user based on the specified identifier. + :param user: User identifier, usually a username or email address """ user = self._do_find_user(user) - if user: return user + if user: + return user raise security.UserNotFoundError() - + def find_role(self, role): """Returns a role based on its name. - + :param role: Role name """ role = self._do_find_role(role) - if role: return role + if role: + return role raise security.RoleNotFoundError() - + def create_role(self, **kwargs): """Creates and returns a new role. - + :param name: Role name :param description: Role description """ role = security.Role(**self._prepare_create_role_args(kwargs)) return self._save_model(role) - + def create_user(self, **kwargs): """Creates and returns a new user. - + :param username: Username :param email: Email address :param password: Unencrypted password @@ -167,35 +171,35 @@ class UserDatastore(object): """ user = security.User(**self._prepare_create_user_args(kwargs)) return self._save_model(user) - + def add_role_to_user(self, user, role): - """Adds a role to a user if the user does not have it already. Returns + """Adds a role to a user if the user does not have it already. Returns the modified user. - + :param user: A User instance or a user identifier :param role: A Role instance or a role name """ return self._save_model(self._do_add_role(user, role)) - + def remove_role_from_user(self, user, role, commit=True): - """Removes a role from a user if the user has the role. Returns the + """Removes a role from a user if the user has the role. Returns the modified user. - + :param user: A User instance or a user identifier :param role: A Role instance or a role name """ return self._save_model(self._do_remove_role(user, role)) - + def deactivate_user(self, user): """Deactivates a user and returns the modified user. - + :param user: A User instance or a user identifier """ return self._save_model(self._do_deactive_user(user)) - + def activate_user(self, user, commit=True): """Activates a user and returns the modified user. - + :param user: A User instance or a user identifier """ - return self._save_model(self._do_active_user(user)) \ No newline at end of file + return self._save_model(self._do_active_user(user)) diff --git a/flask_security/datastore/mongoengine.py b/flask_security/datastore/mongoengine.py index 7e28c39..37bdde5 100644 --- a/flask_security/datastore/mongoengine.py +++ b/flask_security/datastore/mongoengine.py @@ -12,60 +12,62 @@ from flask.ext import security from flask.ext.security import UserMixin, RoleMixin from flask.ext.security.datastore import UserDatastore - + + class MongoEngineUserDatastore(UserDatastore): - """A MongoEngine datastore implementation for Flask-Security. - Example usage:: - + """A MongoEngine datastore implementation for Flask-Security. + Example usage:: + from flask import Flask from flask.ext.mongoengine import MongoEngine from flask.ext.security import Security from flask.ext.security.datastore.mongoengine import 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) Security(app, MongoEngineUserDatastore(db)) """ - + def get_models(self): db = self.db - + class Role(db.Document, RoleMixin): """MongoEngine Role model""" - + name = db.StringField(required=True, unique=True, max_length=80) description = db.StringField(max_length=255) - + class User(db.Document, UserMixin, self.user_account_mixin): """MongoEngine User model""" - + username = db.StringField(unique=True, max_length=255) 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=[]) + roles = db.ListField(db.ReferenceField(Role), default=[]) created_at = db.DateTimeField() modified_at = db.DateTimeField() - + return User, Role - + def _save_model(self, model): model.save() return model - + def _do_with_id(self, id): - try: return security.User.objects.get(id=id) - except: return None - + try: + return security.User.objects.get(id=id) + except: + return None + def _do_find_user(self, user): return security.User.objects(username=user).first() or \ security.User.objects(email=user).first() - + def _do_find_role(self, role): return security.Role.objects(name=role).first() - \ No newline at end of file diff --git a/flask_security/datastore/sqlalchemy.py b/flask_security/datastore/sqlalchemy.py index e239b51..b42dd84 100644 --- a/flask_security/datastore/sqlalchemy.py +++ b/flask_security/datastore/sqlalchemy.py @@ -12,45 +12,46 @@ from flask.ext import security from flask.ext.security import UserMixin, RoleMixin from flask.ext.security.datastore import UserDatastore - + + class SQLAlchemyUserDatastore(UserDatastore): - """A SQLAlchemy datastore implementation for Flask-Security. - Example usage:: - + """A SQLAlchemy datastore implementation for Flask-Security. + Example usage:: + from flask import Flask from flask.ext.security import Security from flask.ext.security.datastore.sqlalchemy import 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) Security(app, SQLAlchemyUserDatastore(db)) """ - + def get_models(self): db = self.db - + 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): """SQLAlchemy Role model""" - + id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255)) - + def __init__(self, name=None, description=None): self.name = name self.description = description - + class User(db.Model, UserMixin, self.user_account_mixin): """SQLAlchemy User model""" - + id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(255), unique=True) email = db.Column(db.String(255), unique=True) @@ -58,12 +59,12 @@ class SQLAlchemyUserDatastore(UserDatastore): active = db.Column(db.Boolean()) created_at = db.Column(db.DateTime()) modified_at = db.Column(db.DateTime()) - - roles= db.relationship('Role', secondary=roles_users, + + roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) - - def __init__(self, username=None, email=None, password=None, - active=True, roles=None, + + def __init__(self, username=None, email=None, password=None, + active=True, roles=None, created_at=None, modified_at=None): self.username = username self.email = email @@ -72,21 +73,20 @@ class SQLAlchemyUserDatastore(UserDatastore): self.roles = roles or [] self.created_at = created_at self.modified_at = modified_at - + return User, Role - + def _save_model(self, model): self.db.session.add(model) self.db.session.commit() return model - + def _do_with_id(self, id): return security.User.query.get(id) - + def _do_find_user(self, user): return security.User.query.filter_by(username=user).first() or \ security.User.query.filter_by(email=user).first() - + def _do_find_role(self, role): return security.Role.query.filter_by(name=role).first() - \ No newline at end of file diff --git a/flask_security/script.py b/flask_security/script.py index a320766..521ceb9 100644 --- a/flask_security/script.py +++ b/flask_security/script.py @@ -11,9 +11,11 @@ import json import re + from flask.ext.script import Command, Option -from flask.ext.security import (UserCreationError, UserNotFoundError, - RoleNotFoundError, user_datastore) + +from flask.ext.security import user_datastore + def pprint(obj): print json.dumps(obj, sort_keys=True, indent=4) @@ -21,7 +23,7 @@ def pprint(obj): class CreateUserCommand(Command): """Create a user""" - + option_list = ( Option('-u', '--username', dest='username', default=None), Option('-e', '--email', dest='email', default=None), @@ -33,14 +35,14 @@ class CreateUserCommand(Command): def run(self, **kwargs): # sanitize active input ai = re.sub(r'\s', '', str(kwargs['active'])) - kwargs['active'] = ai.lower() in ['', 'y','yes', '1', 'active'] - + kwargs['active'] = ai.lower() in ['', 'y', 'yes', '1', 'active'] + # sanitize role input a bit ri = re.sub(r'\s', '', kwargs['roles']) kwargs['roles'] = [] if ri == '' else ri.split(',') - + user_datastore.create_user(**kwargs) - + print 'User created successfully.' kwargs['password'] = '****' pprint(kwargs) @@ -55,11 +57,11 @@ class CreateRoleCommand(Command): ) def run(self, **kwargs): - role = user_datastore.create_role(**kwargs) + user_datastore.create_role(**kwargs) print 'Role "%(name)s" created successfully.' % kwargs -class _RoleCommand(Command): +class _RoleCommand(Command): option_list = ( Option('-u', '--user', dest='user_identifier'), Option('-r', '--role', dest='role_name'), @@ -68,7 +70,7 @@ class _RoleCommand(Command): class AddRoleCommand(_RoleCommand): """Add a role to a user""" - + def run(self, user_identifier, role_name): user_datastore.add_role_to_user(user_identifier, role_name) print "Role '%s' added to user '%s' successfully" % (role_name, user_identifier) @@ -76,27 +78,29 @@ class AddRoleCommand(_RoleCommand): class RemoveRoleCommand(_RoleCommand): """Add a role to a user""" - + def run(self, user_identifier, role_name): user_datastore.remove_role_from_user(user_identifier, role_name) print "Role '%s' removed from user '%s' successfully" % (role_name, user_identifier) - + class _ToggleActiveCommand(Command): option_list = ( Option('-u', '--user', dest='user_identifier'), ) - + + class DeactivateUserCommand(_ToggleActiveCommand): """Deactive a user""" - + def run(self, user_identifier): user_datastore.deactivate_user(user_identifier) print "User '%s' has been deactivated" % user_identifier - + + class ActivateUserCommand(_ToggleActiveCommand): """Deactive a user""" - + def run(self, user_identifier): user_datastore.activate_user(user_identifier) - print "User '%s' has been activated" % user_identifier \ No newline at end of file + print "User '%s' has been activated" % user_identifier diff --git a/tests/unit_tests.py b/tests/unit_tests.py index ece4c8e..3c63799 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -2,6 +2,7 @@ import unittest import flask_security from flask_security import RoleMixin, UserMixin, AnonymousUser + class Role(RoleMixin): def __init__(self, name, description=None): self.name = name @@ -13,32 +14,33 @@ class User(UserMixin): self.username = username self.email = email self.roles = roles - + # set the models or we'll get errors flask_security.User = User flask_security.Role = Role - + admin = Role('admin') admin2 = Role('admin') editor = Role('editor') user = User('matt', 'matt@lp.com', [admin, editor]) + class SecurityEntityTests(unittest.TestCase): - - def test_role_mixin_equal(self): + + def test_role_mixin_equal(self): self.assertEqual(admin, admin2) - - def test_role_mixin_not_equal(self): + + def test_role_mixin_not_equal(self): self.assertNotEqual(admin, editor) - + def test_user_mixin_has_role_with_string(self): self.assertTrue(user.has_role('admin')) - + def test_user_mixin_has_role_with_role_obj(self): self.assertTrue(user.has_role(Role('admin'))) - + def test_anonymous_user_has_no_roles(self): au = AnonymousUser() self.assertEqual(0, len(au.roles)) - self.assertFalse(au.has_role('admin')) \ No newline at end of file + self.assertFalse(au.has_role('admin'))