soulsync/tests/test_ws_connect_gate.py
BoulderBadgeDad b5b9d6e5f4 #852 tests: cover the login-mode WS gate (the reported bypass was the login modal)
The integration test only exercised the launch-PIN path; the actual #852 report is
the "Sign in to SoulSync" username/password modal. Add login-mode cases: socketio
connect is rejected when require_login is on + unauthenticated, allowed once
login_authenticated. Confirms the WS gate covers both overlays.
2026-06-11 17:38:24 -07:00

107 lines
3.9 KiB
Python

"""#852: the socketio handshake bypasses the HTTP launch-PIN/login gate
(before_request doesn't run for it). The connect handler must enforce the same
check, or removing the overlay + opening a socket streams live data unauthenticated."""
from __future__ import annotations
import os
import tempfile
import pytest
from core.security.ws_gate import is_ws_connection_blocked as blocked
# ── pure gate logic ────────────────────────────────────────────────────────
def _b(**kw):
base = dict(require_login=False, login_authenticated=False,
require_pin=False, pin_verified=False, proxy_authed=False)
base.update(kw)
return blocked(**base)
def test_nothing_on_allows():
assert _b() is False
def test_login_on_unauth_blocks():
assert _b(require_login=True) is True
def test_login_on_authed_allows():
assert _b(require_login=True, login_authenticated=True) is False
def test_pin_on_unverified_blocks():
assert _b(require_pin=True) is True
def test_pin_on_verified_allows():
assert _b(require_pin=True, pin_verified=True) is False
def test_pin_on_proxy_authed_allows():
assert _b(require_pin=True, proxy_authed=True) is False
def test_login_takes_precedence_over_pin():
# login on + unauth -> blocked even if the PIN was verified
assert _b(require_login=True, require_pin=True, pin_verified=True, proxy_authed=True) is True
# ── integration: real socketio connect via the gate ────────────────────────
_TMP = tempfile.mkdtemp(prefix='soulsync-testdb-wsgate-')
os.environ['DATABASE_PATH'] = os.path.join(_TMP, 'w.db')
os.environ['SOULSYNC_TEST_DB_READY'] = '1'
web_server = pytest.importorskip('web_server')
def _pin_on(monkeypatch):
real = web_server.config_manager.get
monkeypatch.setattr(web_server.config_manager, 'get',
lambda k, d=None: True if k == 'security.require_pin_on_launch' else real(k, d))
def test_socket_rejected_when_gate_on_and_unauthenticated(monkeypatch):
_pin_on(monkeypatch)
flask_client = web_server.app.test_client() # no launch_pin_verified
sio = web_server.socketio.test_client(web_server.app, flask_test_client=flask_client)
assert sio.is_connected() is False # the #852 hole, now closed
def test_socket_allowed_when_gate_off():
flask_client = web_server.app.test_client()
sio = web_server.socketio.test_client(web_server.app, flask_test_client=flask_client)
assert sio.is_connected() is True
def test_socket_allowed_when_pin_verified(monkeypatch):
_pin_on(monkeypatch)
flask_client = web_server.app.test_client()
with flask_client.session_transaction() as s:
s['launch_pin_verified'] = True
sio = web_server.socketio.test_client(web_server.app, flask_test_client=flask_client)
assert sio.is_connected() is True
# ── login mode (the "Sign in to SoulSync" path — #852 report was this one) ──
def _login_on(monkeypatch):
real = web_server.config_manager.get
monkeypatch.setattr(web_server.config_manager, 'get',
lambda k, d=None: True if k == 'security.require_login' else real(k, d))
def test_socket_rejected_when_login_required_and_unauthenticated(monkeypatch):
_login_on(monkeypatch)
flask_client = web_server.app.test_client() # no login_authenticated
sio = web_server.socketio.test_client(web_server.app, flask_test_client=flask_client)
assert sio.is_connected() is False # login overlay can't be bypassed via WS
def test_socket_allowed_when_login_authenticated(monkeypatch):
_login_on(monkeypatch)
flask_client = web_server.app.test_client()
with flask_client.session_transaction() as s:
s['login_authenticated'] = True
sio = web_server.socketio.test_client(web_server.app, flask_test_client=flask_client)
assert sio.is_connected() is True