Files
wehub-resource-sync cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

330 lines
12 KiB
Python

"""Unit tests for password utilities."""
from invokeai.app.services.auth.password_utils import (
get_password_strength,
hash_password,
validate_password_strength,
verify_password,
)
class TestPasswordHashing:
"""Tests for password hashing functionality."""
def test_hash_password_returns_different_hash_each_time(self):
"""Test that hashing the same password twice produces different hashes (due to salt)."""
password = "TestPassword123"
hash1 = hash_password(password)
hash2 = hash_password(password)
assert hash1 != hash2
assert hash1 != password
assert hash2 != password
def test_hash_password_with_special_characters(self):
"""Test hashing passwords with special characters."""
password = "Test!@#$%^&*()_+{}[]|:;<>?,./~`"
hashed = hash_password(password)
assert hashed is not None
assert verify_password(password, hashed)
def test_hash_password_with_unicode(self):
"""Test hashing passwords with Unicode characters."""
password = "Test密码123パスワード"
hashed = hash_password(password)
assert hashed is not None
assert verify_password(password, hashed)
def test_hash_password_empty_string(self):
"""Test hashing empty password (should work but fail validation)."""
password = ""
hashed = hash_password(password)
assert hashed is not None
assert verify_password(password, hashed)
def test_hash_password_very_long(self):
"""Test hashing very long passwords (bcrypt has 72 byte limit)."""
# Create a password longer than 72 bytes
password = "A" * 100
hashed = hash_password(password)
assert hashed is not None
# Verify with original password
assert verify_password(password, hashed)
# Should also match the truncated version
assert verify_password("A" * 72, hashed)
def test_hash_password_with_newlines(self):
"""Test hashing passwords containing newlines."""
password = "Test\nPassword\n123"
hashed = hash_password(password)
assert hashed is not None
assert verify_password(password, hashed)
class TestPasswordVerification:
"""Tests for password verification functionality."""
def test_verify_password_correct(self):
"""Test verifying correct password."""
password = "TestPassword123"
hashed = hash_password(password)
assert verify_password(password, hashed) is True
def test_verify_password_incorrect(self):
"""Test verifying incorrect password."""
password = "TestPassword123"
hashed = hash_password(password)
assert verify_password("WrongPassword123", hashed) is False
def test_verify_password_case_sensitive(self):
"""Test that password verification is case-sensitive."""
password = "TestPassword123"
hashed = hash_password(password)
assert verify_password("testpassword123", hashed) is False
assert verify_password("TESTPASSWORD123", hashed) is False
def test_verify_password_whitespace_sensitive(self):
"""Test that whitespace matters in password verification."""
password = "TestPassword123"
hashed = hash_password(password)
assert verify_password(" TestPassword123", hashed) is False
assert verify_password("TestPassword123 ", hashed) is False
assert verify_password("Test Password123", hashed) is False
def test_verify_password_with_special_characters(self):
"""Test verifying passwords with special characters."""
password = "Test!@#$%^&*()_+"
hashed = hash_password(password)
assert verify_password(password, hashed) is True
assert verify_password("Test!@#$%^&*()_+X", hashed) is False
def test_verify_password_with_unicode(self):
"""Test verifying passwords with Unicode."""
password = "Test密码123"
hashed = hash_password(password)
assert verify_password(password, hashed) is True
assert verify_password("Test密码124", hashed) is False
def test_verify_password_empty_against_hashed(self):
"""Test verifying empty password."""
password = ""
hashed = hash_password(password)
assert verify_password("", hashed) is True
assert verify_password("notEmpty", hashed) is False
def test_verify_password_invalid_hash_format(self):
"""Test verifying password against invalid hash format."""
password = "TestPassword123"
# Should return False for invalid hash, not raise exception
assert verify_password(password, "not_a_valid_hash") is False
assert verify_password(password, "") is False
class TestPasswordStrengthValidation:
"""Tests for password strength validation."""
def test_validate_strong_password(self):
"""Test validating a strong password."""
valid, message = validate_password_strength("StrongPass123")
assert valid is True
assert message == ""
def test_validate_password_too_short(self):
"""Test validating password shorter than 8 characters."""
valid, message = validate_password_strength("Short1")
assert valid is False
assert "at least 8 characters" in message
def test_validate_password_minimum_length(self):
"""Test validating password with exactly 8 characters."""
valid, message = validate_password_strength("Pass123A")
assert valid is True
assert message == ""
def test_validate_password_no_uppercase(self):
"""Test validating password without uppercase letters."""
valid, message = validate_password_strength("lowercase123")
assert valid is False
assert "uppercase" in message.lower()
def test_validate_password_no_lowercase(self):
"""Test validating password without lowercase letters."""
valid, message = validate_password_strength("UPPERCASE123")
assert valid is False
assert "lowercase" in message.lower()
def test_validate_password_no_digits(self):
"""Test validating password without digits."""
valid, message = validate_password_strength("NoDigitsHere")
assert valid is False
assert "number" in message.lower()
def test_validate_password_with_special_characters(self):
"""Test that special characters are allowed but not required."""
# With special characters
valid, message = validate_password_strength("Pass!@#$123")
assert valid is True
# Without special characters (but meets other requirements)
valid, message = validate_password_strength("Password123")
assert valid is True
def test_validate_password_with_spaces(self):
"""Test validating password with spaces."""
# Password with spaces that meets requirements
valid, message = validate_password_strength("Pass Word 123")
assert valid is True
assert message == ""
def test_validate_password_unicode(self):
"""Test validating password with Unicode characters."""
# Unicode with uppercase, lowercase, and digits
valid, message = validate_password_strength("密码Pass123")
assert valid is True
def test_validate_password_empty(self):
"""Test validating empty password."""
valid, message = validate_password_strength("")
assert valid is False
assert "at least 8 characters" in message
def test_validate_password_all_requirements_barely_met(self):
"""Test password that barely meets all requirements."""
# 8 chars, 1 upper, 1 lower, 1 digit
valid, message = validate_password_strength("Passwor1")
assert valid is True
assert message == ""
def test_validate_password_very_long(self):
"""Test validating very long password."""
# Very long password that meets requirements
password = "A" * 50 + "a" * 50 + "1" * 50
valid, message = validate_password_strength(password)
assert valid is True
assert message == ""
class TestGetPasswordStrength:
"""Tests for get_password_strength function."""
def test_weak_password_too_short(self):
"""Test that passwords shorter than 8 characters are 'weak'."""
assert get_password_strength("Ab1") == "weak"
assert get_password_strength("Ab1defg") == "weak" # 7 chars
assert get_password_strength("") == "weak"
def test_moderate_password_missing_uppercase(self):
"""Test that 8+ char passwords missing uppercase are 'moderate'."""
assert get_password_strength("lowercase1") == "moderate"
def test_moderate_password_missing_lowercase(self):
"""Test that 8+ char passwords missing lowercase are 'moderate'."""
assert get_password_strength("UPPERCASE1") == "moderate"
def test_moderate_password_missing_digit(self):
"""Test that 8+ char passwords missing digits are 'moderate'."""
assert get_password_strength("NoDigitsHere") == "moderate"
def test_moderate_password_only_lowercase_and_digit(self):
"""Test that 8+ char passwords with only lowercase and digit are 'moderate'."""
assert get_password_strength("lowercase1") == "moderate"
def test_strong_password(self):
"""Test that 8+ char passwords with upper, lower, and digit are 'strong'."""
assert get_password_strength("StrongPass1") == "strong"
assert get_password_strength("Pass123A") == "strong"
def test_strong_password_with_special_chars(self):
"""Test that passwords meeting all requirements plus special chars are 'strong'."""
assert get_password_strength("Pass!@#$123") == "strong"
def test_exactly_8_characters_meeting_requirements(self):
"""Test that exactly 8 characters meeting requirements is 'strong'."""
assert get_password_strength("Pass123A") == "strong"
def test_exactly_8_characters_missing_uppercase(self):
"""Test that exactly 8 characters missing uppercase is 'moderate'."""
assert get_password_strength("pass123a") == "moderate"
def test_strength_progression(self):
"""Test that strength improves as requirements are met."""
# Too short - weak
assert get_password_strength("Abc1") == "weak"
# Long enough but only lowercase - moderate
assert get_password_strength("abcdefgh") == "moderate"
# Meets all requirements - strong
assert get_password_strength("Abcdefg1") == "strong"
class TestPasswordSecurityProperties:
"""Tests for security properties of password handling."""
def test_timing_attack_resistance_same_length(self):
"""Test that password verification takes similar time for correct and incorrect passwords.
Note: This is a basic check. Real timing attack resistance requires more sophisticated testing.
"""
import time
password = "TestPassword123"
hashed = hash_password(password)
# Measure time for correct password
start = time.perf_counter()
for _ in range(100):
verify_password(password, hashed)
correct_time = time.perf_counter() - start
# Measure time for incorrect password of same length
start = time.perf_counter()
for _ in range(100):
verify_password("WrongPassword12", hashed)
incorrect_time = time.perf_counter() - start
# Times should be relatively similar (within 50% difference)
# This is a loose check as bcrypt is designed to be slow and timing-resistant
ratio = max(correct_time, incorrect_time) / min(correct_time, incorrect_time)
assert ratio < 1.5, "Timing difference too large, potential timing attack vulnerability"
def test_different_hashes_for_same_password(self):
"""Test that the same password produces different hashes (salt randomization)."""
password = "TestPassword123"
hashes = {hash_password(password) for _ in range(10)}
# All hashes should be unique due to random salt
assert len(hashes) == 10
def test_hash_output_format(self):
"""Test that hash output follows bcrypt format."""
password = "TestPassword123"
hashed = hash_password(password)
# Bcrypt hashes start with $2b$ (or other valid bcrypt identifiers)
assert hashed.startswith("$2")
# Bcrypt hashes are 60 characters long
assert len(hashed) == 60