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() return identity.model_copy(update={ "roles": roles, "permissions": tuple(sorted(scopes)), "permission_scopes": scopes, "data_scope": "self", "customer_ids": tuple(str(value) for value in customers), "portal": "api", })