2026-09-09 21:55:37 +08:00
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import text
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
|
|
from app.core.contracts import RequestContext
|
|
|
|
|
|
from app.core.errors import UnauthorizedAgentError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IdentityRepository:
|
|
|
|
|
|
"""Read existing RBAC tables; never authorize from client role claims."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, session: AsyncSession) -> None:
|
|
|
|
|
|
self.session = session
|
|
|
|
|
|
|
|
|
|
|
|
async def load_context(self, identity: RequestContext) -> RequestContext:
|
|
|
|
|
|
now = datetime.now(UTC).replace(tzinfo=None)
|
|
|
|
|
|
params = {"user_id": int(identity.user_id), "now": now}
|
|
|
|
|
|
status = await self.session.scalar(
|
|
|
|
|
|
text("SELECT status FROM sys_user WHERE id=:user_id"), params
|
|
|
|
|
|
)
|
|
|
|
|
|
if status != "正常":
|
|
|
|
|
|
raise UnauthorizedAgentError("账号不存在或未启用")
|
|
|
|
|
|
rows = (await self.session.execute(text("""
|
|
|
|
|
|
SELECT r.role_code, p.permission_code, p.data_scope
|
|
|
|
|
|
FROM sys_user_role ur JOIN sys_role r ON r.id=ur.role_id
|
|
|
|
|
|
LEFT JOIN sys_role_permission rp ON rp.role_id=r.id
|
|
|
|
|
|
LEFT JOIN sys_permission p ON p.id=rp.permission_id
|
|
|
|
|
|
WHERE ur.user_id=:user_id AND r.status='active'
|
|
|
|
|
|
AND ur.assigned_at<=:now AND (ur.expires_at IS NULL OR ur.expires_at>:now)
|
|
|
|
|
|
"""), params)).mappings().all()
|
|
|
|
|
|
roles = tuple(sorted({str(row["role_code"]) for row in rows}))
|
|
|
|
|
|
scopes: dict[str, str] = {}
|
|
|
|
|
|
rank = {"self": 0, "own_customers": 1, "all": 2}
|
|
|
|
|
|
for row in rows:
|
|
|
|
|
|
if row["permission_code"] and row["data_scope"] in rank:
|
|
|
|
|
|
code, scope = str(row["permission_code"]), str(row["data_scope"])
|
|
|
|
|
|
if code not in scopes or rank[scope] > rank[scopes[code]]:
|
|
|
|
|
|
scopes[code] = scope
|
|
|
|
|
|
customers = (await self.session.scalars(text("""
|
|
|
|
|
|
SELECT customer_id FROM sys_customer_assignment
|
|
|
|
|
|
WHERE employee_id=:user_id AND assigned_at<=:now
|
|
|
|
|
|
AND (unassigned_at IS NULL OR unassigned_at>:now)
|
|
|
|
|
|
"""), params)).all()
|
2026-09-11 12:54:51 +08:00
|
|
|
|
# data_scope 取该身份所有权限里的**最高**范围。
|
|
|
|
|
|
#
|
|
|
|
|
|
# 原先这里写死 "self",于是上面刚算出来的 scope 白算了:permission_scopes 里
|
|
|
|
|
|
# 明明有 all,data_scope 却永远是 self,凡是按 `context.data_scope == "all"`
|
|
|
|
|
|
# 判断能否看全量的路径全部走不通(风控的 risk_query_service.py:198、
|
|
|
|
|
|
# risk_analysis_service.py:126、risk_evidence_archive_service.py:218、
|
|
|
|
|
|
# risk_action_service.py:212 都是这样判断的)。实测 9002(risk_operator) 与
|
|
|
|
|
|
# 9003(admin) 拿着 all 级权限却什么都查不到。
|
|
|
|
|
|
#
|
|
|
|
|
|
# 没有 all 权限的角色行为不变(如 customer 仍是 self),所以这不放松任何既有边界。
|
|
|
|
|
|
data_scope = max(scopes.values(), key=lambda value: rank[value]) if scopes else "self"
|
2026-09-09 21:55:37 +08:00
|
|
|
|
return identity.model_copy(update={
|
|
|
|
|
|
"roles": roles, "permissions": tuple(sorted(scopes)),
|
2026-09-11 12:54:51 +08:00
|
|
|
|
"permission_scopes": scopes, "data_scope": data_scope,
|
2026-09-09 21:55:37 +08:00
|
|
|
|
"customer_ids": tuple(str(value) for value in customers),
|
|
|
|
|
|
"portal": "api",
|
|
|
|
|
|
})
|