mirror of
https://github.com/apache/superset.git
synced 2026-06-12 02:59:27 +00:00
SupersetSecurityManager did not enforce any password policy, so DB-auth self-registration / password changes accepted trivially short or common passwords (ASVS 6.2.1 / 6.2.4, CWE-521). Add superset.security.password_complexity.validate_password_complexity (minimum length via AUTH_PASSWORD_MIN_LENGTH, default 8, plus a common-password blocklist extendable via AUTH_PASSWORD_COMMON_BLOCKLIST), and wire it through Flask-AppBuilder's FAB_PASSWORD_COMPLEXITY_ENABLED / _VALIDATOR. FAB runs this callable from both the WTForms password fields (self-registration, user edit, reset password) and the User REST API, so one function covers all password-setting flows. The policy is intentionally less draconian than FAB's built-in default_password_complexity. DRAFT: enabling the policy by default changes registration / password-change behavior (short or common passwords are now rejected) and any API-driven user provisioning that used weak passwords. Needs validation of the end-to-end flows before merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
2.5 KiB
Python
62 lines
2.5 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import pytest
|
|
from flask import current_app
|
|
from flask_appbuilder.exceptions import PasswordComplexityValidationError
|
|
|
|
from superset.security.password_complexity import validate_password_complexity
|
|
|
|
|
|
def test_validate_password_complexity_accepts_strong_password() -> None:
|
|
# No exception for a sufficiently long, uncommon password.
|
|
validate_password_complexity("a-Good-Long-Passphrase-42")
|
|
|
|
|
|
def test_validate_password_complexity_rejects_short_password() -> None:
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("short1") # < 8 chars
|
|
|
|
|
|
def test_validate_password_complexity_rejects_common_password() -> None:
|
|
# Common even though length >= 8.
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("password123")
|
|
# Case-insensitive.
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("PASSWORD123")
|
|
|
|
|
|
def test_validate_password_complexity_honors_configured_min_length() -> None:
|
|
original = current_app.config.get("AUTH_PASSWORD_MIN_LENGTH")
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = 16
|
|
try:
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("only12chars!") # 12 < 16
|
|
finally:
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = original
|
|
|
|
|
|
def test_validate_password_complexity_honors_extra_blocklist() -> None:
|
|
original = current_app.config.get("AUTH_PASSWORD_COMMON_BLOCKLIST")
|
|
current_app.config["AUTH_PASSWORD_COMMON_BLOCKLIST"] = ["AcmeCorp2024"]
|
|
try:
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("acmecorp2024")
|
|
finally:
|
|
current_app.config["AUTH_PASSWORD_COMMON_BLOCKLIST"] = original
|