fix(security): retire check-governance + scope /reports/dupont to session entity
- data_quality.py: remove GET /check-governance (no caller repo-wide; its entity_id param had no session auth -> cross-tenant read; duplicate of governance-check) -> the path now returns 404 - reports.py: /reports/dupont resolves the analysis target from the session account (assert_entity_slug_scope / resolve_session_slug); explicit cross-entity entity param -> 403, illegal value -> 400; the 2026-08-31 cross-entity exemption is revoked (docstring updated) - frontend: drop the checkGovernance api definition; DupontAnalysis.vue entity radio group -> read-only session entity tag, request without param - tests: TestCheckGovernanceRetired (404 x2), TestDupontSessionScope (3), TestDupont reworked to account-bound tokens (8 tests) - docs/api-verification-split.md: drop the retired row, note 334/404 -> 333/403 pytest: 10 failed / 957 passed / 1 xfailed vs HEAD baseline 10 failed / 951 passed / 1 xfailed (identical failure set -> zero regression) (cma-residual-cross-entity-cleanup-20260914)
This commit is contained in:
@@ -8,7 +8,7 @@ CMA管理报表中心 — 管理会计OS
|
||||
3. KPI趋势报告 — 选定KPI的历史趋势
|
||||
4. 四维度绩效评分卡 — BSC健康度雷达图
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException, Request
|
||||
import json
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -19,7 +19,7 @@ from app.database import get_db
|
||||
from app.auth_middleware import require_role, require_auth
|
||||
from app.models import KPIDefinition, KPIValue, BudgetPlan, StrategicMap, KPIAlert, User, Subject, ActionPlan, OperationLog, ReportHistory
|
||||
from app.utils.deviation_engine import calc_period_deviation, calc_period_diff, calc_deviation, get_budget_for_kpi
|
||||
from app.deps import get_entity_id
|
||||
from app.deps import get_entity_id, assert_entity_slug_scope, resolve_session_slug
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("cma.reports")
|
||||
@@ -1885,15 +1885,22 @@ def _get_dupont_kpi(db: Session, entity_id: int, kpi_code: str, period: str = "2
|
||||
|
||||
@router.get("/dupont")
|
||||
def get_dupont_analysis(
|
||||
entity: str = Query("bohai"),
|
||||
request: Request,
|
||||
entity: Optional[str] = Query(None, description="hanke/bohai(须与会话账套一致);缺省按会话账套"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""杜邦分析 — ROE三级拆解 (CMA P2)
|
||||
豁免多租户隔离(2026-08-31):跨实体对比分析端点,entity 参数显式指定
|
||||
分析对象(bohai→entity 2 / hanke→entity 1),非默认全库查询,故不叠加
|
||||
Depends(get_entity_id)(叠加会导致 token 绑定的 entity 与显式 entity 参数
|
||||
不一致时被 403 拦截,破坏跨企业对比功能)"""
|
||||
if entity == "bohai":
|
||||
|
||||
多租户口径(cma-residual-cross-entity-cleanup-20260914,取代 2026-08-31 豁免):
|
||||
分析对象 = 会话账套(登录即定企业,页内无跨企业切换)。
|
||||
- 显式 entity 与会话账套不一致 → 403(文案同 get_entity_id);非法值 → 400
|
||||
- 无会话 token(Bot/存量脚本通道)→ 缺省 bohai(保留原默认,仅非会话通道)
|
||||
旧豁免("跨实体对比分析端点")已撤销:该端点为单对象分析,非并排对比。
|
||||
"""
|
||||
slug = assert_entity_slug_scope(request, entity)
|
||||
if slug is None:
|
||||
slug = resolve_session_slug(request, "bohai")
|
||||
if slug == "bohai":
|
||||
# 博海标准KPI(F_REVENUE/F_NET_PROFIT)无verified值 → 优先DB读,读不到回退文档确认常量
|
||||
net_profit = _get_dupont_kpi(db, 2, "F_NET_PROFIT")
|
||||
if net_profit is None:
|
||||
@@ -1963,7 +1970,7 @@ def get_dupont_analysis(
|
||||
},
|
||||
}
|
||||
|
||||
if entity == "hanke":
|
||||
if slug == "hanke":
|
||||
# 酣客: F_REVENUE/F_NET_PROFIT 从DB读 verified 值(2026H1: 713.27 / -90.86)
|
||||
revenue = _get_dupont_kpi(db, 1, "F_REVENUE")
|
||||
net_profit = _get_dupont_kpi(db, 1, "F_NET_PROFIT")
|
||||
|
||||
Reference in New Issue
Block a user