Files
core/tests/test_oauth_glitchtip_alerts.py

179 lines
7.8 KiB
Python
Raw Normal View History

[0.9.29] - 2025-09-26 ### 🚨 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
2025-09-26 21:03:45 +03:00
"""
🚨 Тесты интеграции OAuth безопасности с GlitchTip
Проверяем отправку алертов безопасности в GlitchTip при критических событиях.
"""
import pytest
from unittest.mock import MagicMock, patch, call
from auth.oauth_security import (
send_rate_limit_alert,
send_open_redirect_alert,
log_oauth_security_event,
_send_security_alert_to_glitchtip,
)
class TestGlitchTipSecurityAlerts:
"""🚨 Тесты отправки алертов безопасности в GlitchTip"""
@patch('sentry_sdk.capture_message')
@patch('sentry_sdk.configure_scope')
def test_critical_security_event_sent_as_error(self, mock_configure_scope, mock_capture_message):
"""🚨 Критические события отправляются как ERROR в GlitchTip"""
mock_scope = MagicMock()
mock_configure_scope.return_value.__enter__.return_value = mock_scope
# Критическое событие
_send_security_alert_to_glitchtip("rate_limit_exceeded", {
"ip": "192.168.1.100",
"attempts": 15,
"severity": "high"
})
# Проверяем настройку scope
mock_scope.set_tag.assert_any_call("security_event", "rate_limit_exceeded")
mock_scope.set_tag.assert_any_call("component", "oauth")
mock_scope.set_tag.assert_any_call("client_ip", "192.168.1.100")
mock_scope.set_context.assert_called_once()
# Проверяем отправку как ERROR
mock_capture_message.assert_called_once_with(
"🚨 CRITICAL OAuth Security Event: rate_limit_exceeded",
level="error"
)
@patch('sentry_sdk.capture_message')
@patch('sentry_sdk.configure_scope')
def test_normal_security_event_sent_as_warning(self, mock_configure_scope, mock_capture_message):
"""⚠️ Обычные события отправляются как WARNING в GlitchTip"""
mock_scope = MagicMock()
mock_configure_scope.return_value.__enter__.return_value = mock_scope
# Обычное событие
_send_security_alert_to_glitchtip("oauth_login_attempt", {
"provider": "github",
"ip": "192.168.1.100"
})
# Проверяем настройку scope
mock_scope.set_tag.assert_any_call("security_event", "oauth_login_attempt")
mock_scope.set_tag.assert_any_call("oauth_provider", "github")
# Проверяем отправку как WARNING
mock_capture_message.assert_called_once_with(
"⚠️ OAuth Security Event: oauth_login_attempt",
level="warning"
)
@patch('sentry_sdk.capture_message')
@patch('sentry_sdk.configure_scope')
def test_open_redirect_alert_integration(self, mock_configure_scope, mock_capture_message):
"""🚨 Тест интеграции алерта open redirect атаки"""
mock_scope = MagicMock()
mock_configure_scope.return_value.__enter__.return_value = mock_scope
# Отправляем алерт о попытке open redirect
send_open_redirect_alert("https://evil.com/steal", "192.168.1.100")
# Проверяем что событие отправлено как критическое
mock_scope.set_tag.assert_any_call("security_event", "open_redirect_attempt")
mock_scope.set_tag.assert_any_call("client_ip", "192.168.1.100")
mock_capture_message.assert_called_once_with(
"🚨 CRITICAL OAuth Security Event: open_redirect_attempt",
level="error"
)
@patch('sentry_sdk.capture_message')
@patch('sentry_sdk.configure_scope')
def test_rate_limit_alert_integration(self, mock_configure_scope, mock_capture_message):
"""🚨 Тест интеграции алерта превышения rate limit"""
mock_scope = MagicMock()
mock_configure_scope.return_value.__enter__.return_value = mock_scope
# Отправляем алерт о превышении rate limit
send_rate_limit_alert("192.168.1.100", 15)
# Проверяем что событие отправлено как критическое
mock_scope.set_tag.assert_any_call("security_event", "rate_limit_exceeded")
mock_scope.set_tag.assert_any_call("client_ip", "192.168.1.100")
mock_capture_message.assert_called_once_with(
"🚨 CRITICAL OAuth Security Event: rate_limit_exceeded",
level="error"
)
@patch('sentry_sdk.configure_scope')
def test_glitchtip_failure_handling(self, mock_configure_scope):
"""❌ Тест обработки ошибок GlitchTip (не ломает основную логику)"""
# Симулируем ошибку GlitchTip
mock_configure_scope.side_effect = Exception("GlitchTip unavailable")
# Функция не должна упасть
try:
_send_security_alert_to_glitchtip("test_event", {"test": "data"})
# Если дошли сюда - хорошо, ошибка обработана
except Exception as e:
pytest.fail(f"GlitchTip error should be handled gracefully: {e}")
@patch('sentry_sdk.capture_message')
@patch('sentry_sdk.configure_scope')
def test_security_context_tags(self, mock_configure_scope, mock_capture_message):
"""🏷️ Тест правильной установки тегов и контекста"""
mock_scope = MagicMock()
mock_configure_scope.return_value.__enter__.return_value = mock_scope
details = {
"ip": "192.168.1.100",
"provider": "github",
"redirect_uri": "https://evil.com",
"attempts": 15,
"severity": "critical"
}
_send_security_alert_to_glitchtip("rate_limit_exceeded", details)
# Проверяем все теги
expected_calls = [
call("security_event", "rate_limit_exceeded"),
call("component", "oauth"),
call("client_ip", "192.168.1.100"),
call("oauth_provider", "github"),
call("has_redirect_uri", "true")
]
for expected_call in expected_calls:
assert expected_call in mock_scope.set_tag.call_args_list
# Проверяем контекст
mock_scope.set_context.assert_called_once_with("security_details", details)
@patch('auth.oauth_security._send_security_alert_to_glitchtip')
def test_log_oauth_security_event_calls_glitchtip(self, mock_glitchtip):
"""🔗 Тест что log_oauth_security_event вызывает GlitchTip"""
event_type = "test_event"
details = {"test": "data"}
log_oauth_security_event(event_type, details)
# Проверяем что GlitchTip функция была вызвана
mock_glitchtip.assert_called_once_with(event_type, details)
def test_critical_events_list(self):
"""📋 Тест что критические события правильно определены"""
# Эти события должны отправляться как ERROR
critical_events = [
"open_redirect_attempt",
"rate_limit_exceeded",
"invalid_provider",
"suspicious_redirect_uri",
"brute_force_detected"
]
# Проверяем что список не пустой и содержит ожидаемые события
assert len(critical_events) > 0
assert "open_redirect_attempt" in critical_events
assert "rate_limit_exceeded" in critical_events