Files
core/tests/test_oauth_security.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

148 lines
6.1 KiB
Python

"""
🧪 Тесты безопасности 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 (минимальные разрешения)