diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index 58095340146..527e51b5390 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -15,6 +15,9 @@ from homeassistant.helpers import aiohttp_client from homeassistant.helpers.storage import Store from homeassistant.util import dt as dt_util +from .const import STORAGE_ACCESS_TOKEN, STORAGE_REFRESH_TOKEN +from .diagnostics import async_redact_lwa_params + _LOGGER = logging.getLogger(__name__) LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token" @@ -24,8 +27,6 @@ PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300 STORAGE_KEY = "alexa_auth" STORAGE_VERSION = 1 STORAGE_EXPIRE_TIME = "expire_time" -STORAGE_ACCESS_TOKEN = "access_token" -STORAGE_REFRESH_TOKEN = "refresh_token" class Auth: @@ -56,7 +57,7 @@ class Auth: } _LOGGER.debug( "Calling LWA to get the access token (first time), with: %s", - json.dumps(lwa_params), + json.dumps(async_redact_lwa_params(lwa_params)), ) return await self._async_request_new_token(lwa_params) @@ -133,7 +134,7 @@ class Auth: return None response_json = await response.json() - _LOGGER.debug("LWA response body : %s", response_json) + _LOGGER.debug("LWA response body : %s", async_redact_lwa_params(response_json)) access_token: str = response_json["access_token"] refresh_token: str = response_json["refresh_token"] diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index f71bc091106..abdef0cb566 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -90,6 +90,9 @@ API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"} # we add PRESET_MODE_NA if a fan / humidifier has only one preset_mode PRESET_MODE_NA = "-" +STORAGE_ACCESS_TOKEN = "access_token" +STORAGE_REFRESH_TOKEN = "refresh_token" + class Cause: """Possible causes for property changes. diff --git a/homeassistant/components/alexa/diagnostics.py b/homeassistant/components/alexa/diagnostics.py new file mode 100644 index 00000000000..54233a0f432 --- /dev/null +++ b/homeassistant/components/alexa/diagnostics.py @@ -0,0 +1,34 @@ +"""Diagnostics helpers for Alexa.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import callback + +STORAGE_ACCESS_TOKEN = "access_token" +STORAGE_REFRESH_TOKEN = "refresh_token" + +TO_REDACT_LWA = { + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + STORAGE_ACCESS_TOKEN, + STORAGE_REFRESH_TOKEN, +} + +TO_REDACT_AUTH = {"correlationToken", "token"} + + +@callback +def async_redact_lwa_params(lwa_params: dict[str, str]) -> dict[str, str]: + """Redact lwa_params.""" + return async_redact_data(lwa_params, TO_REDACT_LWA) + + +@callback +def async_redact_auth_data(mapping: Mapping[Any, Any]) -> dict[str, str]: + """React auth data.""" + return async_redact_data(mapping, TO_REDACT_AUTH) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 5613da52db5..68702bc0533 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -144,7 +144,6 @@ async def async_api_accept_grant( Async friendly. """ auth_code: str = directive.payload["grant"]["code"] - _LOGGER.debug("AcceptGrant code: %s", auth_code) if config.supports_auth: await config.async_accept_grant(auth_code) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index a8101896116..88f66e93fc1 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -25,6 +25,7 @@ from .const import ( CONF_LOCALE, EVENT_ALEXA_SMART_HOME, ) +from .diagnostics import async_redact_auth_data from .errors import AlexaBridgeUnreachableError, AlexaError from .handlers import HANDLERS from .state_report import AlexaDirective @@ -149,12 +150,21 @@ class SmartHomeView(HomeAssistantView): user: User = request["hass_user"] message: dict[str, Any] = await request.json() - _LOGGER.debug("Received Alexa Smart Home request: %s", message) + if _LOGGER.isEnabledFor(logging.DEBUG): + _LOGGER.debug( + "Received Alexa Smart Home request: %s", + async_redact_auth_data(message), + ) response = await async_handle_message( hass, self.smart_home_config, message, context=core.Context(user_id=user.id) ) - _LOGGER.debug("Sending Alexa Smart Home response: %s", response) + if _LOGGER.isEnabledFor(logging.DEBUG): + _LOGGER.debug( + "Sending Alexa Smart Home response: %s", + async_redact_auth_data(response), + ) + return b"" if response is None else self.json(response) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index f1cf13a0a7e..20e66dfa084 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -34,6 +34,7 @@ from .const import ( DOMAIN, Cause, ) +from .diagnostics import async_redact_auth_data from .entities import ENTITY_ADAPTERS, AlexaEntity, generate_alexa_id from .errors import AlexaInvalidEndpointError, NoTokenAvailable, RequireRelink @@ -43,6 +44,8 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) DEFAULT_TIMEOUT = 10 +TO_REDACT = {"correlationToken", "token"} + class AlexaDirective: """An incoming Alexa directive.""" @@ -379,7 +382,9 @@ async def async_send_changereport_message( response_text = await response.text() if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug("Sent: %s", json.dumps(message_serialized)) + _LOGGER.debug( + "Sent: %s", json.dumps(async_redact_auth_data(message_serialized)) + ) _LOGGER.debug("Received (%s): %s", response.status, response_text) if response.status == HTTPStatus.ACCEPTED: @@ -533,7 +538,9 @@ async def async_send_doorbell_event_message( response_text = await response.text() if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug("Sent: %s", json.dumps(message_serialized)) + _LOGGER.debug( + "Sent: %s", json.dumps(async_redact_auth_data(message_serialized)) + ) _LOGGER.debug("Received (%s): %s", response.status, response_text) if response.status == HTTPStatus.ACCEPTED: diff --git a/tests/components/alexa/test_smart_home_http.py b/tests/components/alexa/test_smart_home_http.py index b0f78e958d7..1426eac5c5d 100644 --- a/tests/components/alexa/test_smart_home_http.py +++ b/tests/components/alexa/test_smart_home_http.py @@ -1,6 +1,7 @@ """Test Smart Home HTTP endpoints.""" from http import HTTPStatus import json +import logging from typing import Any import pytest @@ -44,11 +45,16 @@ async def do_http_discovery(config, hass, hass_client): ], ) async def test_http_api( - hass: HomeAssistant, hass_client: ClientSessionGenerator, config: dict[str, Any] + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + hass_client: ClientSessionGenerator, + config: dict[str, Any], ) -> None: - """With `smart_home:` HTTP API is exposed.""" - response = await do_http_discovery(config, hass, hass_client) - response_data = await response.json() + """With `smart_home:` HTTP API is exposed and debug log is redacted.""" + with caplog.at_level(logging.DEBUG): + response = await do_http_discovery(config, hass, hass_client) + response_data = await response.json() + assert "'correlationToken': '**REDACTED**'" in caplog.text # Here we're testing just the HTTP view glue -- details of discovery are # covered in other tests. @@ -61,5 +67,4 @@ async def test_http_api_disabled( """Without `smart_home:`, the HTTP API is disabled.""" config = {"alexa": {}} response = await do_http_discovery(config, hass, hass_client) - assert response.status == HTTPStatus.NOT_FOUND