### 🚨 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:
178
tests/test_oauth_glitchtip_alerts.py
Normal file
178
tests/test_oauth_glitchtip_alerts.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
🚨 Тесты интеграции 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
|
||||
Reference in New Issue
Block a user