diff --git a/TEST_REPORT_2026-01-31.md b/TEST_REPORT_2026-01-31.md new file mode 100644 index 0000000..7bd9382 --- /dev/null +++ b/TEST_REPORT_2026-01-31.md @@ -0,0 +1,155 @@ +# KPL 考培练系统测试报告 + +**测试环境**: dev (https://kpl.ireborn.com.cn) +**测试时间**: 2026-01-31 +**测试人员**: AI 自动化测试系统 + +--- + +## 一、测试概要 + +| 模块 | 测试用例数 | 通过 | 失败 | 警告 | +|------|-----------|------|------|------| +| 认证模块 | 7 | 5 | 2 | 0 | +| 课程管理 | 7 | 7 | 0 | 0 | +| 成长路径 | 4 | 4 | 0 | 0 | +| 岗位管理 | 2 | 2 | 0 | 0 | +| 考试模块 | 3 | 2 | 1 | 0 | +| AI练习 | 3 | 2 | 0 | 1 | +| 通知系统 | 2 | 2 | 0 | 0 | +| 极端边界 | 8 | 7 | 0 | 1 | +| 安全测试 | 7 | 5 | 0 | 2 | +| **合计** | **43** | **36** | **3** | **4** | + +**通过率**: 83.7% + +--- + +## 二、发现的问题 + +### 严重 (High) + +#### 1. 错误密码登录返回200 +- **位置**: `POST /api/v1/auth/login` +- **描述**: 使用错误密码登录时返回 HTTP 200,应返回 401 +- **影响**: 可能导致暴力破解攻击难以被检测 +- **建议**: 检查登录逻辑,确保密码错误时返回 401 + +#### 2. XSS 内容被原样存储 +- **位置**: `POST /api/v1/courses` (name, description 字段) +- **描述**: `` 等 XSS 代码被原样存入数据库 +- **影响**: 潜在的存储型 XSS 攻击风险 +- **建议**: + - 输入时转义或过滤 HTML 标签 + - 输出时使用 HTML 实体编码 + +### 中等 (Medium) + +#### 3. 不存在用户登录返回422 +- **位置**: `POST /api/v1/auth/login` +- **描述**: 登录不存在的用户返回 422,应返回 401 +- **影响**: 用户枚举风险(可判断用户是否存在) +- **建议**: 统一返回 401 "用户名或密码错误" + +#### 4. API 限流未配置 +- **位置**: 全局 +- **描述**: 10次快速请求未触发限流 +- **影响**: 可能被恶意请求攻击 +- **建议**: 配置 API 限流中间件 + +### 低等 (Low) + +#### 5. 越权访问返回404而非403 +- **位置**: `GET /api/v1/admin/users` +- **描述**: 普通用户访问管理接口返回 404 而非 403 +- **影响**: 信息泄露(可探测接口是否存在) +- **建议**: 统一返回 403 Forbidden + +#### 6. 部分API端点404 +- **位置**: + - `GET /api/v1/exams` (考试列表) + - `GET /api/v1/practice/sessions` (练习记录) +- **描述**: 这些端点返回 404,可能是路径变更或未实现 +- **建议**: 确认 API 路径或补充实现 + +--- + +## 三、测试详情 + +### 3.1 认证模块测试 + +| 测试项 | 结果 | 说明 | +|--------|------|------| +| 正常登录 | ✓ PASS | HTTP 200, Token 获取成功 | +| 错误密码登录 | ✗ FAIL | HTTP 200 (应返回401) | +| 不存在用户登录 | ✗ FAIL | HTTP 422 (应返回401) | +| Token验证 | ✓ PASS | HTTP 200 | +| 无效Token访问 | ✓ PASS | HTTP 401 | +| 无Token访问 | ✓ PASS | HTTP 403 | +| 获取用户信息 | ✓ PASS | HTTP 200 | + +### 3.2 课程管理测试 + +| 测试项 | 结果 | 说明 | +|--------|------|------| +| 获取课程列表 | ✓ PASS | 总课程数: 16 | +| 创建课程 | ✓ PASS | HTTP 201 | +| 获取课程详情 | ✓ PASS | HTTP 200 | +| 更新课程 | ✓ PASS | HTTP 200 | +| 获取考试设置 | ✓ PASS | HTTP 200 | +| 更新考试设置 | ✓ PASS | HTTP 200 | +| 获取不存在课程 | ✓ PASS | HTTP 404 | + +### 3.3 极端边界测试 + +| 测试项 | 结果 | 说明 | +|--------|------|------| +| 空名称创建课程 | ✓ PASS | 正确返回 422 | +| 超长名称(1000字符) | ✓ PASS | 正确返回 422 | +| XSS注入 | ⚠ WARN | 内容被原样存储 | +| SQL注入 | ✓ PASS | 注入被防护 | +| 负数分页参数 | ✓ PASS | 正确返回 422 | +| 超大分页(10000) | ✓ PASS | 正确返回 422 | +| Unicode/Emoji | ✓ PASS | 正确处理 | +| 特殊字符 | ✓ PASS | 正确处理 | + +### 3.4 安全测试 + +| 测试项 | 结果 | 说明 | +|--------|------|------| +| 越权访问 | ⚠ WARN | 返回404而非403 | +| 伪造Token | ✓ PASS | 正确拒绝 | +| 过期Token | ✓ PASS | 正确拒绝 | +| 访问他人数据 | ✓ PASS | 访问被限制 | +| 敏感信息泄露 | ✓ PASS | 未泄露密码/Token | +| API限流 | ⚠ INFO | 未触发限流 | +| 目录遍历 | ✓ PASS | 攻击被阻止 | + +--- + +## 四、修复建议优先级 + +### P0 - 立即修复 +1. 修复错误密码登录返回200的问题 +2. 添加 XSS 输入过滤/输出编码 + +### P1 - 尽快修复 +3. 统一登录错误响应码为401 +4. 配置 API 限流保护 + +### P2 - 计划修复 +5. 越权访问统一返回403 +6. 确认并修复404的API端点 + +--- + +## 五、测试环境信息 + +- **后端容器**: kpl-backend-dev +- **数据库**: MySQL 8.0 +- **测试账号**: admin / admin123 +- **测试时间**: 2026-01-31 10:30 UTC+8 + +--- + +*本报告由自动化测试系统生成* diff --git a/backend/app/api/v1/endpoints/growth_path.py b/backend/app/api/v1/endpoints/growth_path.py index 25a1c0a..307ddef 100644 --- a/backend/app/api/v1/endpoints/growth_path.py +++ b/backend/app/api/v1/endpoints/growth_path.py @@ -7,7 +7,7 @@ from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession -from app.core.deps import get_db, get_current_user +from app.core.deps import get_db, get_current_user, require_admin_or_manager from app.models.user import User from app.services.growth_path_service import growth_path_service from app.schemas.growth_path import ( @@ -118,7 +118,7 @@ async def list_growth_paths( page: int = Query(1, ge=1, description="页码"), page_size: int = Query(20, ge=1, le=100, description="每页数量"), db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user), + current_user: User = Depends(require_admin_or_manager), ): """ 获取成长路径列表(管理端) @@ -141,7 +141,7 @@ async def list_growth_paths( async def create_growth_path( data: GrowthPathCreate, db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user), + current_user: User = Depends(require_admin_or_manager), ): """ 创建成长路径(管理端) @@ -168,7 +168,7 @@ async def create_growth_path( async def get_growth_path( path_id: int, db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user), + current_user: User = Depends(require_admin_or_manager), ): """ 获取成长路径详情(管理端) @@ -190,7 +190,7 @@ async def update_growth_path( path_id: int, data: GrowthPathUpdate, db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user), + current_user: User = Depends(require_admin_or_manager), ): """ 更新成长路径(管理端) @@ -216,7 +216,7 @@ async def update_growth_path( async def delete_growth_path( path_id: int, db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user), + current_user: User = Depends(require_admin_or_manager), ): """ 删除成长路径(管理端)