Don't expose config or diagnostic entities to cloud (#57771)

pull/57785/head
Erik Montnemery 2021-10-15 18:35:32 +02:00 committed by GitHub
parent 8b33aa3702
commit 0f2b5ea28e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 15 deletions

View File

@ -15,9 +15,14 @@ from homeassistant.components.alexa import (
errors as alexa_errors, errors as alexa_errors,
state_report as alexa_state_report, state_report as alexa_state_report,
) )
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_BAD_REQUEST from homeassistant.const import (
CLOUD_NEVER_EXPOSED_ENTITIES,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
HTTP_BAD_REQUEST,
)
from homeassistant.core import HomeAssistant, callback, split_entity_id from homeassistant.core import HomeAssistant, callback, split_entity_id
from homeassistant.helpers import entity_registry, start from homeassistant.helpers import entity_registry as er, start
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -110,7 +115,7 @@ class AlexaConfig(alexa_config.AbstractConfig):
self._prefs.async_listen_updates(self._async_prefs_updated) self._prefs.async_listen_updates(self._async_prefs_updated)
self.hass.bus.async_listen( self.hass.bus.async_listen(
entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, er.EVENT_ENTITY_REGISTRY_UPDATED,
self._handle_entity_registry_updated, self._handle_entity_registry_updated,
) )
@ -128,13 +133,23 @@ class AlexaConfig(alexa_config.AbstractConfig):
if entity_expose is not None: if entity_expose is not None:
return entity_expose return entity_expose
entity_registry = er.async_get(self.hass)
registry_entry = entity_registry.async_get(entity_id)
if registry_entry:
auxiliary_entity = registry_entry.entity_category in (
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
)
else:
auxiliary_entity = False
default_expose = self._prefs.alexa_default_expose default_expose = self._prefs.alexa_default_expose
# Backwards compat # Backwards compat
if default_expose is None: if default_expose is None:
return True return not auxiliary_entity
return split_entity_id(entity_id)[0] in default_expose return not auxiliary_entity and split_entity_id(entity_id)[0] in default_expose
@callback @callback
def async_invalidate_access_token(self): def async_invalidate_access_token(self):
@ -340,7 +355,7 @@ class AlexaConfig(alexa_config.AbstractConfig):
elif action == "remove": elif action == "remove":
to_remove.append(entity_id) to_remove.append(entity_id)
elif action == "update" and bool( elif action == "update" and bool(
set(event.data["changes"]) & entity_registry.ENTITY_DESCRIBING_ATTRIBUTES set(event.data["changes"]) & er.ENTITY_DESCRIBING_ATTRIBUTES
): ):
to_update.append(entity_id) to_update.append(entity_id)
if "old_entity_id" in event.data: if "old_entity_id" in event.data:

View File

