Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ omit =
homeassistant/components/lutron_caseta.py
homeassistant/components/*/lutron_caseta.py

homeassistant/components/mailgun.py
homeassistant/components/*/mailgun.py

homeassistant/components/matrix.py
Expand Down
53 changes: 9 additions & 44 deletions homeassistant/components/ifttt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ifttt/
"""
from ipaddress import ip_address
import json
import logging
from urllib.parse import urlparse

import requests
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries
from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.util.network import is_local
from homeassistant.helpers import config_entry_flow

REQUIREMENTS = ['pyfttt==0.3']
DEPENDENCIES = ['webhook']
Expand Down Expand Up @@ -100,43 +97,11 @@ async def async_unload_entry(hass, entry):
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True


@config_entries.HANDLERS.register(DOMAIN)
class ConfigFlow(config_entries.ConfigFlow):
"""Handle an IFTTT config flow."""

async def async_step_user(self, user_input=None):
"""Handle a user initiated set up flow."""
if self._async_current_entries():
return self.async_abort(reason='one_instance_allowed')

try:
url_parts = urlparse(self.hass.config.api.base_url)

if is_local(ip_address(url_parts.hostname)):
return self.async_abort(reason='not_internet_accessible')
except ValueError:
# If it's not an IP address, it's very likely publicly accessible
pass

if user_input is None:
return self.async_show_form(
step_id='user',
)

webhook_id = self.hass.components.webhook.async_generate_id()
webhook_url = \
self.hass.components.webhook.async_generate_url(webhook_id)

return self.async_create_entry(
title='IFTTT Webhook',
data={
CONF_WEBHOOK_ID: webhook_id
},
description_placeholders={
'applet_url': 'https://ifttt.com/maker_webhooks',
'webhook_url': webhook_url,
'docs_url':
'https://www.home-assistant.io/components/ifttt/'
}
)
config_entry_flow.register_webhook_flow(
DOMAIN,
'IFTTT Webhook',
{
'applet_url': 'https://ifttt.com/maker_webhooks',
'docs_url': 'https://www.home-assistant.io/components/ifttt/'
}
)
50 changes: 0 additions & 50 deletions homeassistant/components/mailgun.py

This file was deleted.

18 changes: 18 additions & 0 deletions homeassistant/components/mailgun/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"config": {
"title": "Mailgun",
"step": {
"user": {
"title": "Set up the Mailgun Webhook",
"description": "Are you sure you want to set up Mailgun?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"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."
}
}
}
67 changes: 67 additions & 0 deletions homeassistant/components/mailgun/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Support for Mailgun.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mailgun/
"""

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow

DOMAIN = 'mailgun'
API_PATH = '/api/{}'.format(DOMAIN)
DEPENDENCIES = ['webhook']
MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN)
CONF_SANDBOX = 'sandbox'
DEFAULT_SANDBOX = False

CONFIG_SCHEMA = vol.Schema({
vol.Optional(DOMAIN): vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_DOMAIN): cv.string,
vol.Optional(CONF_SANDBOX, default=DEFAULT_SANDBOX): cv.boolean,
vol.Optional(CONF_WEBHOOK_ID): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)


async def async_setup(hass, config):
"""Set up the Mailgun component."""
if DOMAIN not in config:
return True

hass.data[DOMAIN] = config[DOMAIN]
return True


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)


async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True


async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True

config_entry_flow.register_webhook_flow(
DOMAIN,
'Mailgun Webhook',
{
'mailgun_url':
'https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks',
'docs_url': 'https://www.home-assistant.io/components/mailgun/'
}
)
18 changes: 18 additions & 0 deletions homeassistant/components/mailgun/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"config": {
"title": "Mailgun",
"step": {
"user": {
"title": "Set up the Mailgun Webhook",
"description": "Are you sure you want to set up Mailgun?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"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."
}
}
}
5 changes: 3 additions & 2 deletions homeassistant/components/notify/mailgun.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

import voluptuous as vol

