179 lines
7.8 KiB
Python
179 lines
7.8 KiB
Python
|
|
"""
|
|||
|
|
🚨 Тесты интеграции 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
|