Fix homekit options being mutated during config_flow/migration (#64003)

pull/63945/head
J. Nick Koston 2022-01-12 12:56:24 -10:00 committed by GitHub
parent f034ea5b4b
commit 1019156899
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 18 additions and 6 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from copy import deepcopy
import ipaddress
import logging
import os
@ -348,8 +349,8 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
@callback
def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: ConfigEntry):
options = dict(entry.options)
data = dict(entry.data)
options = deepcopy(dict(entry.options))
data = deepcopy(dict(entry.data))
modified = False
for importable_option in CONFIG_OPTIONS:
if importable_option not in entry.options and importable_option in entry.data:

View File

@ -2,9 +2,11 @@
from __future__ import annotations
import asyncio
from copy import deepcopy
import random
import re
import string
from typing import Final
import voluptuous as vol
@ -117,7 +119,7 @@ DEFAULT_DOMAINS = [
"water_heater",
]
_EMPTY_ENTITY_FILTER = {
_EMPTY_ENTITY_FILTER: Final = {
CONF_INCLUDE_DOMAINS: [],
CONF_EXCLUDE_DOMAINS: [],
CONF_INCLUDE_ENTITIES: [],
@ -152,7 +154,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None):
"""Choose specific domains in bridge mode."""
if user_input is not None:
entity_filter = _EMPTY_ENTITY_FILTER.copy()
entity_filter = deepcopy(_EMPTY_ENTITY_FILTER)
entity_filter[CONF_INCLUDE_DOMAINS] = user_input[CONF_INCLUDE_DOMAINS]
self.hk_data[CONF_FILTER] = entity_filter
return await self.async_step_pairing()
@ -493,7 +495,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
self.hk_options.update(user_input)
return await self.async_step_include_exclude()
self.hk_options = dict(self.config_entry.options)
self.hk_options = deepcopy(dict(self.config_entry.options))
entity_filter = self.hk_options.get(CONF_FILTER, {})
homekit_mode = self.hk_options.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE)
domains = entity_filter.get(CONF_INCLUDE_DOMAINS, [])

View File

@ -2,9 +2,14 @@
from unittest.mock import patch
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.homekit.const import DOMAIN, SHORT_BRIDGE_NAME
from homeassistant.components.homekit.const import (
CONF_FILTER,
DOMAIN,
SHORT_BRIDGE_NAME,
)
from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_IMPORT
from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.helpers.entityfilter import CONF_INCLUDE_DOMAINS
from homeassistant.setup import async_setup_component
from .util import PATH_HOMEKIT, async_init_entry
@ -347,6 +352,10 @@ async def test_options_flow_exclude_mode_basic(hass, mock_get_source_ip):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "include_exclude"
# Inject garbage to ensure the options data
# is being deep copied and we cannot mutate it in flight
config_entry.options[CONF_FILTER][CONF_INCLUDE_DOMAINS].append("garbage")
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"entities": ["climate.old"], "include_exclude_mode": "exclude"},