Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia."
},
"create_entry": {
"default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Mailgun] ({mailgun_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants."
"default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Mailgun] ({mailgun_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/json\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants."
Comment thread
rohankapoorcom marked this conversation as resolved.
Outdated
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "Only a single instance is necessary."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
"default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
},
"create_entry": {
"default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun Webhook]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694."
"default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun Webhook]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694."
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/lb.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg."
},
"create_entry": {
"default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Mailgun]({mailgun_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren."
"default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Mailgun]({mailgun_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren."
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "Wymagana jest tylko jedna instancja."
},
"create_entry": {
"default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane."
"default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane."
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria."
},
"create_entry": {
"default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks with Mailgun] ({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/x-www-form-urlencoded \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada."
"default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks with Mailgun] ({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/json \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada."
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
},
"create_entry": {
"default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c [Webhooks \u0434\u043b\u044f Mailgun]({mailgun_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445."
"default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c [Webhooks \u0434\u043b\u044f Mailgun]({mailgun_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445."
},
"step": {
"user": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/.translations/sl.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "Za po\u0161iljanje dogodkov Home Assistentu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov."
"default": "Za po\u0161iljanje dogodkov Home Assistentu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/json\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov."
},
"step": {
"user": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002"
},
"create_entry": {
"default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [Webhooks with Mailgun]({mailgun_url}) \u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002"
"default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [Webhooks with Mailgun]({mailgun_url}) \u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002"
},
"step": {
"user": {
Expand Down
41 changes: 35 additions & 6 deletions homeassistant/components/mailgun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mailgun/
"""
import hashlib
import hmac
import json
import logging

import voluptuous as vol

Expand All @@ -12,7 +16,7 @@
from homeassistant.helpers import config_entry_flow

DOMAIN = 'mailgun'
API_PATH = '/api/{}'.format(DOMAIN)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['webhook']
MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN)
CONF_SANDBOX = 'sandbox'
Expand All @@ -38,9 +42,35 @@ async def async_setup(hass, config):

async def handle_webhook(hass, webhook_id, request):
"""Handle incoming webhook with Mailgun inbound messages."""
data = dict(await request.post())
data['webhook_id'] = webhook_id
hass.bus.async_fire(MESSAGE_RECEIVED, data)
body = await request.text()
try:
data = json.loads(body) if body else {}
except ValueError:
return None

if isinstance(data, dict) and 'signature' in data.keys():
if await verify_webhook(hass, **data['signature']):
Comment thread
rohankapoorcom marked this conversation as resolved.
data['webhook_id'] = webhook_id
hass.bus.async_fire(MESSAGE_RECEIVED, data)
return

_LOGGER.warning(
'Mailgun webhook received an unauthenticated message - webhook_id: %s',
webhook_id
)


async def verify_webhook(hass, token, timestamp, signature):
"""Verify webhook was signed by Mailgun."""
if DOMAIN not in hass.data:
_LOGGER.error('Cannot validate Mailgun webhook, missing API Key')
return False
hmac_digest = hmac.new(
key=bytes(hass.data[DOMAIN][CONF_API_KEY], 'utf-8'),
msg=bytes('{}{}'.format(timestamp, token), 'utf-8'),
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, hmac_digest)


async def async_setup_entry(hass, entry):
Expand All @@ -59,8 +89,7 @@ async def async_unload_entry(hass, entry):
DOMAIN,
'Mailgun Webhook',
{
'mailgun_url':
'https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks',
'mailgun_url': 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', # noqa: E501 pylint: disable=line-too-long
'docs_url': 'https://www.home-assistant.io/components/mailgun/'
}
)
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
"default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
}
}
}
111 changes: 95 additions & 16 deletions tests/components/mailgun/test_init.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,118 @@
"""Test the init file of Mailgun."""
from unittest.mock import patch
import hashlib
import hmac
from unittest.mock import Mock

import pytest

from homeassistant import data_entry_flow
from homeassistant.components import mailgun

from homeassistant.const import CONF_API_KEY, CONF_DOMAIN
from homeassistant.core import callback
from homeassistant.setup import async_setup_component

API_KEY = 'abc123'
mailgun_events = []
Comment thread
rohankapoorcom marked this conversation as resolved.
Outdated

async def test_config_flow_registers_webhook(hass, aiohttp_client):
"""Test setting up Mailgun and sending webhook."""
with patch('homeassistant.util.get_local_ip', return_value='example.com'):
result = await hass.config_entries.flow.async_init('mailgun', context={
'source': 'user'
})

@pytest.fixture
async def fixture(hass, aiohttp_client):
"""Initialize a Home Assistant Server for testing this module."""
await async_setup_component(hass, mailgun.DOMAIN, {
mailgun.DOMAIN: {
CONF_API_KEY: API_KEY,
CONF_DOMAIN: 'example.com'
},
})

hass.config.api = Mock(base_url='http://example.com')
result = await hass.config_entries.flow.async_init('mailgun', context={
'source': 'user'
})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result

result = await hass.config_entries.flow.async_configure(
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
webhook_id = result['result'].data['webhook_id']

mailgun_events = []

@callback
def handle_event(event):
"""Handle Mailgun event."""
mailgun_events.append(event)

hass.bus.async_listen(mailgun.MESSAGE_RECEIVED, handle_event)

client = await aiohttp_client(hass.http.app)
await client.post('/api/webhook/{}'.format(webhook_id), data={
'hello': 'mailgun'
return await aiohttp_client(hass.http.app), webhook_id


async def test_mailgun_webhook_with_missing_signature(fixture):
"""Test that webhook doesn't trigger an event without a signature."""
client, webhook_id = fixture

event_count = len(mailgun_events)

await client.post('/api/webhook/{}'.format(webhook_id), json={
'hello': 'mailgun',
'signature': {}
})

assert len(mailgun_events) == event_count

await client.post('/api/webhook/{}'.format(webhook_id), json={
'hello': 'mailgun',
})

assert len(mailgun_events) == event_count


async def test_mailgun_webhook_with_different_api_key(fixture):
"""Test that webhook doesn't trigger an event with a wrong signature."""
client, webhook_id = fixture

timestamp = '1529006854'
token = 'a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0'

event_count = len(mailgun_events)

await client.post('/api/webhook/{}'.format(webhook_id), json={
'hello': 'mailgun',
'signature': {
'signature': hmac.new(
key=b'random_api_key',
msg=bytes('{}{}'.format(timestamp, token), 'utf-8'),
digestmod=hashlib.sha256
).hexdigest(),
'timestamp': timestamp,
'token': token
}
})

assert len(mailgun_events) == event_count


async def test_mailgun_webhook_with_correct_api_key(fixture):
"""Test that webhook triggers an event after validating a signature."""
client, webhook_id = fixture

timestamp = '1529006854'
token = 'a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0'

event_count = len(mailgun_events)

await client.post('/api/webhook/{}'.format(webhook_id), json={
'hello': 'mailgun',
'signature': {
'signature': hmac.new(
key=bytes(API_KEY, 'utf-8'),
msg=bytes('{}{}'.format(timestamp, token), 'utf-8'),
digestmod=hashlib.sha256
).hexdigest(),
'timestamp': timestamp,
'token': token
}
})

assert len(mailgun_events) == 1
assert mailgun_events[0].data['webhook_id'] == webhook_id
assert mailgun_events[0].data['hello'] == 'mailgun'
assert len(mailgun_events) == event_count + 1
assert mailgun_events[-1].data['webhook_id'] == webhook_id
assert mailgun_events[-1].data['hello'] == 'mailgun'