mirror of
https://github.com/wassname/flask-security.git
synced 2026-08-07 11:22:21 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7d0ea9cce | ||
|
|
94c7c09dc2 | ||
|
|
f6405797f1 | ||
|
|
2cffb6634d | ||
|
|
824a52b883 | ||
|
|
fe7e4c4afc | ||
|
|
591bc27a5e | ||
|
|
5bc37add88 | ||
|
|
8b036f2a3e | ||
|
|
76ad77a233 | ||
|
|
679cee7969 | ||
|
|
3d7b97ac31 | ||
|
|
3a0af73231 | ||
|
|
3458391791 | ||
|
|
37908ca335 | ||
|
|
f83cd56ff2 | ||
|
|
c10ec29fe5 | ||
|
|
d2fe7aefcb | ||
|
|
708ddeb6c4 | ||
|
|
2aeee348d4 | ||
|
|
52b177cd2e | ||
|
|
dab2fc8c8b | ||
|
|
31e3ab5470 | ||
|
|
6bdcd2930d | ||
|
|
1076887900 | ||
|
|
285fe888da | ||
|
|
bf139847cb | ||
|
|
15c9ef1d07 | ||
|
|
d1ec38ea18 | ||
|
|
577a3a8110 | ||
|
|
7b474efd7a | ||
|
|
f12bc17e7e | ||
|
|
999f882f61 | ||
|
|
a140c01b57 | ||
|
|
76cf3eaf6a | ||
|
|
a6b5d3053c | ||
|
|
0a48997fdd | ||
|
|
96f1b3e0d1 | ||
|
|
0facdaacd9 | ||
|
|
9cadf855a4 |
@@ -4,6 +4,7 @@ python:
|
|||||||
- "2.6"
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
- "pypy"
|
- "pypy"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@@ -3,6 +3,28 @@ Flask-Security Changelog
|
|||||||
|
|
||||||
Here you can see the full list of changes between each Flask-Security release.
|
Here you can see the full list of changes between each Flask-Security release.
|
||||||
|
|
||||||
|
Version 1.7.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released October 13th 2014
|
||||||
|
|
||||||
|
- Fixed a bug related to changing existing passwords from plaintext to hashed
|
||||||
|
- Fixed a bug in form validation that did not enforce case insensivitiy
|
||||||
|
- Fixed a bug with validating redirects
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.7.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released June 10th 2014
|
||||||
|
|
||||||
|
- Fixed a bug where redirection to `SECURITY_POST_LOGIN_VIEW` was not respected
|
||||||
|
- Fixed string encoding in various places to be friendly to unicode
|
||||||
|
- Now using `werkzeug.security.safe_str_cmp` to check tokens
|
||||||
|
- Removed user information from JSON output on `/reset` responses
|
||||||
|
- Added Python 3.4 support
|
||||||
|
|
||||||
|
|
||||||
Version 1.7.2
|
Version 1.7.2
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ copyright = u'2012, Matt Wright'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.7.2'
|
version = '1.7.4'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -15,7 +15,7 @@ Core
|
|||||||
``SECURITY_URL_PREFIX`` Specifies the URL prefix for the
|
``SECURITY_URL_PREFIX`` Specifies the URL prefix for the
|
||||||
Flask-Security blueprint. Defaults to
|
Flask-Security blueprint. Defaults to
|
||||||
``None``.
|
``None``.
|
||||||
``SECURITY_FLASH_MESSAGES`` Specifies wether or not to flash
|
``SECURITY_FLASH_MESSAGES`` Specifies whether or not to flash
|
||||||
messages during security procedures.
|
messages during security procedures.
|
||||||
Defaults to ``True``.
|
Defaults to ``True``.
|
||||||
``SECURITY_PASSWORD_HASH`` Specifies the password hash algorithm to
|
``SECURITY_PASSWORD_HASH`` Specifies the password hash algorithm to
|
||||||
@@ -69,37 +69,37 @@ URLs and Views
|
|||||||
``SECURITY_CONFIRM_ERROR_VIEW`` Specifies the view to redirect to if a
|
``SECURITY_CONFIRM_ERROR_VIEW`` Specifies the view to redirect to if a
|
||||||
confirmation error occurs. This value can be set
|
confirmation error occurs. This value can be set
|
||||||
to a URL or an endpoint name. If this value is
|
to a URL or an endpoint name. If this value is
|
||||||
``None`` the user is presented the default view
|
``None``, the user is presented the default view
|
||||||
to resend a confirmation link. Defaults to
|
to resend a confirmation link. Defaults to
|
||||||
``None``.
|
``None``.
|
||||||
``SECURITY_POST_REGISTER_VIEW`` Specifies the view to redirect to after a user
|
``SECURITY_POST_REGISTER_VIEW`` Specifies the view to redirect to after a user
|
||||||
successfully registers. This value can be set to
|
successfully registers. This value can be set to
|
||||||
a URL or an endpoint name. If this value is
|
a URL or an endpoint name. If this value is
|
||||||
``None`` the user is redirected to the value of
|
``None``, the user is redirected to the value of
|
||||||
``SECURITY_POST_LOGIN_VIEW``. Defaults to
|
``SECURITY_POST_LOGIN_VIEW``. Defaults to
|
||||||
``None``.
|
``None``.
|
||||||
``SECURITY_POST_CONFIRM_VIEW`` Specifies the view to redirect to after a user
|
``SECURITY_POST_CONFIRM_VIEW`` Specifies the view to redirect to after a user
|
||||||
successfully confirms their email. This value
|
successfully confirms their email. This value
|
||||||
can be set to a URL or an endpoint name. If this
|
can be set to a URL or an endpoint name. If this
|
||||||
value is ``None`` the user is redirected to the
|
value is ``None``, the user is redirected to the
|
||||||
value of ``SECURITY_POST_LOGIN_VIEW``. Defaults
|
value of ``SECURITY_POST_LOGIN_VIEW``. Defaults
|
||||||
to ``None``.
|
to ``None``.
|
||||||
``SECURITY_POST_RESET_VIEW`` Specifies the view to redirect to after a user
|
``SECURITY_POST_RESET_VIEW`` Specifies the view to redirect to after a user
|
||||||
successfully resets their password. This value
|
successfully resets their password. This value
|
||||||
can be set to a URL or an endpoint name. If this
|
can be set to a URL or an endpoint name. If this
|
||||||
value is ``None`` the user is redirected to the
|
value is ``None``, the user is redirected to the
|
||||||
value of ``SECURITY_POST_LOGIN_VIEW``. Defaults
|
value of ``SECURITY_POST_LOGIN_VIEW``. Defaults
|
||||||
to ``None``.
|
to ``None``.
|
||||||
``SECURITY_POST_CHANGE_VIEW`` Specifies the view to redirect to after a user
|
``SECURITY_POST_CHANGE_VIEW`` Specifies the view to redirect to after a user
|
||||||
successfully changes their password. This value
|
successfully changes their password. This value
|
||||||
can be set to a URL or an endpoint name. If this
|
can be set to a URL or an endpoint name. If this
|
||||||
value is ``None`` the user is redirected to the
|
value is ``None``, the user is redirected to the
|
||||||
value of ``SECURITY_POST_LOGIN_VIEW``. Defaults
|
value of ``SECURITY_POST_LOGIN_VIEW``. Defaults
|
||||||
to ``None``.
|
to ``None``.
|
||||||
``SECURITY_UNAUTHORIZED_VIEW`` Specifies the view to redirect to if a user
|
``SECURITY_UNAUTHORIZED_VIEW`` Specifies the view to redirect to if a user
|
||||||
attempts to access a URL/endpoint that they do
|
attempts to access a URL/endpoint that they do
|
||||||
not have permission to access. If this value is
|
not have permission to access. If this value is
|
||||||
``None`` the user is presented with a default
|
``None``, the user is presented with a default
|
||||||
HTTP 403 response. Defaults to ``None``.
|
HTTP 403 response. Defaults to ``None``.
|
||||||
=============================== ================================================
|
=============================== ================================================
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ Feature Flags
|
|||||||
========================= ======================================================
|
========================= ======================================================
|
||||||
``SECURITY_CONFIRMABLE`` Specifies if users are required to confirm their email
|
``SECURITY_CONFIRMABLE`` Specifies if users are required to confirm their email
|
||||||
address when registering a new account. If this value
|
address when registering a new account. If this value
|
||||||
is `True` Flask-Security creates an endpoint to handle
|
is `True`, Flask-Security creates an endpoint to handle
|
||||||
confirmations and requests to resend confirmation
|
confirmations and requests to resend confirmation
|
||||||
instructions. The URL for this endpoint is specified
|
instructions. The URL for this endpoint is specified
|
||||||
by the ``SECURITY_CONFIRM_URL`` configuration option.
|
by the ``SECURITY_CONFIRM_URL`` configuration option.
|
||||||
@@ -158,11 +158,11 @@ Feature Flags
|
|||||||
specified by the ``SECURITY_RESET_URL`` configuration
|
specified by the ``SECURITY_RESET_URL`` configuration
|
||||||
option. Defaults to ``False``.
|
option. Defaults to ``False``.
|
||||||
``SECURITY_TRACKABLE`` Specifies if Flask-Security should track basic user
|
``SECURITY_TRACKABLE`` Specifies if Flask-Security should track basic user
|
||||||
login statistics. If set to ``True`` ensure your
|
login statistics. If set to ``True``, ensure your
|
||||||
models have the required fields/attribues. Defaults to
|
models have the required fields/attribues. Defaults to
|
||||||
``False``
|
``False``
|
||||||
``SECURITY_PASSWORDLESS`` Specifies if Flask-Security should enable the
|
``SECURITY_PASSWORDLESS`` Specifies if Flask-Security should enable the
|
||||||
passwordless login feature. If set to ``True`` users
|
passwordless login feature. If set to ``True``, users
|
||||||
are not required to enter a password to login but are
|
are not required to enter a password to login but are
|
||||||
sent an email with a login link. This feature is
|
sent an email with a login link. This feature is
|
||||||
experimental and should be used with caution. Defaults
|
experimental and should be used with caution. Defaults
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Customizing Views
|
|||||||
=================
|
=================
|
||||||
|
|
||||||
Flask-Security bootstraps your application with various views for handling its
|
Flask-Security bootstraps your application with various views for handling its
|
||||||
configured features to get you up and running as quick as possible. However,
|
configured features to get you up and running as quickly as possible. However,
|
||||||
you'll probably want to change the way these views look to be more in line with
|
you'll probably want to change the way these views look to be more in line with
|
||||||
your application's visual design.
|
your application's visual design.
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ Flask application context processor:
|
|||||||
* ``<template_name>_form``: A form object for the view
|
* ``<template_name>_form``: A form object for the view
|
||||||
* ``security``: The Flask-Security extension object
|
* ``security``: The Flask-Security extension object
|
||||||
|
|
||||||
To add more values to the template context you can specify a context processor
|
To add more values to the template context, you can specify a context processor
|
||||||
for all views or a specific view. For example::
|
for all views or a specific view. For example::
|
||||||
|
|
||||||
security = Security(app, user_datastore)
|
security = Security(app, user_datastore)
|
||||||
@@ -106,7 +106,7 @@ The following is a list of all the available form overrides:
|
|||||||
Emails
|
Emails
|
||||||
------
|
------
|
||||||
|
|
||||||
Flask-Security is also packaged with a default tempalte for each email that it
|
Flask-Security is also packaged with a default template for each email that it
|
||||||
may send. Templates are located within the subfolder named ``security/email``.
|
may send. Templates are located within the subfolder named ``security/email``.
|
||||||
The following is a list of email templates:
|
The following is a list of email templates:
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ Overriding these templates is simple:
|
|||||||
|
|
||||||
Each template is passed a template context object that includes values for any
|
Each template is passed a template context object that includes values for any
|
||||||
links that are required in the email. If you require more values in the
|
links that are required in the email. If you require more values in the
|
||||||
templates you can specify an email context processor with the
|
templates, you can specify an email context processor with the
|
||||||
``mail_context_processor`` decorator. For example::
|
``mail_context_processor`` decorator. For example::
|
||||||
|
|
||||||
security = Security(app, user_datastore)
|
security = Security(app, user_datastore)
|
||||||
@@ -146,7 +146,7 @@ Emails with Celery
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
Sometimes it makes sense to send emails via a task queue, such as `Celery`_.
|
Sometimes it makes sense to send emails via a task queue, such as `Celery`_.
|
||||||
To delay the sending of emails you can use the ``@security.send_mail_task``
|
To delay the sending of emails, you can use the ``@security.send_mail_task``
|
||||||
decorator like so::
|
decorator like so::
|
||||||
|
|
||||||
# Setup the task
|
# Setup the task
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ instance, you may assign roles such as `Admin`, `Editor`, `SuperUser`, or a
|
|||||||
combination of said roles to a user. Access control is based on the role name
|
combination of said roles to a user. Access control is based on the role name
|
||||||
and all roles should be uniquely named. This feature is implemented using the
|
and all roles should be uniquely named. This feature is implemented using the
|
||||||
`Flask-Principal`_ extension. If you'd like to implement more granular access
|
`Flask-Principal`_ extension. If you'd like to implement more granular access
|
||||||
control you can refer to the Flask-Princpal `documentation on this topic`_.
|
control, you can refer to the Flask-Principal `documentation on this topic`_.
|
||||||
|
|
||||||
|
|
||||||
Password Encryption
|
Password Encryption
|
||||||
|
|||||||
+3
-3
@@ -5,7 +5,7 @@ Flask-Security assumes you'll be using libraries such as SQLAlchemy,
|
|||||||
MongoEngine or Peewee to define a data model that includes a `User` and
|
MongoEngine or Peewee to define a data model that includes a `User` and
|
||||||
`Role` model. The fields on your models must follow a particular convention
|
`Role` model. The fields on your models must follow a particular convention
|
||||||
depending on the functionality your app requires. Aside from this, you're
|
depending on the functionality your app requires. Aside from this, you're
|
||||||
free to add any additional fields to your model(s) if you want. At the bear
|
free to add any additional fields to your model(s) if you want. At the bare
|
||||||
minimum your `User` and `Role` model should include the following fields:
|
minimum your `User` and `Role` model should include the following fields:
|
||||||
|
|
||||||
**User**
|
**User**
|
||||||
@@ -32,7 +32,7 @@ Confirmable
|
|||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
|
|
||||||
If you enable account confirmation by setting your application's
|
If you enable account confirmation by setting your application's
|
||||||
`SECURITY_CONFIRMABLE` configuration value to `True` your `User` model will
|
`SECURITY_CONFIRMABLE` configuration value to `True`, your `User` model will
|
||||||
require the following additional field:
|
require the following additional field:
|
||||||
|
|
||||||
* ``confirmed_at``
|
* ``confirmed_at``
|
||||||
@@ -41,7 +41,7 @@ Trackable
|
|||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
|
||||||
If you enable user tracking by setting your application's `SECURITY_TRACKABLE`
|
If you enable user tracking by setting your application's `SECURITY_TRACKABLE`
|
||||||
configuration value to `True` your `User` model will require the following
|
configuration value to `True`, your `User` model will require the following
|
||||||
additional fields:
|
additional fields:
|
||||||
|
|
||||||
* ``last_login_at``
|
* ``last_login_at``
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '1.7.2'
|
__version__ = '1.7.4'
|
||||||
|
|
||||||
from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
|
from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
|
||||||
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
|
from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from itsdangerous import URLSafeTimedSerializer
|
|||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from werkzeug.datastructures import ImmutableList
|
from werkzeug.datastructures import ImmutableList
|
||||||
from werkzeug.local import LocalProxy
|
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 .utils import config_value as cv, get_config, md5, url_for_security, string_types
|
||||||
from .views import create_blueprint
|
from .views import create_blueprint
|
||||||
@@ -193,7 +194,7 @@ def _token_loader(token):
|
|||||||
try:
|
try:
|
||||||
data = _security.remember_token_serializer.loads(token)
|
data = _security.remember_token_serializer.loads(token)
|
||||||
user = _security.datastore.find_user(id=data[0])
|
user = _security.datastore.find_user(id=data[0])
|
||||||
if user and md5(user.password) == data[1]:
|
if user and safe_str_cmp(md5(user.password), data[1]):
|
||||||
return user
|
return user
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -349,6 +350,9 @@ class _SecurityState(object):
|
|||||||
rv.update(fn())
|
rv.update(fn())
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def context_processor(self, fn):
|
||||||
|
self._add_ctx_processor(None, fn)
|
||||||
|
|
||||||
def forgot_password_context_processor(self, fn):
|
def forgot_password_context_processor(self, fn):
|
||||||
self._add_ctx_processor('forgot_password', fn)
|
self._add_ctx_processor('forgot_password', fn)
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class UserDatastore(object):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_user(self, id_or_email):
|
def get_user(self, id_or_email):
|
||||||
"""Returns a user matching the specified ID or email address"""
|
"""Returns a user matching the specified ID or email address."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def find_user(self, *args, **kwargs):
|
def find_user(self, *args, **kwargs):
|
||||||
@@ -97,7 +97,7 @@ class UserDatastore(object):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def add_role_to_user(self, user, role):
|
def add_role_to_user(self, user, role):
|
||||||
"""Adds a role tp a user
|
"""Adds a role to a user.
|
||||||
|
|
||||||
:param user: The user to manipulate
|
:param user: The user to manipulate
|
||||||
:param role: The role to add to the user
|
:param role: The role to add to the user
|
||||||
@@ -110,7 +110,7 @@ class UserDatastore(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def remove_role_from_user(self, user, role):
|
def remove_role_from_user(self, user, role):
|
||||||
"""Removes a role from a user
|
"""Removes a role from a user.
|
||||||
|
|
||||||
:param user: The user to manipulate
|
:param user: The user to manipulate
|
||||||
:param role: The role to remove from the user
|
:param role: The role to remove from the user
|
||||||
@@ -120,6 +120,7 @@ class UserDatastore(object):
|
|||||||
if role in user.roles:
|
if role in user.roles:
|
||||||
rv = True
|
rv = True
|
||||||
user.roles.remove(role)
|
user.roles.remove(role)
|
||||||
|
self.put(user)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def toggle_active(self, user):
|
def toggle_active(self, user):
|
||||||
@@ -155,7 +156,7 @@ class UserDatastore(object):
|
|||||||
|
|
||||||
def find_or_create_role(self, name, **kwargs):
|
def find_or_create_role(self, name, **kwargs):
|
||||||
"""Returns a role matching the given name or creates it with any
|
"""Returns a role matching the given name or creates it with any
|
||||||
additionally provided parameters
|
additionally provided parameters.
|
||||||
"""
|
"""
|
||||||
kwargs["name"] = name
|
kwargs["name"] = name
|
||||||
return self.find_role(name) or self.create_role(**kwargs)
|
return self.find_role(name) or self.create_role(**kwargs)
|
||||||
@@ -167,7 +168,7 @@ class UserDatastore(object):
|
|||||||
return self.put(user)
|
return self.put(user)
|
||||||
|
|
||||||
def delete_user(self, user):
|
def delete_user(self, user):
|
||||||
"""Delete the specified user
|
"""Deletes the specified user.
|
||||||
|
|
||||||
:param user: The user to delete
|
:param user: The user to delete
|
||||||
"""
|
"""
|
||||||
@@ -300,7 +301,7 @@ class PeeweeUserDatastore(PeeweeDatastore, UserDatastore):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
def add_role_to_user(self, user, role):
|
def add_role_to_user(self, user, role):
|
||||||
"""Adds a role tp a user
|
"""Adds a role to a user.
|
||||||
|
|
||||||
:param user: The user to manipulate
|
:param user: The user to manipulate
|
||||||
:param role: The role to add to the user
|
:param role: The role to add to the user
|
||||||
@@ -315,7 +316,7 @@ class PeeweeUserDatastore(PeeweeDatastore, UserDatastore):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def remove_role_from_user(self, user, role):
|
def remove_role_from_user(self, user, role):
|
||||||
"""Removes a role from a user
|
"""Removes a role from a user.
|
||||||
|
|
||||||
:param user: The user to manipulate
|
:param user: The user to manipulate
|
||||||
:param role: The role to remove from the user
|
:param role: The role to remove from the user
|
||||||
|
|||||||
+16
-4
@@ -75,13 +75,13 @@ def get_form_field_label(key):
|
|||||||
|
|
||||||
|
|
||||||
def unique_user_email(form, field):
|
def unique_user_email(form, field):
|
||||||
if _datastore.find_user(email=field.data) is not None:
|
if _datastore.get_user(field.data) is not None:
|
||||||
msg = get_message('EMAIL_ALREADY_ASSOCIATED', email=field.data)[0]
|
msg = get_message('EMAIL_ALREADY_ASSOCIATED', email=field.data)[0]
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
def valid_user_email(form, field):
|
def valid_user_email(form, field):
|
||||||
form.user = _datastore.find_user(email=field.data)
|
form.user = _datastore.get_user(field.data)
|
||||||
if form.user is None:
|
if form.user is None:
|
||||||
raise ValidationError(get_message('USER_DOES_NOT_EXIST')[0])
|
raise ValidationError(get_message('USER_DOES_NOT_EXIST')[0])
|
||||||
|
|
||||||
@@ -175,6 +175,14 @@ class ForgotPasswordForm(Form, UserEmailFormMixin):
|
|||||||
|
|
||||||
submit = SubmitField(get_form_field_label('recover_password'))
|
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):
|
class PasswordlessLoginForm(Form, UserEmailFormMixin):
|
||||||
"""The passwordless login form"""
|
"""The passwordless login form"""
|
||||||
@@ -244,8 +252,12 @@ class ConfirmRegisterForm(Form, RegisterFormMixin,
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RegisterForm(ConfirmRegisterForm, PasswordConfirmFormMixin):
|
class RegisterForm(ConfirmRegisterForm, PasswordConfirmFormMixin,
|
||||||
pass
|
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):
|
class ResetPasswordForm(Form, NewPasswordFormMixin, PasswordConfirmFormMixin):
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{% if security.registerable or security.recoverable or security.confirmabled %}
|
{% if security.registerable or security.recoverable or security.confirmable %}
|
||||||
<h2>Menu</h2>
|
<h2>Menu</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for_security('login') }}">Login</a></li>
|
<li><a href="{{ url_for_security('login') }}{% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}">Login</a></li>
|
||||||
{% if security.registerable %}
|
{% if security.registerable %}
|
||||||
<li><a href="{{ url_for_security('register') }}">Register</a><br/></li>
|
<li><a href="{{ url_for_security('register') }}{% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}">Register</a><br/></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if security.recoverable %}
|
{% if security.recoverable %}
|
||||||
<li><a href="{{ url_for_security('forgot_password') }}">Forgot password</a><br/></li>
|
<li><a href="{{ url_for_security('forgot_password') }}">Forgot password</a><br/></li>
|
||||||
|
|||||||
+22
-10
@@ -99,13 +99,15 @@ def get_hmac(password):
|
|||||||
|
|
||||||
:param password: The password to sign
|
:param password: The password to sign
|
||||||
"""
|
"""
|
||||||
if _security.password_salt is None:
|
salt = _security.password_salt
|
||||||
|
|
||||||
|
if salt is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
'The configuration value `SECURITY_PASSWORD_SALT` must '
|
'The configuration value `SECURITY_PASSWORD_SALT` must '
|
||||||
'not be None when the value of `SECURITY_PASSWORD_HASH` is '
|
'not be None when the value of `SECURITY_PASSWORD_HASH` is '
|
||||||
'set to "%s"' % _security.password_hash)
|
'set to "%s"' % _security.password_hash)
|
||||||
|
|
||||||
h = hmac.new(_security.password_salt.encode('utf-8'), password.encode('utf-8'), hashlib.sha512)
|
h = hmac.new(encode_string(salt), encode_string(password), hashlib.sha512)
|
||||||
return base64.b64encode(h.digest())
|
return base64.b64encode(h.digest())
|
||||||
|
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@ def verify_password(password, password_hash):
|
|||||||
"""Returns ``True`` if the password matches the supplied hash.
|
"""Returns ``True`` if the password matches the supplied hash.
|
||||||
|
|
||||||
:param password: A plaintext password to verify
|
:param password: A plaintext password to verify
|
||||||
:param password_hash: The expected hash value of the password (usually form your database)
|
:param password_hash: The expected hash value of the password (usually from your database)
|
||||||
"""
|
"""
|
||||||
if _security.password_hash != 'plaintext':
|
if _security.password_hash != 'plaintext':
|
||||||
password = get_hmac(password)
|
password = get_hmac(password)
|
||||||
@@ -133,7 +135,7 @@ def verify_and_update_password(password, user):
|
|||||||
password = get_hmac(password)
|
password = get_hmac(password)
|
||||||
verified, new_password = _pwd_context.verify_and_update(password, user.password)
|
verified, new_password = _pwd_context.verify_and_update(password, user.password)
|
||||||
if verified and new_password:
|
if verified and new_password:
|
||||||
user.password = new_password
|
user.password = encrypt_password(password)
|
||||||
_datastore.put(user)
|
_datastore.put(user)
|
||||||
return verified
|
return verified
|
||||||
|
|
||||||
@@ -141,7 +143,7 @@ def verify_and_update_password(password, user):
|
|||||||
def encrypt_password(password):
|
def encrypt_password(password):
|
||||||
"""Encrypts the specified plaintext password using the configured encryption options.
|
"""Encrypts the specified plaintext password using the configured encryption options.
|
||||||
|
|
||||||
:param password: The plaintext passwrod to encrypt
|
:param password: The plaintext password to encrypt
|
||||||
"""
|
"""
|
||||||
if _security.password_hash == 'plaintext':
|
if _security.password_hash == 'plaintext':
|
||||||
return password
|
return password
|
||||||
@@ -149,8 +151,18 @@ def encrypt_password(password):
|
|||||||
return _pwd_context.encrypt(signed)
|
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):
|
def md5(data):
|
||||||
return hashlib.md5(data.encode('ascii')).hexdigest()
|
return hashlib.md5(encode_string(data)).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def do_flash(message, category=None):
|
def do_flash(message, category=None):
|
||||||
@@ -196,11 +208,11 @@ def url_for_security(endpoint, **values):
|
|||||||
|
|
||||||
|
|
||||||
def validate_redirect_url(url):
|
def validate_redirect_url(url):
|
||||||
if url is None:
|
if url is None or url.strip() == '':
|
||||||
return False
|
return False
|
||||||
url_next = urlsplit(url)
|
url_next = urlsplit(url)
|
||||||
url_base = urlsplit(request.host_url)
|
url_base = urlsplit(request.host_url)
|
||||||
if url_next.netloc and url_next.netloc != url_base.netloc:
|
if (url_next.netloc or url_next.scheme) and url_next.netloc != url_base.netloc:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -222,8 +234,8 @@ def get_post_login_redirect(declared=None):
|
|||||||
return get_post_action_redirect('SECURITY_POST_LOGIN_VIEW', declared)
|
return get_post_action_redirect('SECURITY_POST_LOGIN_VIEW', declared)
|
||||||
|
|
||||||
|
|
||||||
def get_post_register_redirect():
|
def get_post_register_redirect(declared=None):
|
||||||
return get_post_action_redirect('SECURITY_POST_REGISTER_VIEW')
|
return get_post_action_redirect('SECURITY_POST_REGISTER_VIEW', declared)
|
||||||
|
|
||||||
|
|
||||||
def find_redirect(key):
|
def find_redirect(key):
|
||||||
|
|||||||
+13
-6
@@ -34,7 +34,7 @@ _security = LocalProxy(lambda: current_app.extensions['security'])
|
|||||||
_datastore = LocalProxy(lambda: _security.datastore)
|
_datastore = LocalProxy(lambda: _security.datastore)
|
||||||
|
|
||||||
|
|
||||||
def _render_json(form, include_auth_token=False):
|
def _render_json(form, include_user=True, include_auth_token=False):
|
||||||
has_errors = len(form.errors) > 0
|
has_errors = len(form.errors) > 0
|
||||||
|
|
||||||
if has_errors:
|
if has_errors:
|
||||||
@@ -42,7 +42,9 @@ def _render_json(form, include_auth_token=False):
|
|||||||
response = dict(errors=form.errors)
|
response = dict(errors=form.errors)
|
||||||
else:
|
else:
|
||||||
code = 200
|
code = 200
|
||||||
response = dict(user=dict(id=str(form.user.id)))
|
response = dict()
|
||||||
|
if include_user:
|
||||||
|
response['user'] = dict(id=str(form.user.id))
|
||||||
if include_auth_token:
|
if include_auth_token:
|
||||||
token = form.user.get_auth_token()
|
token = form.user.get_auth_token()
|
||||||
response['user']['authentication_token'] = token
|
response['user']['authentication_token'] = token
|
||||||
@@ -78,7 +80,7 @@ def login():
|
|||||||
return redirect(get_post_login_redirect(form.next.data))
|
return redirect(get_post_login_redirect(form.next.data))
|
||||||
|
|
||||||
if request.json:
|
if request.json:
|
||||||
return _render_json(form, True)
|
return _render_json(form, include_auth_token=True)
|
||||||
|
|
||||||
return _security.render_template(config_value('LOGIN_USER_TEMPLATE'),
|
return _security.render_template(config_value('LOGIN_USER_TEMPLATE'),
|
||||||
login_user_form=form,
|
login_user_form=form,
|
||||||
@@ -120,8 +122,13 @@ def register():
|
|||||||
login_user(user)
|
login_user(user)
|
||||||
|
|
||||||
if not request.json:
|
if not request.json:
|
||||||
return redirect(get_post_register_redirect())
|
if 'next' in form:
|
||||||
return _render_json(form, True)
|
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:
|
if request.json:
|
||||||
return _render_json(form)
|
return _render_json(form)
|
||||||
@@ -247,7 +254,7 @@ def forgot_password():
|
|||||||
do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email))
|
do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email))
|
||||||
|
|
||||||
if request.json:
|
if request.json:
|
||||||
return _render_json(form)
|
return _render_json(form, include_user=False)
|
||||||
|
|
||||||
return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
|
return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
|
||||||
forgot_password_form=form,
|
forgot_password_form=form,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class PyTest(TestCommand):
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Flask-Security',
|
name='Flask-Security',
|
||||||
version='1.7.2',
|
version='1.7.4',
|
||||||
url='https://github.com/mattupstate/flask-security',
|
url='https://github.com/mattupstate/flask-security',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
author='Matt Wright',
|
author='Matt Wright',
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM CHANGE PASSWORD
|
CUSTOM CHANGE PASSWORD
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM FORGOT PASSWORD
|
CUSTOM FORGOT PASSWORD
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM LOGIN USER
|
CUSTOM LOGIN USER
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM REGISTER USER
|
CUSTOM REGISTER USER
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM RESET PASSWORD
|
CUSTOM RESET PASSWORD
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM SEND CONFIRMATION
|
CUSTOM SEND CONFIRMATION
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM SEND LOGIN
|
CUSTOM SEND LOGIN
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CUSTOM RESET INSTRUCTIONS
|
CUSTOM RESET INSTRUCTIONS
|
||||||
|
{{ global }}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ def test_authenticate_with_invalid_next(client, get_message):
|
|||||||
assert get_message('INVALID_REDIRECT') in response.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):
|
def test_authenticate_case_insensitive_email(app, client):
|
||||||
response = authenticate(client, 'MATT@lp.com', follow_redirects=True)
|
response = authenticate(client, 'MATT@lp.com', follow_redirects=True)
|
||||||
assert b'Hello matt@lp.com' in response.data
|
assert b'Hello matt@lp.com' in response.data
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ def test_view_configuration(client):
|
|||||||
response = client.get('/custom_login')
|
response = client.get('/custom_login')
|
||||||
assert b"<h1>Login</h1>" in response.data
|
assert b"<h1>Login</h1>" in response.data
|
||||||
|
|
||||||
response = authenticate(client, endpoint='/custom_login', follow_redirects=True)
|
response = authenticate(client, endpoint='/custom_login')
|
||||||
assert b'Post Login' in response.data
|
assert 'location' in response.headers
|
||||||
|
assert response.headers['Location'] == 'http://localhost/post_login'
|
||||||
|
|
||||||
response = logout(client, endpoint='/custom_logout', follow_redirects=True)
|
response = logout(client, endpoint='/custom_logout')
|
||||||
assert b'Post Logout' in response.data
|
assert 'location' in response.headers
|
||||||
|
assert response.headers['Location'] == 'http://localhost/post_logout'
|
||||||
|
|
||||||
response = client.get('/http', headers={
|
response = client.get('/http', headers={
|
||||||
'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus")
|
'Authorization': 'Basic %s' % base64.b64encode(b"joe@lp.com:bogus")
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ def test_confirmable_flag(app, client, sqlalchemy_datastore, get_message):
|
|||||||
email = 'dude@lp.com'
|
email = 'dude@lp.com'
|
||||||
|
|
||||||
with capture_registrations() as registrations:
|
with capture_registrations() as registrations:
|
||||||
response = client.post('/register', data=dict(email=email, password='password'))
|
data = dict(email=email, password='password', next='')
|
||||||
|
response = client.post('/register', data=data)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
|
||||||
@@ -85,7 +86,8 @@ def test_confirmable_flag(app, client, sqlalchemy_datastore, get_message):
|
|||||||
|
|
||||||
# Test user was deleted before confirmation
|
# Test user was deleted before confirmation
|
||||||
with capture_registrations() as registrations:
|
with capture_registrations() as registrations:
|
||||||
client.post('/register', data=dict(email='mary@lp.com', password='password'))
|
data = dict(email='mary@lp.com', password='password', next='')
|
||||||
|
client.post('/register', data=data)
|
||||||
|
|
||||||
user = registrations[0]['user']
|
user = registrations[0]['user']
|
||||||
token = registrations[0]['confirm_token']
|
token = registrations[0]['confirm_token']
|
||||||
@@ -102,7 +104,7 @@ def test_confirmable_flag(app, client, sqlalchemy_datastore, get_message):
|
|||||||
@pytest.mark.settings(confirm_email_within='1 milliseconds')
|
@pytest.mark.settings(confirm_email_within='1 milliseconds')
|
||||||
def test_expired_confirmation_token(client, get_message):
|
def test_expired_confirmation_token(client, get_message):
|
||||||
with capture_registrations() as registrations:
|
with capture_registrations() as registrations:
|
||||||
data = dict(email='mary@lp.com', password='password')
|
data = dict(email='mary@lp.com', password='password', next='')
|
||||||
client.post('/register', data=data, follow_redirects=True)
|
client.post('/register', data=data, follow_redirects=True)
|
||||||
|
|
||||||
user = registrations[0]['user']
|
user = registrations[0]['user']
|
||||||
@@ -118,7 +120,7 @@ def test_expired_confirmation_token(client, get_message):
|
|||||||
@pytest.mark.registerable()
|
@pytest.mark.registerable()
|
||||||
@pytest.mark.settings(login_without_confirmation=True)
|
@pytest.mark.settings(login_without_confirmation=True)
|
||||||
def test_login_when_unconfirmed(client, get_message):
|
def test_login_when_unconfirmed(client, get_message):
|
||||||
data = dict(email='mary@lp.com', password='password')
|
data = dict(email='mary@lp.com', password='password', next='')
|
||||||
response = client.post('/register', data=data, follow_redirects=True)
|
response = client.post('/register', data=data, follow_redirects=True)
|
||||||
assert b'mary@lp.com' in response.data
|
assert b'mary@lp.com' in response.data
|
||||||
|
|
||||||
@@ -131,7 +133,8 @@ def test_confirmation_different_user_when_logged_in(client, get_message):
|
|||||||
|
|
||||||
with capture_registrations() as registrations:
|
with capture_registrations() as registrations:
|
||||||
for e in e1, e2:
|
for e in e1, e2:
|
||||||
client.post('/register', data=dict(email=e, password='password'))
|
data = dict(email=e, password='password', next='')
|
||||||
|
client.post('/register', data=data)
|
||||||
logout(client)
|
logout(client)
|
||||||
|
|
||||||
token1 = registrations[0]['confirm_token']
|
token1 = registrations[0]['confirm_token']
|
||||||
@@ -144,3 +147,15 @@ def test_confirmation_different_user_when_logged_in(client, get_message):
|
|||||||
response = client.get('/confirm/' + token2, follow_redirects=True)
|
response = client.get('/confirm/' + token2, follow_redirects=True)
|
||||||
assert get_message('EMAIL_CONFIRMED') in response.data
|
assert get_message('EMAIL_CONFIRMED') in response.data
|
||||||
assert b'Hello lady@lp.com' 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
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ from utils import authenticate
|
|||||||
send_confirmation_template='custom_security/send_confirmation.html',
|
send_confirmation_template='custom_security/send_confirmation.html',
|
||||||
register_user_template='custom_security/register_user.html')
|
register_user_template='custom_security/register_user.html')
|
||||||
def test_context_processors(client, app):
|
def test_context_processors(client, app):
|
||||||
|
@app.security.context_processor
|
||||||
|
def default_ctx_processor():
|
||||||
|
return {'global': 'global'}
|
||||||
|
|
||||||
@app.security.forgot_password_context_processor
|
@app.security.forgot_password_context_processor
|
||||||
def forgot_password():
|
def forgot_password():
|
||||||
return {'foo': 'bar'}
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
response = client.get('/reset')
|
response = client.get('/reset')
|
||||||
|
assert b'global' in response.data
|
||||||
assert b'bar' in response.data
|
assert b'bar' in response.data
|
||||||
|
|
||||||
@app.security.login_context_processor
|
@app.security.login_context_processor
|
||||||
@@ -36,6 +41,7 @@ def test_context_processors(client, app):
|
|||||||
return {'foo': 'bar'}
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
response = client.get('/login')
|
response = client.get('/login')
|
||||||
|
assert b'global' in response.data
|
||||||
assert b'bar' in response.data
|
assert b'bar' in response.data
|
||||||
|
|
||||||
@app.security.register_context_processor
|
@app.security.register_context_processor
|
||||||
@@ -43,6 +49,7 @@ def test_context_processors(client, app):
|
|||||||
return {'foo': 'bar'}
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
response = client.get('/register')
|
response = client.get('/register')
|
||||||
|
assert b'global' in response.data
|
||||||
assert b'bar' in response.data
|
assert b'bar' in response.data
|
||||||
|
|
||||||
@app.security.reset_password_context_processor
|
@app.security.reset_password_context_processor
|
||||||
@@ -50,6 +57,7 @@ def test_context_processors(client, app):
|
|||||||
return {'foo': 'bar'}
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
response = client.get('/reset')
|
response = client.get('/reset')
|
||||||
|
assert b'global' in response.data
|
||||||
assert b'bar' in response.data
|
assert b'bar' in response.data
|
||||||
|
|
||||||
@app.security.change_password_context_processor
|
@app.security.change_password_context_processor
|
||||||
@@ -58,6 +66,7 @@ def test_context_processors(client, app):
|
|||||||
|
|
||||||
authenticate(client)
|
authenticate(client)
|
||||||
response = client.get('/change')
|
response = client.get('/change')
|
||||||
|
assert b'global' in response.data
|
||||||
assert b'bar' in response.data
|
assert b'bar' in response.data
|
||||||
|
|
||||||
@app.security.send_confirmation_context_processor
|
@app.security.send_confirmation_context_processor
|
||||||
@@ -65,6 +74,7 @@ def test_context_processors(client, app):
|
|||||||
return {'foo': 'bar'}
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
response = client.get('/confirm')
|
response = client.get('/confirm')
|
||||||
|
assert b'global' in response.data
|
||||||
assert b'bar' in response.data
|
assert b'bar' in response.data
|
||||||
|
|
||||||
@app.security.mail_context_processor
|
@app.security.mail_context_processor
|
||||||
@@ -75,6 +85,7 @@ def test_context_processors(client, app):
|
|||||||
client.post('/reset', data=dict(email='matt@lp.com'))
|
client.post('/reset', data=dict(email='matt@lp.com'))
|
||||||
|
|
||||||
email = outbox[0]
|
email = outbox[0]
|
||||||
|
assert 'global' in email.html
|
||||||
assert 'bar' in email.html
|
assert 'bar' in email.html
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+22
-1
@@ -12,7 +12,7 @@ from flask_security import Security
|
|||||||
from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \
|
from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \
|
||||||
SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \
|
SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \
|
||||||
ChangePasswordForm, TextField, PasswordField, email_required, email_validator, valid_user_email
|
ChangePasswordForm, TextField, PasswordField, email_required, email_validator, valid_user_email
|
||||||
from flask_security.utils import capture_reset_password_requests
|
from flask_security.utils import capture_reset_password_requests, md5, string_types
|
||||||
|
|
||||||
from utils import authenticate, init_app_with_options, populate_data
|
from utils import authenticate, init_app_with_options, populate_data
|
||||||
|
|
||||||
@@ -170,3 +170,24 @@ def test_change_hash_type(app, sqlalchemy_datastore):
|
|||||||
|
|
||||||
response = client.post('/login', data=dict(email='matt@lp.com', password='password'))
|
response = client.post('/login', data=dict(email='matt@lp.com', password='password'))
|
||||||
assert response.status_code == 302
|
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
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def test_recoverable_flag(app, client, get_message):
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})
|
})
|
||||||
assert response.headers['Content-Type'] == 'application/json'
|
assert response.headers['Content-Type'] == 'application/json'
|
||||||
assert 'user' in response.jdata['response']
|
assert 'user' not in response.jdata['response']
|
||||||
|
|
||||||
logout(client)
|
logout(client)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ def test_registerable_flag(client, app, get_message):
|
|||||||
def on_user_registerd(app, user, confirm_token):
|
def on_user_registerd(app, user, confirm_token):
|
||||||
recorded.append(user)
|
recorded.append(user)
|
||||||
|
|
||||||
data = dict(email='dude@lp.com', password='password', password_confirm='password')
|
data = dict(
|
||||||
|
email='dude@lp.com', password='password', password_confirm='password',
|
||||||
|
next=''
|
||||||
|
)
|
||||||
with app.mail.record_messages() as outbox:
|
with app.mail.record_messages() as outbox:
|
||||||
response = client.post('/register', data=data, follow_redirects=True)
|
response = client.post('/register', data=data, follow_redirects=True)
|
||||||
|
|
||||||
@@ -45,10 +48,21 @@ def test_registerable_flag(client, app, get_message):
|
|||||||
logout(client)
|
logout(client)
|
||||||
|
|
||||||
# Test registering with an existing email
|
# Test registering with an existing email
|
||||||
data = dict(email='dude@lp.com', password='password', password_confirm='password')
|
data = dict(
|
||||||
|
email='dude@lp.com', password='password', password_confirm='password',
|
||||||
|
next=''
|
||||||
|
)
|
||||||
response = client.post('/register', data=data, follow_redirects=True)
|
response = client.post('/register', data=data, follow_redirects=True)
|
||||||
assert get_message('EMAIL_ALREADY_ASSOCIATED', email='dude@lp.com') in response.data
|
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
|
# Test registering with JSON
|
||||||
data = '{ "email": "dude2@lp.com", "password": "password"}'
|
data = '{ "email": "dude2@lp.com", "password": "password"}'
|
||||||
response = client.post('/register', data=data, headers={'Content-Type': 'application/json'})
|
response = client.post('/register', data=data, headers={'Content-Type': 'application/json'})
|
||||||
@@ -68,7 +82,8 @@ def test_registerable_flag(client, app, get_message):
|
|||||||
# Test ?next param
|
# Test ?next param
|
||||||
data = dict(email='dude3@lp.com',
|
data = dict(email='dude3@lp.com',
|
||||||
password='password',
|
password='password',
|
||||||
password_confirm='password')
|
password_confirm='password',
|
||||||
|
next='')
|
||||||
|
|
||||||
response = client.post('/register?next=/page1', data=data, follow_redirects=True)
|
response = client.post('/register?next=/page1', data=data, follow_redirects=True)
|
||||||
assert b'Page 1' in response.data
|
assert b'Page 1' in response.data
|
||||||
@@ -81,7 +96,8 @@ def test_custom_register_url(client):
|
|||||||
|
|
||||||
data = dict(email='dude@lp.com',
|
data = dict(email='dude@lp.com',
|
||||||
password='password',
|
password='password',
|
||||||
password_confirm='password')
|
password_confirm='password',
|
||||||
|
next='')
|
||||||
|
|
||||||
response = client.post('/custom_register', data=data, follow_redirects=True)
|
response = client.post('/custom_register', data=data, follow_redirects=True)
|
||||||
assert b'Post Register' in response.data
|
assert b'Post Register' in response.data
|
||||||
@@ -95,7 +111,10 @@ def test_custom_register_tempalate(client):
|
|||||||
|
|
||||||
@pytest.mark.settings(send_register_email=False)
|
@pytest.mark.settings(send_register_email=False)
|
||||||
def test_disable_register_emails(client, app):
|
def test_disable_register_emails(client, app):
|
||||||
data = dict(email='dude@lp.com', password='password', password_confirm='password')
|
data = dict(
|
||||||
|
email='dude@lp.com', password='password', password_confirm='password',
|
||||||
|
next=''
|
||||||
|
)
|
||||||
with app.mail.record_messages() as outbox:
|
with app.mail.record_messages() as outbox:
|
||||||
client.post('/register', data=data, follow_redirects=True)
|
client.post('/register', data=data, follow_redirects=True)
|
||||||
assert len(outbox) == 0
|
assert len(outbox) == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user