mirror of
https://github.com/home-assistant/core.git
synced 2026-10-06 22:38:02 -04:00
Keep secret values out of configuration error messages (#183760)
This commit is contained in:
@@ -42,7 +42,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEVICE_INPUT = "device_input"
|
||||
|
||||
INPUT_PIN_SCHEMA = probatio.Schema({probatio.Required(CONF_PIN, default=""): cv.string})
|
||||
INPUT_PIN_SCHEMA = probatio.Schema(
|
||||
{probatio.Required(probatio.Secret(CONF_PIN), default=""): cv.string}
|
||||
)
|
||||
|
||||
DEFAULT_START_OFF = False
|
||||
|
||||
|
||||
@@ -43,11 +43,13 @@ PIN_SCHEMA = selector.TextSelector(
|
||||
|
||||
PIN_ONLY_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN, default=DEFAULT_PIN): PIN_SCHEMA,
|
||||
probatio.Required(probatio.Secret(CONF_PIN), default=DEFAULT_PIN): PIN_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
REAUTH_SCHEMA = probatio.Schema({probatio.Required(CONF_PIN): PIN_SCHEMA})
|
||||
REAUTH_SCHEMA = probatio.Schema(
|
||||
{probatio.Required(probatio.Secret(CONF_PIN)): PIN_SCHEMA}
|
||||
)
|
||||
|
||||
|
||||
def _user_schema(
|
||||
@@ -63,7 +65,9 @@ def _user_schema(
|
||||
for address, discovery in discoveries.items()
|
||||
}
|
||||
),
|
||||
probatio.Required(CONF_PIN, default=DEFAULT_PIN): PIN_SCHEMA,
|
||||
probatio.Required(
|
||||
probatio.Secret(CONF_PIN), default=DEFAULT_PIN
|
||||
): PIN_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -117,7 +117,11 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_show_form(
|
||||
step_id="2fa",
|
||||
data_schema=probatio.Schema(
|
||||
{probatio.Optional(CONF_PIN): probatio.All(str, probatio.Length(min=1))}
|
||||
{
|
||||
probatio.Optional(probatio.Secret(CONF_PIN)): probatio.All(
|
||||
str, probatio.Length(min=1)
|
||||
)
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -181,7 +181,7 @@ class BraviaTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
step_id="pin",
|
||||
data_schema=probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
@@ -210,7 +210,7 @@ class BraviaTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
step_id="psk",
|
||||
data_schema=probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
|
||||
@@ -30,14 +30,14 @@ USER_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||
probatio.Required(CONF_PORT, default=DEFAULT_PORT): probatio.Port(),
|
||||
probatio.Optional(CONF_PIN, default=DEFAULT_PIN): cv.string,
|
||||
probatio.Optional(probatio.Secret(CONF_PIN), default=DEFAULT_PIN): cv.string,
|
||||
probatio.Required(CONF_TYPE, default=BRIDGE): probatio.In(DEVICE_TYPE_LIST),
|
||||
probatio.Optional(CONF_VEDO_PIN): cv.string,
|
||||
}
|
||||
)
|
||||
STEP_REAUTH_DATA_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): cv.string,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): cv.string,
|
||||
probatio.Optional(CONF_VEDO_PIN): cv.string,
|
||||
}
|
||||
)
|
||||
@@ -239,7 +239,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
probatio.Required(
|
||||
CONF_PORT, default=reconfigure_entry.data[CONF_PORT]
|
||||
): probatio.Port(),
|
||||
probatio.Optional(CONF_PIN): cv.string,
|
||||
probatio.Optional(probatio.Secret(CONF_PIN)): cv.string,
|
||||
probatio.Optional(CONF_VEDO_PIN): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -43,7 +43,9 @@ CONFIG_SCHEMA = probatio.Schema(
|
||||
probatio.Optional(
|
||||
CONF_USER_AGENT, default=DEFAULT_USER_AGENT
|
||||
): cv.string,
|
||||
probatio.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
|
||||
probatio.Optional(
|
||||
probatio.Secret(CONF_PIN), default=DEFAULT_PIN
|
||||
): cv.positive_int,
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN, default="000000"): probatio.All(
|
||||
probatio.Required(probatio.Secret(CONF_PIN), default="000000"): probatio.All(
|
||||
TextSelector(TextSelectorConfig(type=TextSelectorType.NUMBER)),
|
||||
probatio.Length(min=6, max=6),
|
||||
),
|
||||
|
||||
@@ -48,7 +48,7 @@ PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
probatio.Required(CONF_BIN): cv.string,
|
||||
probatio.Required(CONF_USERNAME): cv.string,
|
||||
probatio.Required(CONF_PIN): cv.string,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): cv.string,
|
||||
probatio.Required(CONF_URL): cv.string,
|
||||
probatio.Optional(CONF_NAME): cv.string,
|
||||
probatio.Optional(CONF_ACCOUNTS, default=[]): probatio.EnsureList()(
|
||||
|
||||
@@ -67,7 +67,7 @@ class FumisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
step_id="dhcp_confirm",
|
||||
data_schema=probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): TextSelector(
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
}
|
||||
@@ -104,7 +104,7 @@ class FumisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
probatio.Required(CONF_MAC): TextSelector(
|
||||
TextSelectorConfig(autocomplete="off")
|
||||
),
|
||||
probatio.Required(CONF_PIN): TextSelector(
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class FumisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
step_id="reconfigure",
|
||||
data_schema=probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): TextSelector(
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
}
|
||||
@@ -170,7 +170,7 @@ class FumisFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
step_id="reauth_confirm",
|
||||
data_schema=probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): TextSelector(
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ from .const import DOMAIN, LOGGER
|
||||
|
||||
BLUETOOTH_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
)
|
||||
|
||||
USER_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_ADDRESS): str,
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ class MotionMountFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
step_id="auth",
|
||||
data_schema=probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): probatio.All(
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): probatio.All(
|
||||
int, probatio.Range(min=1, max=9999)
|
||||
),
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ def async_setup_services(
|
||||
probatio.Schema(
|
||||
{
|
||||
probatio.Required(ATTR_DEVICE_ID): cv.string,
|
||||
probatio.Required(CONF_PIN): cv.string,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): cv.string,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
@@ -156,7 +156,9 @@ class PanasonicVieraConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="pairing",
|
||||
data_schema=probatio.Schema({probatio.Required(CONF_PIN): str}),
|
||||
data_schema=probatio.Schema(
|
||||
{probatio.Required(probatio.Secret(CONF_PIN)): str}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class PhilipsJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors: dict[str, str] = {}
|
||||
schema = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ CLOUD_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_USERNAME): str,
|
||||
probatio.Required(CONF_PASSWORD): str,
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
)
|
||||
LOCAL_SCHEMA = probatio.Schema(
|
||||
{
|
||||
probatio.Required(CONF_HOST): str,
|
||||
probatio.Required(CONF_PORT, default=1000): int,
|
||||
probatio.Required(CONF_PIN): str,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): str,
|
||||
}
|
||||
)
|
||||
HA_STATES = [
|
||||
|
||||
@@ -391,7 +391,9 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
step_id="encrypted_pairing",
|
||||
errors=errors,
|
||||
description_placeholders={"device": self._title},
|
||||
data_schema=probatio.Schema({probatio.Required(CONF_PIN): str}),
|
||||
data_schema=probatio.Schema(
|
||||
{probatio.Required(probatio.Secret(CONF_PIN)): str}
|
||||
),
|
||||
)
|
||||
|
||||
@callback
|
||||
@@ -679,5 +681,7 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
step_id="reauth_confirm_encrypted",
|
||||
errors=errors,
|
||||
description_placeholders={"device": reauth_entry.title},
|
||||
data_schema=probatio.Schema({probatio.Required(CONF_PIN): str}),
|
||||
data_schema=probatio.Schema(
|
||||
{probatio.Required(probatio.Secret(CONF_PIN)): str}
|
||||
),
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ from .coordinator import SubaruConfigEntry
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_CONTACT_METHOD = "contact_method"
|
||||
CONF_VALIDATION_CODE = "validation_code"
|
||||
PIN_SCHEMA = probatio.Schema({probatio.Required(CONF_PIN): str})
|
||||
PIN_SCHEMA = probatio.Schema({probatio.Required(probatio.Secret(CONF_PIN)): str})
|
||||
|
||||
|
||||
class SubaruConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
@@ -61,7 +61,7 @@ PLATFORM_SCHEMA = CLIMATE_PLATFORM_SCHEMA.extend(
|
||||
probatio.Coerce(int), probatio.Range(min=1)
|
||||
),
|
||||
probatio.Optional(CONF_USERNAME): cv.string,
|
||||
probatio.Optional(CONF_PIN): cv.string,
|
||||
probatio.Optional(probatio.Secret(CONF_PIN)): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class VenstarConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
probatio.Required(CONF_HOST): str,
|
||||
probatio.Optional(CONF_USERNAME): str,
|
||||
probatio.Optional(CONF_PASSWORD): str,
|
||||
probatio.Optional(CONF_PIN): str,
|
||||
probatio.Optional(probatio.Secret(CONF_PIN)): str,
|
||||
probatio.Optional(CONF_SSL, default=False): bool,
|
||||
}
|
||||
),
|
||||
|
||||
@@ -91,7 +91,11 @@ def _get_pairing_schema(input_dict: dict[str, Any] | None = None) -> probatio.Sc
|
||||
input_dict = {}
|
||||
|
||||
return probatio.Schema(
|
||||
{probatio.Required(CONF_PIN, default=input_dict.get(CONF_PIN, "")): str}
|
||||
{
|
||||
probatio.Required(
|
||||
probatio.Secret(CONF_PIN), default=input_dict.get(CONF_PIN, "")
|
||||
): str
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ DEFAULT_TIMEOUT = 5
|
||||
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
probatio.Required(CONF_HOST): cv.string,
|
||||
probatio.Required(CONF_PIN): cv.string,
|
||||
probatio.Required(probatio.Secret(CONF_PIN)): cv.string,
|
||||
probatio.Optional(CONF_ALLOW_UNREACHABLE, default=True): cv.boolean,
|
||||
probatio.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ from .core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
|
||||
from .core_config import _PACKAGE_DEFINITION_SCHEMA, _PACKAGES_CONFIG_SCHEMA
|
||||
from .exceptions import ConfigValidationError, HomeAssistantError
|
||||
from .helpers import config_validation as cv
|
||||
from .helpers.redact import REDACTED
|
||||
from .helpers.translation import async_get_exception_message
|
||||
from .helpers.typing import ConfigType
|
||||
from .loader import ComponentProtocol, Integration, IntegrationNotFound
|
||||
@@ -497,11 +498,14 @@ def stringify_invalid(
|
||||
output = Exception.__str__(exc)
|
||||
if error_type := exc.error_type:
|
||||
output += " for " + error_type
|
||||
offending_item_summary = repr(_get_by_path(config, exc.path))
|
||||
if len(offending_item_summary) > max_sub_error_length:
|
||||
offending_item_summary = (
|
||||
f"{offending_item_summary[: max_sub_error_length - 3]}..."
|
||||
)
|
||||
if exc.secret:
|
||||
offending_item_summary = REDACTED
|
||||
else:
|
||||
offending_item_summary = repr(_get_by_path(config, exc.path))
|
||||
if len(offending_item_summary) > max_sub_error_length:
|
||||
offending_item_summary = (
|
||||
f"{offending_item_summary[: max_sub_error_length - 3]}..."
|
||||
)
|
||||
return (
|
||||
f"{message_prefix}: {output} '{path}', got {offending_item_summary}"
|
||||
f"{message_suffix}"
|
||||
|
||||
@@ -20,6 +20,7 @@ from homeassistant.const import CONF_PACKAGES, __version__
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigValidationError, HomeAssistantError
|
||||
from homeassistant.helpers import check_config, config_validation as cv
|
||||
from homeassistant.helpers.redact import REDACTED
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import Integration, async_get_integration
|
||||
from homeassistant.util.yaml import SECRET_YAML, load_yaml_dict
|
||||
@@ -1532,6 +1533,33 @@ async def test_stringify_invalid_suggests_close_keys(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key", "expected_value"),
|
||||
[
|
||||
pytest.param(probatio.Required("password"), "'hunter2'", id="plain"),
|
||||
pytest.param(
|
||||
probatio.Required(probatio.Secret("password")), REDACTED, id="secret"
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_stringify_invalid_redacts_a_secret(
|
||||
hass: HomeAssistant, key: probatio.Marker, expected_value: str
|
||||
) -> None:
|
||||
"""Test a key marked secret reports the reason without its value."""
|
||||
schema = probatio.Schema({key: probatio.All(str, probatio.Length(min=12))})
|
||||
config = {"password": "hunter2"}
|
||||
|
||||
with pytest.raises(probatio.MultipleInvalid) as exc_info:
|
||||
schema(config)
|
||||
|
||||
assert config_util.stringify_invalid(
|
||||
hass, exc_info.value.errors[0], "demo", config, None, 500
|
||||
) == (
|
||||
"Invalid config for 'demo': length of value must be at least 12 for "
|
||||
f"dictionary value 'password', got {expected_value}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_dir",
|
||||
["packages", "packages_include_dir_named"],
|
||||
|
||||
Reference in New Issue
Block a user