Flask-Security-1.7.5/0000755000076500000240000000000012627667302014474 5ustar mattstaff00000000000000Flask-Security-1.7.5/flask_security/0000755000076500000240000000000012627667302017523 5ustar mattstaff00000000000000Flask-Security-1.7.5/flask_security/__init__.py0000644000076500000240000000170512627667276021651 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security ~~~~~~~~~~~~~~ Flask-Security is a Flask extension that aims to add quick and simple security via Flask-Login, Flask-Principal, Flask-WTF, and passlib. :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore from .decorators import auth_token_required, http_auth_required, \ login_required, roles_accepted, roles_required, auth_required from .forms import ForgotPasswordForm, LoginForm, RegisterForm, \ ResetPasswordForm, PasswordlessLoginForm, ConfirmRegisterForm from .signals import confirm_instructions_sent, password_reset, \ reset_password_instructions_sent, user_confirmed, user_registered from .utils import login_user, logout_user, url_for_security __version__ = '1.7.5' Flask-Security-1.7.5/flask_security/changeable.py0000644000076500000240000000247512627667222022157 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.changeable ~~~~~~~~~~~~~~~~~~~~~~~~~ Flask-Security recoverable module :copyright: (c) 2012 by Matt Wright. :author: Eskil Heyn Olsen :license: MIT, see LICENSE for more details. """ from flask import current_app as app from werkzeug.local import LocalProxy from .signals import password_changed from .utils import send_mail, encrypt_password, config_value # Convenient references _security = LocalProxy(lambda: app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) def send_password_changed_notice(user): """Sends the password changed notice email for the specified user. :param user: The user to send the notice to """ if config_value('SEND_PASSWORD_CHANGE_EMAIL'): subject = config_value('EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE') send_mail(subject, user.email, 'change_notice', user=user) def change_user_password(user, password): """Change the specified user's password :param user: The user to change_password :param password: The unencrypted new password """ user.password = encrypt_password(password) _datastore.put(user) send_password_changed_notice(user) password_changed.send(app._get_current_object(), user=user._get_current_object()) Flask-Security-1.7.5/flask_security/confirmable.py0000644000076500000240000000463012627667222022362 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.confirmable ~~~~~~~~~~~~~~~~~~~~~~~~~~ Flask-Security confirmable module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from datetime import datetime from flask import current_app as app from werkzeug.local import LocalProxy from .utils import send_mail, md5, url_for_security, get_token_status,\ config_value from .signals import user_confirmed, confirm_instructions_sent # Convenient references _security = LocalProxy(lambda: app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) def generate_confirmation_link(user): token = generate_confirmation_token(user) return url_for_security('confirm_email', token=token, _external=True), token def send_confirmation_instructions(user): """Sends the confirmation instructions email for the specified user. :param user: The user to send the instructions to :param token: The confirmation token """ confirmation_link, token = generate_confirmation_link(user) send_mail(config_value('EMAIL_SUBJECT_CONFIRM'), user.email, 'confirmation_instructions', user=user, confirmation_link=confirmation_link) confirm_instructions_sent.send(app._get_current_object(), user=user) return token def generate_confirmation_token(user): """Generates a unique confirmation token for the specified user. :param user: The user to work with """ data = [str(user.id), md5(user.email)] return _security.confirm_serializer.dumps(data) def requires_confirmation(user): """Returns `True` if the user requires confirmation.""" return (_security.confirmable and not _security.login_without_confirmation and user.confirmed_at is None) def confirm_email_token_status(token): """Returns the expired status, invalid status, and user of a confirmation token. For example:: expired, invalid, user = confirm_email_token_status('...') :param token: The confirmation token """ return get_token_status(token, 'confirm', 'CONFIRM_EMAIL') def confirm_user(user): """Confirms the specified user :param user: The user to confirm """ if user.confirmed_at is not None: return False user.confirmed_at = datetime.utcnow() _datastore.put(user) user_confirmed.send(app._get_current_object(), user=user) return True Flask-Security-1.7.5/flask_security/core.py0000644000076500000240000003664612627667222021045 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.core ~~~~~~~~~~~~~~~~~~~ Flask-Security core module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from flask import current_app, render_template from flask_login import AnonymousUserMixin, UserMixin as BaseUserMixin, \ LoginManager, current_user from flask_principal import Principal, RoleNeed, UserNeed, Identity, \ identity_loaded from itsdangerous import URLSafeTimedSerializer from passlib.context import CryptContext from werkzeug.datastructures import ImmutableList from werkzeug.local import LocalProxy from werkzeug.security import safe_str_cmp from .utils import config_value as cv, get_config, md5, url_for_security, string_types from .views import create_blueprint from .forms import LoginForm, ConfirmRegisterForm, RegisterForm, \ ForgotPasswordForm, ChangePasswordForm, ResetPasswordForm, \ SendConfirmationForm, PasswordlessLoginForm # Convenient references _security = LocalProxy(lambda: current_app.extensions['security']) #: Default Flask-Security configuration _default_config = { 'BLUEPRINT_NAME': 'security', 'URL_PREFIX': None, 'SUBDOMAIN': None, 'FLASH_MESSAGES': True, 'PASSWORD_HASH': 'plaintext', 'PASSWORD_SALT': None, 'LOGIN_URL': '/login', 'LOGOUT_URL': '/logout', 'REGISTER_URL': '/register', 'RESET_URL': '/reset', 'CHANGE_URL': '/change', 'CONFIRM_URL': '/confirm', 'POST_LOGIN_VIEW': '/', 'POST_LOGOUT_VIEW': '/', 'CONFIRM_ERROR_VIEW': None, 'POST_REGISTER_VIEW': None, 'POST_CONFIRM_VIEW': None, 'POST_RESET_VIEW': None, 'POST_CHANGE_VIEW': None, 'UNAUTHORIZED_VIEW': None, 'FORGOT_PASSWORD_TEMPLATE': 'security/forgot_password.html', 'LOGIN_USER_TEMPLATE': 'security/login_user.html', 'REGISTER_USER_TEMPLATE': 'security/register_user.html', 'RESET_PASSWORD_TEMPLATE': 'security/reset_password.html', 'CHANGE_PASSWORD_TEMPLATE': 'security/change_password.html', 'SEND_CONFIRMATION_TEMPLATE': 'security/send_confirmation.html', 'SEND_LOGIN_TEMPLATE': 'security/send_login.html', 'CONFIRMABLE': False, 'REGISTERABLE': False, 'RECOVERABLE': False, 'TRACKABLE': False, 'PASSWORDLESS': False, 'CHANGEABLE': False, 'SEND_REGISTER_EMAIL': True, 'SEND_PASSWORD_CHANGE_EMAIL': True, 'SEND_PASSWORD_RESET_NOTICE_EMAIL': True, 'LOGIN_WITHIN': '1 days', 'CONFIRM_EMAIL_WITHIN': '5 days', 'RESET_PASSWORD_WITHIN': '5 days', 'LOGIN_WITHOUT_CONFIRMATION': False, 'EMAIL_SENDER': 'no-reply@localhost', 'TOKEN_AUTHENTICATION_KEY': 'auth_token', 'TOKEN_AUTHENTICATION_HEADER': 'Authentication-Token', 'TOKEN_MAX_AGE': None, 'CONFIRM_SALT': 'confirm-salt', 'RESET_SALT': 'reset-salt', 'LOGIN_SALT': 'login-salt', 'CHANGE_SALT': 'change-salt', 'REMEMBER_SALT': 'remember-salt', 'DEFAULT_REMEMBER_ME': False, 'DEFAULT_HTTP_AUTH_REALM': 'Login Required', 'EMAIL_SUBJECT_REGISTER': 'Welcome', 'EMAIL_SUBJECT_CONFIRM': 'Please confirm your email', 'EMAIL_SUBJECT_PASSWORDLESS': 'Login instructions', 'EMAIL_SUBJECT_PASSWORD_NOTICE': 'Your password has been reset', 'EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE': 'Your password has been changed', 'EMAIL_SUBJECT_PASSWORD_RESET': 'Password reset instructions', 'USER_IDENTITY_ATTRIBUTES': ['email'], 'PASSWORD_SCHEMES': [ 'bcrypt', 'des_crypt', 'pbkdf2_sha256', 'pbkdf2_sha512', 'sha256_crypt', 'sha512_crypt', # And always last one... 'plaintext' ], 'DEPRECATED_PASSWORD_SCHEMES': ['auto'] } #: Default Flask-Security messages _default_messages = { 'UNAUTHORIZED': ( 'You do not have permission to view this resource.', 'error'), 'CONFIRM_REGISTRATION': ( 'Thank you. Confirmation instructions have been sent to %(email)s.', 'success'), 'EMAIL_CONFIRMED': ( 'Thank you. Your email has been confirmed.', 'success'), 'ALREADY_CONFIRMED': ( 'Your email has already been confirmed.', 'info'), 'INVALID_CONFIRMATION_TOKEN': ( 'Invalid confirmation token.', 'error'), 'EMAIL_ALREADY_ASSOCIATED': ( '%(email)s is already associated with an account.', 'error'), 'PASSWORD_MISMATCH': ( 'Password does not match', 'error'), 'RETYPE_PASSWORD_MISMATCH': ( 'Passwords do not match', 'error'), 'INVALID_REDIRECT': ( 'Redirections outside the domain are forbidden', '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'), 'CONFIRMATION_REQUIRED': ( 'Email requires confirmation.', 'error'), 'CONFIRMATION_REQUEST': ( 'Confirmation instructions have been sent to %(email)s.', 'info'), 'CONFIRMATION_EXPIRED': ( 'You did not confirm your email within %(within)s. New instructions to confirm your email ' 'have been sent to %(email)s.', 'error'), 'LOGIN_EXPIRED': ( 'You did not login within %(within)s. New instructions to login have been sent to ' '%(email)s.', 'error'), 'LOGIN_EMAIL_SENT': ( 'Instructions to login have been sent to %(email)s.', 'success'), 'INVALID_LOGIN_TOKEN': ( 'Invalid login token.', 'error'), 'DISABLED_ACCOUNT': ( 'Account is disabled.', 'error'), 'EMAIL_NOT_PROVIDED': ( 'Email not provided', 'error'), 'INVALID_EMAIL_ADDRESS': ( 'Invalid email address', 'error'), 'PASSWORD_NOT_PROVIDED': ( 'Password not provided', 'error'), 'PASSWORD_NOT_SET': ( 'No password is set for this user', 'error'), 'PASSWORD_INVALID_LENGTH': ( 'Password must be at least 6 characters', 'error'), 'USER_DOES_NOT_EXIST': ( 'Specified user does not exist', 'error'), 'INVALID_PASSWORD': ( 'Invalid password', 'error'), 'PASSWORDLESS_LOGIN_SUCCESSFUL': ( 'You have successfuly logged in.', 'success'), 'PASSWORD_RESET': ( 'You successfully reset your password and you have been logged in automatically.', 'success'), 'PASSWORD_IS_THE_SAME': ( 'Your new password must be different than your previous password.', 'error'), 'PASSWORD_CHANGE': ( 'You successfully changed your password.', 'success'), 'LOGIN': ( 'Please log in to access this page.', 'info'), 'REFRESH': ( 'Please reauthenticate to access this page.', 'info'), } _default_forms = { 'login_form': LoginForm, 'confirm_register_form': ConfirmRegisterForm, 'register_form': RegisterForm, 'forgot_password_form': ForgotPasswordForm, 'reset_password_form': ResetPasswordForm, 'change_password_form': ChangePasswordForm, 'send_confirmation_form': SendConfirmationForm, 'passwordless_login_form': PasswordlessLoginForm, } def _user_loader(user_id): return _security.datastore.find_user(id=user_id) def _token_loader(token): try: data = _security.remember_token_serializer.loads(token, max_age=_security.token_max_age) user = _security.datastore.find_user(id=data[0]) if user and safe_str_cmp(md5(user.password), data[1]): return user except: pass return _security.login_manager.anonymous_user() def _identity_loader(): if not isinstance(current_user._get_current_object(), AnonymousUserMixin): identity = Identity(current_user.id) return identity def _on_identity_loaded(sender, identity): if hasattr(current_user, 'id'): identity.provides.add(UserNeed(current_user.id)) for role in current_user.roles: identity.provides.add(RoleNeed(role.name)) identity.user = current_user def _get_login_manager(app, anonymous_user): lm = LoginManager() lm.anonymous_user = anonymous_user or AnonymousUser lm.login_view = '%s.login' % cv('BLUEPRINT_NAME', app=app) lm.user_loader(_user_loader) lm.token_loader(_token_loader) if cv('FLASH_MESSAGES', app=app): lm.login_message, lm.login_message_category = cv('MSG_LOGIN', app=app) lm.needs_refresh_message, lm.needs_refresh_message_category = cv('MSG_REFRESH', app=app) else: lm.login_message = None lm.needs_refresh_message = None lm.init_app(app) return lm def _get_principal(app): p = Principal(app, use_sessions=False) p.identity_loader(_identity_loader) return p def _get_pwd_context(app): pw_hash = cv('PASSWORD_HASH', app=app) schemes = cv('PASSWORD_SCHEMES', app=app) deprecated = cv('DEPRECATED_PASSWORD_SCHEMES', app=app) if pw_hash not in schemes: allowed = (', '.join(schemes[:-1]) + ' and ' + schemes[-1]) raise ValueError("Invalid hash scheme %r. Allowed values are %s" % (pw_hash, allowed)) return CryptContext(schemes=schemes, default=pw_hash, deprecated=deprecated) def _get_serializer(app, name): secret_key = app.config.get('SECRET_KEY') salt = app.config.get('SECURITY_%s_SALT' % name.upper()) return URLSafeTimedSerializer(secret_key=secret_key, salt=salt) def _get_state(app, datastore, anonymous_user=None, **kwargs): for key, value in get_config(app).items(): kwargs[key.lower()] = value kwargs.update(dict( app=app, datastore=datastore, login_manager=_get_login_manager(app, anonymous_user), principal=_get_principal(app), pwd_context=_get_pwd_context(app), remember_token_serializer=_get_serializer(app, 'remember'), login_serializer=_get_serializer(app, 'login'), reset_serializer=_get_serializer(app, 'reset'), confirm_serializer=_get_serializer(app, 'confirm'), _context_processors={}, _send_mail_task=None, _unauthorized_callback=None )) for key, value in _default_forms.items(): if key not in kwargs or not kwargs[key]: kwargs[key] = value return _SecurityState(**kwargs) def _context_processor(): return dict(url_for_security=url_for_security, security=_security) class RoleMixin(object): """Mixin for `Role` model definitions""" def __eq__(self, other): return (self.name == other or self.name == getattr(other, 'name', None)) def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(self.name) class UserMixin(BaseUserMixin): """Mixin for `User` model definitions""" @property def is_active(self): """Returns `True` if the user is active.""" return self.active def get_auth_token(self): """Returns the user's authentication token.""" data = [str(self.id), md5(self.password)] return _security.remember_token_serializer.dumps(data) def has_role(self, role): """Returns `True` if the user identifies with the specified role. :param role: A role name or `Role` instance""" if isinstance(role, string_types): return role in (role.name for role in self.roles) else: return role in self.roles class AnonymousUser(AnonymousUserMixin): """AnonymousUser definition""" def __init__(self): self.roles = ImmutableList() def has_role(self, *args): """Returns `False`""" return False class _SecurityState(object): def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key.lower(), value) def _add_ctx_processor(self, endpoint, fn): group = self._context_processors.setdefault(endpoint, []) fn not in group and group.append(fn) def _run_ctx_processor(self, endpoint): rv = {} for g in [None, endpoint]: for fn in self._context_processors.setdefault(g, []): rv.update(fn()) return rv def context_processor(self, fn): self._add_ctx_processor(None, fn) def forgot_password_context_processor(self, fn): self._add_ctx_processor('forgot_password', fn) def login_context_processor(self, fn): self._add_ctx_processor('login', fn) def register_context_processor(self, fn): self._add_ctx_processor('register', fn) def reset_password_context_processor(self, fn): self._add_ctx_processor('reset_password', fn) def change_password_context_processor(self, fn): self._add_ctx_processor('change_password', fn) def send_confirmation_context_processor(self, fn): self._add_ctx_processor('send_confirmation', fn) def send_login_context_processor(self, fn): self._add_ctx_processor('send_login', fn) def mail_context_processor(self, fn): self._add_ctx_processor('mail', fn) def send_mail_task(self, fn): self._send_mail_task = fn def unauthorized_handler(self, fn): self._unauthorized_callback = fn class Security(object): """The :class:`Security` class initializes the Flask-Security extension. :param app: The application. :param datastore: An instance of a user datastore. """ def __init__(self, app=None, datastore=None, **kwargs): self.app = app self.datastore = datastore if app is not None and datastore is not None: self._state = self.init_app(app, datastore, **kwargs) def init_app(self, app, datastore=None, register_blueprint=True, login_form=None, confirm_register_form=None, register_form=None, forgot_password_form=None, reset_password_form=None, change_password_form=None, send_confirmation_form=None, passwordless_login_form=None, anonymous_user=None): """Initializes the Flask-Security extension for the specified application and datastore implentation. :param app: The application. :param datastore: An instance of a user datastore. :param register_blueprint: to register the Security blueprint or not. """ datastore = datastore or self.datastore for key, value in _default_config.items(): app.config.setdefault('SECURITY_' + key, value) for key, value in _default_messages.items(): app.config.setdefault('SECURITY_MSG_' + key, value) identity_loaded.connect_via(app)(_on_identity_loaded) state = _get_state(app, datastore, login_form=login_form, confirm_register_form=confirm_register_form, register_form=register_form, forgot_password_form=forgot_password_form, reset_password_form=reset_password_form, change_password_form=change_password_form, send_confirmation_form=send_confirmation_form, passwordless_login_form=passwordless_login_form, anonymous_user=anonymous_user) if register_blueprint: app.register_blueprint(create_blueprint(state, __name__)) app.context_processor(_context_processor) state.render_template = self.render_template app.extensions['security'] = state return state def render_template(self, *args, **kwargs): return render_template(*args, **kwargs) def __getattr__(self, name): return getattr(self._state, name, None) Flask-Security-1.7.5/flask_security/datastore.py0000644000076500000240000002436612627667222022077 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.datastore ~~~~~~~~~~~~~~~~~~~~~~~~ This module contains an user datastore classes. :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from .utils import get_identity_attributes, string_types class Datastore(object): def __init__(self, db): self.db = db def commit(self): pass def put(self, model): raise NotImplementedError def delete(self, model): raise NotImplementedError class SQLAlchemyDatastore(Datastore): def commit(self): self.db.session.commit() def put(self, model): self.db.session.add(model) return model def delete(self, model): self.db.session.delete(model) class MongoEngineDatastore(Datastore): def put(self, model): model.save() return model def delete(self, model): model.delete() class PeeweeDatastore(Datastore): def put(self, model): model.save() return model def delete(self, model): model.delete_instance() class UserDatastore(object): """Abstracted user datastore. :param user_model: A user model class definition :param role_model: A role model class definition """ def __init__(self, user_model, role_model): self.user_model = user_model self.role_model = role_model def _prepare_role_modify_args(self, user, role): if isinstance(user, string_types): user = self.find_user(email=user) if isinstance(role, string_types): role = self.find_role(role) return user, role def _prepare_create_user_args(self, **kwargs): kwargs.setdefault('active', True) roles = kwargs.get('roles', []) for i, role in enumerate(roles): rn = role.name if isinstance(role, self.role_model) else role # see if the role exists roles[i] = self.find_role(rn) kwargs['roles'] = roles return kwargs def get_user(self, id_or_email): """Returns a user matching the specified ID or email address.""" raise NotImplementedError def find_user(self, *args, **kwargs): """Returns a user matching the provided parameters.""" raise NotImplementedError def find_role(self, *args, **kwargs): """Returns a role matching the provided name.""" raise NotImplementedError def add_role_to_user(self, user, role): """Adds a role to a user. :param user: The user to manipulate :param role: The role to add to the user """ user, role = self._prepare_role_modify_args(user, role) if role not in user.roles: user.roles.append(role) self.put(user) return True return False def remove_role_from_user(self, user, role): """Removes a role from a user. :param user: The user to manipulate :param role: The role to remove from the user """ rv = False user, role = self._prepare_role_modify_args(user, role) if role in user.roles: rv = True user.roles.remove(role) self.put(user) return rv def toggle_active(self, user): """Toggles a user's active status. Always returns True.""" user.active = not user.active return True def deactivate_user(self, user): """Deactivates a specified user. Returns `True` if a change was made. :param user: The user to deactivate """ if user.active: user.active = False return True return False def activate_user(self, user): """Activates a specified user. Returns `True` if a change was made. :param user: The user to activate """ if not user.active: user.active = True return True return False def create_role(self, **kwargs): """Creates and returns a new role from the given parameters.""" role = self.role_model(**kwargs) return self.put(role) def find_or_create_role(self, name, **kwargs): """Returns a role matching the given name or creates it with any additionally provided parameters. """ kwargs["name"] = name return self.find_role(name) or self.create_role(**kwargs) def create_user(self, **kwargs): """Creates and returns a new user from the given parameters.""" kwargs = self._prepare_create_user_args(**kwargs) user = self.user_model(**kwargs) return self.put(user) def delete_user(self, user): """Deletes the specified user. :param user: The user to delete """ self.delete(user) class SQLAlchemyUserDatastore(SQLAlchemyDatastore, UserDatastore): """A SQLAlchemy datastore implementation for Flask-Security that assumes the use of the Flask-SQLAlchemy extension. """ def __init__(self, db, user_model, role_model): SQLAlchemyDatastore.__init__(self, db) UserDatastore.__init__(self, user_model, role_model) def get_user(self, identifier): if self._is_numeric(identifier): return self.user_model.query.get(identifier) for attr in get_identity_attributes(): query = getattr(self.user_model, attr).ilike(identifier) rv = self.user_model.query.filter(query).first() if rv is not None: return rv def _is_numeric(self, value): try: int(value) except (TypeError, ValueError): return False return True def find_user(self, **kwargs): return self.user_model.query.filter_by(**kwargs).first() def find_role(self, role): return self.role_model.query.filter_by(name=role).first() class MongoEngineUserDatastore(MongoEngineDatastore, UserDatastore): """A MongoEngine datastore implementation for Flask-Security that assumes the use of the Flask-MongoEngine extension. """ def __init__(self, db, user_model, role_model): MongoEngineDatastore.__init__(self, db) UserDatastore.__init__(self, user_model, role_model) def get_user(self, identifier): from mongoengine import ValidationError try: return self.user_model.objects(id=identifier).first() except ValidationError: pass for attr in get_identity_attributes(): query_key = '%s__iexact' % attr query = {query_key: identifier} rv = self.user_model.objects(**query).first() if rv is not None: return rv def find_user(self, **kwargs): try: from mongoengine.queryset import Q, QCombination except ImportError: from mongoengine.queryset.visitor import Q, QCombination from mongoengine.errors import ValidationError queries = map(lambda i: Q(**{i[0]: i[1]}), kwargs.items()) query = QCombination(QCombination.AND, queries) try: return self.user_model.objects(query).first() except ValidationError: # pragma: no cover return None def find_role(self, role): return self.role_model.objects(name=role).first() # TODO: Not sure why this was added but tests pass without it # def add_role_to_user(self, user, role): # rv = super(MongoEngineUserDatastore, self).add_role_to_user(user, role) # if rv: # self.put(user) # return rv class PeeweeUserDatastore(PeeweeDatastore, UserDatastore): """A PeeweeD datastore implementation for Flask-Security that assumes the use of the Flask-Peewee extension. :param user_model: A user model class definition :param role_model: A role model class definition :param role_link: A model implementing the many-to-many user-role relation """ def __init__(self, db, user_model, role_model, role_link): PeeweeDatastore.__init__(self, db) UserDatastore.__init__(self, user_model, role_model) self.UserRole = role_link def get_user(self, identifier): try: return self.user_model.get(self.user_model.id == identifier) except ValueError: pass for attr in get_identity_attributes(): column = getattr(self.user_model, attr) try: return self.user_model.get(column ** identifier) except self.user_model.DoesNotExist: pass def find_user(self, **kwargs): try: return self.user_model.filter(**kwargs).get() except self.user_model.DoesNotExist: return None def find_role(self, role): try: return self.role_model.filter(name=role).get() except self.role_model.DoesNotExist: return None def create_user(self, **kwargs): """Creates and returns a new user from the given parameters.""" roles = kwargs.pop('roles', []) user = self.user_model(**self._prepare_create_user_args(**kwargs)) user = self.put(user) for role in roles: self.add_role_to_user(user, role) self.put(user) return user def add_role_to_user(self, user, role): """Adds a role to a user. :param user: The user to manipulate :param role: The role to add to the user """ user, role = self._prepare_role_modify_args(user, role) result = self.UserRole.select() \ .where(self.UserRole.user == user.id, self.UserRole.role == role.id) if result.count(): return False else: self.put(self.UserRole.create(user=user.id, role=role.id)) return True def remove_role_from_user(self, user, role): """Removes a role from a user. :param user: The user to manipulate :param role: The role to remove from the user """ user, role = self._prepare_role_modify_args(user, role) result = self.UserRole.select() \ .where(self.UserRole.user == user, self.UserRole.role == role) if result.count(): query = self.UserRole.delete().where( self.UserRole.user == user, self.UserRole.role == role) query.execute() return True else: return False Flask-Security-1.7.5/flask_security/decorators.py0000644000076500000240000001621212627667222022245 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.decorators ~~~~~~~~~~~~~~~~~~~~~~~~~ Flask-Security decorators module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from collections import namedtuple from functools import wraps from flask import current_app, Response, request, redirect, _request_ctx_stack from flask_login import current_user, login_required # pragma: no flakes from flask_principal import RoleNeed, Permission, Identity, identity_changed from werkzeug.local import LocalProxy from . import utils # Convenient references _security = LocalProxy(lambda: current_app.extensions['security']) _default_unauthorized_html = """

Unauthorized

The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.

""" BasicAuth = namedtuple('BasicAuth', 'username, password') def _get_unauthorized_response(text=None, headers=None): text = text or _default_unauthorized_html headers = headers or {} return Response(text, 401, headers) def _get_unauthorized_view(): cv = utils.get_url(utils.config_value('UNAUTHORIZED_VIEW')) utils.do_flash(*utils.get_message('UNAUTHORIZED')) return redirect(cv or request.referrer or '/') def _check_token(): header_key = _security.token_authentication_header args_key = _security.token_authentication_key header_token = request.headers.get(header_key, None) token = request.args.get(args_key, header_token) if request.get_json(silent=True): if not isinstance(request.json, list): token = request.json.get(args_key, token) user = _security.login_manager.token_callback(token) if user and user.is_authenticated: app = current_app._get_current_object() _request_ctx_stack.top.user = user identity_changed.send(app, identity=Identity(user.id)) return True return False def _check_http_auth(): auth = request.authorization or BasicAuth(username=None, password=None) user = _security.datastore.find_user(email=auth.username) if user and utils.verify_and_update_password(auth.password, user): _security.datastore.commit() app = current_app._get_current_object() _request_ctx_stack.top.user = user identity_changed.send(app, identity=Identity(user.id)) return True return False def http_auth_required(realm): """Decorator that protects endpoints using Basic HTTP authentication. The username should be set to the user's email address. :param realm: optional realm name""" def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if _check_http_auth(): return fn(*args, **kwargs) if _security._unauthorized_callback: return _security._unauthorized_callback() else: r = _security.default_http_auth_realm if callable(realm) else realm h = {'WWW-Authenticate': 'Basic realm="%s"' % r} return _get_unauthorized_response(headers=h) return wrapper if callable(realm): return decorator(realm) return decorator def auth_token_required(fn): """Decorator that protects endpoints using token authentication. The token should be added to the request by the client by using a query string variable with a name equal to the configuration value of `SECURITY_TOKEN_AUTHENTICATION_KEY` or in a request header named that of the configuration value of `SECURITY_TOKEN_AUTHENTICATION_HEADER` """ @wraps(fn) def decorated(*args, **kwargs): if _check_token(): return fn(*args, **kwargs) if _security._unauthorized_callback: return _security._unauthorized_callback() else: return _get_unauthorized_response() return decorated def auth_required(*auth_methods): """ Decorator that protects enpoints through multiple mechanisms Example:: @app.route('/dashboard') @auth_required('token', 'session') def dashboard(): return 'Dashboard' :param auth_methods: Specified mechanisms. """ login_mechanisms = { 'token': lambda: _check_token(), 'basic': lambda: _check_http_auth(), 'session': lambda: current_user.is_authenticated } def wrapper(fn): @wraps(fn) def decorated_view(*args, **kwargs): h = {} mechanisms = [(method, login_mechanisms.get(method)) for method in auth_methods] for method, mechanism in mechanisms: if mechanism and mechanism(): return fn(*args, **kwargs) elif method == 'basic': r = _security.default_http_auth_realm h['WWW-Authenticate'] = 'Basic realm="%s"' % r if _security._unauthorized_callback: return _security._unauthorized_callback() else: return _get_unauthorized_response(headers=h) return decorated_view return wrapper def roles_required(*roles): """Decorator which specifies that a user must have all the specified roles. Example:: @app.route('/dashboard') @roles_required('admin', 'editor') def dashboard(): return 'Dashboard' The current user must have both the `admin` role and `editor` role in order to view the page. :param args: The required roles. """ def wrapper(fn): @wraps(fn) def decorated_view(*args, **kwargs): perms = [Permission(RoleNeed(role)) for role in roles] for perm in perms: if not perm.can(): if _security._unauthorized_callback: return _security._unauthorized_callback() else: return _get_unauthorized_view() return fn(*args, **kwargs) return decorated_view return wrapper def roles_accepted(*roles): """Decorator which specifies that a user must have at least one of the specified roles. Example:: @app.route('/create_post') @roles_accepted('editor', 'author') def create_post(): return 'Create Post' The current user must have either the `editor` role or `author` role in order to view the page. :param args: The possible roles. """ def wrapper(fn): @wraps(fn) def decorated_view(*args, **kwargs): perm = Permission(*[RoleNeed(role) for role in roles]) if perm.can(): return fn(*args, **kwargs) if _security._unauthorized_callback: return _security._unauthorized_callback() else: return _get_unauthorized_view() return decorated_view return wrapper def anonymous_user_required(f): @wraps(f) def wrapper(*args, **kwargs): if current_user.is_authenticated: return redirect(utils.get_url(_security.post_login_view)) return f(*args, **kwargs) return wrapper Flask-Security-1.7.5/flask_security/forms.py0000644000076500000240000002215612627667222021232 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.forms ~~~~~~~~~~~~~~~~~~~~ Flask-Security forms module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ import inspect from flask import request, current_app, flash from flask_wtf import Form as BaseForm from wtforms import StringField, PasswordField, validators, \ SubmitField, HiddenField, BooleanField, ValidationError, Field from flask_login import current_user from werkzeug.local import LocalProxy from .confirmable import requires_confirmation from .utils import verify_and_update_password, get_message, config_value, validate_redirect_url # Convenient reference _datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) _default_field_labels = { 'email': 'Email Address', 'password': 'Password', 'remember_me': 'Remember Me', 'login': 'Login', 'retype_password': 'Retype Password', 'register': 'Register', 'send_confirmation': 'Resend Confirmation Instructions', 'recover_password': 'Recover Password', 'reset_password': 'Reset Password', 'retype_password': 'Retype Password', 'new_password': 'New Password', 'change_password': 'Change Password', 'send_login_link': 'Send Login Link' } class ValidatorMixin(object): def __call__(self, form, field): if self.message and self.message.isupper(): self.message = get_message(self.message)[0] return super(ValidatorMixin, self).__call__(form, field) class EqualTo(ValidatorMixin, validators.EqualTo): pass class Required(ValidatorMixin, validators.Required): pass class Email(ValidatorMixin, validators.Email): pass class Length(ValidatorMixin, validators.Length): pass email_required = Required(message='EMAIL_NOT_PROVIDED') email_validator = Email(message='INVALID_EMAIL_ADDRESS') password_required = Required(message='PASSWORD_NOT_PROVIDED') password_length = Length(min=6, max=128, message='PASSWORD_INVALID_LENGTH') def get_form_field_label(key): return _default_field_labels.get(key, '') def unique_user_email(form, field): if _datastore.get_user(field.data) is not None: msg = get_message('EMAIL_ALREADY_ASSOCIATED', email=field.data)[0] raise ValidationError(msg) def valid_user_email(form, field): form.user = _datastore.get_user(field.data) if form.user is None: raise ValidationError(get_message('USER_DOES_NOT_EXIST')[0]) class Form(BaseForm): def __init__(self, *args, **kwargs): if current_app.testing: self.TIME_LIMIT = None super(Form, self).__init__(*args, **kwargs) class EmailFormMixin(): email = StringField( get_form_field_label('email'), validators=[email_required, email_validator]) class UserEmailFormMixin(): user = None email = StringField( get_form_field_label('email'), validators=[email_required, email_validator, valid_user_email]) class UniqueEmailFormMixin(): email = StringField( get_form_field_label('email'), validators=[email_required, email_validator, unique_user_email]) class PasswordFormMixin(): password = PasswordField( get_form_field_label('password'), validators=[password_required]) class NewPasswordFormMixin(): password = PasswordField( get_form_field_label('password'), validators=[password_required, password_length]) class PasswordConfirmFormMixin(): password_confirm = PasswordField( get_form_field_label('retype_password'), validators=[EqualTo('password', message='RETYPE_PASSWORD_MISMATCH')]) class NextFormMixin(): next = HiddenField() def validate_next(self, field): if field.data and not validate_redirect_url(field.data): field.data = '' flash(*get_message('INVALID_REDIRECT')) raise ValidationError(get_message('INVALID_REDIRECT')[0]) class RegisterFormMixin(): submit = SubmitField(get_form_field_label('register')) def to_dict(form): def is_field_and_user_attr(member): return isinstance(member, Field) and \ hasattr(_datastore.user_model, member.name) fields = inspect.getmembers(form, is_field_and_user_attr) return dict((key, value.data) for key, value in fields) class SendConfirmationForm(Form, UserEmailFormMixin): """The default forgot password form""" submit = SubmitField(get_form_field_label('send_confirmation')) def __init__(self, *args, **kwargs): super(SendConfirmationForm, self).__init__(*args, **kwargs) if request.method == 'GET': self.email.data = request.args.get('email', None) def validate(self): if not super(SendConfirmationForm, self).validate(): return False if self.user.confirmed_at is not None: self.email.errors.append(get_message('ALREADY_CONFIRMED')[0]) return False return True class ForgotPasswordForm(Form, UserEmailFormMixin): """The default forgot password form""" submit = SubmitField(get_form_field_label('recover_password')) def validate(self): if not super(ForgotPasswordForm, self).validate(): return False if requires_confirmation(self.user): self.email.errors.append(get_message('CONFIRMATION_REQUIRED')[0]) return False return True class PasswordlessLoginForm(Form, UserEmailFormMixin): """The passwordless login form""" submit = SubmitField(get_form_field_label('send_login_link')) def __init__(self, *args, **kwargs): super(PasswordlessLoginForm, self).__init__(*args, **kwargs) def validate(self): if not super(PasswordlessLoginForm, self).validate(): return False if not self.user.is_active: self.email.errors.append(get_message('DISABLED_ACCOUNT')[0]) return False return True class LoginForm(Form, NextFormMixin): """The default login form""" email = StringField(get_form_field_label('email')) password = PasswordField(get_form_field_label('password')) remember = BooleanField(get_form_field_label('remember_me')) submit = SubmitField(get_form_field_label('login')) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) if not self.next.data: self.next.data = request.args.get('next', '') self.remember.default = config_value('DEFAULT_REMEMBER_ME') def validate(self): if not super(LoginForm, self).validate(): return False if self.email.data.strip() == '': self.email.errors.append(get_message('EMAIL_NOT_PROVIDED')[0]) return False if self.password.data.strip() == '': self.password.errors.append(get_message('PASSWORD_NOT_PROVIDED')[0]) return False self.user = _datastore.get_user(self.email.data) if self.user is None: self.email.errors.append(get_message('USER_DOES_NOT_EXIST')[0]) return False if not self.user.password: self.password.errors.append(get_message('PASSWORD_NOT_SET')[0]) return False if not verify_and_update_password(self.password.data, self.user): self.password.errors.append(get_message('INVALID_PASSWORD')[0]) return False if requires_confirmation(self.user): self.email.errors.append(get_message('CONFIRMATION_REQUIRED')[0]) return False if not self.user.is_active: self.email.errors.append(get_message('DISABLED_ACCOUNT')[0]) return False return True class ConfirmRegisterForm(Form, RegisterFormMixin, UniqueEmailFormMixin, NewPasswordFormMixin): pass class RegisterForm(ConfirmRegisterForm, PasswordConfirmFormMixin, NextFormMixin): def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) if not self.next.data: self.next.data = request.args.get('next', '') class ResetPasswordForm(Form, NewPasswordFormMixin, PasswordConfirmFormMixin): """The default reset password form""" submit = SubmitField(get_form_field_label('reset_password')) class ChangePasswordForm(Form, PasswordFormMixin): """The default change password form""" new_password = PasswordField( get_form_field_label('new_password'), validators=[password_required, password_length]) new_password_confirm = PasswordField( get_form_field_label('retype_password'), validators=[EqualTo('new_password', message='RETYPE_PASSWORD_MISMATCH')]) submit = SubmitField(get_form_field_label('change_password')) def validate(self): if not super(ChangePasswordForm, self).validate(): return False if not verify_and_update_password(self.password.data, current_user): self.password.errors.append(get_message('INVALID_PASSWORD')[0]) return False if self.password.data.strip() == self.new_password.data.strip(): self.password.errors.append(get_message('PASSWORD_IS_THE_SAME')[0]) return False return True Flask-Security-1.7.5/flask_security/passwordless.py0000644000076500000240000000312312627667222022626 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.passwordless ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Flask-Security passwordless module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from flask import current_app as app from werkzeug.local import LocalProxy from .signals import login_instructions_sent from .utils import send_mail, url_for_security, get_token_status, \ config_value # Convenient references _security = LocalProxy(lambda: app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) def send_login_instructions(user): """Sends the login instructions email for the specified user. :param user: The user to send the instructions to :param token: The login token """ token = generate_login_token(user) login_link = url_for_security('token_login', token=token, _external=True) send_mail(config_value('EMAIL_SUBJECT_PASSWORDLESS'), user.email, 'login_instructions', user=user, login_link=login_link) login_instructions_sent.send(app._get_current_object(), user=user, login_token=token) def generate_login_token(user): """Generates a unique login token for the specified user. :param user: The user the token belongs to """ return _security.login_serializer.dumps([str(user.id)]) def login_token_status(token): """Returns the expired status, invalid status, and user of a login token. For example:: expired, invalid, user = login_token_status('...') :param token: The login token """ return get_token_status(token, 'login', 'LOGIN') Flask-Security-1.7.5/flask_security/recoverable.py0000644000076500000240000000551512627667222022375 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.recoverable ~~~~~~~~~~~~~~~~~~~~~~~~~~ Flask-Security recoverable module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from flask import current_app as app from werkzeug.local import LocalProxy from werkzeug.security import safe_str_cmp from .signals import password_reset, reset_password_instructions_sent from .utils import send_mail, md5, encrypt_password, url_for_security, \ get_token_status, config_value # Convenient references _security = LocalProxy(lambda: app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) def send_reset_password_instructions(user): """Sends the reset password instructions email for the specified user. :param user: The user to send the instructions to """ token = generate_reset_password_token(user) reset_link = url_for_security('reset_password', token=token, _external=True) send_mail(config_value('EMAIL_SUBJECT_PASSWORD_RESET'), user.email, 'reset_instructions', user=user, reset_link=reset_link) reset_password_instructions_sent.send(app._get_current_object(), user=user, token=token) def send_password_reset_notice(user): """Sends the password reset notice email for the specified user. :param user: The user to send the notice to """ if config_value('SEND_PASSWORD_RESET_NOTICE_EMAIL'): send_mail(config_value('EMAIL_SUBJECT_PASSWORD_NOTICE'), user.email, 'reset_notice', user=user) def generate_reset_password_token(user): """Generates a unique reset password token for the specified user. :param user: The user to work with """ password_hash = md5(user.password) if user.password else None data = [str(user.id), password_hash] return _security.reset_serializer.dumps(data) def reset_password_token_status(token): """Returns the expired status, invalid status, and user of a password reset token. For example:: expired, invalid, user, data = reset_password_token_status('...') :param token: The password reset token """ expired, invalid, user, data = get_token_status(token, 'reset', 'RESET_PASSWORD', return_data=True) if not invalid: if user.password: password_hash = md5(user.password) if not safe_str_cmp(password_hash, data[1]): invalid = True return expired, invalid, user def update_password(user, password): """Update the specified user's password :param user: The user to update_password :param password: The unencrypted new password """ user.password = encrypt_password(password) _datastore.put(user) send_password_reset_notice(user) password_reset.send(app._get_current_object(), user=user) Flask-Security-1.7.5/flask_security/registerable.py0000644000076500000240000000244312627667222022551 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.registerable ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Flask-Security registerable module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from flask import current_app as app from werkzeug.local import LocalProxy from .confirmable import generate_confirmation_link from .signals import user_registered from .utils import do_flash, get_message, send_mail, encrypt_password, \ config_value # Convenient references _security = LocalProxy(lambda: app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) def register_user(**kwargs): confirmation_link, token = None, None kwargs['password'] = encrypt_password(kwargs['password']) user = _datastore.create_user(**kwargs) _datastore.commit() if _security.confirmable: confirmation_link, token = generate_confirmation_link(user) do_flash(*get_message('CONFIRM_REGISTRATION', email=user.email)) user_registered.send(app._get_current_object(), user=user, confirm_token=token) if config_value('SEND_REGISTER_EMAIL'): send_mail(config_value('EMAIL_SUBJECT_REGISTER'), user.email, 'welcome', user=user, confirmation_link=confirmation_link) return user Flask-Security-1.7.5/flask_security/script.py0000644000076500000240000000663412627667222021413 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.script ~~~~~~~~~~~~~~~~~~~~~ Flask-Security script module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from __future__ import print_function try: import simplejson as json except ImportError: import json import re from flask import current_app from flask_script import Command, Option from werkzeug.local import LocalProxy from .utils import encrypt_password _datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) def pprint(obj): print(json.dumps(obj, sort_keys=True, indent=4)) def commit(fn): def wrapper(*args, **kwargs): fn(*args, **kwargs) _datastore.commit() return wrapper class CreateUserCommand(Command): """Create a user""" option_list = ( Option('-e', '--email', dest='email', default=None), Option('-p', '--password', dest='password', default=None), Option('-a', '--active', dest='active', default=''), ) @commit def run(self, **kwargs): # sanitize active input ai = re.sub(r'\s', '', str(kwargs['active'])) kwargs['active'] = ai.lower() in ['', 'y', 'yes', '1', 'active'] from flask_security.forms import ConfirmRegisterForm from werkzeug.datastructures import MultiDict form = ConfirmRegisterForm(MultiDict(kwargs), csrf_enabled=False) if form.validate(): kwargs['password'] = encrypt_password(kwargs['password']) _datastore.create_user(**kwargs) print('User created successfully.') kwargs['password'] = '****' pprint(kwargs) else: print('Error creating user') pprint(form.errors) class CreateRoleCommand(Command): """Create a role""" option_list = ( Option('-n', '--name', dest='name', default=None), Option('-d', '--desc', dest='description', default=None), ) @commit def run(self, **kwargs): _datastore.create_role(**kwargs) print('Role "%(name)s" created successfully.' % kwargs) class _RoleCommand(Command): option_list = ( Option('-u', '--user', dest='user_identifier'), Option('-r', '--role', dest='role_name'), ) class AddRoleCommand(_RoleCommand): """Add a role to a user""" @commit def run(self, user_identifier, role_name): _datastore.add_role_to_user(user_identifier, role_name) print("Role '%s' added to user '%s' successfully" % (role_name, user_identifier)) class RemoveRoleCommand(_RoleCommand): """Remove a role from a user""" @commit def run(self, user_identifier, role_name): _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): """Deactivate a user""" @commit def run(self, user_identifier): _datastore.deactivate_user(user_identifier) print("User '%s' has been deactivated" % user_identifier) class ActivateUserCommand(_ToggleActiveCommand): """Activate a user""" @commit def run(self, user_identifier): _datastore.activate_user(user_identifier) print("User '%s' has been activated" % user_identifier) Flask-Security-1.7.5/flask_security/signals.py0000644000076500000240000000127212627667222021540 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.signals ~~~~~~~~~~~~~~~~~~~~~~ Flask-Security signals module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ import blinker signals = blinker.Namespace() user_registered = signals.signal("user-registered") user_confirmed = signals.signal("user-confirmed") confirm_instructions_sent = signals.signal("confirm-instructions-sent") login_instructions_sent = signals.signal("login-instructions-sent") password_reset = signals.signal("password-reset") password_changed = signals.signal("password-changed") reset_password_instructions_sent = signals.signal("password-reset-instructions-sent") Flask-Security-1.7.5/flask_security/templates/0000755000076500000240000000000012627667302021521 5ustar mattstaff00000000000000Flask-Security-1.7.5/flask_security/templates/.DS_Store0000644000076500000240000001400412052511622023164 0ustar mattstaff00000000000000Bud1ritybwspsecuritybwspblobbplist00  \WindowBounds[ShowSidebar]ShowStatusBar[ShowPathbar[ShowToolbar\SidebarWidth_{{316, 89}, {839, 1024}}  ". {{ field.label }} {{ field(**kwargs)|safe }} {% if field.errors %} {% endif %}

{% endmacro %} {% macro render_field(field) %}

{{ field(**kwargs)|safe }}

{% endmacro %}Flask-Security-1.7.5/flask_security/templates/security/_menu.html0000644000076500000240000000132712417007016025347 0ustar mattstaff00000000000000{% if security.registerable or security.recoverable or security.confirmable %}

Menu

{% endif %} Flask-Security-1.7.5/flask_security/templates/security/_messages.html0000644000076500000240000000040712052511622026206 0ustar mattstaff00000000000000{%- with messages = get_flashed_messages(with_categories=true) -%} {% if messages %} {% endif %} {%- endwith %}Flask-Security-1.7.5/flask_security/templates/security/change_password.html0000644000076500000240000000105312120140042027373 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Change password

{{ change_password_form.hidden_tag() }} {{ render_field_with_errors(change_password_form.password) }} {{ render_field_with_errors(change_password_form.new_password) }} {{ render_field_with_errors(change_password_form.new_password_confirm) }} {{ render_field(change_password_form.submit) }}
Flask-Security-1.7.5/flask_security/templates/security/email/0000755000076500000240000000000012627667302024457 5ustar mattstaff00000000000000Flask-Security-1.7.5/flask_security/templates/security/email/change_notice.html0000644000076500000240000000033512120140042030103 0ustar mattstaff00000000000000

Your password has been changed.

{% if security.recoverable %}

If you did not change your password, click here to reset it.

{% endif %} Flask-Security-1.7.5/flask_security/templates/security/email/change_notice.txt0000644000076500000240000000031212120140042027751 0ustar mattstaff00000000000000Your password has been changed {% if security.recoverable %} If you did not change your password, click the link below to reset it. {{ url_for_security('forgot_password', _external=True) }} {% endif %} Flask-Security-1.7.5/flask_security/templates/security/email/confirmation_instructions.html0000644000076500000240000000017112052511622032641 0ustar mattstaff00000000000000

Please confirm your email through the link below:

Confirm my account

Flask-Security-1.7.5/flask_security/templates/security/email/confirmation_instructions.txt0000644000076500000240000000011212052511622032507 0ustar mattstaff00000000000000Please confirm your email through the link below: {{ confirmation_link }}Flask-Security-1.7.5/flask_security/templates/security/email/login_instructions.html0000644000076500000240000000020712052511622031261 0ustar mattstaff00000000000000

Welcome {{ user.email }}!

You can log into your through the link below:

Login now

Flask-Security-1.7.5/flask_security/templates/security/email/login_instructions.txt0000644000076500000240000000013212052511622031131 0ustar mattstaff00000000000000Welcome {{ user.email }}! You can log into your through the link below: {{ login_link }}Flask-Security-1.7.5/flask_security/templates/security/email/reset_instructions.html0000644000076500000240000000010712052511622031272 0ustar mattstaff00000000000000

Click here to reset your password

Flask-Security-1.7.5/flask_security/templates/security/email/reset_instructions.txt0000644000076500000240000000007612052511622031152 0ustar mattstaff00000000000000Click the link below to reset your password: {{ reset_link }}Flask-Security-1.7.5/flask_security/templates/security/email/reset_notice.html0000644000076500000240000000004312052511622030006 0ustar mattstaff00000000000000

Your password has been reset

Flask-Security-1.7.5/flask_security/templates/security/email/reset_notice.txt0000644000076500000240000000003412052511622027661 0ustar mattstaff00000000000000Your password has been resetFlask-Security-1.7.5/flask_security/templates/security/email/welcome.html0000644000076500000240000000030612052511622026760 0ustar mattstaff00000000000000

Welcome {{ user.email }}!

{% if security.confirmable %}

You can confirm your email through the link below:

Confirm my account

{% endif %}Flask-Security-1.7.5/flask_security/templates/security/email/welcome.txt0000644000076500000240000000022012052511622026626 0ustar mattstaff00000000000000Welcome {{ user.email }}! {% if security.confirmable %} You can confirm your email through the link below: {{ confirmation_link }} {% endif %}Flask-Security-1.7.5/flask_security/templates/security/forgot_password.html0000644000076500000240000000071412052511622027463 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Send password reset instructions

{{ forgot_password_form.hidden_tag() }} {{ render_field_with_errors(forgot_password_form.email) }} {{ render_field(forgot_password_form.submit) }}
{% include "security/_menu.html" %}Flask-Security-1.7.5/flask_security/templates/security/login_user.html0000644000076500000240000000106312054740547026421 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Login

{{ login_user_form.hidden_tag() }} {{ render_field_with_errors(login_user_form.email) }} {{ render_field_with_errors(login_user_form.password) }} {{ render_field_with_errors(login_user_form.remember) }} {{ render_field(login_user_form.next) }} {{ render_field(login_user_form.submit) }}
{% include "security/_menu.html" %}Flask-Security-1.7.5/flask_security/templates/security/register_user.html0000644000076500000240000000114712052511622027124 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Register

{{ register_user_form.hidden_tag() }} {{ render_field_with_errors(register_user_form.email) }} {{ render_field_with_errors(register_user_form.password) }} {% if register_user_form.password_confirm %} {{ render_field_with_errors(register_user_form.password_confirm) }} {% endif %} {{ render_field(register_user_form.submit) }}
{% include "security/_menu.html" %}Flask-Security-1.7.5/flask_security/templates/security/reset_password.html0000644000076500000240000000103212052511622027277 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Reset password

{{ reset_password_form.hidden_tag() }} {{ render_field_with_errors(reset_password_form.password) }} {{ render_field_with_errors(reset_password_form.password_confirm) }} {{ render_field(reset_password_form.submit) }}
{% include "security/_menu.html" %}Flask-Security-1.7.5/flask_security/templates/security/send_confirmation.html0000644000076500000240000000072512052511622027744 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Resend confirmation instructions

{{ send_confirmation_form.hidden_tag() }} {{ render_field_with_errors(send_confirmation_form.email) }} {{ render_field(send_confirmation_form.submit) }}
{% include "security/_menu.html" %}Flask-Security-1.7.5/flask_security/templates/security/send_login.html0000644000076500000240000000062212052511622026360 0ustar mattstaff00000000000000{% from "security/_macros.html" import render_field_with_errors, render_field %} {% include "security/_messages.html" %}

Login

{{ send_login_form.hidden_tag() }} {{ render_field_with_errors(send_login_form.email) }} {{ render_field(send_login_form.submit) }}
{% include "security/_menu.html" %}Flask-Security-1.7.5/flask_security/utils.py0000644000076500000240000003162012627667222021240 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.utils ~~~~~~~~~~~~~~~~~~~~ Flask-Security utils module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ import base64 import hashlib import hmac import sys try: from urlparse import urlsplit except ImportError: # pragma: no cover from urllib.parse import urlsplit from contextlib import contextmanager from datetime import datetime, timedelta from flask import url_for, flash, current_app, request, session, render_template from flask_login import login_user as _login_user, logout_user as _logout_user from flask_mail import Message from flask_principal import Identity, AnonymousIdentity, identity_changed from itsdangerous import BadSignature, SignatureExpired from werkzeug.local import LocalProxy from .signals import user_registered, login_instructions_sent, reset_password_instructions_sent # Convenient references _security = LocalProxy(lambda: current_app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) _pwd_context = LocalProxy(lambda: _security.pwd_context) PY3 = sys.version_info[0] == 3 if PY3: # pragma: no cover string_types = str, # pragma: no flakes text_type = str # pragma: no flakes else: # pragma: no cover string_types = basestring, # pragma: no flakes text_type = unicode # pragma: no flakes def login_user(user, remember=None): """Performs the login routine. :param user: The user to login :param remember: Flag specifying if the remember cookie should be set. Defaults to ``False`` """ if remember is None: remember = config_value('DEFAULT_REMEMBER_ME') if not _login_user(user, remember): # pragma: no cover return False if _security.trackable: if 'X-Forwarded-For' in request.headers: remote_addr = request.headers.getlist("X-Forwarded-For")[0].rpartition(' ')[-1] else: remote_addr = request.remote_addr or 'untrackable' old_current_login, new_current_login = user.current_login_at, datetime.utcnow() old_current_ip, new_current_ip = user.current_login_ip, remote_addr user.last_login_at = old_current_login or new_current_login user.current_login_at = new_current_login user.last_login_ip = old_current_ip or new_current_ip user.current_login_ip = new_current_ip user.login_count = user.login_count + 1 if user.login_count else 1 _datastore.put(user) identity_changed.send(current_app._get_current_object(), identity=Identity(user.id)) return True def logout_user(): """Logs out the current. This will also clean up the remember me cookie if it exists.""" for key in ('identity.name', 'identity.auth_type'): session.pop(key, None) identity_changed.send(current_app._get_current_object(), identity=AnonymousIdentity()) _logout_user() def get_hmac(password): """Returns a Base64 encoded HMAC+SHA512 of the password signed with the salt specified by ``SECURITY_PASSWORD_SALT``. :param password: The password to sign """ salt = _security.password_salt if salt is None: raise RuntimeError( 'The configuration value `SECURITY_PASSWORD_SALT` must ' 'not be None when the value of `SECURITY_PASSWORD_HASH` is ' 'set to "%s"' % _security.password_hash) h = hmac.new(encode_string(salt), encode_string(password), hashlib.sha512) return base64.b64encode(h.digest()) def verify_password(password, password_hash): """Returns ``True`` if the password matches the supplied hash. :param password: A plaintext password to verify :param password_hash: The expected hash value of the password (usually from your database) """ if _security.password_hash != 'plaintext': password = get_hmac(password) return _pwd_context.verify(password, password_hash) def verify_and_update_password(password, user): """Returns ``True`` if the password is valid for the specified user. Additionally, the hashed password in the database is updated if the hashing algorithm happens to have changed. :param password: A plaintext password to verify :param user: The user to verify against """ if _pwd_context.identify(user.password) != 'plaintext': password = get_hmac(password) verified, new_password = _pwd_context.verify_and_update(password, user.password) if verified and new_password: user.password = encrypt_password(password) _datastore.put(user) return verified def encrypt_password(password): """Encrypts the specified plaintext password using the configured encryption options. :param password: The plaintext password to encrypt """ if _security.password_hash == 'plaintext': return password signed = get_hmac(password).decode('ascii') return _pwd_context.encrypt(signed) def encode_string(string): """Encodes a string to bytes, if it isn't already. :param string: The string to encode""" if isinstance(string, text_type): string = string.encode('utf-8') return string def md5(data): return hashlib.md5(encode_string(data)).hexdigest() def do_flash(message, category=None): """Flash a message depending on if the `FLASH_MESSAGES` configuration value is set. :param message: The flash message :param category: The flash message category """ if config_value('FLASH_MESSAGES'): flash(message, category) def get_url(endpoint_or_url): """Returns a URL if a valid endpoint is found. Otherwise, returns the provided value. :param endpoint_or_url: The endpoint name or URL to default to """ try: return url_for(endpoint_or_url) except: return endpoint_or_url def slash_url_suffix(url, suffix): """Adds a slash either to the beginning or the end of a suffix (which is to be appended to a URL), depending on whether or not the URL ends with a slash.""" return url.endswith('/') and ('%s/' % suffix) or ('/%s' % suffix) def get_security_endpoint_name(endpoint): return '%s.%s' % (_security.blueprint_name, endpoint) def url_for_security(endpoint, **values): """Return a URL for the security blueprint :param endpoint: the endpoint of the URL (name of the function) :param values: the variable arguments of the URL rule :param _external: if set to `True`, an absolute URL is generated. Server address can be changed via `SERVER_NAME` configuration variable which defaults to `localhost`. :param _anchor: if provided this is added as anchor to the URL. :param _method: if provided this explicitly specifies an HTTP method. """ endpoint = get_security_endpoint_name(endpoint) return url_for(endpoint, **values) def validate_redirect_url(url): if url is None or url.strip() == '': return False url_next = urlsplit(url) url_base = urlsplit(request.host_url) if (url_next.netloc or url_next.scheme) and url_next.netloc != url_base.netloc: return False return True def get_post_action_redirect(config_key, declared=None): urls = [ get_url(request.args.get('next')), get_url(request.form.get('next')), find_redirect(config_key) ] if declared: urls.insert(0, declared) for url in urls: if validate_redirect_url(url): return url def get_post_login_redirect(declared=None): return get_post_action_redirect('SECURITY_POST_LOGIN_VIEW', declared) def get_post_register_redirect(declared=None): return get_post_action_redirect('SECURITY_POST_REGISTER_VIEW', declared) def find_redirect(key): """Returns the URL to redirect to after a user logs in successfully. :param key: The session or application configuration key to search for """ rv = (get_url(session.pop(key.lower(), None)) or get_url(current_app.config[key.upper()] or None) or '/') return rv def get_config(app): """Conveniently get the security configuration for the specified application without the annoying 'SECURITY_' prefix. :param app: The application to inspect """ items = app.config.items() prefix = 'SECURITY_' def strip_prefix(tup): return (tup[0].replace('SECURITY_', ''), tup[1]) return dict([strip_prefix(i) for i in items if i[0].startswith(prefix)]) def get_message(key, **kwargs): rv = config_value('MSG_' + key) return rv[0] % kwargs, rv[1] def config_value(key, app=None, default=None): """Get a Flask-Security configuration value. :param key: The configuration key without the prefix `SECURITY_` :param app: An optional specific application to inspect. Defaults to Flask's `current_app` :param default: An optional default value if the value is not set """ app = app or current_app return get_config(app).get(key.upper(), default) def get_max_age(key, app=None): td = get_within_delta(key + '_WITHIN', app) return td.seconds + td.days * 24 * 3600 def get_within_delta(key, app=None): """Get a timedelta object from the application configuration following the internal convention of:: Examples of valid config values:: 5 days 10 minutes :param key: The config value key without the 'SECURITY_' prefix :param app: Optional application to inspect. Defaults to Flask's `current_app` """ txt = config_value(key, app=app) values = txt.split() return timedelta(**{values[1]: int(values[0])}) def send_mail(subject, recipient, template, **context): """Send an email via the Flask-Mail extension. :param subject: Email subject :param recipient: Email recipient :param template: The name of the email template :param context: The context to render the template with """ context.setdefault('security', _security) context.update(_security._run_ctx_processor('mail')) msg = Message(subject, sender=_security.email_sender, recipients=[recipient]) ctx = ('security/email', template) msg.body = render_template('%s/%s.txt' % ctx, **context) msg.html = render_template('%s/%s.html' % ctx, **context) if _security._send_mail_task: _security._send_mail_task(msg) return mail = current_app.extensions.get('mail') mail.send(msg) def get_token_status(token, serializer, max_age=None, return_data=False): """Get the status of a token. :param token: The token to check :param serializer: The name of the seriailzer. Can be one of the following: ``confirm``, ``login``, ``reset`` :param max_age: The name of the max age config option. Can be on of the following: ``CONFIRM_EMAIL``, ``LOGIN``, ``RESET_PASSWORD`` """ serializer = getattr(_security, serializer + '_serializer') max_age = get_max_age(max_age) user, data = None, None expired, invalid = False, False try: data = serializer.loads(token, max_age=max_age) except SignatureExpired: d, data = serializer.loads_unsafe(token) expired = True except (BadSignature, TypeError, ValueError): invalid = True if data: user = _datastore.find_user(id=data[0]) expired = expired and (user is not None) if return_data: return expired, invalid, user, data else: return expired, invalid, user def get_identity_attributes(app=None): app = app or current_app attrs = app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] try: attrs = [f.strip() for f in attrs.split(',')] except AttributeError: pass return attrs @contextmanager def capture_passwordless_login_requests(): login_requests = [] def _on(app, **data): login_requests.append(data) login_instructions_sent.connect(_on) try: yield login_requests finally: login_instructions_sent.disconnect(_on) @contextmanager def capture_registrations(): """Testing utility for capturing registrations. :param confirmation_sent_at: An optional datetime object to set the user's `confirmation_sent_at` to """ registrations = [] def _on(app, **data): registrations.append(data) user_registered.connect(_on) try: yield registrations finally: user_registered.disconnect(_on) @contextmanager def capture_reset_password_requests(reset_password_sent_at=None): """Testing utility for capturing password reset requests. :param reset_password_sent_at: An optional datetime object to set the user's `reset_password_sent_at` to """ reset_requests = [] def _on(app, **data): reset_requests.append(data) reset_password_instructions_sent.connect(_on) try: yield reset_requests finally: reset_password_instructions_sent.disconnect(_on) Flask-Security-1.7.5/flask_security/views.py0000644000076500000240000002700212627667222021234 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ flask_security.views ~~~~~~~~~~~~~~~~~~~~ Flask-Security views module :copyright: (c) 2012 by Matt Wright. :license: MIT, see LICENSE for more details. """ from flask import current_app, redirect, request, jsonify, \ after_this_request, Blueprint from flask_login import current_user from werkzeug.datastructures import MultiDict from werkzeug.local import LocalProxy from .confirmable import send_confirmation_instructions, \ confirm_user, confirm_email_token_status from .decorators import login_required, anonymous_user_required from .passwordless import send_login_instructions, \ login_token_status from .recoverable import reset_password_token_status, \ send_reset_password_instructions, update_password from .changeable import change_user_password from .registerable import register_user from .utils import config_value, do_flash, get_url, get_post_login_redirect, \ get_post_register_redirect, get_message, login_user, logout_user, \ url_for_security as url_for, slash_url_suffix # Convenient references _security = LocalProxy(lambda: current_app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) def _render_json(form, include_user=True, include_auth_token=False): has_errors = len(form.errors) > 0 if has_errors: code = 400 response = dict(errors=form.errors) else: code = 200 response = dict() if include_user: response['user'] = dict(id=str(form.user.id)) if include_auth_token: token = form.user.get_auth_token() response['user']['authentication_token'] = token return jsonify(dict(meta=dict(code=code), response=response)) def _commit(response=None): _datastore.commit() return response def _ctx(endpoint): return _security._run_ctx_processor(endpoint) @anonymous_user_required def login(): """View function for login view""" form_class = _security.login_form if request.json: form = form_class(MultiDict(request.json)) else: form = form_class() if form.validate_on_submit(): login_user(form.user, remember=form.remember.data) after_this_request(_commit) if not request.json: return redirect(get_post_login_redirect(form.next.data)) if request.json: return _render_json(form, include_auth_token=True) return _security.render_template(config_value('LOGIN_USER_TEMPLATE'), login_user_form=form, **_ctx('login')) def logout(): """View function which handles a logout request.""" if current_user.is_authenticated: logout_user() return redirect(request.args.get('next', None) or get_url(_security.post_logout_view)) @anonymous_user_required def register(): """View function which handles a registration request.""" if _security.confirmable or request.json: form_class = _security.confirm_register_form else: form_class = _security.register_form if request.json: form_data = MultiDict(request.json) else: form_data = request.form form = form_class(form_data) if form.validate_on_submit(): user = register_user(**form.to_dict()) form.user = user if not _security.confirmable or _security.login_without_confirmation: after_this_request(_commit) login_user(user) if not request.json: if 'next' in form: redirect_url = get_post_register_redirect(form.next.data) else: redirect_url = get_post_register_redirect() return redirect(redirect_url) return _render_json(form, include_auth_token=True) if request.json: return _render_json(form) return _security.render_template(config_value('REGISTER_USER_TEMPLATE'), register_user_form=form, **_ctx('register')) def send_login(): """View function that sends login instructions for passwordless login""" form_class = _security.passwordless_login_form if request.json: form = form_class(MultiDict(request.json)) else: form = form_class() if form.validate_on_submit(): send_login_instructions(form.user) if request.json is None: do_flash(*get_message('LOGIN_EMAIL_SENT', email=form.user.email)) if request.json: return _render_json(form) return _security.render_template(config_value('SEND_LOGIN_TEMPLATE'), send_login_form=form, **_ctx('send_login')) @anonymous_user_required def token_login(token): """View function that handles passwordless login via a token""" expired, invalid, user = login_token_status(token) if invalid: do_flash(*get_message('INVALID_LOGIN_TOKEN')) if expired: send_login_instructions(user) do_flash(*get_message('LOGIN_EXPIRED', email=user.email, within=_security.login_within)) if invalid or expired: return redirect(url_for('login')) login_user(user) after_this_request(_commit) do_flash(*get_message('PASSWORDLESS_LOGIN_SUCCESSFUL')) return redirect(get_post_login_redirect()) def send_confirmation(): """View function which sends confirmation instructions.""" form_class = _security.send_confirmation_form if request.json: form = form_class(MultiDict(request.json)) else: form = form_class() if form.validate_on_submit(): send_confirmation_instructions(form.user) if request.json is None: do_flash(*get_message('CONFIRMATION_REQUEST', email=form.user.email)) if request.json: return _render_json(form) return _security.render_template(config_value('SEND_CONFIRMATION_TEMPLATE'), send_confirmation_form=form, **_ctx('send_confirmation')) def confirm_email(token): """View function which handles a email confirmation request.""" expired, invalid, user = confirm_email_token_status(token) if not user or invalid: invalid = True do_flash(*get_message('INVALID_CONFIRMATION_TOKEN')) if expired: send_confirmation_instructions(user) do_flash(*get_message('CONFIRMATION_EXPIRED', email=user.email, within=_security.confirm_email_within)) if invalid or expired: return redirect(get_url(_security.confirm_error_view) or url_for('send_confirmation')) if user != current_user: logout_user() login_user(user) if confirm_user(user): after_this_request(_commit) msg = 'EMAIL_CONFIRMED' else: msg = 'ALREADY_CONFIRMED' do_flash(*get_message(msg)) return redirect(get_url(_security.post_confirm_view) or get_url(_security.post_login_view)) @anonymous_user_required def forgot_password(): """View function that handles a forgotten password request.""" form_class = _security.forgot_password_form if request.json: form = form_class(MultiDict(request.json)) else: form = form_class() if form.validate_on_submit(): send_reset_password_instructions(form.user) if request.json is None: do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email)) if request.json: return _render_json(form, include_user=False) return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'), forgot_password_form=form, **_ctx('forgot_password')) @anonymous_user_required def reset_password(token): """View function that handles a reset password request.""" expired, invalid, user = reset_password_token_status(token) if invalid: do_flash(*get_message('INVALID_RESET_PASSWORD_TOKEN')) if expired: do_flash(*get_message('PASSWORD_RESET_EXPIRED', email=user.email, within=_security.reset_password_within)) if invalid or expired: return redirect(url_for('forgot_password')) form = _security.reset_password_form() if form.validate_on_submit(): after_this_request(_commit) update_password(user, form.password.data) do_flash(*get_message('PASSWORD_RESET')) login_user(user) return redirect(get_url(_security.post_reset_view) or get_url(_security.post_login_view)) return _security.render_template(config_value('RESET_PASSWORD_TEMPLATE'), reset_password_form=form, reset_password_token=token, **_ctx('reset_password')) @login_required def change_password(): """View function which handles a change password request.""" form_class = _security.change_password_form if request.json: form = form_class(MultiDict(request.json)) else: form = form_class() if form.validate_on_submit(): after_this_request(_commit) change_user_password(current_user, form.new_password.data) if request.json is None: do_flash(*get_message('PASSWORD_CHANGE')) return redirect(get_url(_security.post_change_view) or get_url(_security.post_login_view)) if request.json: form.user = current_user return _render_json(form) return _security.render_template(config_value('CHANGE_PASSWORD_TEMPLATE'), change_password_form=form, **_ctx('change_password')) def create_blueprint(state, import_name): """Creates the security extension blueprint""" bp = Blueprint(state.blueprint_name, import_name, url_prefix=state.url_prefix, subdomain=state.subdomain, template_folder='templates') bp.route(state.logout_url, endpoint='logout')(logout) if state.passwordless: bp.route(state.login_url, methods=['GET', 'POST'], endpoint='login')(send_login) bp.route(state.login_url + slash_url_suffix(state.login_url, ''), endpoint='token_login')(token_login) else: bp.route(state.login_url, methods=['GET', 'POST'], endpoint='login')(login) if state.registerable: bp.route(state.register_url, methods=['GET', 'POST'], endpoint='register')(register) if state.recoverable: bp.route(state.reset_url, methods=['GET', 'POST'], endpoint='forgot_password')(forgot_password) bp.route(state.reset_url + slash_url_suffix(state.reset_url, ''), methods=['GET', 'POST'], endpoint='reset_password')(reset_password) if state.changeable: bp.route(state.change_url, methods=['GET', 'POST'], endpoint='change_password')(change_password) if state.confirmable: bp.route(state.confirm_url, methods=['GET', 'POST'], endpoint='send_confirmation')(send_confirmation) bp.route(state.confirm_url + slash_url_suffix(state.confirm_url, ''), methods=['GET', 'POST'], endpoint='confirm_email')(confirm_email) return bp Flask-Security-1.7.5/Flask_Security.egg-info/0000755000076500000240000000000012627667302021115 5ustar mattstaff00000000000000Flask-Security-1.7.5/Flask_Security.egg-info/dependency_links.txt0000644000076500000240000000000112627667277025176 0ustar mattstaff00000000000000 Flask-Security-1.7.5/Flask_Security.egg-info/not-zip-safe0000644000076500000240000000000112310602445023325 0ustar mattstaff00000000000000 Flask-Security-1.7.5/Flask_Security.egg-info/PKG-INFO0000644000076500000240000000367212627667277022235 0ustar mattstaff00000000000000Metadata-Version: 1.1 Name: Flask-Security Version: 1.7.5 Summary: Simple security for Flask apps Home-page: https://github.com/mattupstate/flask-security Author: Matt Wright Author-email: matt@nobien.net License: MIT Description: Flask-Security ============== .. image:: https://secure.travis-ci.org/mattupstate/flask-security.png?branch=develop .. image:: https://pypip.in/v/Flask-Security/badge.png :target: https://pypi.python.org/pypi/Flask-Security/ :alt: Latest Version .. image:: https://coveralls.io/repos/mattupstate/flask-security/badge.png?branch=develop :target: https://coveralls.io/r/mattupstate/flask-security .. image:: https://pypip.in/d/Flask-Security/badge.png :target: https://pypi.python.org/pypi//Flask-Security/ :alt: Downloads .. image:: https://pypip.in/license/Flask-Security/badge.png :target: https://pypi.python.org/pypi/Flask-Security/ :alt: License Flask-Security quickly adds security features to your Flask application. Resources --------- - `Documentation `_ - `Issue Tracker `_ - `Code `_ - `Development Version `_ Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Flask-Security-1.7.5/Flask_Security.egg-info/requires.txt0000644000076500000240000000017412627667277023532 0ustar mattstaff00000000000000Flask>=0.9 Flask-Login>=0.3.0,<0.4 Flask-Mail>=0.7.3 Flask-Principal>=0.3.3 Flask-WTF>=0.8 itsdangerous>=0.17 passlib>=1.6.1Flask-Security-1.7.5/Flask_Security.egg-info/SOURCES.txt0000644000076500000240000000566012627667302023010 0ustar mattstaff00000000000000MANIFEST.in README.rst requirements-dev.txt requirements.txt setup.cfg setup.py tox.ini Flask_Security.egg-info/PKG-INFO Flask_Security.egg-info/SOURCES.txt Flask_Security.egg-info/dependency_links.txt Flask_Security.egg-info/not-zip-safe Flask_Security.egg-info/requires.txt Flask_Security.egg-info/top_level.txt flask_security/__init__.py flask_security/changeable.py flask_security/confirmable.py flask_security/core.py flask_security/datastore.py flask_security/decorators.py flask_security/forms.py flask_security/passwordless.py flask_security/recoverable.py flask_security/registerable.py flask_security/script.py flask_security/signals.py flask_security/utils.py flask_security/views.py flask_security/templates/.DS_Store flask_security/templates/security/.DS_Store flask_security/templates/security/_macros.html flask_security/templates/security/_menu.html flask_security/templates/security/_messages.html flask_security/templates/security/change_password.html flask_security/templates/security/forgot_password.html flask_security/templates/security/login_user.html flask_security/templates/security/register_user.html flask_security/templates/security/reset_password.html flask_security/templates/security/send_confirmation.html flask_security/templates/security/send_login.html flask_security/templates/security/email/change_notice.html flask_security/templates/security/email/change_notice.txt flask_security/templates/security/email/confirmation_instructions.html flask_security/templates/security/email/confirmation_instructions.txt flask_security/templates/security/email/login_instructions.html flask_security/templates/security/email/login_instructions.txt flask_security/templates/security/email/reset_instructions.html flask_security/templates/security/email/reset_instructions.txt flask_security/templates/security/email/reset_notice.html flask_security/templates/security/email/reset_notice.txt flask_security/templates/security/email/welcome.html flask_security/templates/security/email/welcome.txt tests/.DS_Store tests/conftest.py tests/conftest.pyc tests/test_changeable.py tests/test_common.py tests/test_configuration.py tests/test_confirmable.py tests/test_context_processors.py tests/test_datastore.py tests/test_entities.py tests/test_hashing.py tests/test_misc.py tests/test_passwordless.py tests/test_recoverable.py tests/test_registerable.py tests/test_trackable.py tests/utils.py tests/utils.pyc tests/templates/_messages.html tests/templates/_nav.html tests/templates/index.html tests/templates/register.html tests/templates/unauthorized.html tests/templates/custom_security/change_password.html tests/templates/custom_security/forgot_password.html tests/templates/custom_security/login_user.html tests/templates/custom_security/register_user.html tests/templates/custom_security/reset_password.html tests/templates/custom_security/send_confirmation.html tests/templates/custom_security/send_login.html tests/templates/security/email/reset_instructions.htmlFlask-Security-1.7.5/Flask_Security.egg-info/top_level.txt0000644000076500000240000000001712627667277023660 0ustar mattstaff00000000000000flask_security Flask-Security-1.7.5/MANIFEST.in0000644000076500000240000000031012332222110016175 0ustar mattstaff00000000000000include README.rst include requirements.txt include requirements-dev.txt include tox.ini recursive-include flask_security/templates *.* recursive-include tests * recursive-exclude tests/__pycache__ * Flask-Security-1.7.5/PKG-INFO0000644000076500000240000000367212627667302015601 0ustar mattstaff00000000000000Metadata-Version: 1.1 Name: Flask-Security Version: 1.7.5 Summary: Simple security for Flask apps Home-page: https://github.com/mattupstate/flask-security Author: Matt Wright Author-email: matt@nobien.net License: MIT Description: Flask-Security ============== .. image:: https://secure.travis-ci.org/mattupstate/flask-security.png?branch=develop .. image:: https://pypip.in/v/Flask-Security/badge.png :target: https://pypi.python.org/pypi/Flask-Security/ :alt: Latest Version .. image:: https://coveralls.io/repos/mattupstate/flask-security/badge.png?branch=develop :target: https://coveralls.io/r/mattupstate/flask-security .. image:: https://pypip.in/d/Flask-Security/badge.png :target: https://pypi.python.org/pypi//Flask-Security/ :alt: Downloads .. image:: https://pypip.in/license/Flask-Security/badge.png :target: https://pypi.python.org/pypi/Flask-Security/ :alt: License Flask-Security quickly adds security features to your Flask application. Resources --------- - `Documentation `_ - `Issue Tracker `_ - `Code `_ - `Development Version `_ Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Flask-Security-1.7.5/README.rst0000644000076500000240000000207712332222110016142 0ustar mattstaff00000000000000Flask-Security ============== .. image:: https://secure.travis-ci.org/mattupstate/flask-security.png?branch=develop .. image:: https://pypip.in/v/Flask-Security/badge.png :target: https://pypi.python.org/pypi/Flask-Security/ :alt: Latest Version .. image:: https://coveralls.io/repos/mattupstate/flask-security/badge.png?branch=develop :target: https://coveralls.io/r/mattupstate/flask-security .. image:: https://pypip.in/d/Flask-Security/badge.png :target: https://pypi.python.org/pypi//Flask-Security/ :alt: Downloads .. image:: https://pypip.in/license/Flask-Security/badge.png :target: https://pypi.python.org/pypi/Flask-Security/ :alt: License Flask-Security quickly adds security features to your Flask application. Resources --------- - `Documentation `_ - `Issue Tracker `_ - `Code `_ - `Development Version `_ Flask-Security-1.7.5/requirements-dev.txt0000644000076500000240000000027312627667222020537 0ustar mattstaff00000000000000Flask-SQLAlchemy>=1.0 bcrypt>=1.0.2,<2.0.0 flask-mongoengine>=0.7.0,<0.7.3 flask-peewee>=0.6.5 pymongo==2.8 pytest>=2.5.2 pytest-cov>=1.6 pytest-flakes>=0.2 pytest-pep8>=1.0.5 tox>=1.7.0 Flask-Security-1.7.5/requirements.txt0000644000076500000240000000017512627667222017764 0ustar mattstaff00000000000000Flask>=0.9 Flask-Login>=0.3.0,<0.4 Flask-Mail>=0.7.3 Flask-Principal>=0.3.3 Flask-WTF>=0.8 itsdangerous>=0.17 passlib>=1.6.1 Flask-Security-1.7.5/setup.cfg0000644000076500000240000000052412627667302016316 0ustar mattstaff00000000000000[build_sphinx] source-dir = docs/ build-dir = docs/_build [upload_sphinx] upload-dir = docs/_build/html [pytest] pep8maxlinelength = 99 pep8ignore = docs/* ALL scripts/* ALL flakes-ignore = ImportStarUsed flask_security/__init__.py UnusedImport docs/* ALL scripts/* ALL [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 Flask-Security-1.7.5/setup.py0000644000076500000240000000344212627667276016223 0ustar mattstaff00000000000000""" Flask-Security ============== """ import multiprocessing # pragma: no flakes import sys from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand def get_requirements(suffix=''): with open('requirements%s.txt' % suffix) as f: rv = f.read().splitlines() return rv def get_long_description(): with open('README.rst') as f: rv = f.read() return rv class PyTest(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [ '-xrs', '--cov', 'flask_security', '--cov-report', 'term-missing', '--pep8', '--flakes', '--cache-clear' ] self.test_suite = True def run_tests(self): import pytest errno = pytest.main(self.test_args) sys.exit(errno) setup( name='Flask-Security', version='1.7.5', url='https://github.com/mattupstate/flask-security', license='MIT', author='Matt Wright', author_email='matt@nobien.net', description='Simple security for Flask apps', long_description=get_long_description(), packages=find_packages(), zip_safe=False, include_package_data=True, platforms='any', install_requires=get_requirements(), tests_require=get_requirements('-dev'), cmdclass={'test': PyTest}, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ] ) Flask-Security-1.7.5/tests/0000755000076500000240000000000012627667302015636 5ustar mattstaff00000000000000Flask-Security-1.7.5/tests/.DS_Store0000644000076500000240000001400412345621153017307 0ustar mattstaff00000000000000Bud1latesbw templatesbwspblobbplist00  \WindowBounds[ShowSidebar]ShowStatusBar[ShowPathbar[ShowToolbar\SidebarWidth_{{960, 4}, {960, 1024}}  *".|jd j j D]#\}}||jd |j /Users/matt/Workspaces/Python/flask-security/tests/conftest.pytindex.ss/profilecSstdddS(Ns index.htmlRs Profile Page(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytprofile2ss /post_logincSstdddS(Ns index.htmlRs Post Login(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt post_login7ss/httpcSsdS(NsHTTP Authentication((((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pythttp<ss/http_custom_realmsMy RealmcSstdddS(Ns index.htmlRsHTTP Authentication(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pythttp_custom_realmAss/tokentmethodstGETtPOSTcSstdddS(Ns index.htmlRsToken Authentication(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyttokenFss /multi_authtsessionR(tbasiccSstdddS(Ns index.htmlRsSession, Token, Basic auth(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt multi_authKss /post_logoutcSstdddS(Ns index.htmlRs Post Logout(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt post_logoutPss/post_registercSstdddS(Ns index.htmlRs Post Register(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt post_registerTss/admintadmincSstdddS(Ns index.htmlRs Admin Page(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyR.Xss/admin_and_editorteditorcSstdddS(Ns index.htmlRsAdmin and Editor Page(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytadmin_and_editor]ss/admin_or_editorcSstdddS(Ns index.htmlRsAdmin or Editor Page(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytadmin_or_editorbss /unauthorizedcSs tdS(Nsunauthorized.html(R(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt unauthorizedgss/page1cSsdS(NsPage 1((((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytpage_1ks(Rt__name__Rtresponse_classtTruetdebugtconfigtFalsetkeywordstuppertkwargstitemsRtmailtrouteR R R R R R(trequesttapptopttkeytvalueR>R R!R"R#R$R(R+R,R-R.R0R1R2R3((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRAsN        $#          csddlm}dttjjddid6dd6d d 6d 6|jd <||d jtffdYdjtffdY}|j fdt |S(Ni(t MongoEnginesflask_security_test_%st.t_tdbt localhostthostiitporttaliastMONGODB_SETTINGStRolecsEeZjdededdZjddZid6ZRS(trequiredtuniquet max_lengthiPitdb_alias(R4t __module__t StringFieldR6tnamet descriptiontmeta((RHtdb_name(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRNstUsercseZjdeddZjddZjdeddZjZ jZ jddZ jddZ j ZjdeZjZjjdgZid6ZRS(RPRQiROidtdefaultRR(R4RSRTR6temailtusernameR9tpasswordt DateTimeFieldt last_login_attcurrent_login_att last_login_iptcurrent_login_iptIntFieldt login_countt BooleanFieldtactivet confirmed_att ListFieldtReferenceFieldtrolesRW((RNRHRX(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRYs    csjjS(N(t connectiont drop_database((RHRX(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyts( tflask_mongoengineREtstrttimetreplaceR8tDocumentRRt addfinalizerR(R@RARERY((RNRHRXs>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytmongoengine_datastoreqs" %(cs2ddlm}tjdddddt|\}d|jd <||jd jd jj d jd jj ddj t ffdY}dj t ffdY}|j jWdQX|jfdt||S(Ni(t SQLAlchemytprefixsflask-security-test-dbtsuffixs.dbtdirs sqlite:///tSQLALCHEMY_DATABASE_URIt roles_userstuser_idsuser.idtrole_idsrole.idRNcsYeZjjdeZjjddeZjjdZRS(t primary_keyiPRPi( R4RStColumntIntegerR6tidtStringRURV((RH(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRNsRYc s.eZjjdeZjjddeZjjdZjjdZ jj Z jj Z jjdZ jjdZjjZjjZjj Zjdddjddd ZRS( R}iRPidRNt secondarytbackreftuserstlazytdynamic(R4RSR~RR6RRR[R\R]tDateTimeR_R`RaRbRdtBooleanRfRgt relationshipRRj((RHRz(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRYscs tjS(N(tostremove((tpath(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRms(tflask_sqlalchemyRuttempfiletmkstempRoR8tTableR~Rt ForeignKeytModelRRt app_contextt create_allRsR(R@RAttmpdirRutfRNRY((RHRRzs>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytsqlalchemy_datastores* !'"% cs[ddlmmmmmddlm}tj dddddt |\}id 6d d 6|j d <||}d |j t ffdYd|j tffdYd|j ffdY}|j)x!|fD]}|jqWWdQX|jfdt||S(Ni(t TextFieldR^t IntegerFieldRetForeignKeyField(tDatabaseRvsflask-security-test-dbRws.dbRxRUspeewee.SqliteDatabasetenginetDATABASERNcs&eZdeZdeZRS(RPtnull(R4RSR6RURV((R(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRNsRYcseZZZdeZdeZdeZdeZdeZ deZ deZ deZ RS(RRZ( R4RSR[R\R6R]R_R`RaRbRdRfRg((ReR^RR(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRYs  t UserRolescsPeZdZddZddZedZedZRS(s| Peewee does not have built-in many-to-many support, so we have to create this mapping class to link users to roles.t related_nameRjRcSs |jjS(N(troleRU(tself((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRmscSs |jjS(N(RRV(R((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRms(R4RSt__doc__tuserRtpropertyRURV((RRNRY(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRs cs tjS(N(RR((R(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRms(tpeeweeRR^RReRtflask_peewee.dbRRRRoR8RRRRt create_tableRsR(R@RARRRRHRR((ReR^RRRNRRYRs>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytpeewee_datastores(* "+ % csfd}|S(Ncstd_S(Nt datastore(Rtsecurity((RAR(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytcreates((RARR((RARs>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytsqlalchemy_appscsfd}|S(Ncstd_S(NR(RR((RAR(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRs((RARR((RARs>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt peewee_appscsfd}|S(Ncstd_S(NR(RR((RARt(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRs((RARtR((RARts>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytmongoengine_appscCs|}t||jS(N(Rt test_client(R@RRA((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytclients  csfd}|S(Ncs&jd|d|}|jdS(Nt SECURITY_MSG_isutf-8(R8tencode(RCR<trv(RA(s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pytfns((RAR((RAs>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyt get_messagestparamst sqlalchemyt mongoengineRcCsL|jdkr|}n0|jdkr0|}n|jdkrH|}n|S(NRRR(tparam(R@RRtRR((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyRs   (%RRRRptpytesttflaskRRt flask_mailRtflask_securityRRRRRRR R R R R RtutilsRRtfixtureRARtRRRRRRRR(((s>/Users/matt/Workspaces/Python/flask-security/tests/conftest.pyts$    RX&*/Flask-Security-1.7.5/tests/templates/0000755000076500000240000000000012627667302017634 5ustar mattstaff00000000000000Flask-Security-1.7.5/tests/templates/_messages.html0000644000076500000240000000040512332222110022440 0ustar mattstaff00000000000000{%- with messages = get_flashed_messages(with_categories=true) -%} {% if messages %}
    {% for category, message in messages %}
  • {{ message }}
  • {% endfor %}
{% endif %} {%- endwith %}Flask-Security-1.7.5/tests/templates/_nav.html0000644000076500000240000000130612627667222021446 0ustar mattstaff00000000000000{%- if current_user.is_authenticated -%}

