python
This commit is contained in:
161
custom_components/yandex_smart_home/http.py
Normal file
161
custom_components/yandex_smart_home/http.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""The Yandex Smart Home HTTP interface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Coroutine, TypeVar
|
||||
|
||||
from aiohttp.web import HTTPServiceUnavailable, Request, Response, json_response
|
||||
from homeassistant.components.http import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID, HomeAssistantView
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from . import DOMAIN, handlers
|
||||
from .const import ISSUE_ID_MISSING_INTEGRATION
|
||||
from .helpers import RequestData, SmartHomePlatform
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import YandexSmartHome
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_T = TypeVar("_T", bound="YandexSmartHomeView")
|
||||
|
||||
|
||||
async def _log_request(request: Request) -> None:
|
||||
"""Log the request."""
|
||||
if body := await request.text():
|
||||
_LOGGER.debug(f"Request: {request.url} ({request.method} data: {body})")
|
||||
else:
|
||||
_LOGGER.debug(f"Request: {request.url} ({request.method})")
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_http(hass: HomeAssistant, component: YandexSmartHome) -> None:
|
||||
"""Register HTTP views for Yandex Smart Home."""
|
||||
hass.http.register_view(YandexSmartHomeUnauthorizedView(component))
|
||||
return hass.http.register_view(YandexSmartHomeAPIView(component))
|
||||
|
||||
|
||||
def async_http_request(
|
||||
func: Callable[[_T, HomeAssistant, Request, RequestData], Awaitable[Response]]
|
||||
) -> Callable[[_T, Request], Coroutine[Any, Any, Response]]:
|
||||
"""Decorate an async function to handle authorized HTTP requests."""
|
||||
|
||||
async def decorator(self: _T, request: Request) -> Response:
|
||||
"""Decorate."""
|
||||
await _log_request(request)
|
||||
|
||||
hass: HomeAssistant = request.app[KEY_HASS]
|
||||
context = self.context(request)
|
||||
|
||||
refresh_token = hass.auth.async_get_refresh_token(request[KEY_HASS_REFRESH_TOKEN_ID])
|
||||
assert refresh_token is not None
|
||||
|
||||
platform = SmartHomePlatform.from_client_id(refresh_token.client_id or "")
|
||||
if not platform:
|
||||
_LOGGER.error(f"Request from unsupported platform, client_id: {refresh_token.client_id}")
|
||||
raise HTTPServiceUnavailable()
|
||||
|
||||
entry_data = self._component.get_direct_connection_entry_data(platform, refresh_token.user.id)
|
||||
if not entry_data and len(hass.config_entries.async_entries(DOMAIN)) == 1:
|
||||
# backward compatibility
|
||||
entry_data = self._component.get_direct_connection_entry_data(platform, None)
|
||||
|
||||
issue_id = f"{ISSUE_ID_MISSING_INTEGRATION}_{platform}_{refresh_token.user.id}"
|
||||
if not entry_data:
|
||||
_LOGGER.error(
|
||||
f"Failed to find Yandex Smart Home integration for request "
|
||||
f"from {platform} (user {refresh_token.user.name})"
|
||||
)
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
issue_id,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key=ISSUE_ID_MISSING_INTEGRATION,
|
||||
translation_placeholders={
|
||||
"platform": platform,
|
||||
"username": refresh_token.user.name or refresh_token.user.id,
|
||||
},
|
||||
)
|
||||
raise HTTPServiceUnavailable()
|
||||
else:
|
||||
ir.async_delete_issue(hass, DOMAIN, issue_id)
|
||||
|
||||
data = RequestData(
|
||||
entry_data=entry_data,
|
||||
context=context,
|
||||
platform=platform,
|
||||
request_user_id=context.user_id,
|
||||
request_id=request.headers.get("X-Request-Id"),
|
||||
)
|
||||
if entry_data.skill:
|
||||
data.request_user_id = entry_data.skill.user_id
|
||||
|
||||
return await func(self, hass, request, data)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class YandexSmartHomeView(HomeAssistantView):
|
||||
def __init__(self, component: YandexSmartHome):
|
||||
self._component = component
|
||||
|
||||
|
||||
class YandexSmartHomeUnauthorizedView(YandexSmartHomeView):
|
||||
"""View to handle Yandex Smart Home unauthorized HTTP requests."""
|
||||
|
||||
url = f"/api/{DOMAIN}/v1.0"
|
||||
extra_urls = [
|
||||
url + "/ping",
|
||||
]
|
||||
name = f"api:{DOMAIN}:unauthorized"
|
||||
requires_auth = False
|
||||
|
||||
@staticmethod
|
||||
async def head(request: Request) -> Response:
|
||||
"""Handle Yandex Smart Home HEAD requests."""
|
||||
await _log_request(request)
|
||||
return Response(status=200)
|
||||
|
||||
@staticmethod
|
||||
async def get(request: Request) -> Response:
|
||||
"""Handle Yandex Smart Home GET requests."""
|
||||
await _log_request(request)
|
||||
return Response(text="Yandex Smart Home", status=200)
|
||||
|
||||
|
||||
class YandexSmartHomeAPIView(YandexSmartHomeView):
|
||||
"""View to handle Yandex Smart Home HTTP requests."""
|
||||
|
||||
url = f"/api/{DOMAIN}/v1.0"
|
||||
extra_urls = [
|
||||
url + "/user/unlink",
|
||||
url + "/user/devices",
|
||||
url + "/user/devices/query",
|
||||
url + "/user/devices/action",
|
||||
]
|
||||
name = f"api:{DOMAIN}"
|
||||
requires_auth = True
|
||||
|
||||
async def _async_handle_request(self, hass: HomeAssistant, request: Request, data: RequestData) -> Response:
|
||||
"""Handle Yandex Smart Home requests."""
|
||||
assert self.url is not None
|
||||
result = await handlers.async_handle_request(
|
||||
hass, data, action=request.path.replace(self.url, "", 1), payload=await request.text()
|
||||
)
|
||||
response = json_response(text=result.as_json())
|
||||
_LOGGER.debug(f"Response: {response.text}")
|
||||
|
||||
return response
|
||||
|
||||
@async_http_request
|
||||
async def post(self, hass: HomeAssistant, request: Request, data: RequestData) -> Response:
|
||||
"""Handle Yandex Smart Home POST requests."""
|
||||
return await self._async_handle_request(hass, request, data)
|
||||
|
||||
@async_http_request
|
||||
async def get(self, hass: HomeAssistant, request: Request, data: RequestData) -> Response:
|
||||
"""Handle Yandex Smart Home GET requests."""
|
||||
return await self._async_handle_request(hass, request, data)
|
||||
Reference in New Issue
Block a user