No longer allow invalid slugs or extra keys (#24176)
* No longer allow slugs * Lint * Remove HASchema * Lint * Fix tests
This commit is contained in:
parent
b3d8f8620c
commit
59ce31f44f
6 changed files with 8 additions and 275 deletions
|
@ -1,6 +1,5 @@
|
|||
"""Helpers for config validation using voluptuous."""
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
@ -17,7 +16,7 @@ from pkg_resources import parse_version
|
|||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
CONF_ABOVE, CONF_ALIAS, CONF_BELOW, CONF_CONDITION, CONF_ENTITY_ID,
|
||||
CONF_ENTITY_NAMESPACE, CONF_NAME, CONF_PLATFORM, CONF_SCAN_INTERVAL,
|
||||
CONF_ENTITY_NAMESPACE, CONF_PLATFORM, CONF_SCAN_INTERVAL,
|
||||
CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, CONF_VALUE_TEMPLATE,
|
||||
CONF_TIMEOUT, ENTITY_MATCH_ALL, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, WEEKDAYS, __version__)
|
||||
|
@ -29,13 +28,6 @@ from homeassistant.util import slugify as util_slugify
|
|||
# pylint: disable=invalid-name
|
||||
|
||||
TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'"
|
||||
OLD_SLUG_VALIDATION = r'^[a-z0-9_]+$'
|
||||
OLD_ENTITY_ID_VALIDATION = r"^(\w+)\.(\w+)$"
|
||||
# Keep track of invalid slugs and entity ids found so we can create a
|
||||
# persistent notification. Rare temporary exception to use a global.
|
||||
INVALID_SLUGS_FOUND = {}
|
||||
INVALID_ENTITY_IDS_FOUND = {}
|
||||
INVALID_EXTRA_KEYS_FOUND = []
|
||||
|
||||
|
||||
# Home Assistant types
|
||||
|
@ -176,17 +168,6 @@ def entity_id(value: Any) -> str:
|
|||
value = string(value).lower()
|
||||
if valid_entity_id(value):
|
||||
return value
|
||||
if re.match(OLD_ENTITY_ID_VALIDATION, value):
|
||||
# To ease the breaking change, we allow old slugs for now
|
||||
# Remove after 0.94 or 1.0
|
||||
fixed = '.'.join(util_slugify(part) for part in value.split('.', 1))
|
||||
INVALID_ENTITY_IDS_FOUND[value] = fixed
|
||||
logging.getLogger(__name__).warning(
|
||||
"Found invalid entity_id %s, please update with %s. This "
|
||||
"will become a breaking change.",
|
||||
value, fixed
|
||||
)
|
||||
return value
|
||||
|
||||
raise vol.Invalid('Entity ID {} is an invalid entity id'.format(value))
|
||||
|
||||
|
@ -377,21 +358,7 @@ def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable:
|
|||
raise vol.Invalid('expected dictionary')
|
||||
|
||||
for key in value.keys():
|
||||
try:
|
||||
slug(key)
|
||||
except vol.Invalid:
|
||||
# To ease the breaking change, we allow old slugs for now
|
||||
# Remove after 0.94 or 1.0
|
||||
if re.match(OLD_SLUG_VALIDATION, key):
|
||||
fixed = util_slugify(key)
|
||||
INVALID_SLUGS_FOUND[key] = fixed
|
||||
logging.getLogger(__name__).warning(
|
||||
"Found invalid slug %s, please update with %s. This "
|
||||
"will be come a breaking change.",
|
||||
key, fixed
|
||||
)
|
||||
else:
|
||||
raise
|
||||
slug(key)
|
||||
|
||||
return schema(value)
|
||||
return verify
|
||||
|
@ -656,88 +623,7 @@ def key_dependency(key, dependency):
|
|||
|
||||
|
||||
# Schemas
|
||||
class HASchema(vol.Schema):
|
||||
"""Schema class that allows us to mark PREVENT_EXTRA errors as warnings."""
|
||||
|
||||
def __call__(self, data):
|
||||
"""Override __call__ to mark PREVENT_EXTRA as warning."""
|
||||
try:
|
||||
return super().__call__(data)
|
||||
except vol.Invalid as orig_err:
|
||||
if self.extra != vol.PREVENT_EXTRA:
|
||||
raise
|
||||
|
||||
# orig_error is of type vol.MultipleInvalid (see super __call__)
|
||||
assert isinstance(orig_err, vol.MultipleInvalid)
|
||||
# pylint: disable=no-member
|
||||
# If it fails with PREVENT_EXTRA, try with ALLOW_EXTRA
|
||||
self.extra = vol.ALLOW_EXTRA
|
||||
# In case it still fails the following will raise
|
||||
try:
|
||||
validated = super().__call__(data)
|
||||
finally:
|
||||
self.extra = vol.PREVENT_EXTRA
|
||||
|
||||
# This is a legacy config, print warning
|
||||
extra_key_errs = [err.path[-1] for err in orig_err.errors
|
||||
if err.error_message == 'extra keys not allowed']
|
||||
|
||||
if not extra_key_errs:
|
||||
# This should not happen (all errors should be extra key
|
||||
# errors). Let's raise the original error anyway.
|
||||
raise orig_err
|
||||
|
||||
WHITELIST = [
|
||||
re.compile(CONF_NAME),
|
||||
re.compile(CONF_PLATFORM),
|
||||
re.compile('.*_topic'),
|
||||
]
|
||||
|
||||
msg = "Your configuration contains extra keys " \
|
||||
"that the platform does not support.\n" \
|
||||
"Please remove "
|
||||
submsg = ', '.join('[{}]'.format(err) for err in
|
||||
extra_key_errs)
|
||||
submsg += '. '
|
||||
|
||||
# Add file+line information, if available
|
||||
if hasattr(data, '__config_file__'):
|
||||
submsg += " (See {}, line {}). ".format(
|
||||
data.__config_file__, data.__line__)
|
||||
|
||||
# Add configuration source information, if available
|
||||
if hasattr(data, '__configuration_source__'):
|
||||
submsg += "\nConfiguration source: {}. ".format(
|
||||
data.__configuration_source__)
|
||||
redacted_data = {}
|
||||
|
||||
# Print configuration causing the error, but filter any potentially
|
||||
# sensitive data
|
||||
for k, v in data.items():
|
||||
if (any(regex.match(k) for regex in WHITELIST) or
|
||||
k in extra_key_errs):
|
||||
redacted_data[k] = v
|
||||
else:
|
||||
redacted_data[k] = '<redacted>'
|
||||
submsg += "\nOffending data: {}".format(
|
||||
json.dumps(redacted_data))
|
||||
|
||||
msg += submsg
|
||||
logging.getLogger(__name__).warning(msg)
|
||||
INVALID_EXTRA_KEYS_FOUND.append(submsg)
|
||||
|
||||
# Return legacy validated config
|
||||
return validated
|
||||
|
||||
def extend(self, schema, required=None, extra=None):
|
||||
"""Extend this schema and convert it to HASchema if necessary."""
|
||||
ret = super().extend(schema, required=required, extra=extra)
|
||||
if extra is not None:
|
||||
return ret
|
||||
return HASchema(ret.schema, required=required, extra=self.extra)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = HASchema({
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): string,
|
||||
vol.Optional(CONF_ENTITY_NAMESPACE): string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL): time_period
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue