Test authenticated admin control channel
This commit is contained in:
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from admin_server import send_admin_command
|
from admin_server import DEFAULT_ADMIN_TOKEN_PATH, send_admin_command
|
||||||
from lan_discovery import LanDiscoveryResponder
|
from lan_discovery import LanDiscoveryResponder
|
||||||
from server_core import FalloutTogetherServer, get_lan_addresses
|
from server_core import FalloutTogetherServer, get_lan_addresses
|
||||||
from server_service import ServerConfig, ServerService
|
from server_service import ServerConfig, ServerService
|
||||||
@@ -125,6 +125,41 @@ def test_headless_admin_roundtrip(tmp_path: Path) -> None:
|
|||||||
thread.join(timeout=3.0)
|
thread.join(timeout=3.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_rejects_unauthenticated_requests_and_does_not_disclose_token(tmp_path: Path) -> None:
|
||||||
|
game_port = _free_port()
|
||||||
|
admin_port = _free_port()
|
||||||
|
service = ServerService(
|
||||||
|
ServerConfig(
|
||||||
|
host="127.0.0.1",
|
||||||
|
port=game_port,
|
||||||
|
admin_port=admin_port,
|
||||||
|
bans_path=str(tmp_path / "bans.json"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
thread = threading.Thread(target=service.serve_forever, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
deadline = time.time() + 5.0
|
||||||
|
while time.time() < deadline and not service.is_running():
|
||||||
|
time.sleep(0.05)
|
||||||
|
assert service.is_running()
|
||||||
|
|
||||||
|
with socket.create_connection(("127.0.0.1", admin_port), timeout=2.0) as conn:
|
||||||
|
conn.sendall(b'{"cmd":"ping"}\n')
|
||||||
|
raw = conn.recv(4096)
|
||||||
|
unauthorized = json.loads(raw.split(b"\n", 1)[0].decode("utf-8"))
|
||||||
|
assert unauthorized.get("ok") is False
|
||||||
|
assert unauthorized.get("error") == "Unauthorized admin request."
|
||||||
|
|
||||||
|
authenticated = send_admin_command({"cmd": "status"}, port=admin_port)
|
||||||
|
assert authenticated.get("ok") is True
|
||||||
|
token = DEFAULT_ADMIN_TOKEN_PATH.read_text(encoding="utf-8").strip()
|
||||||
|
assert token
|
||||||
|
assert token not in json.dumps(authenticated, sort_keys=True)
|
||||||
|
|
||||||
|
service.stop()
|
||||||
|
thread.join(timeout=3.0)
|
||||||
|
|
||||||
|
|
||||||
def test_lan_discovery_sets_broadcast_option() -> None:
|
def test_lan_discovery_sets_broadcast_option() -> None:
|
||||||
class DummyServer:
|
class DummyServer:
|
||||||
def get_stats(self):
|
def get_stats(self):
|
||||||
@@ -139,7 +174,6 @@ def test_lan_discovery_sets_broadcast_option() -> None:
|
|||||||
def _log(self, message: str, *, level: str = "info") -> None:
|
def _log(self, message: str, *, level: str = "info") -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Bind an ephemeral discovery port to avoid colliding with a real server.
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
sock.bind(("127.0.0.1", 0))
|
sock.bind(("127.0.0.1", 0))
|
||||||
port = int(sock.getsockname()[1])
|
port = int(sock.getsockname()[1])
|
||||||
@@ -149,7 +183,6 @@ def test_lan_discovery_sets_broadcast_option() -> None:
|
|||||||
responder.start()
|
responder.start()
|
||||||
try:
|
try:
|
||||||
assert responder._socket is not None
|
assert responder._socket is not None
|
||||||
# SO_BROADCAST should be enabled; querying may return 0/1 depending on OS.
|
|
||||||
value = responder._socket.getsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST)
|
value = responder._socket.getsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST)
|
||||||
assert value in (0, 1)
|
assert value in (0, 1)
|
||||||
probe = {
|
probe = {
|
||||||
|
|||||||
Reference in New Issue
Block a user