Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Mailgun."""
2 
3 import hashlib
4 import hmac
5 import json
6 import logging
7 
8 from aiohttp import web
9 import voluptuous as vol
10 
11 from homeassistant.components import webhook
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID
14 from homeassistant.core import HomeAssistant
15 from homeassistant.helpers import config_entry_flow
17 from homeassistant.helpers.typing import ConfigType
18 
19 from .const import DOMAIN
20 
21 _LOGGER = logging.getLogger(__name__)
22 
23 CONF_SANDBOX = "sandbox"
24 
25 DEFAULT_SANDBOX = False
26 
27 MESSAGE_RECEIVED = f"{DOMAIN}_message_received"
28 
29 CONFIG_SCHEMA = vol.Schema(
30  {
31  vol.Optional(DOMAIN): vol.Schema(
32  {
33  vol.Required(CONF_API_KEY): cv.string,
34  vol.Required(CONF_DOMAIN): cv.string,
35  vol.Optional(CONF_SANDBOX, default=DEFAULT_SANDBOX): cv.boolean,
36  }
37  )
38  },
39  extra=vol.ALLOW_EXTRA,
40 )
41 
42 
43 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
44  """Set up the Mailgun component."""
45  if DOMAIN not in config:
46  return True
47 
48  hass.data[DOMAIN] = config[DOMAIN]
49  return True
50 
51 
52 async def handle_webhook(
53  hass: HomeAssistant, webhook_id: str, request: web.Request
54 ) -> None:
55  """Handle incoming webhook with Mailgun inbound messages."""
56  body = await request.text()
57  try:
58  data = json.loads(body) if body else {}
59  except ValueError:
60  return
61 
62  if (
63  isinstance(data, dict)
64  and "signature" in data
65  and await verify_webhook(hass, **data["signature"])
66  ):
67  data["webhook_id"] = webhook_id
68  hass.bus.async_fire(MESSAGE_RECEIVED, data)
69  return
70 
71  _LOGGER.warning(
72  "Mailgun webhook received an unauthenticated message - webhook_id: %s",
73  webhook_id,
74  )
75 
76 
77 async def verify_webhook(hass, token=None, timestamp=None, signature=None):
78  """Verify webhook was signed by Mailgun."""
79  if DOMAIN not in hass.data:
80  _LOGGER.warning("Cannot validate Mailgun webhook, missing API Key")
81  return True
82 
83  if not (token and timestamp and signature):
84  return False
85 
86  hmac_digest = hmac.new(
87  key=bytes(hass.data[DOMAIN][CONF_API_KEY], "utf-8"),
88  msg=bytes(f"{timestamp}{token}", "utf-8"),
89  digestmod=hashlib.sha256,
90  ).hexdigest()
91 
92  return hmac.compare_digest(signature, hmac_digest)
93 
94 
95 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
96  """Configure based on config entry."""
97  webhook.async_register(
98  hass, DOMAIN, "Mailgun", entry.data[CONF_WEBHOOK_ID], handle_webhook
99  )
100  return True
101 
102 
103 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
104  """Unload a config entry."""
105  webhook.async_unregister(hass, entry.data[CONF_WEBHOOK_ID])
106  return True
107 
108 
109 async_remove_entry = config_entry_flow.webhook_async_remove_entry
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:43
None handle_webhook(HomeAssistant hass, str webhook_id, web.Request request)
Definition: __init__.py:54
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:103
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:95
def verify_webhook(hass, token=None, timestamp=None, signature=None)
Definition: __init__.py:77