17dd2e02ba
Multi-dimension review of v0.8.2. Excludes the deliberately deferred default_config.yaml weak-default-key item (C1). Backend: - calibration: create_default_calibration no longer exceeds led_count for small odd counts (bounded trim + regression test) - game-integration: generic webhook now requires auth_token; constant-time compare_digest in all adapters; per-IP failed-auth rate limit on the ingest route; auth_token encrypted at rest via secret_box (migration-safe) - playlist engine: serialize _state/_task under the lifecycle lock to close a delete-mid-play race (+ concurrency tests) - main: stop the calibration session on shutdown (restore prior target) - home_assistant: validate HA host via the LAN classifier on create/update - perf: drop slow preview-WS clients instead of blocking the send loop; cache composite full-strip resize linspaces; effect_stream lava reuses scratch Frontend: - setup/auto-calibration wizard: guard _state after awaits (cancel-safe), await session teardown before output start, busy-gate skip-calibration, manual display input keeps focus, move focus on step change - calibration: destroy EntitySelect on modal close - color-strips test: dirty-flag-gated render + cached ctx/ImageData - a11y/TV: focus-visible for new wizard/auto-cal/corner controls, aria-labels on the spatial corner/edge picker; theme-aware syntax tokens; dead/undefined CSS tokens removed; .modal-error styled; i18n titles (en/ru/zh) Android: - ApiKeyManager: EncryptedSharedPreferences with verified, data-safe legacy migration that never rotates an existing key - CaptureService: validate MediaProjection token before promoting; satisfy the startForeground 5s contract on the bail path - NotificationListener: connection-scoped executor with lazy fallback - BLE: request BLUETOOTH_SCAN/CONNECT at runtime + guard handler-thread SecurityExceptions - Root: cancellation-aware su grant probe Adds 14 tests. Gate: ruff + tsc 5.9.3 + esbuild + pytest (2185 passed) + compileDebugKotlin all green.
53 lines
1.8 KiB
Python
53 lines
1.8 KiB
Python
"""Tests for Home Assistant source host classification.
|
|
|
|
The HA source host is user-supplied and stored as ``host:port`` (e.g.
|
|
``192.168.1.100:8123``). Before the WebSocket runtime connects to it, the
|
|
route layer gates the host with the shared LAN classifier so an
|
|
(authenticated or default-anonymous) caller cannot weaponise it into a
|
|
public network-scan oracle — mirroring the LED device providers.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from ledgrab.api.routes.home_assistant import _validate_ha_host
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"host",
|
|
[
|
|
"127.0.0.1:8123",
|
|
"10.0.0.5:8123",
|
|
"192.168.1.100:8123",
|
|
"172.16.0.10", # no port
|
|
"homeassistant.local:8123", # mDNS label
|
|
"hass", # bare hostname
|
|
"[fe80::1]:8123", # bracketed IPv6 link-local + port
|
|
"[fc00::1]:8123", # bracketed IPv6 ULA + port
|
|
"169.254.169.254:80", # link-local — allowed by the shared LAN policy
|
|
"", # empty passes (upstream schema requires non-empty)
|
|
None, # update path may pass None (host unchanged)
|
|
],
|
|
)
|
|
def test_validate_ha_host_accepts_lan(host) -> None:
|
|
"""Loopback / private / link-local / hostnames must not raise.
|
|
|
|
Link-local (169.254/16, fe80::/10) is part of the shared
|
|
``validate_lan_host`` LAN policy used by every LED device provider, so
|
|
HA reuses it unchanged rather than hand-rolling a stricter variant.
|
|
"""
|
|
_validate_ha_host(host)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"host",
|
|
[
|
|
"1.1.1.1:8123", # public IPv4 + port
|
|
"8.8.8.8", # public IPv4, no port
|
|
"[2606:4700:4700::1111]:8123", # public IPv6 + port
|
|
],
|
|
)
|
|
def test_validate_ha_host_rejects_public(host) -> None:
|
|
"""Genuinely-public IPs (the network-scan-oracle risk) are rejected."""
|
|
with pytest.raises(ValueError, match="LedGrab is LAN-only"):
|
|
_validate_ha_host(host)
|