fix: B6 评审修复——鉴权审计统一 deny/401 留痕、start_date/end_date 对齐 PRD、simulate 回挂鉴权、异常与审计注入收敛
- P1-1: 403/401 全部经 deps.deny/_authz_audit 留痕(event_type='authz') - P2-1: GET /alerts 参数名 start_date/end_date 对齐 PRD;page/page_size 用 fastapi.Query - P2-2: /api/simulate/trade 回挂 get_auth_context(risk_demo 或本人),越权 403+审计 - P2-3: suitability/aml 直调补审计+request_ref 透传 - P3-1: 多角色 fail-closed 口径固化进测试;P3-3/P3-4 core_ro/risk_repo 必传; P3-5 LookupError→NotFoundError 统一;P3-9 alert_service import 上提 - 测试: test_risk_api 权限矩阵/审计断言/多角色组合/非 dev 拒绝; test_trade_gateway 客户本人 vs 越权 403(P3-4 模拟 _repo 注入 sqlite) - 挂账: P3-2/P3-7(响应外壳+disclaimer)→B7;P3-6(scan 幂等)→B9b 前
This commit is contained in:
+35
-2
@@ -126,7 +126,10 @@ def client(env, monkeypatch):
|
||||
repo, engine = env
|
||||
monkeypatch.setattr(risk_api, "_repo", lambda: repo)
|
||||
monkeypatch.setattr(risk_api, "CoreReadOnlyRepository", lambda: CoreReadOnlyRepository(engine=engine))
|
||||
monkeypatch.setattr(risk_api, "CoreReadOnlyRepository", lambda: CoreReadOnlyRepository(engine=engine))
|
||||
# 401/越权审计经 deps 内 RiskRepository 兜底构造,统一注入 sqlite(B6 评审 P3-4)
|
||||
from app.api import deps as deps_mod
|
||||
|
||||
monkeypatch.setattr(deps_mod, "RiskRepository", lambda: repo)
|
||||
app = FastAPI()
|
||||
app.include_router(risk_router)
|
||||
app.add_exception_handler(PermissionDenied, permission_denied_handler)
|
||||
@@ -322,5 +325,35 @@ def test_assert_customer_access_scope_denial(env):
|
||||
|
||||
auth = AuthContext(actor_id="STAFF-40001", roles=["compliance"])
|
||||
with pytest.raises(PermissionDenied) as ei:
|
||||
assert_customer_access(auth, "CUST-1001", risk_repo=repo)
|
||||
assert_customer_access(
|
||||
auth, "CUST-1001", core_ro=CoreReadOnlyRepository(engine=env[1]), risk_repo=repo
|
||||
)
|
||||
assert ei.value.code == "AUTH_403_SCOPE"
|
||||
|
||||
|
||||
def test_authz_denials_are_audited(client, env):
|
||||
"""B6 评审 P1-1:403/401 全部留痕(AUTH_403_ROLE / AUTH_401_MISSING_DEBUG_HEADERS)。"""
|
||||
repo, engine = env
|
||||
assert client.get("/api/risk/alerts").status_code == 401 # 无 debug 头
|
||||
assert client.post("/api/risk/aml/scan", headers=ADV_01).status_code == 403
|
||||
assert client.post(
|
||||
"/api/risk/alerts/ALT-E1/handle",
|
||||
json={"handler_result": "confirmed_normal"},
|
||||
headers=COMPLIANCE,
|
||||
).status_code == 403
|
||||
assert _counts(engine, "audit_log", "decision='unauthenticated' AND input_summary LIKE '%AUTH_401%'") == 1
|
||||
assert _counts(engine, "audit_log", "decision='forbidden' AND input_summary LIKE '%AUTH_403_ROLE%'") == 2
|
||||
|
||||
|
||||
def test_multi_role_combinations_fixed_behavior(client):
|
||||
"""B6 评审 P3-1:多角色口径固化——并集权限、归属 fail-closed。"""
|
||||
# customer+compliance:借 compliance 角色看 aml 台账(审计类跨客户,固化允许)
|
||||
r = client.get("/api/risk/alerts", headers=_h("customer,compliance", "CUST-1001"))
|
||||
assert r.status_code == 200 and r.json()["items"][0]["alert_type"] == "aml"
|
||||
# customer+advisor 查非本人客户:customer 分支 deny 即终止(最窄范围 fail-closed)
|
||||
r = client.post(
|
||||
"/api/risk/suitability/check",
|
||||
json={"customer_id": "CUST-1001", "product_id": "PROD-161725"},
|
||||
headers=_h("customer,advisor", "CUST-3001"),
|
||||
)
|
||||
assert r.status_code == 403 and "AUTH_403_NOT_OWNER" in r.json()["detail"]
|
||||
|
||||
Reference in New Issue
Block a user