Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Docs and Chores
+++++++++++++++
- (:issue:`1208`) Remove support for Pony ORM
- (:pr:`1240`) Remove deprecated get_token_status() and convert uses to check_and_get_token_status()
- Replace ``bleach`` (deprecated/unmaintained) with ``nh3`` for username sanitization

Backwards Compatibility Concerns
+++++++++++++++++++++++++++++++++
Expand All @@ -53,6 +54,9 @@ Backwards Compatibility Concerns
- With the addition of email template path configuration variables, the
:py:meth:`flask_security.send_mail` method's ``template`` parameter now requires a
complete template path (relative to the application or blueprint root).
- Username sanitization now uses ``nh3`` instead of ``bleach``. If you enabled
:py:data:`SECURITY_USERNAME_ENABLE` you must now install ``nh3`` (included in the
``common`` extra) rather than ``bleach``.

Notes
+++++
Expand Down
13 changes: 7 additions & 6 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,12 @@ These configuration keys are used globally across all features.

.. danger::
Make sure your mapper methods guard against malicious user input. For example,
if you allow ``username`` as an identity method you could use `bleach`_::
if you allow ``username`` as an identity method you could use `nh3`_::

def uia_username_mapper(identity):
# we allow pretty much anything - but we bleach it.
return bleach.clean(identity, strip=True)
# we allow pretty much anything - but we sanitize it.
# tags=set() strips all HTML tags rather than allowing nh3's defaults.
return nh3.clean(identity, tags=set())

Default::

Expand All @@ -383,7 +384,7 @@ These configuration keys are used globally across all features.
.. versionchanged:: 4.0.0
Changed from list to list of dict.

.. _bleach: https://pypi.org/project/bleach/
.. _nh3: https://pypi.org/project/nh3/

.. py:data:: SECURITY_USER_IDENTITY_MAPPINGS

Expand Down Expand Up @@ -987,8 +988,8 @@ Registerable

Validation and normalization is encapsulated in :class:`.UsernameUtil`.
Note that the default validation restricts username input to be unicode
letters and numbers. It also uses ``bleach`` to scrub any risky input. Be
sure your application requirements includes `bleach`_.
letters and numbers. It also uses ``nh3`` to scrub any risky input. Be
sure your application requirements includes `nh3`_.

Default: ``False``

Expand Down
2 changes: 1 addition & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Supported extras are:

* ``babel`` - Translation services. It will install babel and Flask-Babel.
* ``fsqla`` - Use flask-sqlalchemy and sqlalchemy as your storage interface.
* ``common`` - Install Flask-Mail, argon2 (the default password hash), bcrypt (old commonly used password hash) and bleach.
* ``common`` - Install Flask-Mail, argon2 (the default password hash), bcrypt (old commonly used password hash) and nh3.
* ``mfa`` - Install packages used for multi-factor (two-factor, unified signin, WebAuthn):
cryptography, qrcode, phonenumberslite (note that for SMS you still need
to pick an SMS provider and install appropriate packages), and webauthn.
Expand Down
2 changes: 1 addition & 1 deletion flask_security/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1992,7 +1992,7 @@ def init_app(
self._check_modules("webauthn", "WEBAUTHN")

if cv("USERNAME_ENABLE", app=app) and self._username_util_cls == UsernameUtil:
self._check_modules("bleach", "USERNAME_ENABLE")
self._check_modules("nh3", "USERNAME_ENABLE")

# Register so other packages can reference our translations.
app.jinja_env.globals["_fsdomain"] = self.i18n_domain.gettext
Expand Down
10 changes: 5 additions & 5 deletions flask_security/username_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ def check_username(self, username: str) -> str | None:

def normalize(self, username: str) -> str:
"""
Given an input username - return a clean (using bleach) and normalized
Given an input username - return a clean (using nh3) and normalized
(using Python's unicodedata.normalize()) version.
Must be called in app context and uses
:py:data:`SECURITY_USERNAME_NORMALIZE_FORM` config variable.
"""
import bleach
import nh3

if not username:
return ""

username = bleach.clean(username.strip(), strip=True)
username = nh3.clean(username.strip(), tags=set())
if not username:
return ""
cf = cv("USERNAME_NORMALIZE_FORM")
Expand All @@ -87,11 +87,11 @@ def validate(self, username: str) -> tuple[str | None, str | None]:
It is important that None be returned if data is an empty string since
otherwise DBs will complain since the field is unique/nullable.
"""
import bleach
import nh3

if not username:
return None, None
uclean = bleach.clean(username.strip(), strip=True)
uclean = nh3.clean(username.strip(), tags=set())
if uclean != username:
return get_message("USERNAME_ILLEGAL_CHARACTERS")[0], None

Expand Down
4 changes: 2 additions & 2 deletions pyproject-too.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ dependencies = [
[project.optional-dependencies]
babel = ["babel>=2.17.0", "flask_babel>=4.0.0", "humanize>=4.12.0"]
fsqla = ["flask_sqlalchemy>=3.1.1", "sqlalchemy>=2.0.41"]
common = ["argon2_cffi>=25.1.0", "bcrypt>=4.2.1", "flask_mail>=0.10.0", "bleach>=6.0.0"]
common = ["argon2_cffi>=25.1.0", "bcrypt>=4.2.1", "flask_mail>=0.10.0", "nh3>=0.2.15"]
mfa = ["cryptography>=45.0.7", "qrcode>=7.4.2", "phonenumberslite>=8.13.11", "webauthn>=2.6.0"]
low = [
# Lowest supported versions
Expand All @@ -60,7 +60,7 @@ low = [
"authlib==1.2.0",
"babel==2.16.0",
"bcrypt==4.0.1",
"bleach==6.0.0",
"nh3==0.2.15",
"freezegun",
"humanize",
"jinja2==3.1.2",
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ dependencies = [
[project.optional-dependencies]
babel = ["babel>=2.17.0", "flask_babel>=4.0.0", "humanize>=4.12.0"]
fsqla = ["flask_sqlalchemy>=3.1.1", "sqlalchemy>=2.0.41"]
common = ["argon2_cffi>=25.1.0", "bcrypt>=4.2.1", "flask_mail>=0.10.0", "bleach>=6.0.0"]
common = ["argon2_cffi>=25.1.0", "bcrypt>=4.2.1", "flask_mail>=0.10.0", "nh3>=0.2.15"]
mfa = ["cryptography>=45.0.7", "qrcode>=7.4.2", "phonenumberslite>=8.13.11", "webauthn>=2.6.0"]
low = [
# Lowest supported versions
Expand All @@ -60,7 +60,7 @@ low = [
"authlib==1.2.0",
"babel==2.16.0",
"bcrypt==4.0.1",
"bleach==6.0.0",
"nh3==0.2.15",
"freezegun",
"humanize",
"jinja2==3.1.2",
Expand Down
2 changes: 1 addition & 1 deletion requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Flask-SQLAlchemy-Lite
argon2-cffi
authlib
bcrypt
bleach
cbor2
coverage
cryptography
Expand All @@ -16,6 +15,7 @@ humanize
mongoengine
mongomock
msgcheck
nh3
peewee
phonenumberslite
pydocstyle
Expand Down