c812a1a595
Integrate the remote firmware workbench, OTA delivery, and timed mode schedules while preserving the local N7 scene and runtime changes.
207 lines
7.7 KiB
Python
207 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import threading
|
|
import urllib.error
|
|
import urllib.request
|
|
from email.message import Message
|
|
from http.server import ThreadingHTTPServer
|
|
|
|
from cognitive_agent.auth import AuthConfig, AuthRuntime
|
|
from cognitive_agent.server import SeatDemoHandler
|
|
|
|
|
|
def _auth(*, enabled: bool) -> AuthRuntime:
|
|
return AuthRuntime(
|
|
AuthConfig(
|
|
enabled=enabled,
|
|
username="test-user" if enabled else "",
|
|
password_hash="unused" if enabled else "",
|
|
session_secret=b"s" * 64 if enabled else b"",
|
|
session_ttl_seconds=3600,
|
|
login_attempts=5,
|
|
login_window_seconds=300,
|
|
requests_per_minute=120,
|
|
trust_proxy_headers=False,
|
|
)
|
|
)
|
|
|
|
|
|
class MutationSecurityHandler(SeatDemoHandler):
|
|
auth = _auth(enabled=False)
|
|
assistant_calls = 0
|
|
internal_calls = 0
|
|
last_hardware_bypass = True
|
|
|
|
def _handle_assistant_stream(self) -> None:
|
|
type(self).assistant_calls += 1
|
|
type(self).last_hardware_bypass = self._hardware_access_bypass_allowed()
|
|
self._read_json_body()
|
|
self._json({"handled": True})
|
|
|
|
def _handle_codex_vehicle_control(self) -> None:
|
|
type(self).internal_calls += 1
|
|
self._read_json_body()
|
|
self._json({"handled": True})
|
|
|
|
|
|
class TestMutationRequestSecurity:
|
|
@classmethod
|
|
def setup_class(cls) -> None:
|
|
cls.server = ThreadingHTTPServer(("127.0.0.1", 0), MutationSecurityHandler)
|
|
cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
|
|
cls.thread.start()
|
|
cls.authority = f"127.0.0.1:{cls.server.server_port}"
|
|
cls.base_url = f"http://{cls.authority}"
|
|
|
|
@classmethod
|
|
def teardown_class(cls) -> None:
|
|
cls.server.shutdown()
|
|
cls.server.server_close()
|
|
cls.thread.join(timeout=3)
|
|
|
|
def setup_method(self) -> None:
|
|
MutationSecurityHandler.assistant_calls = 0
|
|
MutationSecurityHandler.internal_calls = 0
|
|
MutationSecurityHandler.last_hardware_bypass = True
|
|
|
|
def post(
|
|
self,
|
|
path: str,
|
|
*,
|
|
content_type: str | None = "application/json",
|
|
origin: str | None = None,
|
|
) -> tuple[int, dict[str, object]]:
|
|
headers = {}
|
|
if content_type is not None:
|
|
headers["Content-Type"] = content_type
|
|
if origin is not None:
|
|
headers["Origin"] = origin
|
|
request = urllib.request.Request(
|
|
f"{self.base_url}{path}",
|
|
data=b"{}",
|
|
headers=headers,
|
|
method="POST",
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=3) as response:
|
|
return response.status, json.loads(response.read().decode("utf-8"))
|
|
except urllib.error.HTTPError as exc:
|
|
return exc.code, json.loads(exc.read().decode("utf-8"))
|
|
|
|
def get(self, path: str) -> tuple[int, dict[str, object]]:
|
|
try:
|
|
with urllib.request.urlopen(f"{self.base_url}{path}", timeout=3) as response:
|
|
return response.status, json.loads(response.read().decode("utf-8"))
|
|
except urllib.error.HTTPError as exc:
|
|
return exc.code, json.loads(exc.read().decode("utf-8"))
|
|
|
|
def test_same_origin_json_assistant_request_is_accepted(self) -> None:
|
|
status, body = self.post(
|
|
"/api/assistant/stream",
|
|
content_type="application/json; charset=utf-8",
|
|
origin=self.base_url,
|
|
)
|
|
|
|
assert status == 200
|
|
assert body == {"handled": True}
|
|
assert MutationSecurityHandler.assistant_calls == 1
|
|
|
|
def test_cross_origin_alias_is_rejected_before_assistant_execution(self) -> None:
|
|
status, body = self.post(
|
|
"/api/assistant/stream",
|
|
origin=f"http://localhost:{self.server.server_port}",
|
|
)
|
|
|
|
assert status == 403
|
|
assert body["error"] == "cross_origin_request"
|
|
assert MutationSecurityHandler.assistant_calls == 0
|
|
|
|
def test_origin_scheme_and_port_must_match(self) -> None:
|
|
for origin in (
|
|
f"https://{self.authority}",
|
|
f"http://127.0.0.1:{self.server.server_port + 1}",
|
|
"null",
|
|
):
|
|
status, body = self.post("/api/assistant/stream", origin=origin)
|
|
assert status == 403
|
|
assert body["error"] == "cross_origin_request"
|
|
assert MutationSecurityHandler.assistant_calls == 0
|
|
|
|
def test_non_json_assistant_request_is_rejected_before_body_parsing(self) -> None:
|
|
for content_type in (None, "text/plain", "application/x-www-form-urlencoded"):
|
|
status, body = self.post(
|
|
"/api/assistant/stream",
|
|
content_type=content_type,
|
|
origin=self.base_url,
|
|
)
|
|
assert status == 415
|
|
assert body["error"] == "unsupported_media_type"
|
|
assert MutationSecurityHandler.assistant_calls == 0
|
|
|
|
def test_no_origin_local_json_requests_remain_compatible(self) -> None:
|
|
assistant_status, _ = self.post("/api/assistant/stream")
|
|
internal_status, _ = self.post("/api/internal/codex/vehicle-control")
|
|
|
|
assert assistant_status == 200
|
|
assert internal_status == 200
|
|
assert MutationSecurityHandler.assistant_calls == 1
|
|
assert MutationSecurityHandler.internal_calls == 1
|
|
|
|
def test_auth_disabled_never_grants_implicit_hardware_bypass(self) -> None:
|
|
status, _ = self.post("/api/assistant/stream")
|
|
|
|
assert status == 200
|
|
assert MutationSecurityHandler.last_hardware_bypass is False
|
|
|
|
def test_vehicle_ota_download_is_public_but_workbench_state_remains_private(self) -> None:
|
|
MutationSecurityHandler.auth = _auth(enabled=True)
|
|
try:
|
|
download_status, download_body = self.get(
|
|
"/api/firmware-workbench/releases/fw-rel-missing1234/download"
|
|
)
|
|
latest_status, latest_body = self.get(
|
|
"/api/firmware-workbench/releases/latest"
|
|
)
|
|
state_status, state_body = self.get("/api/firmware-workbench/state")
|
|
finally:
|
|
MutationSecurityHandler.auth = _auth(enabled=False)
|
|
|
|
assert download_status == 404
|
|
assert download_body["error"] == "not_found"
|
|
assert latest_status in {200, 404}
|
|
if latest_status == 404:
|
|
assert latest_body["error"] == "not_found"
|
|
else:
|
|
assert latest_body["download_url"]
|
|
assert state_status == 401
|
|
assert state_body["error"] == "authentication_required"
|
|
|
|
def test_hardware_bypass_requires_auth_enabled_and_direct_loopback(self) -> None:
|
|
handler = object.__new__(SeatDemoHandler)
|
|
handler.client_address = ("127.0.0.1", 12345)
|
|
handler.headers = Message()
|
|
|
|
handler.auth = _auth(enabled=False)
|
|
assert handler._hardware_access_bypass_allowed() is False
|
|
|
|
handler.auth = _auth(enabled=True)
|
|
assert handler._hardware_access_bypass_allowed() is False
|
|
|
|
session = handler.auth.sessions.issue("test-user")
|
|
handler.headers["Cookie"] = f"{handler.auth.config.cookie_name}={session}"
|
|
assert handler._hardware_access_bypass_allowed() is True
|
|
|
|
handler.headers["X-Forwarded-For"] = "203.0.113.10"
|
|
assert handler._hardware_access_bypass_allowed() is False
|
|
|
|
def test_wireless_passive_state_bypass_does_not_widen_control_bypass(self) -> None:
|
|
handler = object.__new__(SeatDemoHandler)
|
|
handler.client_address = ("127.0.0.1", 12345)
|
|
handler.headers = Message()
|
|
handler.auth = _auth(enabled=False)
|
|
|
|
assert handler._passive_state_access_bypass_allowed("wireless_mqtt") is True
|
|
assert handler._passive_state_access_bypass_allowed("wired_can") is False
|
|
assert handler._hardware_access_bypass_allowed() is False
|