From 3c107a63fc1bfea1a42c76bb5e2a27f76188d748 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 7 Jul 2023 16:49:22 +0000 Subject: [PATCH 1/5] Allow homeassistant in URL schema --- homeassistant/helpers/config_validation.py | 2 +- tests/helpers/test_config_validation.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 90aa499af4bab8..28881cd89470e6 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -732,7 +732,7 @@ def url(value: Any) -> str: """Validate an URL.""" url_in = str(value) - if urlparse(url_in).scheme in ["http", "https"]: + if urlparse(url_in).scheme in ["http", "https", "homeassistant"]: return cast(str, vol.Schema(vol.Url())(url_in)) raise vol.Invalid("invalid url") diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 5ea6df42349d28..95009085edbd51 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -123,6 +123,7 @@ def test_url() -> None: "http://home-assistant.io", "http://home-assistant.io/test/", "https://community.home-assistant.io/", + "homeassistant://api/hassio_ingress/XXXXXXX", ): assert schema(value) From 81b12030870166dc4306895a45ee24e947535653 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 7 Jul 2023 17:18:34 +0000 Subject: [PATCH 2/5] Make validator specific for MQTT --- homeassistant/components/mqtt/mixins.py | 9 +++++++-- homeassistant/components/mqtt/util.py | 13 ++++++++++++- homeassistant/helpers/config_validation.py | 2 +- tests/components/mqtt/test_mixins.py | 1 + tests/helpers/test_config_validation.py | 1 - 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index ec437f08d39bca..2d8a78312c2de7 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -96,7 +96,12 @@ async_subscribe_topics, async_unsubscribe_topics, ) -from .util import get_mqtt_data, mqtt_config_entry_enabled, valid_subscribe_topic +from .util import ( + get_mqtt_data, + mqtt_config_entry_enabled, + valid_config_url, + valid_subscribe_topic, +) _LOGGER = logging.getLogger(__name__) @@ -215,7 +220,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType vol.Optional(CONF_SW_VERSION): cv.string, vol.Optional(CONF_VIA_DEVICE): cv.string, vol.Optional(CONF_SUGGESTED_AREA): cv.string, - vol.Optional(CONF_CONFIGURATION_URL): cv.url, + vol.Optional(CONF_CONFIGURATION_URL): valid_config_url, } ), validate_device_has_at_least_one_identifier, diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index 896ba21f8022ec..91b4ea4968541b 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -6,7 +6,8 @@ import os from pathlib import Path import tempfile -from typing import Any +from typing import Any, cast +from urllib.parse import urlparse import async_timeout import voluptuous as vol @@ -131,6 +132,16 @@ def valid_subscribe_topic(topic: Any) -> str: return validated_topic +def valid_config_url(value: Any) -> str: + """Validate a an URL from the MQTT device schema.""" + url_in = str(value) + + if urlparse(url_in).scheme in ["http", "https", "homeassistant"]: + return cast(str, vol.Schema(vol.Url())(url_in)) + + raise vol.Invalid("invalid url") + + def valid_subscribe_topic_template(value: Any) -> template.Template: """Validate either a jinja2 template or a valid MQTT subscription topic.""" tpl = cv.template(value) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 28881cd89470e6..90aa499af4bab8 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -732,7 +732,7 @@ def url(value: Any) -> str: """Validate an URL.""" url_in = str(value) - if urlparse(url_in).scheme in ["http", "https", "homeassistant"]: + if urlparse(url_in).scheme in ["http", "https"]: return cast(str, vol.Schema(vol.Url())(url_in)) raise vol.Invalid("invalid url") diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 5a30a3a65de052..9a36fb8ca6f950 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest +import voluptuous as vol from homeassistant.components import mqtt, sensor from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 95009085edbd51..5ea6df42349d28 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -123,7 +123,6 @@ def test_url() -> None: "http://home-assistant.io", "http://home-assistant.io/test/", "https://community.home-assistant.io/", - "homeassistant://api/hassio_ingress/XXXXXXX", ): assert schema(value) From 599933fc4efa403d2227e962c36ec4baf9aaa225 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 7 Jul 2023 20:32:59 +0000 Subject: [PATCH 3/5] Create new shared cofiguration_url validator --- homeassistant/components/mqtt/mixins.py | 3 +-- homeassistant/components/mqtt/util.py | 13 +--------- homeassistant/helpers/config_validation.py | 29 ++++++++++++++++++++-- tests/components/mqtt/test_mixins.py | 1 - tests/helpers/test_config_validation.py | 29 ++++++++++++++++++++++ 5 files changed, 58 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 2d8a78312c2de7..22d771ff6fb8f8 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -99,7 +99,6 @@ from .util import ( get_mqtt_data, mqtt_config_entry_enabled, - valid_config_url, valid_subscribe_topic, ) @@ -220,7 +219,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType vol.Optional(CONF_SW_VERSION): cv.string, vol.Optional(CONF_VIA_DEVICE): cv.string, vol.Optional(CONF_SUGGESTED_AREA): cv.string, - vol.Optional(CONF_CONFIGURATION_URL): valid_config_url, + vol.Optional(CONF_CONFIGURATION_URL): cv.configuration_url, } ), validate_device_has_at_least_one_identifier, diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index 91b4ea4968541b..896ba21f8022ec 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -6,8 +6,7 @@ import os from pathlib import Path import tempfile -from typing import Any, cast -from urllib.parse import urlparse +from typing import Any import async_timeout import voluptuous as vol @@ -132,16 +131,6 @@ def valid_subscribe_topic(topic: Any) -> str: return validated_topic -def valid_config_url(value: Any) -> str: - """Validate a an URL from the MQTT device schema.""" - url_in = str(value) - - if urlparse(url_in).scheme in ["http", "https", "homeassistant"]: - return cast(str, vol.Schema(vol.Url())(url_in)) - - raise vol.Invalid("invalid url") - - def valid_subscribe_topic_template(value: Any) -> template.Template: """Validate either a jinja2 template or a valid MQTT subscription topic.""" tpl = cv.template(value) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 90aa499af4bab8..6103751074347a 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -25,6 +25,7 @@ import voluptuous as vol import voluptuous_serialize +from homeassistant.backports.enum import StrEnum from homeassistant.const import ( ATTR_AREA_ID, ATTR_DEVICE_ID, @@ -106,6 +107,22 @@ TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM', 'HH:MM:SS' or 'HH:MM:SS.F'" + +class UrlProtocolSchema(StrEnum): + """Valid URL protocol schema values.""" + + HTTP = "http" + HTTPS = "https" + HOMEASSISANT = "homeassistant" + + +EXTERNAL_URL_PROTOCOL_SCHEMA_LIST = frozenset( + {UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} +) +COMBINED_URL_PROTOCOL_SCHEMA_LIST = frozenset( + {UrlProtocolSchema.HOMEASSISANT, UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} +) + # Home Assistant types byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1)) @@ -728,16 +745,24 @@ def socket_timeout(value: Any | None) -> object: # pylint: disable=no-value-for-parameter -def url(value: Any) -> str: +def url( + value: Any, + _schema_list: frozenset[UrlProtocolSchema] = EXTERNAL_URL_PROTOCOL_SCHEMA_LIST, +) -> str: """Validate an URL.""" url_in = str(value) - if urlparse(url_in).scheme in ["http", "https"]: + if urlparse(url_in).scheme in _schema_list: return cast(str, vol.Schema(vol.Url())(url_in)) raise vol.Invalid("invalid url") +def configuration_url(value: Any) -> str: + """Validate an URL that allows the homeassistant schema.""" + return url(value, COMBINED_URL_PROTOCOL_SCHEMA_LIST) + + def url_no_path(value: Any) -> str: """Validate a url without a path.""" url_in = url(value) diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 9a36fb8ca6f950..5a30a3a65de052 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -3,7 +3,6 @@ from unittest.mock import patch import pytest -import voluptuous as vol from homeassistant.components import mqtt, sensor from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 5ea6df42349d28..b5c8cc1716e2f9 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -127,6 +127,35 @@ def test_url() -> None: assert schema(value) +def test_configuration_url() -> None: + """Test URL.""" + schema = vol.Schema(cv.configuration_url) + + for value in ( + "invalid", + None, + 100, + "htp://ha.io", + "http//ha.io", + "http://??,**", + "https://??,**", + "homeassistant://??,**", + ): + with pytest.raises(vol.MultipleInvalid): + schema(value) + + for value in ( + "http://localhost", + "https://localhost/test/index.html", + "http://home-assistant.io", + "http://home-assistant.io/test/", + "https://community.home-assistant.io/", + "homeassistant://api", + "homeassistant://api/hassio_ingress/XXXXXXX", + ): + assert schema(value) + + def test_url_no_path() -> None: """Test URL.""" schema = vol.Schema(cv.url_no_path) From 48376885ed589c3bc979efcc8f91306f1879807d Mon Sep 17 00:00:00 2001 From: jbouwh Date: Wed, 12 Jul 2023 07:15:10 +0000 Subject: [PATCH 4/5] Undo black magic, rename constant --- homeassistant/components/mqtt/mixins.py | 6 +----- homeassistant/helpers/config_validation.py | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 22d771ff6fb8f8..54dea780dabaa4 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -96,11 +96,7 @@ async_subscribe_topics, async_unsubscribe_topics, ) -from .util import ( - get_mqtt_data, - mqtt_config_entry_enabled, - valid_subscribe_topic, -) +from .util import get_mqtt_data, mqtt_config_entry_enabled, valid_subscribe_topic _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 6103751074347a..0c6855d3c28d33 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -119,7 +119,7 @@ class UrlProtocolSchema(StrEnum): EXTERNAL_URL_PROTOCOL_SCHEMA_LIST = frozenset( {UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} ) -COMBINED_URL_PROTOCOL_SCHEMA_LIST = frozenset( +CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST = frozenset( {UrlProtocolSchema.HOMEASSISANT, UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} ) @@ -760,7 +760,7 @@ def url( def configuration_url(value: Any) -> str: """Validate an URL that allows the homeassistant schema.""" - return url(value, COMBINED_URL_PROTOCOL_SCHEMA_LIST) + return url(value, CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST) def url_no_path(value: Any) -> str: From c412d0dcf47162619b1b59558d8c43cdddd85854 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Wed, 12 Jul 2023 07:16:36 +0000 Subject: [PATCH 5/5] Correct typo --- homeassistant/helpers/config_validation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 0c6855d3c28d33..8d0ee78eca7831 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -113,14 +113,14 @@ class UrlProtocolSchema(StrEnum): HTTP = "http" HTTPS = "https" - HOMEASSISANT = "homeassistant" + HOMEASSISTANT = "homeassistant" EXTERNAL_URL_PROTOCOL_SCHEMA_LIST = frozenset( {UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} ) CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST = frozenset( - {UrlProtocolSchema.HOMEASSISANT, UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} + {UrlProtocolSchema.HOMEASSISTANT, UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} ) # Home Assistant types