diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index 60b4cde4708..11b6f3e9865 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -41,6 +41,7 @@ ATTR_WEBHOOK_ENCRYPTED = 'encrypted' ATTR_WEBHOOK_ENCRYPTED_DATA = 'encrypted_data' ATTR_WEBHOOK_TYPE = 'type' +ERR_ENCRYPTION_REQUIRED = 'encryption_required' ERR_INVALID_COMPONENT = 'invalid_component' ERR_RENDER_FAILURE = 'render_failure' ERR_SAVE_FAILURE = 'save_failure' diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 61188b50e1b..9efd1fcd9f8 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -20,15 +20,16 @@ from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import HomeAssistantType -from .const import (ATTR_APP_COMPONENT, DATA_DELETED_IDS, - ATTR_DEVICE_NAME, ATTR_EVENT_DATA, ATTR_EVENT_TYPE, - DATA_REGISTRATIONS, ATTR_TEMPLATE, ATTR_TEMPLATE_VARIABLES, - ATTR_WEBHOOK_DATA, ATTR_WEBHOOK_ENCRYPTED, - ATTR_WEBHOOK_ENCRYPTED_DATA, ATTR_WEBHOOK_TYPE, - CONF_SECRET, DOMAIN, ERR_RENDER_FAILURE, - WEBHOOK_PAYLOAD_SCHEMA, WEBHOOK_SCHEMAS, - WEBHOOK_TYPE_CALL_SERVICE, WEBHOOK_TYPE_FIRE_EVENT, - WEBHOOK_TYPE_RENDER_TEMPLATE, WEBHOOK_TYPE_UPDATE_LOCATION, +from .const import (ATTR_APP_COMPONENT, ATTR_DEVICE_NAME, ATTR_EVENT_DATA, + ATTR_EVENT_TYPE, ATTR_SUPPORTS_ENCRYPTION, ATTR_TEMPLATE, + ATTR_TEMPLATE_VARIABLES, ATTR_WEBHOOK_DATA, + ATTR_WEBHOOK_ENCRYPTED, ATTR_WEBHOOK_ENCRYPTED_DATA, + ATTR_WEBHOOK_TYPE, CONF_SECRET, DATA_DELETED_IDS, + DATA_REGISTRATIONS, DOMAIN, ERR_ENCRYPTION_REQUIRED, + ERR_RENDER_FAILURE, WEBHOOK_PAYLOAD_SCHEMA, + WEBHOOK_SCHEMAS, WEBHOOK_TYPE_CALL_SERVICE, + WEBHOOK_TYPE_FIRE_EVENT, WEBHOOK_TYPE_RENDER_TEMPLATE, + WEBHOOK_TYPE_UPDATE_LOCATION, WEBHOOK_TYPE_UPDATE_REGISTRATION) from .helpers import (_decrypt_payload, empty_okay_response, error_response, @@ -78,6 +79,12 @@ async def handle_webhook(store: Store, hass: HomeAssistantType, _LOGGER.warning('Received invalid JSON from mobile_app') return empty_okay_response(status=HTTP_BAD_REQUEST) + if (ATTR_WEBHOOK_ENCRYPTED not in req_data and + registration[ATTR_SUPPORTS_ENCRYPTION]): + _LOGGER.warning("Refusing to accept unencrypted webhook from %s", + registration[ATTR_DEVICE_NAME]) + return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required") + try: req_data = WEBHOOK_PAYLOAD_SCHEMA(req_data) except vol.Invalid as ex: diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index a935110754c..bbdfcde93e7 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -149,3 +149,18 @@ async def test_webhook_handle_decryption(webhook_client): # noqa: F811 decrypted_data = decrypted_data.decode("utf-8") assert json.loads(decrypted_data) == {'rendered': 'Hello world'} + + +async def test_webhook_requires_encryption(webhook_client): # noqa: F811 + """Test that encrypted registrations only accept encrypted data.""" + resp = await webhook_client.post( + '/api/webhook/mobile_app_test', + json=RENDER_TEMPLATE + ) + + assert resp.status == 400 + + webhook_json = await resp.json() + assert 'error' in webhook_json + assert webhook_json['success'] is False + assert webhook_json['error']['code'] == 'encryption_required'