core/homeassistant/components/webhook/__init__.py

118 lines
3.2 KiB
Python
Raw Normal View History

"""Webhooks for Home Assistant."""
import logging
from aiohttp.web import Response
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.auth.util import generate_secret
from homeassistant.components import websocket_api
from homeassistant.components.http.view import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
2019-07-31 19:25:30 +00:00
DOMAIN = "webhook"
URL_WEBHOOK_PATH = "/api/webhook/{webhook_id}"
2019-07-31 19:25:30 +00:00
WS_TYPE_LIST = "webhook/list"
2019-07-31 19:25:30 +00:00
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_LIST}
)
@callback
@bind_hass
def async_register(hass, domain, name, webhook_id, handler):
"""Register a webhook."""
handlers = hass.data.setdefault(DOMAIN, {})
if webhook_id in handlers:
2019-07-31 19:25:30 +00:00
raise ValueError("Handler is already defined!")
2019-07-31 19:25:30 +00:00
handlers[webhook_id] = {"domain": domain, "name": name, "handler": handler}
@callback
@bind_hass
def async_unregister(hass, webhook_id):
"""Remove a webhook."""
handlers = hass.data.setdefault(DOMAIN, {})
handlers.pop(webhook_id, None)
@callback
def async_generate_id():
"""Generate a webhook_id."""
return generate_secret(entropy=32)
@callback
@bind_hass
def async_generate_url(hass, webhook_id):
"""Generate the full URL for a webhook_id."""
2019-07-31 19:25:30 +00:00
return "{}{}".format(hass.config.api.base_url, async_generate_path(webhook_id))
@callback
def async_generate_path(webhook_id):
"""Generate the path component for a webhook_id."""
return URL_WEBHOOK_PATH.format(webhook_id=webhook_id)
@bind_hass
async def async_handle_webhook(hass, webhook_id, request):
"""Handle a webhook."""
handlers = hass.data.setdefault(DOMAIN, {})
webhook = handlers.get(webhook_id)
# Always respond successfully to not give away if a hook exists or not.
if webhook is None:
2019-07-31 19:25:30 +00:00
_LOGGER.warning("Received message for unregistered webhook %s", webhook_id)
return Response(status=200)
try:
2019-07-31 19:25:30 +00:00
response = await webhook["handler"](hass, webhook_id, request)
if response is None:
response = Response(status=200)
return response
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error processing webhook %s", webhook_id)
return Response(status=200)
async def async_setup(hass, config):
"""Initialize the webhook component."""
hass.http.register_view(WebhookView)
hass.components.websocket_api.async_register_command(
2019-07-31 19:25:30 +00:00
WS_TYPE_LIST, websocket_list, SCHEMA_WS_LIST
)
return True
class WebhookView(HomeAssistantView):
"""Handle incoming webhook requests."""
url = URL_WEBHOOK_PATH
name = "api:webhook"
requires_auth = False
async def post(self, request, webhook_id):
"""Handle webhook call."""
2019-07-31 19:25:30 +00:00
hass = request.app["hass"]
return await async_handle_webhook(hass, webhook_id, request)
@callback
def websocket_list(hass, connection, msg):
"""Return a list of webhooks."""
handlers = hass.data.setdefault(DOMAIN, {})
2019-07-31 19:25:30 +00:00
result = [
{"webhook_id": webhook_id, "domain": info["domain"], "name": info["name"]}
for webhook_id, info in handlers.items()
]
connection.send_message(websocket_api.result_message(msg["id"], result))