369 lines
12 KiB
Python
369 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from opensquilla.sandbox.run_mode import RunMode
|
|
|
|
|
|
class _SessionManager:
|
|
def __init__(self):
|
|
self.node = SimpleNamespace(
|
|
session_key="agent:main:webchat:abc",
|
|
agent_id="main",
|
|
origin=None,
|
|
)
|
|
self.sessions = {self.node.session_key: self.node}
|
|
self.created: list[tuple[str, str]] = []
|
|
|
|
async def get_session(self, session_key: str):
|
|
return self.sessions.get(session_key)
|
|
|
|
async def get_or_create(self, session_key: str, agent_id: str = "main", **kwargs):
|
|
existing = self.sessions.get(session_key)
|
|
if existing is not None:
|
|
return existing, False
|
|
node = SimpleNamespace(
|
|
session_key=session_key,
|
|
agent_id=agent_id,
|
|
origin=None,
|
|
**kwargs,
|
|
)
|
|
self.sessions[session_key] = node
|
|
self.created.append((session_key, agent_id))
|
|
return node, True
|
|
|
|
async def update(self, session_key: str, **fields):
|
|
node = self.sessions[session_key]
|
|
for key, value in fields.items():
|
|
setattr(node, key, value)
|
|
return node
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_default_run_context_is_full_host_access() -> None:
|
|
from opensquilla.sandbox.config import SandboxSettings
|
|
from opensquilla.sandbox.run_context import get_run_context
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
sandbox=SandboxSettings(),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
|
|
context = await get_run_context(
|
|
manager,
|
|
manager.node.session_key,
|
|
config=config,
|
|
workspace="/workspace",
|
|
)
|
|
|
|
assert context.run_mode is RunMode.FULL
|
|
assert context.source == "default"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_context_initializes_from_global_default_and_persists_override() -> None:
|
|
from opensquilla.sandbox.run_context import get_run_context, set_run_mode
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
|
|
ctx = await get_run_context(
|
|
manager,
|
|
manager.node.session_key,
|
|
config=config,
|
|
workspace="/tmp/ws",
|
|
)
|
|
assert ctx.run_mode == RunMode.STANDARD
|
|
assert ctx.source == "default"
|
|
|
|
updated = await set_run_mode(manager, manager.node.session_key, RunMode.TRUSTED, config=config)
|
|
assert updated.run_mode == RunMode.TRUSTED
|
|
assert manager.node.origin["sandbox_run_context"]["run_mode"] == "trusted"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_run_mode_persists_first_workspace_and_preserves_origin_keys() -> None:
|
|
from opensquilla.sandbox.run_context import normalize_workspace_path, set_run_mode
|
|
|
|
manager = _SessionManager()
|
|
manager.node.origin = {"other": {"kept": True}}
|
|
config = SimpleNamespace(
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
expected_workspace = normalize_workspace_path("/tmp/ws")
|
|
|
|
updated = await set_run_mode(
|
|
manager,
|
|
manager.node.session_key,
|
|
RunMode.TRUSTED,
|
|
config=config,
|
|
workspace="/tmp/ws",
|
|
)
|
|
|
|
assert updated.workspace == expected_workspace
|
|
assert manager.node.origin["other"] == {"kept": True}
|
|
assert manager.node.origin["sandbox_run_context"]["workspace"] == expected_workspace
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_saved_context_wins_over_later_global_default() -> None:
|
|
from opensquilla.sandbox.run_context import get_run_context, normalize_workspace_path
|
|
|
|
manager = _SessionManager()
|
|
manager.node.origin = {"sandbox_run_context": {"run_mode": "standard", "workspace": "/tmp/old"}}
|
|
config = SimpleNamespace(
|
|
sandbox=SimpleNamespace(run_mode="full", sandbox=False, security_grading=False),
|
|
permissions=SimpleNamespace(default_mode="full"),
|
|
)
|
|
expected_workspace = normalize_workspace_path("/tmp/old")
|
|
|
|
ctx = await get_run_context(
|
|
manager,
|
|
manager.node.session_key,
|
|
config=config,
|
|
workspace="/tmp/new",
|
|
)
|
|
|
|
assert ctx.run_mode == RunMode.STANDARD
|
|
assert ctx.workspace == expected_workspace
|
|
assert ctx.source == "saved"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rpc_run_context_get_reports_missing_session() -> None:
|
|
from opensquilla.gateway.auth import Principal
|
|
from opensquilla.gateway.rpc import RpcContext
|
|
from opensquilla.gateway.rpc_sandbox import _handle_sandbox_run_context_get
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
workspace_dir="/tmp/ws",
|
|
agents=[],
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
ctx = RpcContext(
|
|
conn_id="c",
|
|
principal=Principal(
|
|
role="operator",
|
|
scopes=frozenset(["operator.read"]),
|
|
is_owner=True,
|
|
authenticated=True,
|
|
),
|
|
session_manager=manager,
|
|
config=config,
|
|
)
|
|
|
|
with pytest.raises(KeyError, match="Session not found"):
|
|
await _handle_sandbox_run_context_get(
|
|
{"sessionKey": "agent:main:webchat:missing"},
|
|
ctx,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rpc_run_context_set_rejects_non_owner_full_mode_without_mutation() -> None:
|
|
from opensquilla.gateway.auth import Principal
|
|
from opensquilla.gateway.rpc import RpcContext, RpcHandlerError
|
|
from opensquilla.gateway.rpc_sandbox import _handle_sandbox_run_context_set
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
workspace_dir="/tmp/ws",
|
|
agents=[],
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
ctx = RpcContext(
|
|
conn_id="c",
|
|
principal=Principal(
|
|
role="operator",
|
|
scopes=frozenset(["operator.write", "operator.read"]),
|
|
is_owner=False,
|
|
authenticated=True,
|
|
),
|
|
session_manager=manager,
|
|
config=config,
|
|
)
|
|
|
|
with pytest.raises(RpcHandlerError, match="requires owner principal"):
|
|
await _handle_sandbox_run_context_set(
|
|
{"sessionKey": manager.node.session_key, "runMode": "full"},
|
|
ctx,
|
|
)
|
|
|
|
assert manager.node.origin is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rpc_run_context_set_allows_owner_full_mode() -> None:
|
|
from opensquilla.gateway.auth import Principal
|
|
from opensquilla.gateway.rpc import RpcContext
|
|
from opensquilla.gateway.rpc_sandbox import _handle_sandbox_run_context_set
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
workspace_dir="/tmp/ws",
|
|
agents=[],
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
ctx = RpcContext(
|
|
conn_id="c",
|
|
principal=Principal(
|
|
role="operator",
|
|
scopes=frozenset(["operator.write", "operator.read"]),
|
|
is_owner=True,
|
|
authenticated=True,
|
|
),
|
|
session_manager=manager,
|
|
config=config,
|
|
)
|
|
|
|
result = await _handle_sandbox_run_context_set(
|
|
{"sessionKey": manager.node.session_key, "runMode": "full"},
|
|
ctx,
|
|
)
|
|
|
|
assert result["runMode"] == "full"
|
|
assert manager.node.origin["sandbox_run_context"]["run_mode"] == "full"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rpc_run_context_set_allows_non_owner_trusted_mode(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from opensquilla.gateway import rpc_sandbox
|
|
from opensquilla.gateway.auth import Principal
|
|
from opensquilla.gateway.rpc import RpcContext
|
|
from opensquilla.gateway.rpc_sandbox import _handle_sandbox_run_context_set
|
|
from opensquilla.sandbox.setup_state import SandboxSetupState, SetupResult
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
workspace_dir="/tmp/ws",
|
|
agents=[],
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
ctx = RpcContext(
|
|
conn_id="c",
|
|
principal=Principal(
|
|
role="operator",
|
|
scopes=frozenset(["operator.write", "operator.read"]),
|
|
is_owner=False,
|
|
authenticated=True,
|
|
),
|
|
session_manager=manager,
|
|
config=config,
|
|
)
|
|
|
|
async def ready_status(config):
|
|
return SetupResult(
|
|
state=SandboxSetupState.READY,
|
|
platform="win32",
|
|
message="ready",
|
|
requires_admin=True,
|
|
)
|
|
|
|
monkeypatch.setattr(rpc_sandbox, "current_sandbox_setup_status", ready_status)
|
|
|
|
result = await _handle_sandbox_run_context_set(
|
|
{"sessionKey": manager.node.session_key, "runMode": "trusted"},
|
|
ctx,
|
|
)
|
|
|
|
assert result["runMode"] == "trusted"
|
|
assert manager.node.origin["sandbox_run_context"]["run_mode"] == "trusted"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rpc_run_context_get_coerces_non_owner_default_full_to_trusted() -> None:
|
|
from opensquilla.gateway.auth import Principal
|
|
from opensquilla.gateway.rpc import RpcContext
|
|
from opensquilla.gateway.rpc_sandbox import _handle_sandbox_run_context_get
|
|
|
|
manager = _SessionManager()
|
|
config = SimpleNamespace(
|
|
workspace_dir="/tmp/ws",
|
|
agents=[],
|
|
sandbox=SimpleNamespace(run_mode="full", sandbox=False, security_grading=False),
|
|
permissions=SimpleNamespace(default_mode="full"),
|
|
)
|
|
ctx = RpcContext(
|
|
conn_id="c",
|
|
principal=Principal(
|
|
role="operator",
|
|
scopes=frozenset(["operator.read"]),
|
|
is_owner=False,
|
|
authenticated=False,
|
|
),
|
|
session_manager=manager,
|
|
config=config,
|
|
)
|
|
|
|
result = await _handle_sandbox_run_context_get(
|
|
{"sessionKey": manager.node.session_key},
|
|
ctx,
|
|
)
|
|
|
|
assert result["runMode"] == "trusted"
|
|
assert result["source"] == "default"
|
|
assert manager.node.origin is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rpc_run_context_set_creates_owner_new_webchat_session(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from opensquilla.gateway import rpc_sandbox
|
|
from opensquilla.gateway.auth import Principal
|
|
from opensquilla.gateway.rpc import RpcContext
|
|
from opensquilla.sandbox.setup_state import SandboxSetupState, SetupResult
|
|
|
|
manager = _SessionManager()
|
|
session_key = "agent:main:webchat:dkkwi6so"
|
|
config = SimpleNamespace(
|
|
workspace_dir="/tmp/ws",
|
|
agents=[],
|
|
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
|
|
permissions=SimpleNamespace(default_mode="off"),
|
|
)
|
|
ctx = RpcContext(
|
|
conn_id="c",
|
|
principal=Principal(
|
|
role="operator",
|
|
scopes=frozenset(["operator.write", "operator.read"]),
|
|
is_owner=True,
|
|
authenticated=True,
|
|
),
|
|
session_manager=manager,
|
|
config=config,
|
|
)
|
|
|
|
async def fake_status(config: object) -> SetupResult:
|
|
return SetupResult(
|
|
state=SandboxSetupState.READY,
|
|
platform="win32",
|
|
message="ready",
|
|
requires_admin=True,
|
|
)
|
|
|
|
monkeypatch.setattr(rpc_sandbox, "current_sandbox_setup_status", fake_status)
|
|
|
|
result = await rpc_sandbox._handle_sandbox_run_context_set(
|
|
{"sessionKey": session_key, "runMode": "trusted"},
|
|
ctx,
|
|
)
|
|
|
|
assert result["runMode"] == "trusted"
|
|
assert manager.created == [(session_key, "main")]
|
|
assert manager.sessions[session_key].origin["sandbox_run_context"]["run_mode"] == "trusted"
|