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

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

View File

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

View File

@ -2,9 +2,14 @@
from unittest.mock import patch from unittest.mock import patch
from homeassistant import config_entries, data_entry_flow 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.config_entries import SOURCE_IGNORE, SOURCE_IMPORT
from homeassistant.const import CONF_NAME, CONF_PORT from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.helpers.entityfilter import CONF_INCLUDE_DOMAINS
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .util import PATH_HOMEKIT, async_init_entry 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["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "include_exclude" 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( result2 = await hass.config_entries.options.async_configure(
result["flow_id"], result["flow_id"],
user_input={"entities": ["climate.old"], "include_exclude_mode": "exclude"}, user_input={"entities": ["climate.old"], "include_exclude_mode": "exclude"},