Files
core/tests/test_oauth_glitchtip_alerts.py
Untone 05c188df62
Some checks failed
Deploy on push / deploy (push) Failing after 39s
[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

179 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
🚨 Тесты интеграции 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