Redact sensitive data in alexa debug logging (#107676)
* Redact sensitive data in alexa debug logging * Add wrappers to diagnostics module * Test http api log is redacted
This commit is contained in:
parent
956921a930
commit
5bdcbc4e8b
7 changed files with 73 additions and 14 deletions
|
@ -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"]
|
||||
|
|
|
@ -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.
|
||||
|
|
34
homeassistant/components/alexa/diagnostics.py
Normal file
34
homeassistant/components/alexa/diagnostics.py
Normal file
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue