mirror of
https://github.com/apache/superset.git
synced 2026-07-05 22:35:34 +00:00
114 lines
5.1 KiB
Python
114 lines
5.1 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:
|
|
"""A sufficiently long, uncommon password passes without raising."""
|
|
validate_password_complexity("a-Good-Long-Passphrase-42")
|
|
|
|
|
|
def test_validate_password_complexity_rejects_short_password() -> None:
|
|
"""A password below the default minimum length is rejected."""
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("short1") # < 8 chars
|
|
|
|
|
|
def test_validate_password_complexity_rejects_common_password() -> None:
|
|
"""Blocklisted passwords are rejected, case-insensitively."""
|
|
# 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:
|
|
"""A configured AUTH_PASSWORD_MIN_LENGTH overrides the default."""
|
|
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:
|
|
"""Entries in AUTH_PASSWORD_COMMON_BLOCKLIST are enforced."""
|
|
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
|
|
|
|
|
|
def test_validate_password_complexity_coerces_string_min_length() -> None:
|
|
"""A string min length (e.g. from an env var) is coerced to int."""
|
|
original = current_app.config.get("AUTH_PASSWORD_MIN_LENGTH")
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = "16" # noqa: S105
|
|
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_tolerates_invalid_min_length() -> None:
|
|
"""A non-numeric min length falls back to the default instead of raising."""
|
|
original = current_app.config.get("AUTH_PASSWORD_MIN_LENGTH")
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = "not-a-number" # noqa: S105
|
|
try:
|
|
validate_password_complexity("a-Good-Long-Passphrase-42")
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("short1") # < default 8
|
|
finally:
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = original
|
|
|
|
|
|
def test_validate_password_complexity_blocklist_string_not_split() -> None:
|
|
"""A bare-string blocklist is one entry, not split into characters."""
|
|
original = current_app.config.get("AUTH_PASSWORD_COMMON_BLOCKLIST")
|
|
current_app.config["AUTH_PASSWORD_COMMON_BLOCKLIST"] = "AcmeCorp2024" # noqa: S105
|
|
try:
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("acmecorp2024")
|
|
# A single character from the string must NOT become blocked.
|
|
validate_password_complexity("a-Good-Long-Passphrase-42")
|
|
finally:
|
|
current_app.config["AUTH_PASSWORD_COMMON_BLOCKLIST"] = original
|
|
|
|
|
|
def test_validate_password_complexity_clamps_non_positive_min_length() -> None:
|
|
"""A zero or negative min length must not disable the length check."""
|
|
original = current_app.config.get("AUTH_PASSWORD_MIN_LENGTH")
|
|
for bad_value in (0, -1):
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = bad_value
|
|
try:
|
|
with pytest.raises(PasswordComplexityValidationError):
|
|
validate_password_complexity("short1") # < default 8
|
|
finally:
|
|
current_app.config["AUTH_PASSWORD_MIN_LENGTH"] = original
|