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:
@@ -342,48 +342,13 @@ RULES_META = {
|
||||
DETAIL_LIMIT = 10 # 每条规则detail最多列出的条数(避免响应过大)
|
||||
|
||||
|
||||
@router.get("/check-governance")
|
||||
def check_governance(
|
||||
entity_id: Optional[int] = Query(0, description="实体ID: 0=全部, 1=酣客, 2=博海"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""DAMA数据治理规则检查(财务七规则)— 返回质量评分+异常清单。
|
||||
|
||||
评分规则: 满分100,error级规则每条扣10分,warning级规则每条扣5分,
|
||||
每条规则最多扣一次分(按规则是否命中,不按count累扣),最低0分。
|
||||
|
||||
收敛说明(2026-08-31 安全修复 P1-2):规则执行统一复用
|
||||
_run_governance_checks(Python 解析 suggestion JSON,SQLite 兼容),
|
||||
删除原 _run_rule_checks(MySQL JSON_EXTRACT/JSON_VALID 版,SQLite 不兼容)。
|
||||
本端点保留原评分口径与响应结构(governance-check 端点使用新的扣分口径)。
|
||||
"""
|
||||
gov = _run_governance_checks(db, entity_id or 0)
|
||||
issues = gov["issues"]
|
||||
|
||||
# 计算评分(check-governance 原口径:error 扣10 / warning 扣5,每规则最多扣一次)
|
||||
score = 100
|
||||
for item in issues:
|
||||
if item["count"] > 0:
|
||||
score -= 10 if item["level"] == "error" else 5
|
||||
score = max(0, score)
|
||||
|
||||
passed = [item["rule"] for item in issues if item["count"] == 0]
|
||||
|
||||
return {
|
||||
"checked_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"entity_id": entity_id or 0,
|
||||
"score": score,
|
||||
"total_rules": len(issues),
|
||||
"issues": issues,
|
||||
"passed": passed,
|
||||
"rules_meta": RULES_META,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 财务七规则检查(governance-check)— 2026-08-30 P1 最终方案
|
||||
# 与 /check-governance 的区别:
|
||||
# * 评分规则不同:error 扣 min(15, count*3),warning 扣 min(10, count*1)
|
||||
# 端点收敛(cma-residual-cross-entity-cleanup-20260914):
|
||||
# /check-governance 已下线 —— 全仓无调用方(前端仅残留 api 定义、无 .vue 调用);
|
||||
# 其 entity_id 参数无会话鉴权(0=全部/1/2 任取)→ 跨租户;与 governance-check 功能重复。
|
||||
# 本端点评分口径:
|
||||
# * 评分规则:error 扣 min(15, count*3),warning 扣 min(10, count*1)
|
||||
# * 规则2/3 用 Python 解析 suggestion JSON(不依赖 MySQL JSON 函数)
|
||||
# * 规则4 区分 error(test/sync/虚拟) 与 manual(待人工确认 warning)
|
||||
# * 规则7 按 status='active' 口径(与 budget.py 一致,避免多版本叠加失真)
|
||||
|
||||
@@ -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