You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by vi...@apache.org on 2026/04/27 14:06:45 UTC
(airflow) branch main updated: Fix FAB password hashing to respect FAB_PASSWORD_HASH_METHOD config (#65735)
This is an automated email from the ASF dual-hosted git repository.
vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new c50210e8276 Fix FAB password hashing to respect FAB_PASSWORD_HASH_METHOD config (#65735)
c50210e8276 is described below
commit c50210e8276669260059f6a087f65a0f40e7df31
Author: Sadha Chilukoori <sr...@gmail.com>
AuthorDate: Mon Apr 27 07:06:36 2026 -0700
Fix FAB password hashing to respect FAB_PASSWORD_HASH_METHOD config (#65735)
---
.../fab/auth_manager/security_manager/override.py | 10 ++--
.../tests/unit/fab/auth_manager/test_security.py | 58 ++++++++++++++++++++++
2 files changed, 65 insertions(+), 3 deletions(-)
diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
index ababfdd85bc..9bcd361f000 100644
--- a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
+++ b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
@@ -549,6 +549,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
jwt_manager.init_app(current_app)
jwt_manager.user_lookup_loader(self.load_user_jwt)
+ def _hash_password(self, password: str) -> str:
+ method = current_app.config.get("FAB_PASSWORD_HASH_METHOD", "scrypt")
+ return generate_password_hash(password, method=method)
+
def reset_password(self, userid: int, password: str) -> bool:
"""
Change/Reset a user's password for auth db.
@@ -561,7 +565,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
user = self.get_user_by_id(userid)
if not user:
return False
- user.password = generate_password_hash(password)
+ user.password = self._hash_password(password)
self.reset_user_sessions(user)
return self.update_user(user)
@@ -1404,7 +1408,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
if hashed_password:
user.password = hashed_password
else:
- user.password = generate_password_hash(password)
+ user.password = self._hash_password(password)
self.session.commit()
log.info(const.LOGMSG_INF_SEC_ADD_USER, username)
@@ -1448,7 +1452,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
if hashed_password:
register_user.password = hashed_password
else:
- register_user.password = generate_password_hash(password)
+ register_user.password = self._hash_password(password)
register_user.registration_hash = str(uuid.uuid1())
try:
self.session.add(register_user)
diff --git a/providers/fab/tests/unit/fab/auth_manager/test_security.py b/providers/fab/tests/unit/fab/auth_manager/test_security.py
index 389c054e11b..e80e5f3b077 100644
--- a/providers/fab/tests/unit/fab/auth_manager/test_security.py
+++ b/providers/fab/tests/unit/fab/auth_manager/test_security.py
@@ -1215,3 +1215,61 @@ def test_users_can_be_found(app, security_manager, session, caplog):
assert len(users) == 1 + prior_user_count
delete_user(app, "Test_cbf")
assert "Error adding new user to database" in caplog.text
+
+
+OVERRIDE_STRING = "airflow.providers.fab.auth_manager.security_manager.override.{}"
+
+
+@mock.patch(OVERRIDE_STRING.format("generate_password_hash"))
+def test_hash_password_uses_fab_password_hash_method(mock_hash, app, security_manager):
+ """_hash_password must forward FAB_PASSWORD_HASH_METHOD from app config to generate_password_hash."""
+ with app.test_request_context():
+ with mock.patch.dict(app.config, {"FAB_PASSWORD_HASH_METHOD": "pbkdf2:sha256"}):
+ security_manager._hash_password("mypassword")
+ mock_hash.assert_called_once_with("mypassword", method="pbkdf2:sha256")
+
+
+@mock.patch(OVERRIDE_STRING.format("generate_password_hash"))
+def test_hash_password_defaults_to_scrypt_when_config_absent(mock_hash, app, security_manager):
+ """_hash_password falls back to scrypt when FAB_PASSWORD_HASH_METHOD is not in config."""
+ with app.test_request_context():
+ with mock.patch.dict(app.config, {}, clear=False) as cfg:
+ cfg.pop("FAB_PASSWORD_HASH_METHOD", None)
+ security_manager._hash_password("mypassword")
+ mock_hash.assert_called_once_with("mypassword", method="scrypt")
+
+
+def test_reset_password_uses_configured_hash_method(app, security_manager):
+ """reset_password must use FAB_PASSWORD_HASH_METHOD, not werkzeug's default."""
+ try:
+ user = create_user(app, "hash_method_reset_test")
+ with app.test_request_context():
+ with mock.patch.dict(app.config, {"FAB_PASSWORD_HASH_METHOD": "pbkdf2:sha256"}):
+ with mock.patch(
+ OVERRIDE_STRING.format("generate_password_hash"), return_value="hashed_pw"
+ ) as mock_hash:
+ security_manager.reset_password(user.id, "newpassword")
+ mock_hash.assert_called_with("newpassword", method="pbkdf2:sha256")
+ finally:
+ delete_user(app, "hash_method_reset_test")
+
+
+@mock.patch(OVERRIDE_STRING.format("generate_password_hash"))
+def test_add_user_uses_configured_hash_method(mock_hash, app, security_manager):
+ """add_user must use FAB_PASSWORD_HASH_METHOD, not werkzeug's default."""
+ mock_hash.return_value = "hashed_pw"
+ with app.test_request_context():
+ with mock.patch.dict(app.config, {"FAB_PASSWORD_HASH_METHOD": "pbkdf2:sha256"}):
+ role = security_manager.find_role("Admin")
+ try:
+ security_manager.add_user(
+ username="hash_method_add_test",
+ first_name="Hash",
+ last_name="Test",
+ email="hash_method_add_test@example.com",
+ role=role,
+ password="plaintext",
+ )
+ mock_hash.assert_called_with("plaintext", method="pbkdf2:sha256")
+ finally:
+ delete_user(app, "hash_method_add_test")