from homeassistant.components.mailgun import CONF_SANDBOX, DATA_MAILGUN
from homeassistant.components.mailgun import (
CONF_SANDBOX, DOMAIN as MAILGUN_DOMAIN)
from homeassistant.components.notify import (
PLATFORM_SCHEMA, BaseNotificationService, ATTR_TITLE, ATTR_TITLE_DEFAULT,
ATTR_DATA)
Expand All @@ -35,7 +36,7 @@

def get_service(hass, config, discovery_info=None):
"""Get the Mailgun notification service."""
data = hass.data[DATA_MAILGUN]
data = hass.data[MAILGUN_DOMAIN]
mailgun_service = MailgunNotificationService(
data.get(CONF_DOMAIN), data.get(CONF_SANDBOX),
data.get(CONF_API_KEY), config.get(CONF_SENDER),
Expand Down
1 change: 1 addition & 0 deletions homeassistant/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ async def async_step_discovery(info):
'ifttt',
'ios',
'lifx',
'mailgun',
'mqtt',
'nest',
'openuv',
Expand Down
58 changes: 58 additions & 0 deletions homeassistant/helpers/config_entry_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Helpers for data entry flows for config entries."""
from functools import partial
from ipaddress import ip_address
from urllib.parse import urlparse

from homeassistant import config_entries
from homeassistant.util.network import is_local


def register_discovery_flow(domain, title, discovery_function,
Expand All @@ -12,6 +15,14 @@ def register_discovery_flow(domain, title, discovery_function,
connection_class))


def register_webhook_flow(domain, title, description_placeholder,
allow_multiple=False):
"""Register flow for webhook integrations."""
config_entries.HANDLERS.register(domain)(
partial(WebhookFlowHandler, domain, title, description_placeholder,
allow_multiple))


class DiscoveryFlowHandler(config_entries.ConfigFlow):
"""Handle a discovery config flow."""

Expand Down Expand Up @@ -84,3 +95,50 @@ async def async_step_import(self, _):
title=self._title,
data={},
)


class WebhookFlowHandler(config_entries.ConfigFlow):
"""Handle a webhook config flow."""

VERSION = 1

def __init__(self, domain, title, description_placeholder,
allow_multiple):
"""Initialize the discovery config flow."""
self._domain = domain
self._title = title
self._description_placeholder = description_placeholder
self._allow_multiple = allow_multiple

async def async_step_user(self, user_input=None):
"""Handle a user initiated set up flow to create a webhook."""
if not self._allow_multiple and self._async_current_entries():
return self.async_abort(reason='one_instance_allowed')

try:
url_parts = urlparse(self.hass.config.api.base_url)

if is_local(ip_address(url_parts.hostname)):
return self.async_abort(reason='not_internet_accessible')
except ValueError:
# If it's not an IP address, it's very likely publicly accessible
pass

if user_input is None:
return self.async_show_form(
step_id='user',
)

webhook_id = self.hass.components.webhook.async_generate_id()
webhook_url = \
self.hass.components.webhook.async_generate_url(webhook_id)

self._description_placeholder['webhook_url'] = webhook_url

return self.async_create_entry(
title=self._title,
data={
'webhook_id': webhook_id
},
description_placeholders=self._description_placeholder
)
12 changes: 1 addition & 11 deletions tests/components/ifttt/test_init.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Test the init file of IFTTT."""
from unittest.mock import Mock, patch
from unittest.mock import patch

from homeassistant import data_entry_flow
from homeassistant.core import callback
Expand Down Expand Up @@ -36,13 +36,3 @@ def handle_event(event):
assert len(ifttt_events) == 1
assert ifttt_events[0].data['webhook_id'] == webhook_id
assert ifttt_events[0].data['hello'] == 'ifttt'


async def test_config_flow_aborts_external_url(hass, aiohttp_client):
"""Test setting up IFTTT and sending webhook."""
hass.config.api = Mock(base_url='http://192.168.1.10')
result = await hass.config_entries.flow.async_init('ifttt', context={
'source': 'user'
})
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
assert result['reason'] == 'not_internet_accessible'
1 change: 1 addition & 0 deletions tests/components/mailgun/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the Mailgun component."""
Loading