From 8c9bf7e41c9fdcec3a173f6756bc80ccdcb1abb9 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 17 Oct 2024 09:36:06 +0200 Subject: [PATCH 1/7] Fail on templated services --- homeassistant/helpers/config_validation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 81ac10f86cc..f9d8071f773 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1380,6 +1380,14 @@ def _make_entity_service_schema(schema: dict, extra: int) -> VolSchemaType: BASE_ENTITY_SCHEMA = _make_entity_service_schema({}, vol.PREVENT_EXTRA) +def _raise_on_templated_service(schema: VolDictType | None) -> None: + if not schema: + return + for key, val in schema.items(): + if val in (dynamic_template, template, template_complex): + raise ValueError(f"Template in service data is not allowed! {key}") + + def make_entity_service_schema( schema: dict | None, *, extra: int = vol.PREVENT_EXTRA ) -> VolSchemaType: @@ -1389,6 +1397,7 @@ def make_entity_service_schema( # the base schema and avoid compiling a new schema which is the case # for ~50% of services. return BASE_ENTITY_SCHEMA + _raise_on_templated_service(schema) return _make_entity_service_schema(schema or {}, extra) From 51e409c1c73cceb10027f350e1baca1ea1ea5c36 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 17 Oct 2024 11:25:09 +0200 Subject: [PATCH 2/7] Check all services --- homeassistant/core.py | 4 ++++ homeassistant/helpers/config_validation.py | 25 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index cdfb5570b44..9b210e1ca28 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -2610,6 +2610,10 @@ class ServiceRegistry: This method must be run in the event loop. """ + # pylint: disable-next=import-outside-toplevel + from .helpers import config_validation as cv + + cv.raise_on_templated_service(domain, service, schema) domain = domain.lower() service = service.lower() service_obj = Service( diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index f9d8071f773..66de8266122 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1380,12 +1380,32 @@ def _make_entity_service_schema(schema: dict, extra: int) -> VolSchemaType: BASE_ENTITY_SCHEMA = _make_entity_service_schema({}, vol.PREVENT_EXTRA) -def _raise_on_templated_service(schema: VolDictType | None) -> None: +def _raise_on_templated_service( + domain: str, _service: str, schema: VolDictType | None +) -> None: if not schema: return for key, val in schema.items(): if val in (dynamic_template, template, template_complex): - raise ValueError(f"Template in service data is not allowed! {key}") + raise ValueError( + f"Template in service data is not allowed! {domain}.{_service}:{key}" + ) + + +def raise_on_templated_service( + domain: str, _service: str, schema: VolDictType | VolSchemaType | None +) -> None: + """Raise if service schema explicitly allows templates.""" + if not schema: + return + if isinstance(schema, dict): + _raise_on_templated_service(domain, _service, schema) + return + if isinstance(schema, (vol.All, vol.Any)): + for val in schema.validators: + raise_on_templated_service(domain, _service, val) + if isinstance(schema, (vol.Schema)): + raise_on_templated_service(domain, _service, schema.schema) def make_entity_service_schema( @@ -1397,7 +1417,6 @@ def make_entity_service_schema( # the base schema and avoid compiling a new schema which is the case # for ~50% of services. return BASE_ENTITY_SCHEMA - _raise_on_templated_service(schema) return _make_entity_service_schema(schema or {}, extra) From df6763328fc688ceb892886243e404ae0e352c8d Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 17 Oct 2024 12:59:08 +0200 Subject: [PATCH 3/7] Remove explicit templating of knx service data --- homeassistant/components/knx/services.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/knx/services.py b/homeassistant/components/knx/services.py index 113be9709ee..c8086c4a62a 100644 --- a/homeassistant/components/knx/services.py +++ b/homeassistant/components/knx/services.py @@ -13,7 +13,7 @@ from xknx.telegram import Telegram from xknx.telegram.address import parse_device_group_address from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite -from homeassistant.const import CONF_TYPE, SERVICE_RELOAD +from homeassistant.const import CONF_TYPE, CONF_VALUE_TEMPLATE, SERVICE_RELOAD from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError, ServiceValidationError import homeassistant.helpers.config_validation as cv @@ -143,6 +143,7 @@ SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( ExposeSchema.EXPOSE_SENSOR_SCHEMA.extend( { vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, + vol.Optional(CONF_VALUE_TEMPLATE): cv.string, } ), vol.Schema( From 6e4b5f31d4c446b16b6f0ee7fa09cc6b860dc32d Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 17 Oct 2024 19:41:33 +0200 Subject: [PATCH 4/7] Extend checks --- homeassistant/helpers/config_validation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 66de8266122..2465d97079e 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1390,6 +1390,9 @@ def _raise_on_templated_service( raise ValueError( f"Template in service data is not allowed! {domain}.{_service}:{key}" ) + if isinstance(val, (vol.All, vol.Any)): + for subval in val.validators: + raise_on_templated_service(domain, _service, subval) def raise_on_templated_service( @@ -1406,6 +1409,10 @@ def raise_on_templated_service( raise_on_templated_service(domain, _service, val) if isinstance(schema, (vol.Schema)): raise_on_templated_service(domain, _service, schema.schema) + if schema in (dynamic_template, template, template_complex): + raise ValueError( + f"Template in service data is not allowed! {domain}.{_service}" + ) def make_entity_service_schema( From e88092dc525b2d29a490f3237da3612eb7b81ae3 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 17 Oct 2024 21:01:15 +0200 Subject: [PATCH 5/7] Extend checks --- homeassistant/helpers/config_validation.py | 57 ++++++++++++++-------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 2465d97079e..7a90f0a1217 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1380,38 +1380,53 @@ def _make_entity_service_schema(schema: dict, extra: int) -> VolSchemaType: BASE_ENTITY_SCHEMA = _make_entity_service_schema({}, vol.PREVENT_EXTRA) -def _raise_on_templated_service( - domain: str, _service: str, schema: VolDictType | None -) -> None: - if not schema: - return - for key, val in schema.items(): - if val in (dynamic_template, template, template_complex): - raise ValueError( - f"Template in service data is not allowed! {domain}.{_service}:{key}" - ) - if isinstance(val, (vol.All, vol.Any)): - for subval in val.validators: - raise_on_templated_service(domain, _service, subval) - - def raise_on_templated_service( domain: str, _service: str, schema: VolDictType | VolSchemaType | None +) -> None: + """Raise if service schema explicitly allows templates.""" + return _raise_on_templated_service(domain, _service, schema, []) + + +def _raise_on_templated_service( + domain: str, + _service: str, + schema: Any, + _path: list[str | int], ) -> None: """Raise if service schema explicitly allows templates.""" if not schema: return if isinstance(schema, dict): - _raise_on_templated_service(domain, _service, schema) + for key, val in schema.items(): + _raise_on_templated_service(domain, _service, val, [*_path, key]) return - if isinstance(schema, (vol.All, vol.Any)): - for val in schema.validators: - raise_on_templated_service(domain, _service, val) + if isinstance(schema, list): + for pos, val in enumerate(schema): + _raise_on_templated_service(domain, _service, val, [*_path, pos]) + return + if isinstance(schema, vol.All): + for pos, val in enumerate(schema.validators): + _raise_on_templated_service(domain, _service, val, [*_path, "All", pos]) + if isinstance(schema, vol.Any): + for pos, val in enumerate(schema.validators): + _raise_on_templated_service(domain, _service, val, [*_path, "Any", pos]) if isinstance(schema, (vol.Schema)): - raise_on_templated_service(domain, _service, schema.schema) + _raise_on_templated_service(domain, _service, schema.schema, _path) + if _path[-5:] == ["All", 0, "entity_id", "Any", 1]: + return + if _path[-7:] == ["All", 0, "entity_id", "Any", 2, "All", 1]: + return + if _path[-10:] == ["All", 0, "device_id", "Any", 1, "All", 1, 0, "Any", 0]: + return + if _path[-10:] == ["All", 0, "area_id", "Any", 1, "All", 1, 0, "Any", 0]: + return + if _path[-10:] == ["All", 0, "floor_id", "Any", 1, "All", 1, 0, "Any", 0]: + return + if _path[-10:] == ["All", 0, "label_id", "Any", 1, "All", 1, 0, "Any", 0]: + return if schema in (dynamic_template, template, template_complex): raise ValueError( - f"Template in service data is not allowed! {domain}.{_service}" + f"Template in service data is not allowed! {domain}.{_service}:{_path}" ) From d8716b6b728f3e3ea41beaa86e10dc6e38e89340 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 21 Oct 2024 13:53:00 +0200 Subject: [PATCH 6/7] Don't fail on unifiprotect --- homeassistant/helpers/config_validation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 7a90f0a1217..1503d764180 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1424,6 +1424,8 @@ def _raise_on_templated_service( return if _path[-10:] == ["All", 0, "label_id", "Any", 1, "All", 1, 0, "Any", 0]: return + if domain == "unifiprotect" and _service == "set_chime_paired_doorbells": + return if schema in (dynamic_template, template, template_complex): raise ValueError( f"Template in service data is not allowed! {domain}.{_service}:{_path}" From 4c0fcbb82493dbb60411af3110115b734d84e614 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 21 Oct 2024 14:17:10 +0200 Subject: [PATCH 7/7] Don't fail on camera --- homeassistant/helpers/config_validation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 1503d764180..b540bede64d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1424,6 +1424,8 @@ def _raise_on_templated_service( return if _path[-10:] == ["All", 0, "label_id", "Any", 1, "All", 1, 0, "Any", 0]: return + if domain == "camera" and _service in ("record", "snapshot"): + return if domain == "unifiprotect" and _service == "set_chime_paired_doorbells": return if schema in (dynamic_template, template, template_complex):