@ -7,9 +7,14 @@ from hass_nabucasa.google_report_state import ErrorResponse
from homeassistant.components.google_assistant.const import DOMAIN as GOOGLE_DOMAIN from homeassistant.components.google_assistant.const import DOMAIN as GOOGLE_DOMAIN
from homeassistant.components.google_assistant.helpers import AbstractConfig from homeassistant.components.google_assistant.helpers import AbstractConfig
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_OK from homeassistant.const import (
CLOUD_NEVER_EXPOSED_ENTITIES,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
HTTP_OK,
)
from homeassistant.core import CoreState, split_entity_id from homeassistant.core import CoreState, split_entity_id
from homeassistant.helpers import entity_registry, start from homeassistant.helpers import entity_registry as er, start
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .const import ( from .const import (
@ -104,7 +109,7 @@ class CloudGoogleConfig(AbstractConfig):
self._prefs.async_listen_updates(self._async_prefs_updated) self._prefs.async_listen_updates(self._async_prefs_updated)
self.hass.bus.async_listen( self.hass.bus.async_listen(
entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, er.EVENT_ENTITY_REGISTRY_UPDATED,
self._handle_entity_registry_updated, self._handle_entity_registry_updated,
) )
@ -126,13 +131,23 @@ class CloudGoogleConfig(AbstractConfig):
if entity_expose is not None: if entity_expose is not None:
return entity_expose return entity_expose
entity_registry = er.async_get(self.hass)
registry_entry = entity_registry.async_get(entity_id)
if registry_entry:
auxiliary_entity = registry_entry.entity_category in (
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
)
else:
auxiliary_entity = False
default_expose = self._prefs.google_default_expose default_expose = self._prefs.google_default_expose
# Backwards compat # Backwards compat
if default_expose is None: if default_expose is None:
return True return not auxiliary_entity
return split_entity_id(entity_id)[0] in default_expose return not auxiliary_entity and split_entity_id(entity_id)[0] in default_expose
@property @property
def agent_user_id(self): def agent_user_id(self):
@ -215,7 +230,7 @@ class CloudGoogleConfig(AbstractConfig):
# Only consider entity registry updates if info relevant for Google has changed # Only consider entity registry updates if info relevant for Google has changed
if event.data["action"] == "update" and not bool( if event.data["action"] == "update" and not bool(
set(event.data["changes"]) & entity_registry.ENTITY_DESCRIBING_ATTRIBUTES set(event.data["changes"]) & er.ENTITY_DESCRIBING_ATTRIBUTES
): ):
return return

View File

@ -8,7 +8,7 @@ from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed, mock_registry
@pytest.fixture() @pytest.fixture()
@ -19,6 +19,23 @@ def cloud_stub():
async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs, cloud_stub): async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs, cloud_stub):
"""Test Alexa config should expose using prefs.""" """Test Alexa config should expose using prefs."""
entity_registry = mock_registry(hass)
entity_entry1 = entity_registry.async_get_or_create(
"switch",
"test",
"switch_config_id",
suggested_object_id="config_switch",
entity_category="config",
)
entity_entry2 = entity_registry.async_get_or_create(
"switch",
"test",
"switch_diagnostic_id",
suggested_object_id="diagnostic_switch",
entity_category="diagnostic",
)
entity_conf = {"should_expose": False} entity_conf = {"should_expose": False}
await cloud_prefs.async_update( await cloud_prefs.async_update(
alexa_entity_configs={"light.kitchen": entity_conf}, alexa_entity_configs={"light.kitchen": entity_conf},
@ -31,11 +48,20 @@ async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs, cloud_stub):
await conf.async_initialize() await conf.async_initialize()
assert not conf.should_expose("light.kitchen") assert not conf.should_expose("light.kitchen")
assert not conf.should_expose(entity_entry1.entity_id)
assert not conf.should_expose(entity_entry2.entity_id)
entity_conf["should_expose"] = True entity_conf["should_expose"] = True
assert conf.should_expose("light.kitchen") assert conf.should_expose("light.kitchen")
# config and diagnostic entities should not be exposed
assert not conf.should_expose(entity_entry1.entity_id)
assert not conf.should_expose(entity_entry2.entity_id)
entity_conf["should_expose"] = None entity_conf["should_expose"] = None
assert conf.should_expose("light.kitchen") assert conf.should_expose("light.kitchen")
# config and diagnostic entities should not be exposed
assert not conf.should_expose(entity_entry1.entity_id)
assert not conf.should_expose(entity_entry2.entity_id)
assert "alexa" not in hass.config.components assert "alexa" not in hass.config.components
await cloud_prefs.async_update( await cloud_prefs.async_update(

View File

@ -11,7 +11,7 @@ from homeassistant.core import CoreState, State
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed, mock_registry
@pytest.fixture @pytest.fixture
@ -215,8 +215,25 @@ async def test_sync_google_on_home_assistant_start(hass, mock_cloud_login, cloud
assert len(mock_sync.mock_calls) == 1 assert len(mock_sync.mock_calls) == 1
async def test_google_config_expose_entity_prefs(mock_conf, cloud_prefs): async def test_google_config_expose_entity_prefs(hass, mock_conf, cloud_prefs):
"""Test Google config should expose using prefs.""" """Test Google config should expose using prefs."""
entity_registry = mock_registry(hass)
entity_entry1 = entity_registry.async_get_or_create(
"switch",
"test",
"switch_config_id",
suggested_object_id="config_switch",
entity_category="config",
)
entity_entry2 = entity_registry.async_get_or_create(
"switch",
"test",
"switch_diagnostic_id",
suggested_object_id="diagnostic_switch",
entity_category="diagnostic",
)
entity_conf = {"should_expose": False} entity_conf = {"should_expose": False}
await cloud_prefs.async_update( await cloud_prefs.async_update(
google_entity_configs={"light.kitchen": entity_conf}, google_entity_configs={"light.kitchen": entity_conf},
@ -224,13 +241,24 @@ async def test_google_config_expose_entity_prefs(mock_conf, cloud_prefs):
) )
state = State("light.kitchen", "on") state = State("light.kitchen", "on")
state_config = State(entity_entry1.entity_id, "on")
state_diagnostic = State(entity_entry2.entity_id, "on")
assert not mock_conf.should_expose(state) assert not mock_conf.should_expose(state)
assert not mock_conf.should_expose(state_config)
assert not mock_conf.should_expose(state_diagnostic)
entity_conf["should_expose"] = True entity_conf["should_expose"] = True
assert mock_conf.should_expose(state) assert mock_conf.should_expose(state)
# config and diagnostic entities should not be exposed
assert not mock_conf.should_expose(state_config)
assert not mock_conf.should_expose(state_diagnostic)
entity_conf["should_expose"] = None entity_conf["should_expose"] = None
assert mock_conf.should_expose(state) assert mock_conf.should_expose(state)
# config and diagnostic entities should not be exposed
assert not mock_conf.should_expose(state_config)
assert not mock_conf.should_expose(state_diagnostic)
await cloud_prefs.async_update( await cloud_prefs.async_update(
google_default_expose=["sensor"], google_default_expose=["sensor"],