mirror of
https://github.com/wassname/flask-security.git
synced 2026-08-14 12:20:13 +08:00
Convert all tests to use pytest. Phew!
This commit is contained in:
@@ -15,9 +15,9 @@ __version__ = '1.7.1'
|
||||
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
|
||||
login_required, roles_accepted, roles_required, auth_required
|
||||
from .forms import ForgotPasswordForm, LoginForm, RegisterForm, \
|
||||
ResetPasswordForm, PasswordlessLoginForm, ConfirmRegisterForm
|
||||
ResetPasswordForm, PasswordlessLoginForm, ConfirmRegisterForm
|
||||
from .signals import confirm_instructions_sent, password_reset, \
|
||||
reset_password_instructions_sent, user_confirmed, user_registered
|
||||
reset_password_instructions_sent, user_confirmed, user_registered
|
||||
from .utils import login_user, logout_user, url_for_security
|
||||
|
||||
@@ -29,8 +29,8 @@ def send_password_changed_notice(user):
|
||||
:param user: The user to send the notice to
|
||||
"""
|
||||
if config_value('SEND_PASSWORD_CHANGE_EMAIL'):
|
||||
send_mail(config_value('EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE'), user.email,
|
||||
'change_notice', user=user)
|
||||
subject = config_value('EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE')
|
||||
send_mail(subject, user.email, 'change_notice', user=user)
|
||||
|
||||
|
||||
def change_user_password(user, password):
|
||||
|
||||
@@ -58,7 +58,9 @@ def generate_confirmation_token(user):
|
||||
|
||||
def requires_confirmation(user):
|
||||
"""Returns `True` if the user requires confirmation."""
|
||||
return _security.confirmable and not _security.login_without_confirmation and user.confirmed_at == None
|
||||
return (_security.confirmable and
|
||||
not _security.login_without_confirmation and
|
||||
user.confirmed_at is None)
|
||||
|
||||
|
||||
def confirm_email_token_status(token):
|
||||
|
||||
+70
-33
@@ -92,38 +92,74 @@ _default_config = {
|
||||
|
||||
#: 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'),
|
||||
'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'),
|
||||
}
|
||||
|
||||
_allowed_password_hash_schemes = [
|
||||
@@ -207,7 +243,8 @@ def _get_principal(app):
|
||||
def _get_pwd_context(app):
|
||||
pw_hash = cv('PASSWORD_HASH', app=app)
|
||||
if pw_hash not in _allowed_password_hash_schemes:
|
||||
allowed = ', '.join(_allowed_password_hash_schemes[:-1]) + ' and ' + _allowed_password_hash_schemes[-1]
|
||||
allowed = (', '.join(_allowed_password_hash_schemes[:-1]) +
|
||||
' and ' + _allowed_password_hash_schemes[-1])
|
||||
raise ValueError("Invalid hash scheme %r. Allowed values are %s" % (pw_hash, allowed))
|
||||
return CryptContext(schemes=_allowed_password_hash_schemes, default=pw_hash)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from collections import namedtuple
|
||||
from functools import wraps
|
||||
|
||||
from flask import current_app, Response, request, redirect, _request_ctx_stack
|
||||
from flask.ext.login import current_user, login_required
|
||||
from flask.ext.login import current_user, login_required # pragma: no flakes
|
||||
from flask.ext.principal import RoleNeed, Permission, Identity, identity_changed
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
|
||||
@@ -223,7 +223,6 @@ class LoginForm(Form, NextFormMixin):
|
||||
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:
|
||||
|
||||
@@ -42,9 +42,9 @@ class CreateUserCommand(Command):
|
||||
"""Create a user"""
|
||||
|
||||
option_list = (
|
||||
Option('-e', '--email', dest='email', default=None),
|
||||
Option('-e', '--email', dest='email', default=None),
|
||||
Option('-p', '--password', dest='password', default=None),
|
||||
Option('-a', '--active', dest='active', default=''),
|
||||
Option('-a', '--active', dest='active', default=''),
|
||||
)
|
||||
|
||||
@commit
|
||||
|
||||
+2
-60
@@ -10,8 +10,6 @@
|
||||
"""
|
||||
|
||||
import base64
|
||||
import blinker
|
||||
import functools
|
||||
import hashlib
|
||||
import hmac
|
||||
import sys
|
||||
@@ -20,16 +18,13 @@ from contextlib import contextmanager
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import url_for, flash, current_app, request, session, render_template
|
||||
from flask.ext.login import login_user as _login_user, \
|
||||
logout_user as _logout_user
|
||||
from flask.ext.login import login_user as _login_user, logout_user as _logout_user
|
||||
from flask.ext.mail import Message
|
||||
from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
|
||||
from itsdangerous import BadSignature, SignatureExpired
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from .signals import user_registered, user_confirmed, \
|
||||
confirm_instructions_sent, login_instructions_sent, \
|
||||
password_reset, password_changed, reset_password_instructions_sent
|
||||
from .signals import user_registered, login_instructions_sent, reset_password_instructions_sent
|
||||
|
||||
# Convenient references
|
||||
_security = LocalProxy(lambda: current_app.extensions['security'])
|
||||
@@ -396,56 +391,3 @@ def capture_reset_password_requests(reset_password_sent_at=None):
|
||||
yield reset_requests
|
||||
finally:
|
||||
reset_password_instructions_sent.disconnect(_on)
|
||||
|
||||
|
||||
class CaptureSignals(object):
|
||||
"""Testing utility for capturing blinker signals.
|
||||
|
||||
Context manager which mocks out selected signals and registers which are `sent` on and what
|
||||
arguments were sent. Instantiate with a list of blinker `NamedSignals` to patch. Each signal
|
||||
has it's `send` mocked out.
|
||||
"""
|
||||
def __init__(self, signals):
|
||||
"""Patch all given signals and make them available as attributes.
|
||||
|
||||
:param signals: list of signals
|
||||
"""
|
||||
self._records = {}
|
||||
self._receivers = {}
|
||||
for signal in signals:
|
||||
self._records[signal] = []
|
||||
self._receivers[signal] = functools.partial(self._record, signal)
|
||||
|
||||
def __getitem__(self, signal):
|
||||
"""All captured signals are available via `ctxt[signal]`.
|
||||
"""
|
||||
if isinstance(signal, blinker.base.NamedSignal):
|
||||
return self._records[signal]
|
||||
else:
|
||||
super(CaptureSignals, self).__setitem__(signal)
|
||||
|
||||
def _record(self, signal, *args, **kwargs):
|
||||
self._records[signal].append((args, kwargs))
|
||||
|
||||
def __enter__(self):
|
||||
for signal, receiver in self._receivers.items():
|
||||
signal.connect(receiver)
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
for signal, receiver in self._receivers.items():
|
||||
signal.disconnect(receiver)
|
||||
|
||||
def signals_sent(self):
|
||||
"""Return a set of the signals sent.
|
||||
:rtype: list of blinker `NamedSignals`.
|
||||
"""
|
||||
return set([signal for signal, _ in self._records.items() if self._records[signal]])
|
||||
|
||||
|
||||
def capture_signals():
|
||||
"""Factory method that creates a `CaptureSignals` with all the flask_security signals."""
|
||||
return CaptureSignals([user_registered, user_confirmed,
|
||||
confirm_instructions_sent, login_instructions_sent,
|
||||
password_reset, password_changed,
|
||||
reset_password_instructions_sent])
|
||||
|
||||
@@ -77,8 +77,9 @@ def login():
|
||||
if not request.json:
|
||||
return redirect(get_post_login_redirect())
|
||||
|
||||
form.next.data = get_url(request.args.get('next')) \
|
||||
or get_url(request.form.get('next')) or ''
|
||||
form.next.data = (get_url(request.args.get('next')) or
|
||||
get_url(request.form.get('next')) or
|
||||
'')
|
||||
|
||||
if request.json:
|
||||
return _render_json(form, True)
|
||||
|
||||
Reference in New Issue
Block a user