Files
group_xinghuo_jinrong/app/utils/auth.py
T
zyi b19a2415f9 feat: 数据分析 Agent 实现(API/服务/表结构元数据/文档/测试)
- 新增 app/api、app/service 数据分析 Agent 全套服务与接口
- schemas.py 重构为 schemas 包(analyst schema)
- 新增 SQL 防注入、guardrail、缓存、字典、LLM 等服务
- 新增 tests 测试套件与 scripts/dev、scripts/setup 脚本
- 补充需求规格、架构说明书、开发清单、表设计等文档
2026-09-09 18:04:45 +08:00

111 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""鉴权:Mock JWT(HS256,开发用)+ AuthContext + RBAC 角色判定。
生产对齐 docs/项目框架设计/技术选型和版本/02-JWT-RBAC鉴权手册.md(RS256 + IdP);
本模块只覆盖数据分析 Agent 开发/联调所需的最小身份与角色能力。
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from jose import jwt, JWTError
from app.config.settings import settings
ALGORITHM = "HS256"
class AuthError(Exception):
"""鉴权失败,携带错误码(对齐 JWT 手册 §10)。"""
def __init__(self, error_code: str, message: str) -> None:
super().__init__(message)
self.error_code = error_code
self.message = message
@dataclass
class AuthContext:
"""注入给业务层的最小身份上下文。"""
subject_id: str
token_type: str
roles: list[str]
permissions: list[str] = field(default_factory=list)
staff_type: str = ""
trace_id: str = ""
agent_type: str = "analyst"
def has_role(self, role: str) -> bool:
return role in self.roles
def has_perm(self, perm: str) -> bool:
return perm in self.permissions
def to_dict(self) -> dict[str, Any]:
return {
"subject_id": self.subject_id,
"token_type": self.token_type,
"roles": self.roles,
"permissions": self.permissions,
"staff_type": self.staff_type,
"trace_id": self.trace_id,
"agent_type": self.agent_type,
}
def create_dev_token(
subject_id: str,
roles: list[str],
staff_type: str = "",
permissions: list[str] | None = None,
expires_hours: int | None = None,
) -> str:
"""开发/联调用:签发员工 Token(HS256)。"""
ttl = expires_hours or settings.jwt_token_ttl_hours
claims: dict[str, Any] = {
"sub": subject_id,
"token_type": "staff",
"roles": roles,
"staff_type": staff_type,
"permissions": permissions or [],
}
return jwt.encode(claims, settings.jwt_dev_secret, algorithm=ALGORITHM)
def verify_token(token: str) -> AuthContext:
"""验签并解析为 AuthContext;失败抛 AuthError(401)。"""
try:
claims = jwt.decode(token, settings.jwt_dev_secret, algorithms=[ALGORITHM])
except JWTError as exc:
raise AuthError("AUTH_401_INVALID_TOKEN", "token 无效或已过期") from exc
return AuthContext(
subject_id=str(claims.get("sub", "")),
token_type=str(claims.get("token_type", "staff")),
roles=list(claims.get("roles", [])),
permissions=list(claims.get("permissions", [])),
staff_type=str(claims.get("staff_type", "")),
)
# 数据分析 Agent 允许进入的员工角色(需求规格 §2.1 角色矩阵)
ANALYST_ROLES = {"analyst", "advisor", "risk_officer", "ops"}
# 角色 → 需求规格 §2.1 的数据域
ROLE_DATA_DOMAIN = {
"analyst": "full", # 全量 + 全明细 + 敏感列可见
"advisor": "assigned", # 仅名下客户 + 明细 + 脱敏
"risk_officer": "risk", # 台账全量 + 客户只读 + 客户脱敏
"ops": "aggregate", # 无客户维度 + 仅聚合
}
def assert_analyst_access(ctx: AuthContext) -> str:
"""校验角色可进入数据分析 Agent,返回数据域。失败抛 AuthError(403)。"""
if ctx.token_type != "staff":
raise AuthError("AUTH_403_ROLE", "数据分析 Agent 仅限内部员工使用")
for role in ctx.roles:
if role in ANALYST_ROLES:
return ROLE_DATA_DOMAIN[role]
raise AuthError("AUTH_403_ROLE", "当前角色无权使用数据分析 Agent")