diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1ef734eb..29824252 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ default_language_version:
python: python3.8
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.0.1
+ rev: v4.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -14,12 +14,12 @@ repos:
- id: check-merge-conflict
- id: fix-byte-order-marker
- repo: https://github.com/asottile/pyupgrade
- rev: v2.26.0
+ rev: v2.31.0
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/psf/black
- rev: 21.9b0
+ rev: 22.1.0
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
diff --git a/CHANGES.rst b/CHANGES.rst
index aed6d121..188a1f37 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,16 +3,30 @@ Flask-Security Changelog
Here you can see the full list of changes between each Flask-Security release.
+Version 4.1.3
+-------------
+
+Released March 2, 2022
+
+Fixes
++++++
+- (:issue:`581`) Fix bug when attempting to disable register_blueprint. (halali)
+- (:pr:`539`) Fix example documentation re: generating localized messages. (kazuhei2)
+- (:pr:`546`) Make roles joinedload compatible with SQLAlchemy 2.0. (keats)
+- (:pr:`586`) Ship py.typed as part of package.
+- (:issue:`580`) Improve documentation around use of bleach and include in common install extra.
+
+
Version 4.1.2
-------------
Released September 22, 2021
Fixes
------
--(:issue:`526`) default_reauthn_handler doesn't honor SECURITY_URL_PREFIX
--(:pr:`528`) Improve German translations (sr-verde)
--(:pr:`527`) Fix two-factor sample code (djpnewton)
++++++
+- (:issue:`526`) default_reauthn_handler doesn't honor SECURITY_URL_PREFIX
+- (:pr:`528`) Improve German translations (sr-verde)
+- (:pr:`527`) Fix two-factor sample code (djpnewton)
Version 4.1.1
--------------
@@ -20,7 +34,7 @@ Version 4.1.1
Released September 10, 2021
Fixes
------
++++++
- (:issue:`518`) Fix corner case where Security object was being reused in tests.
- (:issue:`512`) If USERNAME_ENABLE is set, change LoginForm field from EmailField
to StringField. Also - dynamically add fields to Login and Registration forms
diff --git a/MANIFEST.in b/MANIFEST.in
index 7a3ba68b..0ac92d8d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,6 +8,7 @@ include babel.ini
include pytest.ini
include tox.ini
include requirements/*.txt
+include flask_security/py.typed
graft docs
graft flask_security/templates
graft flask_security/translations
diff --git a/docs/conf.py b/docs/conf.py
index ef5311cd..e9fa374e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -57,7 +57,7 @@
# built documents.
#
# The short X.Y version.
-version = "4.1.2"
+version = "4.1.3"
# The full version, including alpha/beta/rc tags.
release = version
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 81e005ae..5676d372 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -731,7 +731,8 @@ Registerable
Validation and normalization is encapsulated in :class:`.UsernameUtil`.
Note that the default validation restricts username input to be unicode
- letters and numbers.
+ letters and numbers. It also uses ``bleach`` to scrub any risky input. Be
+ sure your application requirements includes ``bleach``.
Default: ``False``
@@ -763,7 +764,7 @@ Registerable
.. py:data:: SECURITY_USERNAME_NORMALIZE_FORM
- Usernames can be unicode normalization is performed using the Python unicodedata.normalize() method.
+ Usernames, by default, are normalized using the Python unicodedata.normalize() method.
Default: ``"NFKD"``
@@ -1090,6 +1091,9 @@ Configuration related to the two-factor authentication feature.
Unified Signin
--------------
+ Unified sign in provides a generalized sign in endpoint that takes an `identity`
+ and a `passcode`.
+
.. versionadded:: 3.4.0
.. py:data:: SECURITY_UNIFIED_SIGNIN
@@ -1406,8 +1410,8 @@ The default messages and error levels can be found in ``core.py``.
* ``SECURITY_MSG_US_SPECIFY_IDENTITY``
* ``SECURITY_MSG_USE_CODE``
* ``SECURITY_MSG_USER_DOES_NOT_EXIST``
-* ``SECURITY_USERNAME_INVALID_LENGTH``
-* ``SECURITY_USERNAME_ILLEGAL_CHARACTERS``
-* ``SECURITY_USERNAME_DISALLOWED_CHARACTERS``
-* ``SECURITY_USERNAME_NOT_PROVIDED``
-* ``SECURITY_USERNAME_ALREADY_ASSOCIATED``
+* ``SECURITY_MSG_USERNAME_INVALID_LENGTH``
+* ``SECURITY_MSG_USERNAME_ILLEGAL_CHARACTERS``
+* ``SECURITY_MSG_USERNAME_DISALLOWED_CHARACTERS``
+* ``SECURITY_MSG_USERNAME_NOT_PROVIDED``
+* ``SECURITY_MSG_USERNAME_ALREADY_ASSOCIATED``
diff --git a/docs/customizing.rst b/docs/customizing.rst
index cb40d2a3..6a7b5b32 100644
--- a/docs/customizing.rst
+++ b/docs/customizing.rst
@@ -182,7 +182,7 @@ In this example, create a file ``flask_security.po`` under a directory:
Then compile it with::
- pybabel compile -d translations/ -i translations/fr_FR/LC_MESSAGES/flask_security.po -l fr_FR
+ pybabel compile -d translations/ -i translations/fr_FR/LC_MESSAGES/flask_security.po -l fr_FR -D flask_security
Finally add your translations directory to your configuration::
diff --git a/docs/index.rst b/docs/index.rst
index b69af56a..0f8cef7c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -45,7 +45,7 @@ extensions out of the box for data persistence:
1. `Flask-SQLAlchemy `_
2. `Flask-MongoEngine `_
3. `Peewee Flask utils `_
-4. `PonyORM `_
+4. `PonyORM `_ - NOTE: not currently supported.
5. `SQLAlchemy sessions `_
diff --git a/docs/models.rst b/docs/models.rst
index 3ba02c10..f73c372e 100644
--- a/docs/models.rst
+++ b/docs/models.rst
@@ -15,7 +15,7 @@ which are a bit of a pain. To make things easier - Flask-Security includes mixin
contain ALL the fields and tables required for all features. They also contain
various `best practice` fields - such as update and create times. These mixins can
be easily extended to add any sort of custom fields and can be found in the
-`models` module (today there is just one for using Flask-SqlAlchemy).
+`models` module (today there is just one for using Flask-SQLAlchemy).
The provided models are versioned since they represent actual DB models, and any
changes require a schema migration (and perhaps a data migration). Applications
@@ -33,7 +33,7 @@ At the bare minimum your `User` and `Role` model should include the following fi
* ``email`` (for most features - unique, non-nullable)
* ``password`` (non-nullable)
* ``active`` (boolean, non-nullable)
-* ``fs_uniquifier`` (unique, non-nullable)
+* ``fs_uniquifier`` (string, 64 bytes, unique, non-nullable)
**Role**
@@ -78,13 +78,13 @@ If you enable two-factor by setting your application's `SECURITY_TWO_FACTOR`
configuration value to `True`, your `User` model will require the following
additional fields:
-* ``tf_totp_secret`` (string)
+* ``tf_totp_secret`` (string, 255 bytes, nullable)
* ``tf_primary_method`` (string)
If you include 'sms' in `SECURITY_TWO_FACTOR_ENABLED_METHODS`, your `User` model
will require the following additional field:
-* ``tf_phone_number`` (string)
+* ``tf_phone_number`` (string, 255 bytes, nullable)
Unified Sign In
^^^^^^^^^^^^^^^
@@ -105,12 +105,19 @@ Separate Identity Domains
If you want authentication tokens to not be invalidated when the user changes their
password add the following to your `User` model:
-* ``fs_token_uniquifier`` (unique, non-nullable)
+* ``fs_token_uniquifier`` (string, 64 bytes, unique, non-nullable)
+
+Username
+~~~~~~~~~
+If you set :py:data:`SECURITY_USERNAME_ENABLE` to `True`, then your `User` model
+requires the following additional field:
+
+* ``username`` (string, 64 bytes, unique, nullable)
Permissions
^^^^^^^^^^^
If you want to protect endpoints with permissions, and assign permissions to roles
-that are then assigned to users the Role model requires:
+that are then assigned to users, the ``Role`` model requires:
* ``permissions`` (UnicodeText)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index ee21461d..e072434a 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -80,6 +80,7 @@ possible using Flask-SQLAlchemy and the built-in model mixins:
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
"pool_pre_ping": True,
}
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# Create database connection object
db = SQLAlchemy(app)
diff --git a/flask_security/__init__.py b/flask_security/__init__.py
index f7346a67..b0ef3f23 100644
--- a/flask_security/__init__.py
+++ b/flask_security/__init__.py
@@ -105,4 +105,4 @@
verify_and_update_password,
)
-__version__ = "4.1.2"
+__version__ = "4.1.3"
diff --git a/flask_security/cli.py b/flask_security/cli.py
index 7973f7bb..0d7180b1 100644
--- a/flask_security/cli.py
+++ b/flask_security/cli.py
@@ -44,7 +44,6 @@ def decorator(__ctx, *args, **kwargs):
return functools.update_wrapper(decorator, f)
-
else:
import flask.cli
diff --git a/flask_security/core.py b/flask_security/core.py
index 53ac9500..7cd58ead 100644
--- a/flask_security/core.py
+++ b/flask_security/core.py
@@ -1118,7 +1118,7 @@ def init_app(
raise ValueError("Datastore must be provided")
self.datastore = self._datastore
- if register_blueprint:
+ if register_blueprint is not None:
self._register_blueprint = register_blueprint
self.register_blueprint = self._register_blueprint
diff --git a/flask_security/datastore.py b/flask_security/datastore.py
index 5da05437..3efac216 100644
--- a/flask_security/datastore.py
+++ b/flask_security/datastore.py
@@ -570,7 +570,7 @@ def find_user(
if config_value("JOIN_USER_ROLES") and hasattr(self.user_model, "roles"):
from sqlalchemy.orm import joinedload
- query = query.options(joinedload("roles"))
+ query = query.options(joinedload(self.user_model.roles))
if case_insensitive:
# While it is of course possible to pass in multiple keys to filter on
diff --git a/flask_security/utils.py b/flask_security/utils.py
index 61a424af..07d7991e 100644
--- a/flask_security/utils.py
+++ b/flask_security/utils.py
@@ -98,7 +98,6 @@ async def view_commit(response=None):
_datastore.commit()
return response
-
else:
def view_commit(response=None):
diff --git a/setup.cfg b/setup.cfg
index e3a7035c..50d75e21 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,6 +9,7 @@ fsqla=
common=
bcrypt>=3.1.5
flask_mail>=0.9.1
+ bleach>=3.3.1
mfa=
cryptography>=3.0.0
pyqrcode>=1.2
diff --git a/tests/test_recoverable.py b/tests/test_recoverable.py
index ae545e93..806033af 100644
--- a/tests/test_recoverable.py
+++ b/tests/test_recoverable.py
@@ -566,3 +566,40 @@ def test_email_normalization(client, get_message):
)
assert response.status_code == 200
assert get_message("PASSWORD_RESET_REQUEST", email="joe@lp.com") in response.data
+
+
+def test_password_normalization(app, client, get_message):
+ with capture_reset_password_requests() as requests:
+ response = client.post(
+ "/reset",
+ json=dict(email="matt@lp.com"),
+ )
+ assert response.status_code == 200
+ token = requests[0]["token"]
+
+ response = client.post(
+ "/reset/" + token,
+ json=dict(password="HöheHöhe", password_confirm="HöheHöhe"),
+ )
+ assert response.status_code == 200
+ logout(client)
+
+ # make sure can log in with new password both normnalized or not
+ response = client.post(
+ "/login",
+ json=dict(email="matt@lp.com", password="HöheHöhe"),
+ )
+ assert response.status_code == 200
+ # verify actually logged in
+ response = client.get("/profile", follow_redirects=False)
+ assert response.status_code == 200
+ logout(client)
+
+ response = client.post(
+ "/login",
+ json=dict(email="matt@lp.com", password="Ho\u0308heHo\u0308he"),
+ )
+ assert response.status_code == 200
+ # verify actually logged in
+ response = client.get("/profile", follow_redirects=False)
+ assert response.status_code == 200
diff --git a/tox.ini b/tox.ini
index e2f770e6..f789dabb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py{36,37,38,39,py3}-{low,release}
+ py{36,37,38,39,py38}-{low,release}
mypy
nobabel
style
@@ -9,14 +9,14 @@ envlist =
makedist
skip_missing_interpreters = true
-[testenv:py{36,37,38,39,py3}-release]
+[testenv:py{36,37,38,39,py38}-release]
deps =
-r requirements/tests.txt
commands =
python setup.py compile_catalog
pytest --basetemp={envtmpdir} {posargs:tests}
-[testenv:py{36,37,38,39,py3}-low]
+[testenv:py{36,37,38,39,py38}-low]
deps =
pytest
@@ -30,14 +30,15 @@ deps =
argon2_cffi==20.1.0
babel==2.7.0
bcrypt==3.2.0
- bleach==3.1.5
+ bleach==3.2.2
cryptography==3.0.0
# next 2 come from minimums from Flask 1.1.1
- jinja2==2.10.1
- itsdangerous==0.24
- mongoengine==0.20.0
- mongomock==3.21.0
- pony==0.7.14
+ jinja2==2.11.0
+ itsdangerous==1.1.0
+ markupsafe==2.0.1
+ mongoengine==0.22.1
+ mongomock==3.22.0
+ pony==0.7.14;python_version<'3.10'
phonenumberslite==8.11.1
pyqrcode==1.2
sqlalchemy==1.3.19