### 🚨 CRITICAL Security Fixes - **🔒 Open Redirect Protection**: Добавлена строгая валидация redirect_uri против whitelist доменов - **🔒 Rate Limiting**: Защита OAuth endpoints от брутфорса (10 попыток за 5 минут на IP) - **🔒 Logout Endpoint**: Критически важный endpoint для безопасного отзыва httpOnly cookies - **🔒 Provider Validation**: Усиленная валидация OAuth провайдеров с логированием атак - **🚨 GlitchTip Alerts**: Автоматические алерты безопасности в GlitchTip при критических событиях ### 🛡️ Security Modules - **auth/oauth_security.py**: Модуль безопасности OAuth с валидацией и rate limiting + GlitchTip алерты - **auth/logout.py**: Безопасный logout с поддержкой JSON API и browser redirect - **tests/test_oauth_security.py**: Комплексные тесты безопасности (11 тестов) - **tests/test_oauth_glitchtip_alerts.py**: Тесты интеграции с GlitchTip (8 тестов) ### 🔧 OAuth Improvements - **Minimal Flow**: Упрощен до минимума - только httpOnly cookie, нет JWT в URL - **Simple Logic**: Нет error параметра = успех, максимальная простота - **DRY Refactoring**: Устранено дублирование кода в logout и валидации ### 🎯 OAuth Endpoints - **Старт**: `v3.dscrs.site/oauth/{provider}` - с rate limiting и валидацией - **Callback**: `v3.dscrs.site/oauth/{provider}/callback` - безопасный redirect_uri - **Logout**: `v3.dscrs.site/auth/logout` - отзыв httpOnly cookies - **Финализация**: `testing.discours.io/oauth?redirect_url=...` - минимальная схема ### 📊 Security Test Coverage - ✅ Open redirect attack prevention - ✅ Rate limiting protection - ✅ Provider validation - ✅ Safe fallback mechanisms - ✅ Cookie security (httpOnly + Secure + SameSite) - ✅ GlitchTip integration (8 тестов алертов) ### 📝 Documentation - Создан `docs/oauth-minimal-flow.md` - полное описание минимального flow - Обновлена документация OAuth в `docs/auth/oauth.md` - Добавлены security best practices
This commit is contained in:
147
tests/test_oauth_security.py
Normal file
147
tests/test_oauth_security.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
🧪 Тесты безопасности OAuth - Критические уязвимости
|
||||
|
||||
Тестирует исправления найденных проблем безопасности.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from auth.oauth_security import (
|
||||
validate_redirect_uri,
|
||||
check_oauth_rate_limit,
|
||||
get_safe_redirect_uri,
|
||||
validate_oauth_provider
|
||||
)
|
||||
|
||||
|
||||
class TestRedirectURIValidation:
|
||||
"""🔒 Тесты валидации redirect URI против open redirect атак"""
|
||||
|
||||
def test_valid_redirect_uris(self):
|
||||
"""✅ Валидные redirect URI должны проходить"""
|
||||
valid_uris = [
|
||||
"https://testing.discours.io/oauth",
|
||||
"http://localhost:3000/oauth", # Разработка
|
||||
]
|
||||
|
||||
for uri in valid_uris:
|
||||
assert validate_redirect_uri(uri), f"Should be valid: {uri}"
|
||||
|
||||
def test_invalid_redirect_uris(self):
|
||||
"""❌ Опасные redirect URI должны блокироваться"""
|
||||
invalid_uris = [
|
||||
"https://evil.com/phishing", # Неразрешенный домен
|
||||
"javascript:alert('xss')", # JavaScript injection
|
||||
"data:text/html,<script>", # Data URI
|
||||
"ftp://malicious.com/", # Неразрешенная схема
|
||||
"https://discours.io.evil.com/", # Subdomain hijacking
|
||||
"", # Пустая строка
|
||||
"not-a-url", # Невалидный URL
|
||||
]
|
||||
|
||||
for uri in invalid_uris:
|
||||
assert not validate_redirect_uri(uri), f"Should be invalid: {uri}"
|
||||
|
||||
def test_redirect_uri_length_limit(self):
|
||||
"""🔒 Слишком длинные URI должны блокироваться"""
|
||||
long_uri = "https://testing.discours.io/" + "a" * 3000
|
||||
assert not validate_redirect_uri(long_uri)
|
||||
|
||||
|
||||
class TestOAuthRateLimit:
|
||||
"""🔒 Тесты rate limiting для OAuth endpoints"""
|
||||
|
||||
def test_rate_limit_allows_normal_usage(self):
|
||||
"""✅ Нормальное использование должно проходить"""
|
||||
# Очищаем rate limits для теста
|
||||
from auth.oauth_security import oauth_rate_limits
|
||||
oauth_rate_limits.clear()
|
||||
|
||||
# Первые 10 запросов должны проходить
|
||||
for i in range(10):
|
||||
assert check_oauth_rate_limit("192.168.1.1")
|
||||
|
||||
def test_rate_limit_blocks_excessive_requests(self):
|
||||
"""❌ Избыточные запросы должны блокироваться"""
|
||||
from auth.oauth_security import oauth_rate_limits
|
||||
oauth_rate_limits.clear()
|
||||
|
||||
# Заполняем лимит
|
||||
for i in range(10):
|
||||
check_oauth_rate_limit("192.168.1.2")
|
||||
|
||||
# 11-й запрос должен блокироваться
|
||||
assert not check_oauth_rate_limit("192.168.1.2")
|
||||
|
||||
def test_rate_limit_per_ip(self):
|
||||
"""🔒 Rate limit должен работать отдельно для каждого IP"""
|
||||
from auth.oauth_security import oauth_rate_limits
|
||||
oauth_rate_limits.clear()
|
||||
|
||||
# Заполняем лимит для одного IP
|
||||
for i in range(10):
|
||||
check_oauth_rate_limit("192.168.1.3")
|
||||
|
||||
# Другой IP должен работать нормально
|
||||
assert check_oauth_rate_limit("192.168.1.4")
|
||||
|
||||
|
||||
class TestSafeRedirectURI:
|
||||
"""🔒 Тесты безопасного получения redirect URI"""
|
||||
|
||||
def test_safe_redirect_uri_with_valid_query_param(self):
|
||||
"""✅ Валидный query параметр должен использоваться"""
|
||||
mock_request = MagicMock()
|
||||
mock_request.query_params.get.return_value = "https://testing.discours.io/success"
|
||||
mock_request.path_params.get.return_value = None
|
||||
|
||||
result = get_safe_redirect_uri(mock_request)
|
||||
assert result == "https://testing.discours.io/success"
|
||||
|
||||
def test_safe_redirect_uri_blocks_malicious(self):
|
||||
"""❌ Вредоносный URI должен заменяться на fallback"""
|
||||
mock_request = MagicMock()
|
||||
mock_request.query_params.get.return_value = "https://evil.com/phishing"
|
||||
mock_request.path_params.get.return_value = None
|
||||
|
||||
result = get_safe_redirect_uri(mock_request)
|
||||
assert result == "https://testing.discours.io" # Fallback
|
||||
|
||||
def test_safe_redirect_uri_fallback_when_empty(self):
|
||||
"""🔒 Пустые параметры должны использовать fallback"""
|
||||
mock_request = MagicMock()
|
||||
mock_request.query_params.get.return_value = None
|
||||
mock_request.path_params.get.return_value = None
|
||||
|
||||
result = get_safe_redirect_uri(mock_request)
|
||||
assert result == "https://testing.discours.io"
|
||||
|
||||
|
||||
class TestProviderValidation:
|
||||
"""🔒 Тесты валидации OAuth провайдеров"""
|
||||
|
||||
@patch('auth.oauth.PROVIDER_CONFIGS', {'github': {}, 'google': {}})
|
||||
def test_valid_provider(self):
|
||||
"""✅ Валидный провайдер должен проходить"""
|
||||
assert validate_oauth_provider("github")
|
||||
assert validate_oauth_provider("google")
|
||||
|
||||
@patch('auth.oauth.PROVIDER_CONFIGS', {'github': {}, 'google': {}})
|
||||
def test_invalid_provider(self):
|
||||
"""❌ Невалидный провайдер должен блокироваться"""
|
||||
assert not validate_oauth_provider("evil_provider")
|
||||
assert not validate_oauth_provider("")
|
||||
assert not validate_oauth_provider(None)
|
||||
|
||||
|
||||
# 🎯 Итого: Тесты покрывают все критичные уязвимости
|
||||
# ✅ Open redirect protection
|
||||
# ✅ Rate limiting
|
||||
# ✅ Provider validation
|
||||
# ✅ Safe fallbacks
|
||||
|
||||
# 🔍 Принципы безопасности:
|
||||
# - Fail securely (блокируем при сомнениях)
|
||||
# - Defense in depth (несколько уровней защиты)
|
||||
# - Principle of least privilege (минимальные разрешения)
|
||||
Reference in New Issue
Block a user