core/homeassistant/components/hassio/websocket_api.py

141 lines
4.2 KiB
Python
Raw Normal View History

"""Websocekt API handlers for the hassio integration."""
import logging
from numbers import Number
2021-11-29 22:03:16 +00:00
import re
from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.connection import ActiveConnection
from homeassistant.core import HomeAssistant, callback
2021-11-29 22:03:16 +00:00
from homeassistant.exceptions import Unauthorized
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from . import HassioAPIError
from .const import (
ATTR_DATA,
ATTR_ENDPOINT,
ATTR_METHOD,
ATTR_SESSION_DATA_USER_ID,
ATTR_TIMEOUT,
ATTR_WS_EVENT,
DOMAIN,
EVENT_SUPERVISOR_EVENT,
WS_ID,
WS_TYPE,
WS_TYPE_API,
WS_TYPE_EVENT,
WS_TYPE_SUBSCRIBE,
)
from .handler import HassIO
SCHEMA_WEBSOCKET_EVENT = vol.Schema(
{vol.Required(ATTR_WS_EVENT): cv.string},
extra=vol.ALLOW_EXTRA,
)
2021-11-29 22:03:16 +00:00
# Endpoints needed for ingress can't require admin because addons can set `panel_admin: false`
# fmt: off
2021-11-29 22:03:16 +00:00
WS_NO_ADMIN_ENDPOINTS = re.compile(
r"^(?:"
r"|/ingress/(session|validate_session)"
r"|/addons/[^/]+/info"
r")$"
2021-11-29 22:03:16 +00:00
)
# fmt: on
2021-11-29 22:03:16 +00:00
_LOGGER: logging.Logger = logging.getLogger(__package__)
@callback
2024-01-08 09:08:09 +00:00
def async_load_websocket_api(hass: HomeAssistant) -> None:
"""Set up the websocket API."""
websocket_api.async_register_command(hass, websocket_supervisor_event)
websocket_api.async_register_command(hass, websocket_supervisor_api)
websocket_api.async_register_command(hass, websocket_subscribe)
@callback
@websocket_api.require_admin
@websocket_api.websocket_command({vol.Required(WS_TYPE): WS_TYPE_SUBSCRIBE})
def websocket_subscribe(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
2024-01-08 09:08:09 +00:00
) -> None:
"""Subscribe to supervisor events."""
@callback
2024-01-08 09:08:09 +00:00
def forward_messages(data: dict[str, str]) -> None:
"""Forward events to websocket."""
connection.send_message(websocket_api.event_message(msg[WS_ID], data))
connection.subscriptions[msg[WS_ID]] = async_dispatcher_connect(
hass, EVENT_SUPERVISOR_EVENT, forward_messages
)
connection.send_message(websocket_api.result_message(msg[WS_ID]))
@callback
@websocket_api.websocket_command(
{
vol.Required(WS_TYPE): WS_TYPE_EVENT,
vol.Required(ATTR_DATA): SCHEMA_WEBSOCKET_EVENT,
}
)
def websocket_supervisor_event(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
2024-01-08 09:08:09 +00:00
) -> None:
"""Publish events from the Supervisor."""
connection.send_result(msg[WS_ID])
async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA])
@websocket_api.websocket_command(
{
vol.Required(WS_TYPE): WS_TYPE_API,
vol.Required(ATTR_ENDPOINT): cv.string,
vol.Required(ATTR_METHOD): cv.string,
vol.Optional(ATTR_DATA): dict,
vol.Optional(ATTR_TIMEOUT): vol.Any(Number, None),
}
)
@websocket_api.async_response
async def websocket_supervisor_api(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
2024-01-08 09:08:09 +00:00
) -> None:
"""Websocket handler to call Supervisor API."""
2021-11-29 22:03:16 +00:00
if not connection.user.is_admin and not WS_NO_ADMIN_ENDPOINTS.match(
msg[ATTR_ENDPOINT]
):
2024-03-17 23:40:38 +00:00
raise Unauthorized
supervisor: HassIO = hass.data[DOMAIN]
command = msg[ATTR_ENDPOINT]
payload = msg.get(ATTR_DATA, {})
if command == "/ingress/session":
# Send user ID on session creation, so the supervisor can correlate session tokens with users
# for every request that is authenticated with the given ingress session token.
payload[ATTR_SESSION_DATA_USER_ID] = connection.user.id
try:
result = await supervisor.send_command(
command,
method=msg[ATTR_METHOD],
timeout=msg.get(ATTR_TIMEOUT, 10),
payload=payload,
source="core.websocket_api",
)
except HassioAPIError as err:
_LOGGER.error("Failed to to call %s - %s", msg[ATTR_ENDPOINT], err)
connection.send_error(
msg[WS_ID], code=websocket_api.ERR_UNKNOWN_ERROR, message=str(err)
)
else:
connection.send_result(msg[WS_ID], result.get(ATTR_DATA, {}))