From fe79e2fc737e2f59c5d8e47bcb3e7a5d6195cb36 Mon Sep 17 00:00:00 2001 From: Chris Wagner Date: Wed, 2 Mar 2022 08:44:31 -0800 Subject: [PATCH] Patch41 (#586) * Fix an example command in the documentation (#539) Co-authored-by: Kazuhei Hamada (cherry picked from commit c7da03c23bcd84f1e7cf79a6f5701147e014f09e) * Patch release - 4.1.3 Cherrypick some small fixes. Backport some documentation changes. Co-authored-by: Kazuhei Hamada --- .pre-commit-config.yaml | 6 +++--- CHANGES.rst | 24 +++++++++++++++++++----- MANIFEST.in | 1 + docs/conf.py | 2 +- docs/configuration.rst | 18 +++++++++++------- docs/customizing.rst | 2 +- docs/index.rst | 2 +- docs/models.rst | 19 +++++++++++++------ docs/quickstart.rst | 1 + flask_security/__init__.py | 2 +- flask_security/cli.py | 1 - flask_security/core.py | 2 +- flask_security/datastore.py | 2 +- flask_security/utils.py | 1 - setup.cfg | 1 + tests/test_recoverable.py | 37 +++++++++++++++++++++++++++++++++++++ tox.ini | 19 ++++++++++--------- 17 files changed, 102 insertions(+), 38 deletions(-) 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