"""
认证安全测试套件

测试范围:
1. 密码安全测试
2. 登录安全测试
3. 会话管理测试
4. 权限验证测试
5. 暴力破解防护测试

作者: 张翔
日期: 2026-04-01
"""

import pytest
import time
from api.auth_api import AuthAPI
from api.user_api import UserAPI
from config.settings import settings


@pytest.mark.security
@pytest.mark.asyncio
class TestAuthenticationSecurity:
    """认证安全测试类"""
    
    async def test_password_strength_validation(self, authenticated_client):
        """
        SEC-AUTH-01: 密码强度验证
        
        验证点:
        1. 弱密码被拒绝
        2. 密码复杂度要求
        3. 密码长度要求
        """
        user_api = UserAPI(authenticated_client)
        
        weak_passwords = [
            "123456",
            "password",
            "admin",
            "qwerty",
            "abc123",
            "111111",
            "1234567890",
            "password123"
        ]
        
        for weak_pwd in weak_passwords:
            user_data = {
                "username": f"weak_pwd_test_{weak_pwd}",
                "password": weak_pwd,
                "email": f"weak_{weak_pwd}@test.com",
                "phone": "13800138000",
                "status": 1
            }
            
            response = await user_api.create_user(user_data)
            
            assert response.status_code in [400, 422], \
                f"弱密码 '{weak_pwd}' 应被拒绝"
    
    async def test_password_hashing(self, authenticated_client):
        """
        SEC-AUTH-02: 密码哈希验证
        
        验证点:
        1. 密码不以明文存储
        2. 使用BCrypt或其他安全哈希
        3. 每次哈希结果不同(盐值)
        """
        user_api = UserAPI(authenticated_client)
        auth_api = AuthAPI(authenticated_client)
        
        user_data = {
            "username": "hash_test_user",
            "password": "Test123!@#",
            "email": "hash_test@test.com",
            "phone": "13800138000",
            "status": 1
        }
        
        response = await user_api.create_user(user_data)
        
        if response.status_code in [201, 200]:
            user_id = response.json().get("id")
            
            login_response = await auth_api.login(
                user_data["username"],
                user_data["password"]
            )
            
            assert login_response.status_code == 200, "密码验证失败"
            
            await user_api.delete_user(user_id)
    
    async def test_brute_force_protection(self, authenticated_client):
        """
        SEC-AUTH-03: 暴力破解防护
        
        验证点:
        1. 多次失败登录被限制
        2. 账户锁定机制
        3. 登录失败提示不泄露信息
        """
        auth_api = AuthAPI(authenticated_client)
        
        failed_attempts = 0
        max_attempts = 10
        
        for i in range(max_attempts):
            response = await auth_api.login("admin", "wrong_password_123")
            
            if response.status_code == 429:
                assert True, "暴力破解防护生效"
                return
            elif response.status_code == 401:
                failed_attempts += 1
            else:
                break
        
        assert failed_attempts >= 3, \
            "应实施暴力破解防护(至少3次失败后限制)"
    
    async def test_session_timeout(self, authenticated_client):
        """
        SEC-AUTH-04: 会话超时测试
        
        验证点:
        1. Token有过期时间
        2. 过期Token无法使用
        3. 会话自动失效
        """
        auth_api = AuthAPI(authenticated_client)
        user_api = UserAPI(authenticated_client)
        
        login_response = await auth_api.login("admin", "admin123")
        token = login_response.json().get("token")
        
        import jwt
        decoded = jwt.decode(token, options={"verify_signature": False})
        
        assert "exp" in decoded, "Token应包含过期时间"
        
        exp_time = decoded["exp"]
        current_time = time.time()
        
        assert exp_time > current_time, "Token不应已过期"
        assert exp_time - current_time <= 86400, "Token有效期不应超过24小时"
    
    async def test_concurrent_session_handling(self, authenticated_client):
        """
        SEC-AUTH-05: 并发会话处理
        
        验证点:
        1. 支持并发登录
        2. 或限制并发会话数
        3. 会话隔离
        """
        auth_api = AuthAPI(authenticated_client)
        
        login_responses = []
        
        for i in range(3):
            response = await auth_api.login("admin", "admin123")
            login_responses.append(response)
        
        successful_logins = sum(
            1 for r in login_responses if r.status_code == 200
        )
        
        assert successful_logins >= 1, "至少应支持一次登录"
    
    async def test_logout_security(self, authenticated_client):
        """
        SEC-AUTH-06: 登出安全测试
        
        验证点:
        1. 登出后Token失效
        2. 无法重复使用登出Token
        """
        auth_api = AuthAPI(authenticated_client)
        user_api = UserAPI(authenticated_client)
        
        login_response = await auth_api.login("admin", "admin123")
        token = login_response.json().get("token")
        
        logout_response = await auth_api.logout()
        
        if logout_response.status_code == 200:
            client_with_old_token = authenticated_client.__class__(
                base_url=settings.API_BASE_URL,
                headers={"Authorization": f"Bearer {token}"}
            )
            
            user_api_old = UserAPI(client_with_old_token)
            response = await user_api_old.get_users_by_page()
            
            assert response.status_code in [401, 403], \
                "登出后Token应失效"
    
    async def test_password_change_security(self, authenticated_client):
        """
        SEC-AUTH-07: 密码修改安全
        
        验证点:
        1. 需要旧密码验证
        2. 新密码强度验证
        3. 修改后需重新登录
        """
        user_api = UserAPI(authenticated_client)
        auth_api = AuthAPI(authenticated_client)
        
        user_data = {
            "username": "pwd_change_test",
            "password": "OldPassword123!@#",
            "email": "pwd_change@test.com",
            "phone": "13800138000",
            "status": 1
        }
        
        create_response = await user_api.create_user(user_data)
        
        if create_response.status_code in [201, 200]:
            user_id = create_response.json().get("id")
            
            login_response = await auth_api.login(
                user_data["username"],
                user_data["password"]
            )
            
            assert login_response.status_code == 200
            
            await user_api.delete_user(user_id)
    
    async def test_account_lockout_mechanism(self, authenticated_client):
        """
        SEC-AUTH-08: 账户锁定机制
        
        验证点:
        1. 多次失败后账户锁定
        2. 锁定时间合理
        3. 管理员可解锁
        """
        auth_api = AuthAPI(authenticated_client)
        
        for i in range(5):
            response = await auth_api.login("admin", "wrong_password")
            
            if response.status_code == 423:
                assert True, "账户锁定机制生效"
                return
        
        pytest.skip("系统未实现账户锁定机制")
    
    async def test_login_csrf_protection(self, authenticated_client):
        """
        SEC-AUTH-09: 登录CSRF防护
        
        验证点:
        1. 登录表单有CSRF Token
        2. CSRF Token验证
        """
        auth_api = AuthAPI(authenticated_client)
        
        login_response = await auth_api.login("admin", "admin123")
        
        assert login_response.status_code == 200
    
    async def test_password_reset_security(self, authenticated_client):
        """
        SEC-AUTH-10: 密码重置安全
        
        验证点:
        1. 需要邮箱验证
        2. 重置链接有时效
        3. 重置链接一次性使用
        """
        pytest.skip("密码重置功能待实现或测试")
