82 lines
2.6 KiB
Python
82 lines
2.6 KiB
Python
"""deps.AuthContext → 数据分析 Agent 内部视图(S3 接缝)。"""
|
|||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from app.api.deps import AuthContext as DepsAuthContext
|
||
|
|
from app.utils.trace import current_trace
|
||
|
|
|
||
|
|
# Demo 四角色 + ops 预留;问数线宽于 chat 矩阵(customer 可进)
|
||
|
|
ROLE_DATA_DOMAIN: dict[str, str] = {
|
||
|
|
"customer": "self",
|
||
|
|
"advisor": "assigned",
|
||
|
|
"analyst": "full",
|
||
|
|
"risk_officer": "risk",
|
||
|
|
"ops": "aggregate",
|
||
|
|
}
|
||
|
|
|
||
|
|
_ROLE_PRIORITY = ("customer", "advisor", "analyst", "risk_officer", "ops")
|
||
|
|
|
||
|
|
|
||
|
|
class AnalystAuthError(Exception):
|
||
|
|
def __init__(self, error_code: str, message: str) -> None:
|
||
|
|
super().__init__(message)
|
||
|
|
self.error_code = error_code
|
||
|
|
self.message = message
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class AnalystAuthContext:
|
||
|
|
"""分析线内部身份(subject_id 对齐 deps.actor_id)。"""
|
||
|
|
|
||
|
|
subject_id: str
|
||
|
|
token_type: str
|
||
|
|
roles: list[str]
|
||
|
|
permissions: list[str] = field(default_factory=list)
|
||
|
|
customer_id: str | None = None
|
||
|
|
trace_id: str = ""
|
||
|
|
agent_type: str = "analyst"
|
||
|
|
|
||
|
|
def has_role(self, role: str) -> bool:
|
||
|
|
return role in self.roles
|
||
|
|
|
||
|
|
|
||
|
|
def analyst_auth_from_deps(
|
||
|
|
ctx: DepsAuthContext,
|
||
|
|
*,
|
||
|
|
trace_id: str | None = None,
|
||
|
|
) -> AnalystAuthContext:
|
||
|
|
return AnalystAuthContext(
|
||
|
|
subject_id=ctx.actor_id,
|
||
|
|
token_type=ctx.token_type,
|
||
|
|
roles=list(ctx.roles),
|
||
|
|
permissions=list(ctx.permissions),
|
||
|
|
customer_id=ctx.customer_id,
|
||
|
|
trace_id=trace_id or current_trace() or "",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def assert_analyst_query_access(ctx: AnalystAuthContext) -> str:
|
||
|
|
"""问数入口鉴权:返回 sql_guard 数据域 key。失败抛 AnalystAuthError。"""
|
||
|
|
if ctx.token_type == "customer" or "customer" in ctx.roles:
|
||
|
|
if not ctx.customer_id:
|
||
|
|
raise AnalystAuthError("AUTH_403_ROLE", "客户 token 缺少 customer_id")
|
||
|
|
return "self"
|
||
|
|
if ctx.token_type != "staff":
|
||
|
|
raise AnalystAuthError("AUTH_403_ROLE", "数据分析问数需有效登录身份")
|
||
|
|
for role in _ROLE_PRIORITY:
|
||
|
|
if role in ctx.roles and role in ROLE_DATA_DOMAIN and role != "customer":
|
||
|
|
return ROLE_DATA_DOMAIN[role]
|
||
|
|
raise AnalystAuthError("AUTH_403_ROLE", "当前角色无权使用数据分析问数")
|
||
|
|
|
||
|
|
|
||
|
|
def resolve_analyst_scope(ctx: AnalystAuthContext, domain: str, repo: Any) -> list[str]:
|
||
|
|
"""按域解析 customer_id 白名单(供 sql_guard)。"""
|
||
|
|
if domain == "self":
|
||
|
|
cid = ctx.customer_id or ctx.subject_id
|
||
|
|
return [cid] if cid else []
|
||
|
|
if domain == "assigned":
|
||
|
|
return repo.resolve_advisor_scope(ctx.subject_id)
|
||
|
|
return []
|