from __future__ import annotations from dataclasses import dataclass from typing import Any from sqlalchemy import and_, or_, select from app.api.deps import CurrentUserContext from app.core.agent_asset_scope import ( AGENT_ASSET_PLATFORM_SCOPE, AGENT_ASSET_PLATFORM_TENANT_ID, AGENT_ASSET_TENANT_SCOPE, ) def stable_user_principal(current_user: CurrentUserContext) -> str: """返回不可由显示名变化影响的审计主体。""" employee_id = str(current_user.employee_id or "").strip() if employee_id: return f"employee:{employee_id}" username = str(current_user.username or "").strip().casefold() if username: return f"username:{username}" raise PermissionError("当前登录用户缺少稳定身份标识。") @dataclass(frozen=True, slots=True) class AgentAssetAccessScope: tenant_id: str is_platform_admin: bool = False @classmethod def from_user(cls, current_user: CurrentUserContext) -> AgentAssetAccessScope: tenant_id = str(current_user.tenant_id or "").strip() if not tenant_id or tenant_id == AGENT_ASSET_PLATFORM_TENANT_ID: raise PermissionError("当前登录会话缺少有效租户。") return cls(tenant_id=tenant_id, is_platform_admin=bool(current_user.is_admin)) def visibility_clause(self, model: Any) -> Any: return or_( and_( model.scope == AGENT_ASSET_PLATFORM_SCOPE, model.tenant_id == AGENT_ASSET_PLATFORM_TENANT_ID, ), and_( model.scope == AGENT_ASSET_TENANT_SCOPE, model.tenant_id == self.tenant_id, ), ) def can_write(self, resource: Any) -> bool: scope = str(getattr(resource, "scope", "") or "").strip() tenant_id = str(getattr(resource, "tenant_id", "") or "").strip() if scope == AGENT_ASSET_PLATFORM_SCOPE: return bool( self.is_platform_admin and tenant_id == AGENT_ASSET_PLATFORM_TENANT_ID ) return scope == AGENT_ASSET_TENANT_SCOPE and tenant_id == self.tenant_id def require_write(self, resource: Any) -> None: scope = str(getattr(resource, "scope", "") or "").strip() tenant_id = str(getattr(resource, "tenant_id", "") or "").strip() if scope == AGENT_ASSET_PLATFORM_SCOPE: if tenant_id != AGENT_ASSET_PLATFORM_TENANT_ID: raise LookupError("Asset not found") if not self.is_platform_admin: raise PermissionError("只有平台管理员可以修改平台资产。") return if scope != AGENT_ASSET_TENANT_SCOPE or tenant_id != self.tenant_id: raise LookupError("Asset not found") def tenant_resource_identity(tenant_id: str) -> tuple[str, str]: normalized = str(tenant_id or "").strip() if not normalized or normalized == AGENT_ASSET_PLATFORM_TENANT_ID: raise ValueError("tenant_id 必须是有效的企业租户。") return normalized, AGENT_ASSET_TENANT_SCOPE def platform_resource_identity() -> tuple[str, str]: return AGENT_ASSET_PLATFORM_TENANT_ID, AGENT_ASSET_PLATFORM_SCOPE def platform_asset_statement(): """平台初始化任务只能查询平台资产,不能按 code 命中租户覆盖项。""" from app.models.agent_asset import AgentAsset return select(AgentAsset).where( AgentAsset.scope == AGENT_ASSET_PLATFORM_SCOPE, AgentAsset.tenant_id == AGENT_ASSET_PLATFORM_TENANT_ID, )