Hello {{ current_user.email }}

{%- endif %}
  • Index
  • Profile
  • {% if current_user.has_role('admin') -%}
  • Admin
  • {% endif -%} {% if current_user.has_role('admin') or current_user.has_role('editor') -%}
  • Admin or Editor
  • {% endif -%}
  • {%- if current_user.is_authenticated -%} Log out {%- else -%} Log in {%- endif -%}
Flask-Security-1.7.5/tests/templates/custom_security/0000755000076500000240000000000012627667302023075 5ustar mattstaff00000000000000Flask-Security-1.7.5/tests/templates/custom_security/change_password.html0000644000076500000240000000005612417007016027116 0ustar mattstaff00000000000000CUSTOM CHANGE PASSWORD {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/custom_security/forgot_password.html0000644000076500000240000000005612417007016027171 0ustar mattstaff00000000000000CUSTOM FORGOT PASSWORD {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/custom_security/login_user.html0000644000076500000240000000005112417007016026110 0ustar mattstaff00000000000000CUSTOM LOGIN USER {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/custom_security/register_user.html0000644000076500000240000000005412417007016026627 0ustar mattstaff00000000000000CUSTOM REGISTER USER {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/custom_security/reset_password.html0000644000076500000240000000005512417007016027012 0ustar mattstaff00000000000000CUSTOM RESET PASSWORD {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/custom_security/send_confirmation.html0000644000076500000240000000006012417007016027443 0ustar mattstaff00000000000000CUSTOM SEND CONFIRMATION {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/custom_security/send_login.html0000644000076500000240000000005112417007016026063 0ustar mattstaff00000000000000CUSTOM SEND LOGIN {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/index.html0000644000076500000240000000011512332222110021577 0ustar mattstaff00000000000000{% include "_messages.html" %} {% include "_nav.html" %}

{{ content }}

Flask-Security-1.7.5/tests/templates/register.html0000644000076500000240000000100512332222110022313 0ustar mattstaff00000000000000{% include "_messages.html" %} {% include "_nav.html" %}

Register

{{ register_user_form.hidden_tag() }} {{ register_user_form.email.label }} {{ register_user_form.email }}
{{ register_user_form.password.label }} {{ register_user_form.password }}
{{ register_user_form.password_confirm.label }} {{ register_user_form.password_confirm }}
{{ register_user_form.submit }}

{{ content }}

Flask-Security-1.7.5/tests/templates/security/0000755000076500000240000000000012627667302021503 5ustar mattstaff00000000000000Flask-Security-1.7.5/tests/templates/security/email/0000755000076500000240000000000012627667302022572 5ustar mattstaff00000000000000Flask-Security-1.7.5/tests/templates/security/email/reset_instructions.html0000644000076500000240000000006112417007016027406 0ustar mattstaff00000000000000CUSTOM RESET INSTRUCTIONS {{ global }} {{ foo }} Flask-Security-1.7.5/tests/templates/unauthorized.html0000644000076500000240000000016612332222110023217 0ustar mattstaff00000000000000{% include "_messages.html" %} {% include "_nav.html" %}

You are not allowed to access the requested resouce

Flask-Security-1.7.5/tests/test_changeable.py0000644000076500000240000001007612627667222021325 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_changeable ~~~~~~~~~~~~~~~ Changeable tests """ import pytest from flask import Flask from flask_security.core import UserMixin from flask_security.signals import password_changed from utils import authenticate pytestmark = pytest.mark.changeable() def test_recoverable_flag(app, client, get_message): recorded = [] @password_changed.connect_via(app) def on_password_changed(app, user): assert isinstance(app, Flask) assert isinstance(user, UserMixin) recorded.append(user) authenticate(client) # Test change view response = client.get('/change', follow_redirects=True) assert b'Change password' in response.data # Test wrong original password response = client.post('/change', data={ 'password': 'notpassword', 'new_password': 'newpassword', 'new_password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('INVALID_PASSWORD') in response.data # Test mismatch response = client.post('/change', data={ 'password': 'password', 'new_password': 'newpassword', 'new_password_confirm': 'notnewpassword' }, follow_redirects=True) assert get_message('RETYPE_PASSWORD_MISMATCH') in response.data # Test missing password response = client.post('/change', data={ 'password': ' ', 'new_password': '', 'new_password_confirm': '' }, follow_redirects=True) assert get_message('PASSWORD_NOT_PROVIDED') in response.data # Test bad password response = client.post('/change', data={ 'password': 'password', 'new_password': 'a', 'new_password_confirm': 'a' }, follow_redirects=True) assert get_message('PASSWORD_INVALID_LENGTH') in response.data # Test same as previous response = client.post('/change', data={ 'password': 'password', 'new_password': 'password', 'new_password_confirm': 'password' }, follow_redirects=True) assert get_message('PASSWORD_IS_THE_SAME') in response.data # Test successful submit sends email notification with app.mail.record_messages() as outbox: response = client.post('/change', data={ 'password': 'password', 'new_password': 'newpassword', 'new_password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('PASSWORD_CHANGE') in response.data assert b'Home Page' in response.data assert len(recorded) == 1 assert len(outbox) == 1 assert "Your password has been changed" in outbox[0].html # Test JSON data = ('{"password": "newpassword", "new_password": "newpassword2", ' '"new_password_confirm": "newpassword2"}') response = client.post('/change', data=data, headers={'Content-Type': 'application/json'}) assert response.status_code == 200 assert response.headers['Content-Type'] == 'application/json' @pytest.mark.settings(change_url='/custom_change') def test_custom_change_url(client): authenticate(client) response = client.get('/custom_change') assert response.status_code == 200 @pytest.mark.settings(change_password_template='custom_security/change_password.html') def test_custom_change_template(client): authenticate(client) response = client.get('/change') assert b'CUSTOM CHANGE PASSWORD' in response.data @pytest.mark.settings(send_password_change_email=False) def test_disable_change_emails(app, client): with app.mail.record_messages() as outbox: client.post('/change', data={ 'password': 'password', 'new_password': 'newpassword', 'new_password_confirm': 'newpassword' }, follow_redirects=True) assert len(outbox) == 0 @pytest.mark.settings(post_change_view='/profile') def test_custom_post_change_view(client): authenticate(client) response = client.post('/change', data={ 'password': 'password', 'new_password': 'newpassword', 'new_password_confirm': 'newpassword' }, follow_redirects=True) assert b'Profile Page' in response.data Flask-Security-1.7.5/tests/test_common.py0000644000076500000240000002334412627667222020546 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_common ~~~~~~~~~~~ Test common functionality """ import base64 try: from cookielib import Cookie except ImportError: from http.cookiejar import Cookie from utils import authenticate, json_authenticate, logout def test_login_view(client): response = client.get('/login') assert b'

Login

' in response.data def test_authenticate(client): response = authenticate(client) assert response.status_code == 302 response = authenticate(client, follow_redirects=True) assert b'Hello matt@lp.com' in response.data def test_authenticate_with_next(client): data = dict(email='matt@lp.com', password='password') response = client.post('/login?next=/page1', data=data, follow_redirects=True) assert b'Page 1' in response.data def test_authenticate_with_invalid_next(client, get_message): data = dict(email='matt@lp.com', password='password') response = client.post('/login?next=http://google.com', data=data) assert get_message('INVALID_REDIRECT') in response.data def test_authenticate_with_invalid_malformed_next(client, get_message): data = dict(email='matt@lp.com', password='password') response = client.post('/login?next=http:///google.com', data=data) assert get_message('INVALID_REDIRECT') in response.data def test_authenticate_case_insensitive_email(app, client): response = authenticate(client, 'MATT@lp.com', follow_redirects=True) assert b'Hello matt@lp.com' in response.data def test_unprovided_username(client, get_message): response = authenticate(client, "") assert get_message('EMAIL_NOT_PROVIDED') in response.data def test_unprovided_password(client, get_message): response = authenticate(client, password="") assert get_message('PASSWORD_NOT_PROVIDED') in response.data def test_invalid_user(client, get_message): response = authenticate(client, email="bogus@bogus.com") assert get_message('USER_DOES_NOT_EXIST') in response.data def test_bad_password(client, get_message): response = authenticate(client, password="bogus") assert get_message('INVALID_PASSWORD') in response.data def test_inactive_user(client, get_message): response = authenticate(client, "tiya@lp.com", "password") assert get_message('DISABLED_ACCOUNT') in response.data def test_unset_password(client, get_message): response = authenticate(client, "jess@lp.com", "password") assert get_message('PASSWORD_NOT_SET') in response.data def test_logout(client): authenticate(client) response = logout(client, follow_redirects=True) assert b'Home Page' in response.data def test_missing_session_access(client, get_message): response = client.get('/profile', follow_redirects=True) assert get_message('LOGIN') in response.data def test_has_session_access(client): authenticate(client) response = client.get("/profile", follow_redirects=True) assert b'profile' in response.data def test_authorized_access(client): authenticate(client) response = client.get("/admin") assert b'Admin Page' in response.data def test_unauthorized_access(client, get_message): authenticate(client, "joe@lp.com") response = client.get("/admin", follow_redirects=True) assert get_message('UNAUTHORIZED') in response.data def test_roles_accepted(client): for user in ("matt@lp.com", "joe@lp.com"): authenticate(client, user) response = client.get("/admin_or_editor") assert b'Admin or Editor Page' in response.data logout(client) authenticate(client, "jill@lp.com") response = client.get("/admin_or_editor", follow_redirects=True) assert b'Home Page' in response.data def test_unauthenticated_role_required(client, get_message): response = client.get('/admin', follow_redirects=True) assert get_message('UNAUTHORIZED') in response.data def test_multiple_role_required(client): for user in ("matt@lp.com", "joe@lp.com"): authenticate(client, user) response = client.get("/admin_and_editor", follow_redirects=True) assert b'Home Page' in response.data client.get('/logout') authenticate(client, 'dave@lp.com') response = client.get("/admin_and_editor", follow_redirects=True) assert b'Admin and Editor Page' in response.data def test_ok_json_auth(client): response = json_authenticate(client) assert response.jdata['meta']['code'] == 200 assert 'authentication_token' in response.jdata['response']['user'] def test_invalid_json_auth(client): response = json_authenticate(client, password='junk') assert b'"code": 400' in response.data def test_token_auth_via_querystring_valid_token(client): response = json_authenticate(client) token = response.jdata['response']['user']['authentication_token'] response = client.get('/token?auth_token=' + token) assert b'Token Authentication' in response.data def test_token_auth_via_header_valid_token(client): response = json_authenticate(client) token = response.jdata['response']['user']['authentication_token'] headers = {"Authentication-Token": token} response = client.get('/token', headers=headers) assert b'Token Authentication' in response.data def test_token_auth_via_querystring_invalid_token(client): response = client.get('/token?auth_token=X') assert 401 == response.status_code def test_token_auth_via_header_invalid_token(client): response = client.get('/token', headers={"Authentication-Token": 'X'}) assert 401 == response.status_code def test_http_auth(client): response = client.get('/http', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password").decode('utf-8') }) assert b'HTTP Authentication' in response.data def test_http_auth_no_authorization(client): response = client.get('/http', headers={}) assert b'

Unauthorized

' in response.data assert 'WWW-Authenticate' in response.headers assert 'Basic realm="Login Required"' == response.headers['WWW-Authenticate'] def test_invalid_http_auth_invalid_username(client): response = client.get('/http', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"bogus:bogus").decode('utf-8') }) assert b'

Unauthorized

' in response.data assert 'WWW-Authenticate' in response.headers assert 'Basic realm="Login Required"' == response.headers['WWW-Authenticate'] def test_invalid_http_auth_bad_password(client): response = client.get('/http', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus").decode('utf-8') }) assert b'

Unauthorized

' in response.data assert 'WWW-Authenticate' in response.headers assert 'Basic realm="Login Required"' == response.headers['WWW-Authenticate'] def test_custom_http_auth_realm(client): response = client.get('/http_custom_realm', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus").decode('utf-8') }) assert b'

Unauthorized

' in response.data assert 'WWW-Authenticate' in response.headers assert 'Basic realm="My Realm"' == response.headers['WWW-Authenticate'] def test_multi_auth_basic(client): response = client.get('/multi_auth', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:password").decode('utf-8') }) assert b'Basic' in response.data response = client.get('/multi_auth') assert response.status_code == 401 def test_multi_auth_basic_invalid(client): response = client.get('/multi_auth', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"bogus:bogus").decode('utf-8') }) assert b'

Unauthorized

' in response.data assert 'WWW-Authenticate' in response.headers assert 'Basic realm="Login Required"' == response.headers['WWW-Authenticate'] response = client.get('/multi_auth') print(response.headers) assert response.status_code == 401 def test_multi_auth_token(client): response = json_authenticate(client) token = response.jdata['response']['user']['authentication_token'] response = client.get('/multi_auth?auth_token=' + token) assert b'Token' in response.data def test_multi_auth_session(client): authenticate(client, ) response = client.get('/multi_auth') assert b'Session' in response.data def test_user_deleted_during_session_reverts_to_anonymous_user(app, client): authenticate(client) with app.test_request_context('/'): user = app.security.datastore.find_user(email='matt@lp.com') app.security.datastore.delete_user(user) app.security.datastore.commit() response = client.get('/') assert b'Hello matt@lp.com' not in response.data def test_remember_token(client): response = authenticate(client, follow_redirects=False) client.cookie_jar.clear_session_cookies() response = client.get('/profile') assert b'profile' in response.data def test_token_loader_does_not_fail_with_invalid_token(client): c = Cookie(version=0, name='remember_token', value='None', port=None, port_specified=False, domain='www.example.com', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False) client.cookie_jar.set_cookie(c) response = client.get('/') assert b'BadSignature' not in response.data def test_sending_auth_token_with_json(client): response = json_authenticate(client) token = response.jdata['response']['user']['authentication_token'] data = '{"auth_token": "%s"}' % token response = client.post('/token', data=data, headers={'Content-Type': 'application/json'}) assert b'Token Authentication' in response.data Flask-Security-1.7.5/tests/test_configuration.py0000644000076500000240000000254312345635310022111 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_configuration ~~~~~~~~~~~~~~~~~~ Basic configuration tests """ import base64 import pytest from utils import authenticate, logout @pytest.mark.settings( logout_url='/custom_logout', login_url='/custom_login', post_login_view='/post_login', post_logout_view='/post_logout', default_http_auth_realm='Custom Realm') def test_view_configuration(client): response = client.get('/custom_login') assert b"

Login

" in response.data response = authenticate(client, endpoint='/custom_login') assert 'location' in response.headers assert response.headers['Location'] == 'http://localhost/post_login' response = logout(client, endpoint='/custom_logout') assert 'location' in response.headers assert response.headers['Location'] == 'http://localhost/post_logout' response = client.get('/http', headers={ 'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus") }) assert b'

Unauthorized

' in response.data assert 'WWW-Authenticate' in response.headers assert 'Basic realm="Custom Realm"' == response.headers['WWW-Authenticate'] @pytest.mark.settings(login_user_template='custom_security/login_user.html') def test_template_configuration(client): response = client.get('/login') assert b'CUSTOM LOGIN USER' in response.data Flask-Security-1.7.5/tests/test_confirmable.py0000644000076500000240000001317512627667222021540 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_confirmable ~~~~~~~~~~~~~~~~ Confirmable tests """ import time import pytest from flask import Flask from flask_security.core import UserMixin from flask_security.signals import user_confirmed, confirm_instructions_sent from flask_security.utils import capture_registrations from utils import authenticate, logout pytestmark = pytest.mark.confirmable() @pytest.mark.registerable() def test_confirmable_flag(app, client, sqlalchemy_datastore, get_message): recorded_confirms = [] recorded_instructions_sent = [] @user_confirmed.connect_via(app) def on_confirmed(app, user): assert isinstance(app, Flask) assert isinstance(user, UserMixin) recorded_confirms.append(user) @confirm_instructions_sent.connect_via(app) def on_instructions_sent(app, user): assert isinstance(app, Flask) assert isinstance(user, UserMixin) recorded_instructions_sent.append(user) # Test login before confirmation email = 'dude@lp.com' with capture_registrations() as registrations: data = dict(email=email, password='password', next='') response = client.post('/register', data=data) assert response.status_code == 302 response = authenticate(client, email=email) assert get_message('CONFIRMATION_REQUIRED') in response.data # Test invalid token response = client.get('/confirm/bogus', follow_redirects=True) assert get_message('INVALID_CONFIRMATION_TOKEN') in response.data # Test JSON response = client.post('/confirm', data='{"email": "matt@lp.com"}', headers={ 'Content-Type': 'application/json' }) assert response.status_code == 200 assert response.headers['Content-Type'] == 'application/json' assert 'user' in response.jdata['response'] assert len(recorded_instructions_sent) == 1 # Test ask for instructions with invalid email response = client.post('/confirm', data=dict(email='bogus@bogus.com')) assert get_message('USER_DOES_NOT_EXIST') in response.data # Test resend instructions response = client.post('/confirm', data=dict(email=email)) assert get_message('CONFIRMATION_REQUEST', email=email) in response.data assert len(recorded_instructions_sent) == 2 # Test confirm token = registrations[0]['confirm_token'] response = client.get('/confirm/' + token, follow_redirects=True) assert get_message('EMAIL_CONFIRMED') in response.data assert len(recorded_confirms) == 1 # Test already confirmed response = client.get('/confirm/' + token, follow_redirects=True) assert get_message('ALREADY_CONFIRMED') in response.data # Test already confirmed when asking for confirmation instructions logout(client) response = client.get('/confirm') assert response.status_code == 200 response = client.post('/confirm', data=dict(email=email)) assert get_message('ALREADY_CONFIRMED') in response.data # Test user was deleted before confirmation with capture_registrations() as registrations: data = dict(email='mary@lp.com', password='password', next='') client.post('/register', data=data) user = registrations[0]['user'] token = registrations[0]['confirm_token'] with app.app_context(): sqlalchemy_datastore.delete(user) sqlalchemy_datastore.commit() response = client.get('/confirm/' + token, follow_redirects=True) assert get_message('INVALID_CONFIRMATION_TOKEN') in response.data @pytest.mark.registerable() @pytest.mark.settings(confirm_email_within='1 milliseconds') def test_expired_confirmation_token(client, get_message): with capture_registrations() as registrations: data = dict(email='mary@lp.com', password='password', next='') client.post('/register', data=data, follow_redirects=True) user = registrations[0]['user'] token = registrations[0]['confirm_token'] time.sleep(1) response = client.get('/confirm/' + token, follow_redirects=True) msg = get_message('CONFIRMATION_EXPIRED', within='1 milliseconds', email=user.email) assert msg in response.data @pytest.mark.registerable() @pytest.mark.settings(login_without_confirmation=True) def test_login_when_unconfirmed(client, get_message): data = dict(email='mary@lp.com', password='password', next='') response = client.post('/register', data=data, follow_redirects=True) assert b'mary@lp.com' in response.data @pytest.mark.registerable() @pytest.mark.settings(login_without_confirmation=True) def test_confirmation_different_user_when_logged_in(client, get_message): e1 = 'dude@lp.com' e2 = 'lady@lp.com' with capture_registrations() as registrations: for e in e1, e2: data = dict(email=e, password='password', next='') client.post('/register', data=data) logout(client) token1 = registrations[0]['confirm_token'] token2 = registrations[1]['confirm_token'] client.get('/confirm/' + token1, follow_redirects=True) logout(client) authenticate(client, email=e1) response = client.get('/confirm/' + token2, follow_redirects=True) assert get_message('EMAIL_CONFIRMED') in response.data assert b'Hello lady@lp.com' in response.data @pytest.mark.registerable() @pytest.mark.settings(recoverable=True) def test_cannot_reset_password_when_email_is_not_confirmed(client, get_message): email = 'dude@lp.com' data = dict(email=email, password='password', next='') response = client.post('/register', data=data, follow_redirects=True) response = client.post('/reset', data=dict(email=email), follow_redirects=True) assert get_message('CONFIRMATION_REQUIRED') in response.data Flask-Security-1.7.5/tests/test_context_processors.py0000644000076500000240000000544512627667222023226 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_context_processors ~~~~~~~~~~~~~~~~~~~~~~~ Context processor tests """ import pytest from utils import authenticate @pytest.mark.recoverable() @pytest.mark.registerable() @pytest.mark.confirmable() @pytest.mark.changeable() @pytest.mark.settings( login_without_confirmation=True, change_password_template='custom_security/change_password.html', login_user_template='custom_security/login_user.html', reset_password_template='custom_security/reset_password.html', forgot_password_template='custom_security/forgot_password.html', send_confirmation_template='custom_security/send_confirmation.html', register_user_template='custom_security/register_user.html') def test_context_processors(client, app): @app.security.context_processor def default_ctx_processor(): return {'global': 'global'} @app.security.forgot_password_context_processor def forgot_password(): return {'foo': 'bar'} response = client.get('/reset') assert b'global' in response.data assert b'bar' in response.data @app.security.login_context_processor def login(): return {'foo': 'bar'} response = client.get('/login') assert b'global' in response.data assert b'bar' in response.data @app.security.register_context_processor def register(): return {'foo': 'bar'} response = client.get('/register') assert b'global' in response.data assert b'bar' in response.data @app.security.reset_password_context_processor def reset_password(): return {'foo': 'bar'} response = client.get('/reset') assert b'global' in response.data assert b'bar' in response.data @app.security.change_password_context_processor def change_password(): return {'foo': 'bar'} authenticate(client) response = client.get('/change') assert b'global' in response.data assert b'bar' in response.data @app.security.send_confirmation_context_processor def send_confirmation(): return {'foo': 'bar'} response = client.get('/confirm') assert b'global' in response.data assert b'bar' in response.data @app.security.mail_context_processor def mail(): return {'foo': 'bar'} client.get('/logout') with app.mail.record_messages() as outbox: client.post('/reset', data=dict(email='matt@lp.com')) email = outbox[0] assert 'global' in email.html assert 'bar' in email.html @pytest.mark.passwordless() @pytest.mark.settings(send_login_template='custom_security/send_login.html') def test_passwordless_login_context_processor(app, client): @app.security.send_login_context_processor def send_login(): return {'foo': 'bar'} response = client.get('/login') assert b'bar' in response.data Flask-Security-1.7.5/tests/test_datastore.py0000644000076500000240000000771612332222110021221 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_datastore ~~~~~~~~~~~~~~ Datastore tests """ from pytest import raises from flask_security import UserMixin, RoleMixin from flask_security.datastore import Datastore, UserDatastore from utils import init_app_with_options class User(UserMixin): pass class Role(RoleMixin): pass def test_unimplemented_datastore_methods(): datastore = Datastore(None) assert datastore.db is None with raises(NotImplementedError): datastore.put(None) with raises(NotImplementedError): datastore.delete(None) assert not datastore.commit() def test_unimplemented_user_datastore_methods(): datastore = UserDatastore(None, None) with raises(NotImplementedError): datastore.find_user(None) with raises(NotImplementedError): datastore.find_role(None) with raises(NotImplementedError): datastore.get_user(None) def test_toggle_active(): datastore = UserDatastore(None, None) user = User() user.active = True assert datastore.toggle_active(user) is True assert not user.active assert datastore.toggle_active(user) is True assert user.active is True def test_deactivate_user(): datastore = UserDatastore(None, None) user = User() user.active = True assert datastore.deactivate_user(user) is True assert not user.active def test_activate_user(): datastore = UserDatastore(None, None) user = User() user.active = False assert datastore.activate_user(user) is True assert user.active is True def test_deactivate_returns_false_if_already_false(): datastore = UserDatastore(None, None) user = User() user.active = False assert not datastore.deactivate_user(user) def test_activate_returns_false_if_already_true(): datastore = UserDatastore(None, None) user = User() user.active = True assert not datastore.activate_user(user) def test_get_user(app, datastore): init_app_with_options(app, datastore, **{ 'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username') }) with app.app_context(): user_id = datastore.find_user(email='matt@lp.com').id user = datastore.get_user(user_id) assert user is not None user = datastore.get_user('matt@lp.com') assert user is not None user = datastore.get_user('matt') assert user is not None def test_find_role(app, datastore): init_app_with_options(app, datastore) role = datastore.find_role('admin') assert role is not None role = datastore.find_role('bogus') assert role is None def test_add_role_to_user(app, datastore): init_app_with_options(app, datastore) # Test with user object user = datastore.find_user(email='matt@lp.com') assert user.has_role('editor') is False assert datastore.add_role_to_user(user, 'editor') is True assert datastore.add_role_to_user(user, 'editor') is False assert user.has_role('editor') is True # Test with email assert datastore.add_role_to_user('jill@lp.com', 'editor') is True user = datastore.find_user(email='jill@lp.com') assert user.has_role('editor') is True # Test remove role assert datastore.remove_role_from_user(user, 'editor') is True assert datastore.remove_role_from_user(user, 'editor') is False def test_create_user_with_roles(app, datastore): init_app_with_options(app, datastore) role = datastore.find_role('admin') datastore.commit() user = datastore.create_user(email='dude@lp.com', username='dude', password='password', roles=[role]) datastore.commit() user = datastore.find_user(email='dude@lp.com') assert user.has_role('admin') is True def test_delete_user(app, datastore): init_app_with_options(app, datastore) user = datastore.find_user(email='matt@lp.com') datastore.delete_user(user) datastore.commit() user = datastore.find_user(email='matt@lp.com') assert user is None Flask-Security-1.7.5/tests/test_entities.py0000644000076500000240000000166412332222110021053 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_entities ~~~~~~~~~~~~~ Entity tests """ from flask_security import RoleMixin, UserMixin, AnonymousUser class Role(RoleMixin): def __init__(self, name): self.name = name class User(UserMixin): def __init__(self, roles): self.roles = roles def test_role_mixin_equal(): admin1 = Role('admin') admin2 = Role('admin') assert admin1 == admin2 def test_role_mixin_not_equal(): admin = Role('admin') editor = Role('editor') assert admin != editor def test_user_mixin_has_role_with_string(): admin = Role('admin') editor = Role('editor') user = User([admin, editor]) assert user.has_role('admin') is True assert user.has_role('editor') is True assert user.has_role(admin) is True assert user.has_role(editor) is True def test_anonymous_user_has_no_roles(): user = AnonymousUser() assert not user.has_role('admin') Flask-Security-1.7.5/tests/test_hashing.py0000644000076500000240000000207112332222110020641 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_hashing ~~~~~~~~~~~~ hashing tests """ from pytest import raises from flask_security.utils import verify_password, encrypt_password from utils import authenticate, init_app_with_options def test_verify_password_bcrypt(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': 'salty' }) with app.app_context(): assert verify_password('pass', encrypt_password('pass')) def test_login_with_bcrypt_enabled(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': 'salty' }) response = authenticate(app.test_client(), follow_redirects=True) assert b'Home Page' in response.data def test_missing_hash_salt_option(app, sqlalchemy_datastore): with raises(RuntimeError): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', }) Flask-Security-1.7.5/tests/test_misc.py0000644000076500000240000001570712627667222020215 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_emails ~~~~~~~~~~~ Email functionality tests """ import pytest from flask_security import Security from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \ SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \ ChangePasswordForm, StringField, PasswordField, email_required, email_validator, \ valid_user_email from flask_security.utils import capture_reset_password_requests, md5, string_types from utils import authenticate, init_app_with_options, populate_data @pytest.mark.recoverable() def test_async_email_task(app, client): app.mail_sent = False @app.security.send_mail_task def send_email(msg): app.mail_sent = True client.post('/reset', data=dict(email='matt@lp.com')) assert app.mail_sent is True def test_register_blueprint_flag(app, sqlalchemy_datastore): app.security = Security(app, datastore=Security, register_blueprint=False) client = app.test_client() response = client.get('/login') assert response.status_code == 404 @pytest.mark.registerable() @pytest.mark.recoverable() @pytest.mark.changeable() def test_basic_custom_forms(app, sqlalchemy_datastore): class MyLoginForm(LoginForm): email = StringField('My Login Email Address Field') class MyRegisterForm(RegisterForm): email = StringField('My Register Email Address Field') class MyForgotPasswordForm(ForgotPasswordForm): email = StringField('My Forgot Email Address Field', validators=[email_required, email_validator, valid_user_email]) class MyResetPasswordForm(ResetPasswordForm): password = StringField('My Reset Password Field') class MyChangePasswordForm(ChangePasswordForm): password = PasswordField('My Change Password Field') app.security = Security(app, datastore=sqlalchemy_datastore, login_form=MyLoginForm, register_form=MyRegisterForm, forgot_password_form=MyForgotPasswordForm, reset_password_form=MyResetPasswordForm, change_password_form=MyChangePasswordForm) populate_data(app) client = app.test_client() response = client.get('/login') assert b'My Login Email Address Field' in response.data response = client.get('/register') assert b'My Register Email Address Field' in response.data response = client.get('/reset') assert b'My Forgot Email Address Field' in response.data with capture_reset_password_requests() as requests: response = client.post('/reset', data=dict(email='matt@lp.com')) token = requests[0]['token'] response = client.get('/reset/' + token) assert b'My Reset Password Field' in response.data authenticate(client) response = client.get('/change') assert b'My Change Password Field' in response.data @pytest.mark.registerable() @pytest.mark.confirmable() def test_confirmable_custom_form(app, sqlalchemy_datastore): app.config['SECURITY_REGISTERABLE'] = True app.config['SECURITY_CONFIRMABLE'] = True class MyRegisterForm(ConfirmRegisterForm): email = StringField('My Register Email Address Field') class MySendConfirmationForm(SendConfirmationForm): email = StringField('My Send Confirmation Email Address Field') app.security = Security(app, datastore=sqlalchemy_datastore, send_confirmation_form=MySendConfirmationForm, confirm_register_form=MyRegisterForm) client = app.test_client() response = client.get('/register') assert b'My Register Email Address Field' in response.data response = client.get('/confirm') assert b'My Send Confirmation Email Address Field' in response.data def test_passwordless_custom_form(app, sqlalchemy_datastore): app.config['SECURITY_PASSWORDLESS'] = True class MyPasswordlessLoginForm(PasswordlessLoginForm): email = StringField('My Passwordless Email Address Field') app.security = Security(app, datastore=sqlalchemy_datastore, passwordless_login_form=MyPasswordlessLoginForm) client = app.test_client() response = client.get('/login') assert b'My Passwordless Email Address Field' in response.data def test_addition_identity_attributes(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username') }) client = app.test_client() response = authenticate(client, email='matt', follow_redirects=True) assert b'Hello matt@lp.com' in response.data def test_flash_messages_off(app, sqlalchemy_datastore, get_message): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_FLASH_MESSAGES': False }) client = app.test_client() response = client.get('/profile') assert get_message('LOGIN') not in response.data def test_invalid_hash_scheme(app, sqlalchemy_datastore, get_message): with pytest.raises(ValueError): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bogus' }) def test_change_hash_type(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_SCHEMES': ['bcrypt', 'plaintext'] }) app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt' app.config['SECURITY_PASSWORD_SALT'] = 'salty' app.security = Security(app, datastore=sqlalchemy_datastore, register_blueprint=False) client = app.test_client() response = client.post('/login', data=dict(email='matt@lp.com', password='password')) assert response.status_code == 302 response = client.get('/logout') response = client.post('/login', data=dict(email='matt@lp.com', password='password')) assert response.status_code == 302 def test_md5(): data = md5(b'hello') assert isinstance(data, string_types) data = md5(u'hellö') assert isinstance(data, string_types) @pytest.mark.settings(password_salt=u'öööööööööööööööööööööööööööööööööö', password_hash='bcrypt') def test_password_unicode_password_salt(client): response = authenticate(client) assert response.status_code == 302 response = authenticate(client, follow_redirects=True) assert b'Hello matt@lp.com' in response.data def test_set_unauthorized_handler(app, client): @app.security.unauthorized_handler def unauthorized(): app.unauthorized_handler_set = True return 'unauthorized-handler-set', 401 app.unauthorized_handler_set = False authenticate(client, "joe@lp.com") response = client.get("/admin", follow_redirects=True) assert app.unauthorized_handler_set is True assert b'unauthorized-handler-set' in response.data assert response.status_code == 401 Flask-Security-1.7.5/tests/test_passwordless.py0000644000076500000240000000621312627667222022003 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_passwordless ~~~~~~~~~~~~~~~~~ Passwordless tests """ import time import pytest from flask import Flask from flask_security.core import UserMixin from flask_security.signals import login_instructions_sent from flask_security.utils import capture_passwordless_login_requests, string_types from utils import logout pytestmark = pytest.mark.passwordless() def test_trackable_flag(app, client, get_message): recorded = [] @login_instructions_sent.connect_via(app) def on_instructions_sent(app, user, login_token): assert isinstance(app, Flask) assert isinstance(user, UserMixin) assert isinstance(login_token, string_types) recorded.append(user) # Test disabled account response = client.post('/login', data=dict(email='tiya@lp.com'), follow_redirects=True) assert get_message('DISABLED_ACCOUNT') in response.data # Test login with json and valid email data = '{"email": "matt@lp.com", "password": "password"}' response = client.post('/login', data=data, headers={'Content-Type': 'application/json'}) assert response.status_code == 200 assert len(recorded) == 1 # Test login with json and invalid email data = '{"email": "nobody@lp.com", "password": "password"}' response = client.post('/login', data=data, headers={'Content-Type': 'application/json'}) assert b'errors' in response.data # Test sends email and shows appropriate response with capture_passwordless_login_requests() as requests: with app.mail.record_messages() as outbox: response = client.post('/login', data=dict(email='matt@lp.com'), follow_redirects=True) assert len(recorded) == 2 assert len(requests) == 1 assert len(outbox) == 1 assert 'user' in requests[0] assert 'login_token' in requests[0] user = requests[0]['user'] assert get_message('LOGIN_EMAIL_SENT', email=user.email) in response.data token = requests[0]['login_token'] response = client.get('/login/' + token, follow_redirects=True) assert get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') in response.data # Test already authenticated response = client.get('/login/' + token, follow_redirects=True) assert get_message('PASSWORDLESS_LOGIN_SUCCESSFUL') not in response.data logout(client) # Test invalid token response = client.get('/login/bogus', follow_redirects=True) assert get_message('INVALID_LOGIN_TOKEN') in response.data # Test login request with invalid email response = client.post('/login', data=dict(email='bogus@bogus.com')) assert get_message('USER_DOES_NOT_EXIST') in response.data @pytest.mark.settings(login_within='1 milliseconds') def test_expired_login_token(client, app, get_message): e = 'matt@lp.com' with capture_passwordless_login_requests() as requests: client.post('/login', data=dict(email=e), follow_redirects=True) token = requests[0]['login_token'] user = requests[0]['user'] time.sleep(1) response = client.get('/login/' + token, follow_redirects=True) assert get_message('LOGIN_EXPIRED', within='1 milliseconds', email=user.email) in response.data Flask-Security-1.7.5/tests/test_recoverable.py0000644000076500000240000001366612627667222021555 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_recoverable ~~~~~~~~~~~~~~~~ Recoverable functionality tests """ import time import pytest from flask import Flask from flask_security.core import UserMixin from flask_security.signals import reset_password_instructions_sent, password_reset from flask_security.utils import capture_reset_password_requests, string_types from utils import authenticate, logout pytestmark = pytest.mark.recoverable() def test_recoverable_flag(app, client, get_message): recorded_resets = [] recorded_instructions_sent = [] @password_reset.connect_via(app) def on_password_reset(app, user): recorded_resets.append(user) @reset_password_instructions_sent.connect_via(app) def on_instructions_sent(app, user, token): assert isinstance(app, Flask) assert isinstance(user, UserMixin) assert isinstance(token, string_types) recorded_instructions_sent.append(user) # Test the reset view response = client.get('/reset') assert b'

Send password reset instructions

' in response.data # Test submitting email to reset password creates a token and sends email with capture_reset_password_requests() as requests: with app.mail.record_messages() as outbox: response = client.post('/reset', data=dict(email='joe@lp.com'), follow_redirects=True) assert len(recorded_instructions_sent) == 1 assert len(outbox) == 1 assert response.status_code == 200 assert get_message('PASSWORD_RESET_REQUEST', email='joe@lp.com') in response.data token = requests[0]['token'] # Test view for reset token response = client.get('/reset/' + token) assert b'

Reset password

' in response.data # Test submitting a new password response = client.post('/reset/' + token, data={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('PASSWORD_RESET') in response.data assert len(recorded_resets) == 1 logout(client) # Test logging in with the new password response = authenticate(client, 'joe@lp.com', 'newpassword', follow_redirects=True) assert b'Hello joe@lp.com' in response.data logout(client) # Test submitting JSON response = client.post('/reset', data='{"email": "joe@lp.com"}', headers={ 'Content-Type': 'application/json' }) assert response.headers['Content-Type'] == 'application/json' assert 'user' not in response.jdata['response'] logout(client) # Test invalid email response = client.post('/reset', data=dict(email='bogus@lp.com'), follow_redirects=True) assert get_message('USER_DOES_NOT_EXIST') in response.data logout(client) # Test invalid token response = client.post('/reset/bogus', data={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('INVALID_RESET_PASSWORD_TOKEN') in response.data # Test mangled token token = ("WyIxNjQ2MzYiLCIxMzQ1YzBlZmVhM2VhZjYwODgwMDhhZGU2YzU0MzZjMiJd.BZEw_Q.lQyo3npdPZtcJ" "_sNHVHP103syjM&url_id=fbb89a8328e58c181ea7d064c2987874bc54a23d") response = client.post('/reset/' + token, data={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('INVALID_RESET_PASSWORD_TOKEN') in response.data @pytest.mark.settings(reset_password_within='1 milliseconds') def test_expired_reset_token(client, get_message): with capture_reset_password_requests() as requests: client.post('/reset', data=dict(email='joe@lp.com'), follow_redirects=True) user = requests[0]['user'] token = requests[0]['token'] time.sleep(1) response = client.post('/reset/' + token, data={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) msg = get_message('PASSWORD_RESET_EXPIRED', within='1 milliseconds', email=user.email) assert msg in response.data def test_used_reset_token(client, get_message): with capture_reset_password_requests() as requests: client.post('/reset', data=dict(email='joe@lp.com'), follow_redirects=True) token = requests[0]['token'] # use the token response = client.post('/reset/' + token, data={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('PASSWORD_RESET') in response.data logout(client) # attempt to use it a second time response2 = client.post('/reset/' + token, data={ 'password': 'otherpassword', 'password_confirm': 'otherpassword' }, follow_redirects=True) msg = get_message('INVALID_RESET_PASSWORD_TOKEN') assert msg in response2.data def test_reset_passwordless_user(client, get_message): with capture_reset_password_requests() as requests: client.post('/reset', data=dict(email='jess@lp.com'), follow_redirects=True) token = requests[0]['token'] # use the token response = client.post('/reset/' + token, data={ 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) assert get_message('PASSWORD_RESET') in response.data @pytest.mark.settings(reset_url='/custom_reset') def test_custom_reset_url(client): response = client.get('/custom_reset') assert response.status_code == 200 @pytest.mark.settings(reset_password_template='custom_security/reset_password.html', forgot_password_template='custom_security/forgot_password.html') def test_custom_reset_templates(client): response = client.get('/reset') assert b'CUSTOM FORGOT PASSWORD' in response.data with capture_reset_password_requests() as requests: client.post('/reset', data=dict(email='joe@lp.com'), follow_redirects=True) token = requests[0]['token'] response = client.get('/reset/' + token) assert b'CUSTOM RESET PASSWORD' in response.data Flask-Security-1.7.5/tests/test_registerable.py0000644000076500000240000001004412627667222021717 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_registerable ~~~~~~~~~~~~~~~~~ Registerable tests """ import pytest from flask import Flask from flask_security.core import UserMixin from flask_security.signals import user_registered from utils import authenticate, logout pytestmark = pytest.mark.registerable() @pytest.mark.settings(post_register_view='/post_register') def test_registerable_flag(client, app, get_message): recorded = [] # Test the register view response = client.get('/register') assert b"

Register

" in response.data # Test registering is successful, sends email, and fires signal @user_registered.connect_via(app) def on_user_registerd(app, user, confirm_token): assert isinstance(app, Flask) assert isinstance(user, UserMixin) assert confirm_token is None recorded.append(user) data = dict( email='dude@lp.com', password='password', password_confirm='password', next='' ) with app.mail.record_messages() as outbox: response = client.post('/register', data=data, follow_redirects=True) assert len(recorded) == 1 assert len(outbox) == 1 assert b'Post Register' in response.data logout(client) # Test user can login after registering response = authenticate(client, email='dude@lp.com', password='password') assert response.status_code == 302 logout(client) # Test registering with an existing email data = dict( email='dude@lp.com', password='password', password_confirm='password', next='' ) response = client.post('/register', data=data, follow_redirects=True) assert get_message('EMAIL_ALREADY_ASSOCIATED', email='dude@lp.com') in response.data # Test registering with an existing email but case insensitive data = dict( email='Dude@lp.com', password='password', password_confirm='password', next='' ) response = client.post('/register', data=data, follow_redirects=True) assert get_message('EMAIL_ALREADY_ASSOCIATED', email='Dude@lp.com') in response.data # Test registering with JSON data = '{ "email": "dude2@lp.com", "password": "password"}' response = client.post('/register', data=data, headers={'Content-Type': 'application/json'}) assert response.headers['content-type'] == 'application/json' assert response.jdata['meta']['code'] == 200 logout(client) # Test registering with invalid JSON data = '{ "email": "bogus", "password": "password"}' response = client.post('/register', data=data, headers={'Content-Type': 'application/json'}) assert response.headers['content-type'] == 'application/json' assert response.jdata['meta']['code'] == 400 logout(client) # Test ?next param data = dict(email='dude3@lp.com', password='password', password_confirm='password', next='') response = client.post('/register?next=/page1', data=data, follow_redirects=True) assert b'Page 1' in response.data @pytest.mark.settings(register_url='/custom_register', post_register_view='/post_register') def test_custom_register_url(client): response = client.get('/custom_register') assert b"

Register

" in response.data data = dict(email='dude@lp.com', password='password', password_confirm='password', next='') response = client.post('/custom_register', data=data, follow_redirects=True) assert b'Post Register' in response.data @pytest.mark.settings(register_user_template='custom_security/register_user.html') def test_custom_register_tempalate(client): response = client.get('/register') assert b'CUSTOM REGISTER USER' in response.data @pytest.mark.settings(send_register_email=False) def test_disable_register_emails(client, app): data = dict( email='dude@lp.com', password='password', password_confirm='password', next='' ) with app.mail.record_messages() as outbox: client.post('/register', data=data, follow_redirects=True) assert len(outbox) == 0 Flask-Security-1.7.5/tests/test_trackable.py0000644000076500000240000000235212627667222021202 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ test_trackable ~~~~~~~~~~~~~~ Trackable tests """ import pytest from utils import authenticate, logout pytestmark = pytest.mark.trackable() def test_trackable_flag(app, client): e = 'matt@lp.com' authenticate(client, email=e) logout(client) authenticate(client, email=e, headers={'X-Forwarded-For': '127.0.0.1'}) with app.app_context(): user = app.security.datastore.find_user(email=e) assert user.last_login_at is not None assert user.current_login_at is not None assert user.last_login_ip == 'untrackable' assert user.current_login_ip == '127.0.0.1' assert user.login_count == 2 def test_trackable_with_multiple_ips_in_headers(app, client): e = 'matt@lp.com' authenticate(client, email=e) logout(client) authenticate(client, email=e, headers={ 'X-Forwarded-For': '99.99.99.99, 88.88.88.88'}) with app.app_context(): user = app.security.datastore.find_user(email=e) assert user.last_login_at is not None assert user.current_login_at is not None assert user.last_login_ip == 'untrackable' assert user.current_login_ip == '88.88.88.88' assert user.login_count == 2 Flask-Security-1.7.5/tests/utils.py0000644000076500000240000000476512345615124017354 0ustar mattstaff00000000000000# -*- coding: utf-8 -*- """ utils ~~~~~ Test utils """ from flask import Response as BaseResponse, json from flask_security import Security from flask_security.utils import encrypt_password _missing = object def authenticate(client, email="matt@lp.com", password="password", endpoint=None, **kwargs): data = dict(email=email, password=password, remember='y') return client.post(endpoint or '/login', data=data, **kwargs) def json_authenticate(client, email="matt@lp.com", password="password", endpoint=None): data = '{"email": "%s", "password": "%s"}' % (email, password) return client.post(endpoint or '/login', content_type="application/json", data=data) def logout(client, endpoint=None, **kwargs): return client.get(endpoint or '/logout', **kwargs) def create_roles(ds): for role in ('admin', 'editor', 'author'): ds.create_role(name=role) ds.commit() def create_users(ds, count=None): users = [('matt@lp.com', 'matt', 'password', ['admin'], True), ('joe@lp.com', 'joe', 'password', ['editor'], True), ('dave@lp.com', 'dave', 'password', ['admin', 'editor'], True), ('jill@lp.com', 'jill', 'password', ['author'], True), ('tiya@lp.com', 'tiya', 'password', [], False), ('jess@lp.com', 'jess', None, [], True)] count = count or len(users) for u in users[:count]: pw = u[2] if pw is not None: pw = encrypt_password(pw) roles = [ds.find_or_create_role(rn) for rn in u[3]] ds.commit() user = ds.create_user(email=u[0], username=u[1], password=pw, active=u[4]) ds.commit() for role in roles: ds.add_role_to_user(user, role) ds.commit() def populate_data(app, user_count=None): ds = app.security.datastore with app.app_context(): create_roles(ds) create_users(ds, user_count) class Response(BaseResponse): # pragma: no cover @property def jdata(self): rv = getattr(self, '_cached_jdata', _missing) if rv is not _missing: return rv try: self._cached_jdata = json.loads(self.data) except ValueError: raise Exception('Invalid JSON response') return self._cached_jdata def init_app_with_options(app, datastore, **options): security_args = options.pop('security_args', {}) app.config.update(**options) app.security = Security(app, datastore=datastore, **security_args) populate_data(app) Flask-Security-1.7.5/tests/utils.pyc0000644000076500000240000000750512345626651017521 0ustar mattstaff00000000000000 TSc@sdZddlmZmZddlmZddlmZe Z ddddZ ddddZ dd Zd Zdd Zdd Zd efdYZdZdS(s% utils ~~~~~ Test utils i(tResponsetjson(tSecurity(tencrypt_passwords matt@lp.comtpasswordcKs7td|d|dd}|j|p*dd||S(NtemailRtremembertys/logintdata(tdicttpost(tclientRRtendpointtkwargsR((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pyt authenticatescCs/d||f}|j|pdddd|S(Ns!{"email": "%s", "password": "%s"}s/logint content_typesapplication/jsonR(R (R RRR R((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pytjson_authenticatescKs|j|pd|S(Ns/logout(tget(R R R ((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pytlogoutscCs/xdD]}|jd|qW|jdS(Ntadminteditortauthortname(sadminseditorR(t create_roletcommit(tdstrole((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pyt create_roless c Csfddddgtfddddgtfdd dddgtfd d dd gtfd ddgtfdddgtfg}|pt|}x|| D]}|d}|dk rt|}ng|dD]}|j|^q}|j|jd|dd|dd|d|d}|jx|D]}|j||q:W|jqWdS(Ns matt@lp.comtmattRRs joe@lp.comtjoeRs dave@lp.comtdaves jill@lp.comtjillRs tiya@lp.comttiyas jess@lp.comtjessiiRitusernameitactivei( tTruetFalsetNonetlenRtfind_or_create_roleRt create_usertadd_role_to_user( RtcounttuserstutpwtrntrolestuserR((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pyt create_users%s$  & 0  cCs:|jj}|jt|t||WdQXdS(N(tsecurityt datastoret app_contextRR2(tappt user_countR((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pyt populate_data;s   RcBseZedZRS(cCsbt|dt}|tk r"|Sytj|j|_Wntk rZtdnX|jS(Nt _cached_jdatasInvalid JSON response(tgetattrt_missingRtloadsRR9t ValueErrort Exception(tselftrv((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pytjdataDs  (t__name__t __module__tpropertyRA(((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pyRBscKsH|jdi}|jj|t|d|||_t|dS(Nt security_argsR4(tpoptconfigtupdateRR3R8(R6R4toptionsRE((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pytinit_app_with_optionsPsN(t__doc__tflaskRt BaseResponseRtflask_securityRtflask_security.utilsRtobjectR;R&RRRRR2R8RJ(((s;/Users/matt/Workspaces/Python/flask-security/tests/utils.pyts    Flask-Security-1.7.5/tox.ini0000644000076500000240000000026412627667222016012 0ustar mattstaff00000000000000[tox] envlist = py26, py27, py33, py34, py35, pypy [testenv] deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-dev.txt commands = python setup.py test