Add security tests for configurations, permissions, and authentication

- **Configurations:** Test minimum `SECRET_KEY` length validation to prevent weak JWT signing keys. Validate proper handling of secure defaults.
- **Permissions:** Add tests for inactive user blocking, API access control, and superuser privilege escalation across organizational roles.
- **Authentication:** Test logout safety, session revocation, token replay prevention, and defense against JWT algorithm confusion attacks.
- Include `# pragma: no cover` for unreachable defensive code in security-sensitive areas.
This commit is contained in:
2025-11-02 11:55:58 +01:00
parent b39b7b4c94
commit c051bbf0aa
7 changed files with 923 additions and 50 deletions

View File

@@ -0,0 +1,135 @@
"""
Security tests for configuration validation (app/core/config.py).
Critical security tests covering:
- SECRET_KEY minimum length validation (prevents weak JWT signing keys)
These tests prevent security misconfigurations.
"""
import pytest
import os
from pydantic import ValidationError
class TestSecretKeySecurityValidation:
"""
Test SECRET_KEY security validation (config.py line 109).
Attack Prevention:
Short SECRET_KEYs can be brute-forced, compromising JWT token security.
System must enforce minimum 32-character requirement.
Covers: config.py:109
"""
def test_secret_key_too_short_rejected(self):
"""
Test that SECRET_KEY shorter than 32 characters is rejected.
Security Risk:
Short keys (e.g., "password123") can be brute-forced, allowing
attackers to forge JWT tokens.
Covers line 109.
"""
# Save original SECRET_KEY
original_secret = os.environ.get("SECRET_KEY")
try:
# Try to set a short SECRET_KEY (only 20 characters)
short_key = "a" * 20 # Too short!
os.environ["SECRET_KEY"] = short_key
# Import Settings class fresh (to pick up new env var)
# The ValidationError should be raised during reload when Settings() is instantiated
import importlib
from app.core import config
# Reload will raise ValidationError because Settings() is instantiated at module level
with pytest.raises(ValidationError, match="at least 32 characters"):
importlib.reload(config)
finally:
# Restore original SECRET_KEY
if original_secret:
os.environ["SECRET_KEY"] = original_secret
else:
os.environ.pop("SECRET_KEY", None)
# Reload config to restore original settings
import importlib
from app.core import config
importlib.reload(config)
def test_secret_key_exactly_32_characters_accepted(self):
"""
Test that SECRET_KEY with exactly 32 characters is accepted.
Minimum secure length.
"""
original_secret = os.environ.get("SECRET_KEY")
try:
# Set exactly 32-character key
key_32 = "a" * 32
os.environ["SECRET_KEY"] = key_32
import importlib
from app.core import config
importlib.reload(config)
# Should work
settings = config.Settings()
assert len(settings.SECRET_KEY) == 32
finally:
if original_secret:
os.environ["SECRET_KEY"] = original_secret
else:
os.environ.pop("SECRET_KEY", None)
import importlib
from app.core import config
importlib.reload(config)
def test_secret_key_long_enough_accepted(self):
"""
Test that SECRET_KEY with 32+ characters is accepted.
Sanity check that valid keys work.
"""
original_secret = os.environ.get("SECRET_KEY")
try:
# Set long key (64 characters)
key_64 = "a" * 64
os.environ["SECRET_KEY"] = key_64
import importlib
from app.core import config
importlib.reload(config)
# Should work
settings = config.Settings()
assert len(settings.SECRET_KEY) >= 32
finally:
if original_secret:
os.environ["SECRET_KEY"] = original_secret
else:
os.environ.pop("SECRET_KEY", None)
import importlib
from app.core import config
importlib.reload(config)
def test_default_secret_key_meets_requirements(self):
"""
Test that the default SECRET_KEY (if no env var) meets requirements.
Ensures our defaults are secure.
"""
from app.core.config import settings
# Current settings should have valid SECRET_KEY
assert len(settings.SECRET_KEY) >= 32, "Default SECRET_KEY must be at least 32 chars"