fix: resolve 4 bugs found in HA integration code review

- Auto-authorize HA events in gateway (system-generated, not user messages)
- Guard _read_events against None/closed WebSocket after failed reconnect
- Use UUID for send() message_id instead of polluting WS sequence counter
- entity_id parameter now takes precedence over data["entity_id"]
This commit is contained in:
0xbyt4 2026-02-28 15:12:18 +03:00
parent b32c642af3
commit 2390728cc3
4 changed files with 16 additions and 6 deletions

View file

@ -17,6 +17,7 @@ import json
import logging import logging
import os import os
import time import time
import uuid
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set
@ -228,6 +229,8 @@ class HomeAssistantAdapter(BasePlatformAdapter):
async def _read_events(self) -> None: async def _read_events(self) -> None:
"""Read events from WebSocket until disconnected.""" """Read events from WebSocket until disconnected."""
if self._ws is None or self._ws.closed:
return
async for ws_msg in self._ws: async for ws_msg in self._ws:
if ws_msg.type == aiohttp.WSMsgType.TEXT: if ws_msg.type == aiohttp.WSMsgType.TEXT:
try: try:
@ -390,7 +393,7 @@ class HomeAssistantAdapter(BasePlatformAdapter):
timeout=aiohttp.ClientTimeout(total=10), timeout=aiohttp.ClientTimeout(total=10),
) as resp: ) as resp:
if resp.status < 300: if resp.status < 300:
return SendResult(success=True, message_id=str(self._next_id())) return SendResult(success=True, message_id=uuid.uuid4().hex[:12])
else: else:
body = await resp.text() body = await resp.text()
return SendResult(success=False, error=f"HTTP {resp.status}: {body}") return SendResult(success=False, error=f"HTTP {resp.status}: {body}")

View file

@ -490,6 +490,12 @@ class GatewayRunner:
4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true) 4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true)
5. Default: deny 5. Default: deny
""" """
# Home Assistant events are system-generated (state changes), not
# user-initiated messages. The HASS_TOKEN already authenticates the
# connection, so HA events are always authorized.
if source.platform == Platform.HOMEASSISTANT:
return True
user_id = source.user_id user_id = source.user_id
if not user_id: if not user_id:
return False return False

View file

@ -130,13 +130,13 @@ class TestBuildServicePayload:
payload = _build_service_payload() payload = _build_service_payload()
assert payload == {} assert payload == {}
def test_data_does_not_overwrite_entity_id(self): def test_entity_id_param_takes_precedence_over_data(self):
payload = _build_service_payload( payload = _build_service_payload(
entity_id="light.a", entity_id="light.a",
data={"entity_id": "light.b"}, data={"entity_id": "light.b"},
) )
# data.update overwrites entity_id set earlier # explicit entity_id parameter wins over data["entity_id"]
assert payload["entity_id"] == "light.b" assert payload["entity_id"] == "light.a"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -106,10 +106,11 @@ def _build_service_payload(
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Build the JSON payload for a HA service call.""" """Build the JSON payload for a HA service call."""
payload: Dict[str, Any] = {} payload: Dict[str, Any] = {}
if entity_id:
payload["entity_id"] = entity_id
if data: if data:
payload.update(data) payload.update(data)
# entity_id parameter takes precedence over data["entity_id"]
if entity_id:
payload["entity_id"] = entity_id
return payload return payload