diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index ec437f08d39..54dea780dab 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -215,7 +215,7 @@ MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All( 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): cv.configuration_url, } ), validate_device_has_at_least_one_identifier, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 90aa499af4b..8d0ee78eca7 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -25,6 +25,7 @@ from uuid import UUID 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 @@ from . import script_variables as script_variables_helper, template as template_ 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" + HOMEASSISTANT = "homeassistant" + + +EXTERNAL_URL_PROTOCOL_SCHEMA_LIST = frozenset( + {UrlProtocolSchema.HTTP, UrlProtocolSchema.HTTPS} +) +CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST = frozenset( + {UrlProtocolSchema.HOMEASSISTANT, 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, CONFIGURATION_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/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 5ea6df42349..b5c8cc1716e 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)