From 3df71eca4538d32a55d7e3ef2a21fd3f5d7a0545 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 7 Aug 2023 05:23:52 +0200 Subject: [PATCH] Ensure webhooks take HA cloud into account (#97801) * Ensure webhooks take HA cloud into account * Avoid circular import --- homeassistant/components/webhook/__init__.py | 28 +++++++++++++------- tests/components/webhook/test_init.py | 14 +++++++++- tests/components/webhook/test_trigger.py | 20 +++++++++++--- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 9711c30b19ec20..5f82ca54283d2c 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -145,16 +145,26 @@ async def async_handle_webhook( return Response(status=HTTPStatus.METHOD_NOT_ALLOWED) if webhook["local_only"] in (True, None) and not isinstance(request, MockRequest): - if TYPE_CHECKING: - assert isinstance(request, Request) - assert request.remote is not None - try: - remote = ip_address(request.remote) - except ValueError: - _LOGGER.debug("Unable to parse remote ip %s", request.remote) - return Response(status=HTTPStatus.OK) + if has_cloud := "cloud" in hass.config.components: + from hass_nabucasa import remote # pylint: disable=import-outside-toplevel + + is_local = True + if has_cloud and remote.is_cloud_request.get(): + is_local = False + else: + if TYPE_CHECKING: + assert isinstance(request, Request) + assert request.remote is not None + + try: + request_remote = ip_address(request.remote) + except ValueError: + _LOGGER.debug("Unable to parse remote ip %s", request.remote) + return Response(status=HTTPStatus.OK) + + is_local = network.is_local(request_remote) - if not network.is_local(remote): + if not is_local: _LOGGER.warning("Received remote request for local webhook %s", webhook_id) if webhook["local_only"]: return Response(status=HTTPStatus.OK) diff --git a/tests/components/webhook/test_init.py b/tests/components/webhook/test_init.py index ff0346a3d8b864..fbe0da158534f2 100644 --- a/tests/components/webhook/test_init.py +++ b/tests/components/webhook/test_init.py @@ -1,7 +1,7 @@ """Test the webhook component.""" from http import HTTPStatus from ipaddress import ip_address -from unittest.mock import patch +from unittest.mock import Mock, patch from aiohttp import web import pytest @@ -206,6 +206,8 @@ async def handle(*args): async def test_webhook_local_only(hass: HomeAssistant, mock_client) -> None: """Test posting a webhook with local only.""" + hass.config.components.add("cloud") + hooks = [] webhook_id = webhook.async_generate_id() @@ -234,6 +236,16 @@ async def handle(*args): # No hook received assert len(hooks) == 1 + # Request from Home Assistant Cloud remote UI + with patch( + "hass_nabucasa.remote.is_cloud_request", Mock(get=Mock(return_value=True)) + ): + resp = await mock_client.post(f"/api/webhook/{webhook_id}", json={"data": True}) + + # No hook received + assert resp.status == HTTPStatus.OK + assert len(hooks) == 1 + async def test_listing_webhook( hass: HomeAssistant, diff --git a/tests/components/webhook/test_trigger.py b/tests/components/webhook/test_trigger.py index 392ab58a30fb3d..990482c500edce 100644 --- a/tests/components/webhook/test_trigger.py +++ b/tests/components/webhook/test_trigger.py @@ -1,6 +1,6 @@ """The tests for the webhook automation trigger.""" from ipaddress import ip_address -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest @@ -68,6 +68,9 @@ async def test_webhook_post( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator ) -> None: """Test triggering with a POST webhook.""" + # Set up fake cloud + hass.config.components.add("cloud") + events = [] @callback @@ -114,6 +117,16 @@ def store_event(event): await hass.async_block_till_done() assert len(events) == 1 + # Request from Home Assistant Cloud remote UI + with patch( + "hass_nabucasa.remote.is_cloud_request", Mock(get=Mock(return_value=True)) + ): + await client.post("/api/webhook/post_webhook", data={"hello": "world"}) + + # No hook received + await hass.async_block_till_done() + assert len(events) == 1 + async def test_webhook_allowed_methods_internet( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator @@ -141,7 +154,6 @@ def store_event(event): }, "action": { "event": "test_success", - "event_data_template": {"hello": "yo {{ trigger.data.hello }}"}, }, } }, @@ -150,7 +162,7 @@ def store_event(event): client = await hass_client_no_auth() - await client.post("/api/webhook/post_webhook", data={"hello": "world"}) + await client.post("/api/webhook/post_webhook") await hass.async_block_till_done() assert len(events) == 0 @@ -160,7 +172,7 @@ def store_event(event): "homeassistant.components.webhook.ip_address", return_value=ip_address("123.123.123.123"), ): - await client.put("/api/webhook/post_webhook", data={"hello": "world"}) + await client.put("/api/webhook/post_webhook") await hass.async_block_till_done() assert len(events) == 1