fix(security): close unauthenticated CMA user API surface + audit tool (cma-api-auth-surface-audit-20260914)
Root cause: main.py include_router had no global auth dependency; user routers (products/cost/predict/...) relied on assert_entity_scope, which treats a missing session token as the Bot service channel and lets the request through. Result: 46 user endpoints returned data without any token on a public site. Changes: - auth_middleware.py: add require_auth_401 (missing Bearer -> 401, delegates to require_auth otherwise); 401/403 semantics are explicit. - router-level Depends(require_auth_401) on products, cost, predict, notifications, permissions, tenant, alignment; endpoint-level on /auth/roles and /admin/cache/clear. - deps.py: document that get_entity_id/assert_entity_scope are NOT auth. - frontend: add the missing Bearer header to 5 tokenless fetch/axios calls that hit the newly protected endpoints (KPIAlignment x2, RolePermissions x2, Dashboard x1). - tests: new test_api_auth_surface_20260914.py (58 cases: no-token 401, token 200, cross-entity 403, bot channel unchanged); adapt test_predict / test_kpi_forecast to send session tokens like production. - scripts/audit_api_auth_surface.py: reusable FastAPI dependency-graph audit (423 endpoints; A bot / B gap / C public), exits 1 when a B-class gap exists.
This commit is contained in:
+10
-15
@@ -10,7 +10,7 @@ from app.database import init_db, get_db
|
||||
from app.api import auth, kpis, kpi_governance, templates, maps, dashboard, data, alerts, ai_analysis, ai_suggestions, alert_rules, users, thresholds, notifications, permissions, action_plans, alignment, org, objectives, versions, budget, cost, predict, reports, security, knowledge, bot_bridge, bot_bridge_v2, bot_suggestions, lead, tenant, customer_dashboard, deviation_push, budget_generate, knowledge_articles, kpi_causality, data_quality, bi_reports, entities, bsc_layers, okr, okr_templates, subjects, driver_budget, bot_kpis, ontology, bot_iron_law, analysis_results, expenses, cash, tax_compliance, verify, growth_quality, products, data_classification, value_sources, zero_based, derivation_rules, cash_classify, cashflow_structure, cash_vouchers_json
|
||||
from app.utils.cache import clear_all as clear_cache, delete as delete_cache
|
||||
from scripts.erp_sync import run_sync as run_erp_sync
|
||||
from app.auth_middleware import require_auth
|
||||
from app.auth_middleware import require_auth, require_auth_401
|
||||
from app.api.audit_log import bot_audit_middleware
|
||||
|
||||
load_dotenv()
|
||||
@@ -118,18 +118,8 @@ _ERP_CHANNEL_DISABLED_REASON = ("ERP直连同步通道已于2026-08-31停用(e
|
||||
|
||||
# ── admin 端点鉴权(2026-09-13)──────────────────────────────
|
||||
# 未带 token → 显式 401(缺少认证),校验逻辑仍完全委托 require_auth(无效 token → 401)。
|
||||
# 说明:FastAPI 的 HTTPBearer(auto_error=True) 对"缺 header"返回 403;本端点验收要求 401,
|
||||
# 故用 auto_error=False 的实例自行判空,不改动全局 require_auth(避免其他端点行为漂移)。
|
||||
_optional_bearer = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
def require_auth_401(
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(_optional_bearer),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
if credentials is None or not credentials.credentials:
|
||||
raise HTTPException(status_code=401, detail="未认证:缺少 Bearer token")
|
||||
return require_auth(credentials=credentials, db=db)
|
||||
# 依赖实现已上移到 app/auth_middleware.py 的 require_auth_401(用户端点统一入口,
|
||||
# cma-api-auth-surface-audit-20260914):原局部定义等价迁移,其他端点行为不变。
|
||||
|
||||
|
||||
@app.post("/api/cma/admin/erp-sync")
|
||||
@@ -184,8 +174,13 @@ def admin_check_alerts(entity_id: Optional[int] = None, user=Depends(require_aut
|
||||
|
||||
|
||||
@app.post("/api/cma/admin/cache/clear")
|
||||
def admin_clear_cache(module: str = None):
|
||||
"""清空缓存,指定module则只清该模块"""
|
||||
def admin_clear_cache(module: str = None, user=Depends(require_auth_401)):
|
||||
"""清空缓存,指定module则只清该模块
|
||||
|
||||
2026-09-14 安全修复(cma-api-auth-surface-audit-20260914):本端点原为无鉴权公开
|
||||
可写端点(无 token 亦可清空全站缓存 → 可被用于性能打击);补 require_auth_401,
|
||||
未带 token → 401。
|
||||
"""
|
||||
if module:
|
||||
delete_cache(module)
|
||||
return {"message": f"缓存已清空: {module}"}
|
||||
|
||||
Reference in New Issue
Block a user