From 49b620219de2e1333e1107aead43deda9e33d0dc Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 17 Mar 2026 01:30:31 +0800 Subject: [PATCH] =?UTF-8?q?refcator:=20=E9=87=8D=E5=91=BD=E5=90=8Dpolicy?= =?UTF-8?q?=E4=B8=BAauthorization;=E7=A7=BB=E9=99=A4envelope=E7=9A=84gener?= =?UTF-8?q?ation(runner=E4=B8=8D=E5=86=8D=E9=87=8D=E8=BD=BD);?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_runtime/host/authorization.py | 61 ++++++++++++ src/plugin_runtime/host/capability_service.py | 14 +-- src/plugin_runtime/host/policy_engine.py | 97 ------------------- src/plugin_runtime/protocol/envelope.py | 5 - 4 files changed, 69 insertions(+), 108 deletions(-) create mode 100644 src/plugin_runtime/host/authorization.py delete mode 100644 src/plugin_runtime/host/policy_engine.py diff --git a/src/plugin_runtime/host/authorization.py b/src/plugin_runtime/host/authorization.py new file mode 100644 index 00000000..d746c4d2 --- /dev/null +++ b/src/plugin_runtime/host/authorization.py @@ -0,0 +1,61 @@ +"""授权管理器 + +负责管理插件的能力授权以及校验 +每个插件在 manifest 中声明能力需求,Host 启动时签发能力令牌。 +""" + +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Set, Tuple + + +@dataclass +class CapabilityPermissionToken: + """能力令牌""" + + plugin_id: str + capabilities: Set[str] = field(default_factory=set) + + +class AuthorizationManager: + """授权管理器 + + 管理所有插件的能力令牌,提供授权校验。 + """ + + def __init__(self) -> None: + self._permission_tokens: Dict[str, CapabilityPermissionToken] = {} + + def register_plugin(self, plugin_id: str, capabilities: List[str]) -> CapabilityPermissionToken: + """为插件签发能力令牌""" + token = CapabilityPermissionToken(plugin_id=plugin_id, capabilities=set(capabilities)) + self._permission_tokens[plugin_id] = token + return token + + def revoke_permission_token(self, plugin_id: str): + """移除插件的能力令牌。""" + self._permission_tokens.pop(plugin_id, None) + + def clear(self) -> None: + """清空所有能力令牌。""" + self._permission_tokens.clear() + + def check_capability(self, plugin_id: str, capability: str) -> Tuple[bool, str]: + """检查插件是否有权调用某项能力 + + Returns: + return (bool, str): (是否有此能力, 原因) + """ + token = self._permission_tokens.get(plugin_id) + if not token: + return False, f"插件 {plugin_id} 未注册能力令牌" + if capability not in token.capabilities: + return False, f"插件 {plugin_id} 未获授权能力: {capability}" + return True, "" + + def get_token(self, plugin_id: str) -> Optional[CapabilityPermissionToken]: + """获取插件的能力令牌""" + return self._permission_tokens.get(plugin_id) + + def list_plugins(self) -> List[str]: + """列出所有已注册的插件""" + return list(self._permission_tokens.keys()) diff --git a/src/plugin_runtime/host/capability_service.py b/src/plugin_runtime/host/capability_service.py index 6685ff60..e0c56c2b 100644 --- a/src/plugin_runtime/host/capability_service.py +++ b/src/plugin_runtime/host/capability_service.py @@ -4,10 +4,9 @@ Host 端实现的能力服务,处理来自插件的 cap.* 请求。 每个能力方法被注册到 RPC Server,接收 Runner 转发的请求并执行实际操作。 """ -from typing import Any, Awaitable, Callable, Dict, List +from typing import Any, Callable, Dict, List, Coroutine, TYPE_CHECKING from src.common.logger import get_logger -from src.plugin_runtime.host.policy_engine import PolicyEngine from src.plugin_runtime.protocol.envelope import ( CapabilityRequestPayload, CapabilityResponsePayload, @@ -15,10 +14,13 @@ from src.plugin_runtime.protocol.envelope import ( ) from src.plugin_runtime.protocol.errors import ErrorCode, RPCError +if TYPE_CHECKING: + from src.plugin_runtime.host.authorization import AuthorizationManager + logger = get_logger("plugin_runtime.host.capability_service") # 能力实现函数类型: (plugin_id, capability, args) -> result -CapabilityImpl = Callable[[str, str, Dict[str, Any]], Awaitable[Any]] +CapabilityImpl = Callable[[str, str, Dict[str, Any]], Coroutine[Any, Any, Any]] class CapabilityService: @@ -31,8 +33,8 @@ class CapabilityService: 4. 执行实际操作并返回结果 """ - def __init__(self, policy_engine: PolicyEngine) -> None: - self._policy = policy_engine + def __init__(self, authorization: "AuthorizationManager") -> None: + self._authorization = authorization # capability_name -> implementation self._implementations: Dict[str, CapabilityImpl] = {} @@ -65,7 +67,7 @@ class CapabilityService: capability = req.capability # 1. 权限校验 - allowed, reason = self._policy.check_capability(plugin_id, capability, envelope.generation) + allowed, reason = self._authorization.check_capability(plugin_id, capability) if not allowed: error_code = ( ErrorCode.E_GENERATION_MISMATCH if "generation 不匹配" in reason else ErrorCode.E_CAPABILITY_DENIED diff --git a/src/plugin_runtime/host/policy_engine.py b/src/plugin_runtime/host/policy_engine.py deleted file mode 100644 index 61b32480..00000000 --- a/src/plugin_runtime/host/policy_engine.py +++ /dev/null @@ -1,97 +0,0 @@ -"""策略引擎 - -负责能力授权校验。 -每个插件在 manifest 中声明能力需求,Host 启动时签发能力令牌。 -""" - -from dataclasses import dataclass, field -from typing import Dict, List, Optional, Set, Tuple - - -@dataclass -class CapabilityToken: - """能力令牌""" - - plugin_id: str - generation: int - capabilities: Set[str] = field(default_factory=set) - - -class PolicyEngine: - """策略引擎 - - 管理所有插件的能力令牌,提供授权校验。 - """ - - def __init__(self) -> None: - self._tokens: Dict[str, Dict[int, CapabilityToken]] = {} - - def register_plugin( - self, - plugin_id: str, - generation: int, - capabilities: List[str], - ) -> CapabilityToken: - """为插件签发能力令牌""" - token = CapabilityToken( - plugin_id=plugin_id, - generation=generation, - capabilities=set(capabilities), - ) - self._tokens.setdefault(plugin_id, {})[generation] = token - return token - - def revoke_plugin(self, plugin_id: str, generation: Optional[int] = None) -> None: - """撤销插件的能力令牌。""" - if generation is None: - self._tokens.pop(plugin_id, None) - return - - generations = self._tokens.get(plugin_id) - if generations is None: - return - - generations.pop(generation, None) - if not generations: - self._tokens.pop(plugin_id, None) - - def clear(self) -> None: - """清空所有能力令牌。""" - self._tokens.clear() - - def check_capability(self, plugin_id: str, capability: str, generation: Optional[int] = None) -> Tuple[bool, str]: - """检查插件是否有权调用某项能力 - - Returns: - (allowed, reason) - """ - generations = self._tokens.get(plugin_id) - if not generations: - return False, f"插件 {plugin_id} 未注册能力令牌" - - if generation is None: - token = generations[max(generations)] - else: - token = generations.get(generation) - if token is None: - active_generation = max(generations) - return False, f"插件 {plugin_id} generation 不匹配: {generation} != {active_generation}" - - if capability not in token.capabilities: - return False, f"插件 {plugin_id} 未获授权能力: {capability}" - - if generation is not None and token.generation != generation: - return False, f"插件 {plugin_id} generation 不匹配: {generation} != {token.generation}" - - return True, "" - - def get_token(self, plugin_id: str) -> Optional[CapabilityToken]: - """获取插件的能力令牌""" - generations = self._tokens.get(plugin_id) - if not generations: - return None - return generations[max(generations)] - - def list_plugins(self) -> List[str]: - """列出所有已注册的插件""" - return list(self._tokens.keys()) diff --git a/src/plugin_runtime/protocol/envelope.py b/src/plugin_runtime/protocol/envelope.py index 56bb4582..ca9e8005 100644 --- a/src/plugin_runtime/protocol/envelope.py +++ b/src/plugin_runtime/protocol/envelope.py @@ -65,8 +65,6 @@ class Envelope(BaseModel): """发送时间戳 (ms)""" timeout_ms: int = Field(default=30000, description="相对超时 (ms)") """相对超时 (ms)""" - generation: int = Field(default=0, description="Runner generation 编号") - """Runner generation 编号""" payload: Dict[str, Any] = Field(default_factory=dict, description="业务数据") """业务数据""" error: Optional[Dict[str, Any]] = Field(default=None, description="错误信息 (仅 response)") @@ -91,7 +89,6 @@ class Envelope(BaseModel): message_type=MessageType.RESPONSE, method=self.method, plugin_id=self.plugin_id, - generation=self.generation, payload=payload or {}, error=error, ) @@ -126,8 +123,6 @@ class HelloResponsePayload(BaseModel): """是否接受连接""" host_version: str = Field(default="", description="Host 版本号") """Host 版本号""" - assigned_generation: int = Field(default=0, description="分配的 generation 编号") - """分配的 generation 编号""" reason: str = Field(default="", description="拒绝原因 (若 accepted=False)") """拒绝原因 (若 `accepted`=`False`)"""