From 9a17b381535726e5047c6d68eba381e194e1c975 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Mon, 7 Sep 2026 17:40:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(context):=20=E8=A1=A5=E6=8B=86=E5=8C=85?= =?UTF-8?q?=E9=81=97=E6=BC=8F=E7=9A=84=20get=5Fcurrent=5Fuser=5Frole=20?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=EF=BC=8C=E4=BF=AE=E5=A4=8D=20host=20?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E9=9D=99=E9=BB=98=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 拆包时 resources.py 独立 import 头漏了 get_current_user_role:host 模式 + 非显式身份 + 存在 host 用户记录时触发 NameError,被 except 吞掉后工具分类限制与禁用模型等管理员策略静默不应用(默认无 host 记录不触发)。 - resources.py/usage.py 顺带补 TYPE_CHECKING 导入净化字符串类型注解,server/context/ 与 server/runtime/ pyflakes F821 清零 - 新增回归测试:host 模式 + HTTP 请求上下文 + host 记录存在,断言策略真正应用到 terminal;红绿验证通过(回退导入测试即红) 测试:25/25 全绿(24 + 新增 1) --- server/context/resources.py | 7 ++- server/context/usage.py | 5 +- test/test_runtime_identity_resources.py | 68 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/server/context/resources.py b/server/context/resources.py index 1574f4f9..870a85aa 100644 --- a/server/context/resources.py +++ b/server/context/resources.py @@ -7,7 +7,10 @@ from __future__ import annotations import time from pathlib import Path -from typing import Optional, Tuple +from typing import Optional, Tuple, TYPE_CHECKING + +if TYPE_CHECKING: + import modules.user_manager from flask import session, has_request_context @@ -25,7 +28,7 @@ from config.model_profiles import get_registered_model_keys from modules.i18n import tr from server import state from server.utils_common import debug_log -from server.auth_helpers import get_current_username, get_current_user_record +from server.auth_helpers import get_current_username, get_current_user_record, get_current_user_role from utils.host_workspace_debug import write_host_workspace_debug from server.context.identity import NoWorkspaceError, RuntimeIdentity, _resolve_user_role diff --git a/server/context/usage.py b/server/context/usage.py index c3874a15..3ccf10be 100644 --- a/server/context/usage.py +++ b/server/context/usage.py @@ -1,7 +1,10 @@ """配额追踪器(UsageTracker)的获取与广播。""" from __future__ import annotations -from typing import Optional +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + import modules.user_manager from modules.usage_tracker import UsageTracker from server import state diff --git a/test/test_runtime_identity_resources.py b/test/test_runtime_identity_resources.py index 61a7c6e9..f3699b7f 100644 --- a/test/test_runtime_identity_resources.py +++ b/test/test_runtime_identity_resources.py @@ -126,5 +126,73 @@ class GetUserResourcesIdentityTest(unittest.TestCase): self.assertIsNotNone(terminal) +class GetUserResourcesHostPolicyTest(unittest.TestCase): + """回归:host 模式 + 非显式身份 + host 用户记录存在时,管理员策略必须应用。 + + 历史 bug(拆包遗漏导入):resources.py 调用未导入的 get_current_user_role, + record 存在时触发 NameError,被 except 吞掉后工具分类/禁用模型策略静默不应用。 + """ + + def test_host_record_applies_admin_policy(self): + import server.context.resources as resources + + host_ws = {"workspace_id": "default", "path": "/tmp/host_ws_policy", "label": "默认"} + record = MagicMock() + record.username = "host" + record.invite_code = None + policy = { + "categories": {}, + "forced_category_states": {}, + "disabled_models": [], + "ui_blocks": {}, + "updated_at": "v1", + } + base = _base_patches() + for p in base: + p.start() + try: + with patch("server.context.resources.TERMINAL_SANDBOX_MODE", "host"), \ + patch("server.context.resources.resolve_host_workspace", return_value=(None, host_ws)), \ + patch("server.context.resources.get_current_user_record", return_value=record), \ + patch("server.context.resources.get_current_user_role", return_value="admin") as get_role, \ + patch("modules.admin_policy_manager.get_effective_policy", return_value=policy) as get_policy: + from flask import Flask + app = Flask("host_policy_test") + app.secret_key = "test" + fake_terminal = MagicMock() + fake_terminal._reaper_closing = False + fake_terminal.model_key = "kimi-k2" + # 缓存命中路径会校验 project_path 是否匹配目标工作区路径, + # 不匹配会原地重建 terminal;设为 resolve 后的真实路径避免重建 + resolved_path = str(Path("/tmp/host_ws_policy").resolve()) + fake_terminal.project_path = resolved_path + fake_terminal.context_manager.project_path = resolved_path + ctx = app.test_request_context("/") + ctx.push() + try: + # host 分支入口要求 session["host_mode"] 为真(非显式路径) + from flask import session as flask_session + flask_session["host_mode"] = True + # 预置对话级 terminal 缓存命中,避免 WebTerminal 构造细节 + with patch.dict(resources.state.user_terminals, {"host::default::conv_policy": fake_terminal}, clear=False): + terminal, _workspace = get_user_resources( + "host", + workspace_id="default", + update_session=False, + conversation_id="conv_policy", + identity=None, # 非显式:HTTP 适配层路径(bug 触发路径) + ) + finally: + ctx.pop() + # 修复后直接证据:role 解析与策略应用都被真正执行 + get_role.assert_called_once_with(record) + get_policy.assert_called_once() + fake_terminal.set_admin_policy.assert_called_once() + self.assertIsNotNone(terminal) + finally: + for p in base: + p.stop() + + if __name__ == "__main__": unittest.main()