From 9b91da23c45f3d465a17ac83bcaa5d36916b327f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 26 Feb 2024 11:43:31 +0100 Subject: [PATCH] Improve logging of google_assistant messages (#110637) * Improve logging of google_assistant messages * Add tests * Add test --- homeassistant/components/cloud/client.py | 12 +- .../components/cloud/google_config.py | 12 +- .../components/google_assistant/button.py | 4 +- .../google_assistant/data_redaction.py | 78 +- .../components/google_assistant/helpers.py | 31 +- .../components/google_assistant/http.py | 14 +- .../components/google_assistant/smart_home.py | 39 +- tests/components/cloud/test_google_config.py | 14 + tests/components/google_assistant/__init__.py | 2 +- .../fixtures/data_redaction.json | 1108 +++++++++++++++++ .../google_assistant/test_data_redaction.py | 14 + .../google_assistant/test_smart_home.py | 20 + 12 files changed, 1301 insertions(+), 47 deletions(-) create mode 100644 tests/components/google_assistant/fixtures/data_redaction.json create mode 100644 tests/components/google_assistant/test_data_redaction.py diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 463d290d49c..e569602f944 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -270,13 +270,23 @@ class CloudClient(Interface): """Process cloud google message to client.""" gconf = await self.get_google_config() + msgid: Any = "" + if isinstance(payload, dict): + msgid = payload.get("requestId") + _LOGGER.debug("Received cloud message %s", msgid) + if not self._prefs.google_enabled: return ga.api_disabled_response( # type: ignore[no-any-return, no-untyped-call] payload, gconf.agent_user_id ) return await ga.async_handle_message( # type: ignore[no-any-return, no-untyped-call] - self._hass, gconf, gconf.cloud_user, payload, google_assistant.SOURCE_CLOUD + self._hass, + gconf, + gconf.agent_user_id, + gconf.cloud_user, + payload, + google_assistant.SOURCE_CLOUD, ) async def async_webhook_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 12d3453a53c..bda2412b476 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -330,10 +330,20 @@ class CloudGoogleConfig(AbstractConfig): """Return if we have a Agent User Id registered.""" return len(self.async_get_agent_users()) > 0 - def get_agent_user_id(self, context: Any) -> str: + def get_agent_user_id_from_context(self, context: Any) -> str: """Get agent user ID making request.""" return self.agent_user_id + def get_agent_user_id_from_webhook(self, webhook_id: str) -> str | None: + """Map webhook ID to a Google agent user ID. + + Return None if no agent user id is found for the webhook_id. + """ + if webhook_id != self._prefs.google_local_webhook_id: + return None + + return self.agent_user_id + def _2fa_disabled_legacy(self, entity_id: str) -> bool | None: """If an entity should be checked for 2FA.""" entity_configs = self._prefs.google_entity_configs diff --git a/homeassistant/components/google_assistant/button.py b/homeassistant/components/google_assistant/button.py index 94c97357b85..139e3032f14 100644 --- a/homeassistant/components/google_assistant/button.py +++ b/homeassistant/components/google_assistant/button.py @@ -51,7 +51,9 @@ class SyncButton(ButtonEntity): async def async_press(self) -> None: """Press the button.""" assert self._context - agent_user_id = self._google_config.get_agent_user_id(self._context) + agent_user_id = self._google_config.get_agent_user_id_from_context( + self._context + ) result = await self._google_config.async_sync_entities(agent_user_id) if result != 200: raise HomeAssistantError( diff --git a/homeassistant/components/google_assistant/data_redaction.py b/homeassistant/components/google_assistant/data_redaction.py index ae6fe5f7098..6a187113bb9 100644 --- a/homeassistant/components/google_assistant/data_redaction.py +++ b/homeassistant/components/google_assistant/data_redaction.py @@ -2,35 +2,77 @@ from __future__ import annotations from collections.abc import Callable +from functools import partial from typing import Any from homeassistant.core import callback -from homeassistant.helpers.redact import async_redact_data, partial_redact +from homeassistant.helpers.redact import REDACTED, async_redact_data, partial_redact -REQUEST_MSG_TO_REDACT: dict[str, Callable[[str], str]] = { +GOOGLE_MSG_TO_REDACT: dict[str, Callable[[str], str]] = { "agentUserId": partial_redact, "uuid": partial_redact, "webhookId": partial_redact, } -RESPONSE_MSG_TO_REDACT = REQUEST_MSG_TO_REDACT | {id: partial_redact} +MDNS_TXT_TO_REDACT = [ + "location_name", + "uuid", + "external_url", + "internal_url", + "base_url", +] -SYNC_MSG_TO_REDACT = REQUEST_MSG_TO_REDACT + +def partial_redact_list_item(x: list[str], to_redact: list[str]) -> list[str]: + """Redact only specified string in a list of strings.""" + if not isinstance(x, list): + return x + result = [] + for itm in x: + if not isinstance(itm, str): + result.append(itm) + continue + for pattern in to_redact: + if itm.startswith(pattern): + result.append(f"{pattern}={REDACTED}") + break + else: + result.append(itm) + return result + + +def partial_redact_txt_list(x: list[str]) -> list[str]: + """Redact strings from home-assistant mDNS txt records.""" + return partial_redact_list_item(x, MDNS_TXT_TO_REDACT) + + +def partial_redact_txt_dict(x: dict[str, str]) -> dict[str, str]: + """Redact strings from home-assistant mDNS txt records.""" + if not isinstance(x, dict): + return x + result = {} + for k, v in x.items(): + result[k] = REDACTED if k in MDNS_TXT_TO_REDACT else v + return result + + +def partial_redact_string(x: str, to_redact: str) -> str: + """Redact only a specified string.""" + if x == to_redact: + return partial_redact(x) + return x @callback -def async_redact_request_msg(msg: dict[str, Any]) -> dict[str, Any]: +def async_redact_msg(msg: dict[str, Any], agent_user_id: str) -> dict[str, Any]: """Mask sensitive data in message.""" - return async_redact_data(msg, REQUEST_MSG_TO_REDACT) - - -@callback -def async_redact_response_msg(msg: dict[str, Any]) -> dict[str, Any]: - """Mask sensitive data in message.""" - return async_redact_data(msg, RESPONSE_MSG_TO_REDACT) - - -@callback -def async_redact_sync_msg(msg: dict[str, Any]) -> dict[str, Any]: - """Mask sensitive data in message.""" - return async_redact_data(msg, SYNC_MSG_TO_REDACT) + return async_redact_data( + msg, + GOOGLE_MSG_TO_REDACT + | { + "data": partial_redact_txt_list, + "id": partial(partial_redact_string, to_redact=agent_user_id), + "texts": partial_redact_txt_list, + "txt": partial_redact_txt_dict, + }, + ) diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 40a71e8eb4f..28479fd1e97 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -46,7 +46,7 @@ from .const import ( NOT_EXPOSE_LOCAL, SOURCE_LOCAL, ) -from .data_redaction import async_redact_request_msg, async_redact_response_msg +from .data_redaction import async_redact_msg from .error import SmartHomeError SYNC_DELAY = 15 @@ -175,9 +175,16 @@ class AbstractConfig(ABC): """Return the webhook ID to be used for actions for a given agent user id via the local SDK.""" @abstractmethod - def get_agent_user_id(self, context): + def get_agent_user_id_from_context(self, context): """Get agent user ID from context.""" + @abstractmethod + def get_agent_user_id_from_webhook(self, webhook_id): + """Map webhook ID to a Google agent user ID. + + Return None if no agent user id is found for the webhook_id. + """ + @abstractmethod def should_expose(self, state) -> bool: """Return if entity should be exposed.""" @@ -409,14 +416,17 @@ class AbstractConfig(ABC): payload = await request.json() if _LOGGER.isEnabledFor(logging.DEBUG): + msgid = "" + if isinstance(payload, dict): + msgid = payload.get("requestId") _LOGGER.debug( - "Received local message from %s (JS %s):\n%s\n", + "Received local message %s from %s (JS %s)", + msgid, request.remote, request.headers.get("HA-Cloud-Version", "unknown"), - pprint.pformat(async_redact_request_msg(payload)), ) - if (agent_user_id := self.get_local_user_id(webhook_id)) is None: + if (agent_user_id := self.get_agent_user_id_from_webhook(webhook_id)) is None: # No agent user linked to this webhook, means that the user has somehow unregistered # removing webhook and stopping processing of this request. _LOGGER.error( @@ -425,7 +435,7 @@ class AbstractConfig(ABC): " found:\n%s\n" ), partial_redact(webhook_id), - pprint.pformat(async_redact_request_msg(payload)), + pprint.pformat(async_redact_msg(payload, agent_user_id)), ) webhook.async_unregister(self.hass, webhook_id) return None @@ -439,15 +449,16 @@ class AbstractConfig(ABC): self.hass, self, agent_user_id, + self.get_local_user_id(webhook_id), payload, SOURCE_LOCAL, ) if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug( - "Responding to local message:\n%s\n", - pprint.pformat(async_redact_response_msg(result)), - ) + if isinstance(payload, dict): + _LOGGER.debug("Responding to local message %s", msgid) + else: + _LOGGER.debug("Empty response to local message %s", msgid) return json_response(result) diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 0eaed0ca48a..0d75a1bede7 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -144,10 +144,21 @@ class GoogleConfig(AbstractConfig): return data[STORE_GOOGLE_LOCAL_WEBHOOK_ID] return None - def get_agent_user_id(self, context): + def get_agent_user_id_from_context(self, context): """Get agent user ID making request.""" return context.user_id + def get_agent_user_id_from_webhook(self, webhook_id): + """Map webhook ID to a Google agent user ID. + + Return None if no agent user id is found for the webhook_id. + """ + for agent_user_id, agent_user_data in self._store.agent_user_ids.items(): + if agent_user_data[STORE_GOOGLE_LOCAL_WEBHOOK_ID] == webhook_id: + return agent_user_id + + return None + def should_expose(self, state) -> bool: """Return if entity should be exposed.""" expose_by_default = self._config.get(CONF_EXPOSE_BY_DEFAULT) @@ -372,6 +383,7 @@ class GoogleAssistantView(HomeAssistantView): request.app["hass"], self.config, request["hass_user"].id, + request["hass_user"].id, message, SOURCE_CLOUD, ) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 19f097151d7..8172d0ca92d 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -3,6 +3,7 @@ import asyncio from collections.abc import Callable, Coroutine from itertools import product import logging +import pprint from typing import Any from homeassistant.const import ATTR_ENTITY_ID, __version__ @@ -18,11 +19,7 @@ from .const import ( EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED, ) -from .data_redaction import ( - async_redact_request_msg, - async_redact_response_msg, - async_redact_sync_msg, -) +from .data_redaction import async_redact_msg from .error import SmartHomeError from .helpers import GoogleEntity, RequestData, async_get_entities @@ -38,19 +35,35 @@ HANDLERS: Registry[ _LOGGER = logging.getLogger(__name__) -async def async_handle_message(hass, config, user_id, message, source): +async def async_handle_message( + hass, config, agent_user_id, local_user_id, message, source +): """Handle incoming API messages.""" + if _LOGGER.isEnabledFor(logging.DEBUG): + _LOGGER.debug( + "Processing message:\n%s", + pprint.pformat(async_redact_msg(message, agent_user_id)), + ) + data = RequestData( - config, user_id, source, message["requestId"], message.get("devices") + config, local_user_id, source, message["requestId"], message.get("devices") ) response = await _process(hass, data, message) + if _LOGGER.isEnabledFor(logging.DEBUG): + if response: + _LOGGER.debug( + "Response:\n%s", + pprint.pformat(async_redact_msg(response["payload"], agent_user_id)), + ) + else: + _LOGGER.debug("Empty response") if response and "errorCode" in response["payload"]: _LOGGER.error( - "Error handling message %s: %s", - async_redact_request_msg(message), - async_redact_response_msg(response["payload"]), + "Error handling message\n:%s\nResponse:\n%s", + pprint.pformat(async_redact_msg(message, agent_user_id)), + pprint.pformat(async_redact_msg(response["payload"], agent_user_id)), ) return response @@ -121,14 +134,12 @@ async def async_devices_sync( context=data.context, ) - agent_user_id = data.config.get_agent_user_id(data.context) + agent_user_id = data.config.get_agent_user_id_from_context(data.context) await data.config.async_connect_agent_user(agent_user_id) devices = await async_devices_sync_response(hass, data.config, agent_user_id) response = create_sync_response(agent_user_id, devices) - _LOGGER.debug("Syncing entities response: %s", async_redact_sync_msg(response)) - return response @@ -299,7 +310,7 @@ async def async_devices_identify( """ return { "device": { - "id": data.config.get_agent_user_id(data.context), + "id": data.config.get_agent_user_id_from_context(data.context), "isLocalOnly": True, "isProxy": True, "deviceInfo": { diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 9f1e7a22dc4..77648353f67 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -841,3 +841,17 @@ async def test_google_config_migrate_expose_entity_prefs_default( assert async_get_entity_settings(hass, water_heater.entity_id) == { "cloud.google_assistant": {"should_expose": False} } + + +async def test_google_config_get_agent_user_id( + hass: HomeAssistant, mock_cloud_login, cloud_prefs +) -> None: + """Test overridden get_agent_user_id_from_webhook method.""" + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + assert ( + config.get_agent_user_id_from_webhook(cloud_prefs.google_local_webhook_id) + == config.agent_user_id + ) + assert config.get_agent_user_id_from_webhook("other_id") != config.agent_user_id diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 6ec35c68ee5..e24e6b740d3 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -54,7 +54,7 @@ class MockConfig(http.GoogleConfig): """Return secure devices pin.""" return self._entity_config - def get_agent_user_id(self, context): + def get_agent_user_id_from_context(self, context): """Get agent user ID making request.""" return context.user_id diff --git a/tests/components/google_assistant/fixtures/data_redaction.json b/tests/components/google_assistant/fixtures/data_redaction.json new file mode 100644 index 00000000000..19d4f461b1f --- /dev/null +++ b/tests/components/google_assistant/fixtures/data_redaction.json @@ -0,0 +1,1108 @@ +[ + { + "raw": {}, + "redacted": {} + }, + { + "raw": { + "inputs": [{ "intent": "action.devices.SYNC" }], + "requestId": "12705793367478976640" + }, + "redacted": { + "inputs": [{ "intent": "action.devices.SYNC" }], + "requestId": "12705793367478976640" + } + }, + { + "raw": { + "agentUserId": "333dee20-1234-1234-1234-2225a0d70d4c", + "devices": [ + { + "id": "light.test", + "name": { "name": "Test" }, + "attributes": { + "colorModel": "hsv", + "colorTemperatureRange": { + "temperatureMaxK": 6535, + "temperatureMinK": 2000 + } + }, + "traits": [ + "action.devices.traits.Brightness", + "action.devices.traits.OnOff", + "action.devices.traits.ColorSetting" + ], + "willReportState": true, + "type": "action.devices.types.LIGHT", + "otherDeviceIds": [{ "deviceId": "light.test" }], + "customData": { + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8", + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64" + } + }, + { + "id": "climate.ecobee", + "name": { "name": "Ecobee" }, + "attributes": { + "thermostatTemperatureUnit": "C", + "thermostatTemperatureRange": { + "minThresholdCelsius": 7, + "maxThresholdCelsius": 35 + }, + "availableThermostatModes": [ + "off", + "cool", + "heatcool", + "auto", + "dry", + "fan-only", + "eco", + "on" + ], + "reversible": false, + "availableFanSpeeds": { + "speeds": [ + { + "speed_name": "on_low", + "speed_values": [ + { "speed_synonym": ["on_low"], "lang": "en" } + ] + }, + { + "speed_name": "on_high", + "speed_values": [ + { "speed_synonym": ["on_high"], "lang": "en" } + ] + }, + { + "speed_name": "auto_low", + "speed_values": [ + { "speed_synonym": ["auto_low"], "lang": "en" } + ] + }, + { + "speed_name": "auto_high", + "speed_values": [ + { "speed_synonym": ["auto_high"], "lang": "en" } + ] + }, + { + "speed_name": "off", + "speed_values": [{ "speed_synonym": ["off"], "lang": "en" }] + } + ], + "ordered": true + } + }, + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.TemperatureSetting", + "action.devices.traits.FanSpeed" + ], + "willReportState": true, + "type": "action.devices.types.THERMOSTAT", + "roomHint": "Living Room" + }, + { + "id": "fan.living_room_fan", + "name": { "name": "Living Room Fan" }, + "attributes": { + "reversible": true, + "supportsFanSpeedPercent": true, + "availableFanSpeeds": { + "speeds": [ + { + "speed_name": "1/3", + "speed_values": [ + { + "speed_synonym": ["Low", "Min", "Slow", "1"], + "lang": "en" + } + ] + }, + { + "speed_name": "2/3", + "speed_values": [ + { "speed_synonym": ["Medium", "2"], "lang": "en" } + ] + }, + { + "speed_name": "3/3", + "speed_values": [ + { + "speed_synonym": ["High", "Max", "Fast", "3"], + "lang": "en" + } + ] + } + ], + "ordered": true + } + }, + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.FanSpeed" + ], + "willReportState": true, + "type": "action.devices.types.FAN", + "otherDeviceIds": [{ "deviceId": "fan.living_room_fan" }], + "customData": { + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8", + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64" + } + }, + { + "id": "light.ceiling_lights", + "name": { "name": "Ceiling Lights" }, + "attributes": { + "colorModel": "hsv", + "colorTemperatureRange": { + "temperatureMaxK": 6535, + "temperatureMinK": 2000 + } + }, + "traits": [ + "action.devices.traits.Brightness", + "action.devices.traits.OnOff", + "action.devices.traits.ColorSetting" + ], + "willReportState": true, + "type": "action.devices.types.LIGHT", + "otherDeviceIds": [{ "deviceId": "light.ceiling_lights" }], + "customData": { + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8", + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64" + } + } + ] + }, + "redacted": { + "agentUserId": "333d***0d4c", + "devices": [ + { + "id": "light.test", + "name": { "name": "Test" }, + "attributes": { + "colorModel": "hsv", + "colorTemperatureRange": { + "temperatureMaxK": 6535, + "temperatureMinK": 2000 + } + }, + "traits": [ + "action.devices.traits.Brightness", + "action.devices.traits.OnOff", + "action.devices.traits.ColorSetting" + ], + "willReportState": true, + "type": "action.devices.types.LIGHT", + "otherDeviceIds": [{ "deviceId": "light.test" }], + "customData": { + "webhookId": "0ed9***cbf8", + "httpPort": 8123, + "uuid": "6b45***aa64" + } + }, + { + "id": "climate.ecobee", + "name": { "name": "Ecobee" }, + "attributes": { + "thermostatTemperatureUnit": "C", + "thermostatTemperatureRange": { + "minThresholdCelsius": 7, + "maxThresholdCelsius": 35 + }, + "availableThermostatModes": [ + "off", + "cool", + "heatcool", + "auto", + "dry", + "fan-only", + "eco", + "on" + ], + "reversible": false, + "availableFanSpeeds": { + "speeds": [ + { + "speed_name": "on_low", + "speed_values": [ + { "speed_synonym": ["on_low"], "lang": "en" } + ] + }, + { + "speed_name": "on_high", + "speed_values": [ + { "speed_synonym": ["on_high"], "lang": "en" } + ] + }, + { + "speed_name": "auto_low", + "speed_values": [ + { "speed_synonym": ["auto_low"], "lang": "en" } + ] + }, + { + "speed_name": "auto_high", + "speed_values": [ + { "speed_synonym": ["auto_high"], "lang": "en" } + ] + }, + { + "speed_name": "off", + "speed_values": [{ "speed_synonym": ["off"], "lang": "en" }] + } + ], + "ordered": true + } + }, + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.TemperatureSetting", + "action.devices.traits.FanSpeed" + ], + "willReportState": true, + "type": "action.devices.types.THERMOSTAT", + "roomHint": "Living Room" + }, + { + "id": "fan.living_room_fan", + "name": { "name": "Living Room Fan" }, + "attributes": { + "reversible": true, + "supportsFanSpeedPercent": true, + "availableFanSpeeds": { + "speeds": [ + { + "speed_name": "1/3", + "speed_values": [ + { + "speed_synonym": ["Low", "Min", "Slow", "1"], + "lang": "en" + } + ] + }, + { + "speed_name": "2/3", + "speed_values": [ + { "speed_synonym": ["Medium", "2"], "lang": "en" } + ] + }, + { + "speed_name": "3/3", + "speed_values": [ + { + "speed_synonym": ["High", "Max", "Fast", "3"], + "lang": "en" + } + ] + } + ], + "ordered": true + } + }, + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.FanSpeed" + ], + "willReportState": true, + "type": "action.devices.types.FAN", + "otherDeviceIds": [{ "deviceId": "fan.living_room_fan" }], + "customData": { + "webhookId": "0ed9***cbf8", + "httpPort": 8123, + "uuid": "6b45***aa64" + } + }, + { + "id": "light.ceiling_lights", + "name": { "name": "Ceiling Lights" }, + "attributes": { + "colorModel": "hsv", + "colorTemperatureRange": { + "temperatureMaxK": 6535, + "temperatureMinK": 2000 + } + }, + "traits": [ + "action.devices.traits.Brightness", + "action.devices.traits.OnOff", + "action.devices.traits.ColorSetting" + ], + "willReportState": true, + "type": "action.devices.types.LIGHT", + "otherDeviceIds": [{ "deviceId": "light.ceiling_lights" }], + "customData": { + "webhookId": "0ed9***cbf8", + "httpPort": 8123, + "uuid": "6b45***aa64" + } + } + ] + } + }, + { + "raw": { + "requestId": "2B1B68D7A25F1F70658C45E7B9C7E8A3", + "inputs": [ + { + "intent": "action.devices.IDENTIFY", + "payload": { + "device": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "name": "home-2", + "type": "home-assistant", + "protocol": "tcp", + "data": [ + "location_name=Home", + "uuid=6b45123412341234123412341234aa64", + "version=2024.3.0.dev0", + "external_url=https://hass.my-domain.com", + "internal_url=http://192.168.0.5:8123", + "base_url=https://hass.my-domain.com", + "requires_api_password=True" + ], + "txt": { + "location_name": "Home", + "uuid": "6b45123412341234123412341234aa64", + "version": "2024.3.0.dev0", + "external_url": "https://hass.my-domain.com", + "internal_url": "http://192.168.0.5:8123", + "base_url": "https://hass.my-domain.com", + "requires_api_password": "True" + } + } + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + } + ] + }, + "redacted": { + "requestId": "2B1B68D7A25F1F70658C45E7B9C7E8A3", + "inputs": [ + { + "intent": "action.devices.IDENTIFY", + "payload": { + "device": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "name": "home-2", + "type": "home-assistant", + "protocol": "tcp", + "data": [ + "location_name=**REDACTED**", + "uuid=**REDACTED**", + "version=2024.3.0.dev0", + "external_url=**REDACTED**", + "internal_url=**REDACTED**", + "base_url=**REDACTED**", + "requires_api_password=True" + ], + "txt": { + "location_name": "**REDACTED**", + "uuid": "**REDACTED**", + "version": "2024.3.0.dev0", + "external_url": "**REDACTED**", + "internal_url": "**REDACTED**", + "base_url": "**REDACTED**", + "requires_api_password": "True" + } + } + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + } + ] + } + }, + { + "raw": { + "device": { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "isLocalOnly": true, + "isProxy": true, + "deviceInfo": { + "hwVersion": "UNKNOWN_HW_VERSION", + "manufacturer": "Home Assistant", + "model": "Home Assistant", + "swVersion": "2024.3.0.dev0" + } + } + }, + "redacted": { + "device": { + "id": "333d***0d4c", + "isLocalOnly": true, + "isProxy": true, + "deviceInfo": { + "hwVersion": "UNKNOWN_HW_VERSION", + "manufacturer": "Home Assistant", + "model": "Home Assistant", + "swVersion": "2024.3.0.dev0" + } + } + } + }, + { + "raw": { + "requestId": "EB19D860FE6503EFD3A783AE040D7339", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": [ + "location_name=Home", + "uuid=6b45123412341234123412341234aa64", + "version=2024.3.0.dev0", + "external_url=https://hass.my-domain.com", + "internal_url=http://192.168.0.5:8123", + "base_url=https://hass.my-domain.com", + "requires_api_password=True" + ] + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + } + ] + }, + "redacted": { + "requestId": "EB19D860FE6503EFD3A783AE040D7339", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333d***0d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { + "id": "333d***0d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": [ + "location_name=**REDACTED**", + "uuid=**REDACTED**", + "version=2024.3.0.dev0", + "external_url=**REDACTED**", + "internal_url=**REDACTED**", + "base_url=**REDACTED**", + "requires_api_password=True" + ] + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + } + ] + } + }, + { + "raw": { + "devices": [ + { "verificationId": "light.test" }, + { "verificationId": "fan.living_room_fan" }, + { "verificationId": "light.ceiling_lights" } + ] + }, + "redacted": { + "devices": [ + { "verificationId": "light.test" }, + { "verificationId": "fan.living_room_fan" }, + { "verificationId": "light.ceiling_lights" } + ] + } + }, + { + "raw": { + "requestId": "3961722FFD1DEB36C4FFC81F919747D6", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": [ + "location_name=Home", + "uuid=6b45123412341234123412341234aa64", + "version=2024.3.0.dev0", + "external_url=https://hass.my-domain.com", + "internal_url=http://192.168.0.5:8123", + "base_url=https://hass.my-domain.com", + "requires_api_password=True" + ] + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + } + ] + }, + "redacted": { + "requestId": "3961722FFD1DEB36C4FFC81F919747D6", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333d***0d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { + "id": "333d***0d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": [ + "location_name=**REDACTED**", + "uuid=**REDACTED**", + "version=2024.3.0.dev0", + "external_url=**REDACTED**", + "internal_url=**REDACTED**", + "base_url=**REDACTED**", + "requires_api_password=True" + ] + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + } + ] + } + }, + { + "raw": { + "requestId": "2B1B68D7A25F1F70658C45E7B9C7E8A3", + "inputs": [ + { + "intent": "action.devices.IDENTIFY", + "payload": { + "device": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "name": "home-2", + "type": "home-assistant", + "protocol": "tcp", + "data": [ + "location_name=Home", + "uuid=6b45123412341234123412341234aa64", + "version=2024.3.0.dev0", + "external_url=https://hass.my-domain.com", + "internal_url=http://192.168.0.5:8123", + "base_url=https://hass.my-domain.com", + "requires_api_password=True" + ], + "txt": "unexpected" + } + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + } + ] + }, + "redacted": { + "requestId": "2B1B68D7A25F1F70658C45E7B9C7E8A3", + "inputs": [ + { + "intent": "action.devices.IDENTIFY", + "payload": { + "device": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "name": "home-2", + "type": "home-assistant", + "protocol": "tcp", + "data": [ + "location_name=**REDACTED**", + "uuid=**REDACTED**", + "version=2024.3.0.dev0", + "external_url=**REDACTED**", + "internal_url=**REDACTED**", + "base_url=**REDACTED**", + "requires_api_password=True" + ], + "txt": "unexpected" + } + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + } + ] + } + }, + { + "raw": { + "requestId": "3961722FFD1DEB36C4FFC81F919747D6", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": "unexpected" + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + } + ] + }, + "redacted": { + "requestId": "3961722FFD1DEB36C4FFC81F919747D6", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333d***0d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { + "id": "333d***0d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": "unexpected" + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + } + ] + } + }, + { + "raw": { + "requestId": "3961722FFD1DEB36C4FFC81F919747D6", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { + "id": "333dee20-1234-1234-1234-2225a0d70d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": ["unexpected", 123, 234] + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45123412341234123412341234aa64", + "webhookId": "0ed912341234123412341234123412341234123412341234123412341234cbf8" + } + } + ] + }, + "redacted": { + "requestId": "3961722FFD1DEB36C4FFC81F919747D6", + "inputs": [ + { + "intent": "action.devices.REACHABLE_DEVICES", + "payload": { + "device": { + "id": "333d***0d4c", + "customData": {}, + "proxyData": {} + }, + "structureData": {} + } + } + ], + "devices": [ + { + "id": "light.ceiling_lights", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { + "id": "333d***0d4c", + "customData": {}, + "radioType": "WIFI", + "scanData": { + "mdnsScanData": { + "serviceName": "home-2._home-assistant._tcp.local", + "texts": ["unexpected", 123, 234] + } + } + }, + { + "id": "light.test", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + }, + { "id": "climate.ecobee", "customData": {} }, + { + "id": "fan.living_room_fan", + "customData": { + "httpPort": 8123, + "uuid": "6b45***aa64", + "webhookId": "0ed9***cbf8" + } + } + ] + } + } +] diff --git a/tests/components/google_assistant/test_data_redaction.py b/tests/components/google_assistant/test_data_redaction.py new file mode 100644 index 00000000000..86b15782fe5 --- /dev/null +++ b/tests/components/google_assistant/test_data_redaction.py @@ -0,0 +1,14 @@ +"""Test data redaction helpers.""" +import json + +from homeassistant.components.google_assistant.data_redaction import async_redact_msg + +from tests.common import load_fixture + + +def test_redact_msg(): + """Test async_redact_msg.""" + messages = json.loads(load_fixture("data_redaction.json", "google_assistant")) + agent_user_id = "333dee20-1234-1234-1234-2225a0d70d4c" + for item in messages: + assert async_redact_msg(item["raw"], agent_user_id) == item["redacted"] diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 9063a8977f6..587d89a1d8a 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -84,6 +84,7 @@ async def test_async_handle_message(hass: HomeAssistant) -> None: hass, config, "test-agent", + "test-agent", { "requestId": REQ_ID, "inputs": [ @@ -104,6 +105,7 @@ async def test_async_handle_message(hass: HomeAssistant) -> None: hass, config, "test-agent", + "test-agent", { "requestId": REQ_ID, "inputs": [ @@ -169,6 +171,7 @@ async def test_sync_message(hass: HomeAssistant, registries) -> None: hass, config, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -299,6 +302,7 @@ async def test_sync_in_area(area_on_device, hass: HomeAssistant, registries) -> hass, config, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -407,6 +411,7 @@ async def test_query_message(hass: HomeAssistant) -> None: hass, BASIC_CONFIG, "test-agent", + "test-agent", { "requestId": REQ_ID, "inputs": [ @@ -494,6 +499,7 @@ async def test_execute( hass, MockConfig(should_report_state=report_state), None, + None, { "requestId": REQ_ID, "inputs": [ @@ -654,6 +660,7 @@ async def test_execute_times_out( hass, MockConfig(should_report_state=report_state), None, + None, { "requestId": REQ_ID, "inputs": [ @@ -800,6 +807,7 @@ async def test_raising_error_trait(hass: HomeAssistant) -> None: hass, BASIC_CONFIG, "test-agent", + "test-agent", { "requestId": REQ_ID, "inputs": [ @@ -891,6 +899,7 @@ async def test_unavailable_state_does_sync(hass: HomeAssistant) -> None: hass, BASIC_CONFIG, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -984,6 +993,7 @@ async def test_device_class_switch( hass, BASIC_CONFIG, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -1033,6 +1043,7 @@ async def test_device_class_binary_sensor( hass, BASIC_CONFIG, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -1086,6 +1097,7 @@ async def test_device_class_cover( hass, BASIC_CONFIG, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -1133,6 +1145,7 @@ async def test_device_media_player( hass, BASIC_CONFIG, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -1171,6 +1184,7 @@ async def test_query_disconnect(hass: HomeAssistant) -> None: hass, config, "test-agent", + "test-agent", {"inputs": [{"intent": "action.devices.DISCONNECT"}], "requestId": REQ_ID}, const.SOURCE_CLOUD, ) @@ -1198,6 +1212,7 @@ async def test_trait_execute_adding_query_data(hass: HomeAssistant) -> None: hass, BASIC_CONFIG, None, + None, { "requestId": REQ_ID, "inputs": [ @@ -1256,6 +1271,7 @@ async def test_identify(hass: HomeAssistant) -> None: hass, BASIC_CONFIG, user_agent_id, + user_agent_id, { "requestId": REQ_ID, "inputs": [ @@ -1344,6 +1360,7 @@ async def test_reachable_devices(hass: HomeAssistant) -> None: hass, config, user_agent_id, + user_agent_id, { "requestId": REQ_ID, "inputs": [ @@ -1430,6 +1447,7 @@ async def test_sync_message_recovery( hass, BASIC_CONFIG, "test-agent", + "test-agent", {"requestId": REQ_ID, "inputs": [{"intent": "action.devices.SYNC"}]}, const.SOURCE_CLOUD, ) @@ -1490,6 +1508,7 @@ async def test_query_recover( hass, BASIC_CONFIG, "test-agent", + "test-agent", { "requestId": REQ_ID, "inputs": [ @@ -1531,6 +1550,7 @@ async def test_proxy_selected( hass, BASIC_CONFIG, "test-agent", + "test-agent", { "requestId": REQ_ID, "inputs": [