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
f993e923a3
commit
7f8a157788
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.helpers.storage import Store
|
||||||
from homeassistant.util import dt as dt_util
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token"
|
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_KEY = "alexa_auth"
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
STORAGE_EXPIRE_TIME = "expire_time"
|
STORAGE_EXPIRE_TIME = "expire_time"
|
||||||
STORAGE_ACCESS_TOKEN = "access_token"
|
|
||||||
STORAGE_REFRESH_TOKEN = "refresh_token"
|
|
||||||
|
|
||||||
|
|
||||||
class Auth:
|
class Auth:
|
||||||
|
@ -56,7 +57,7 @@ class Auth:
|
||||||
}
|
}
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Calling LWA to get the access token (first time), with: %s",
|
"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)
|
return await self._async_request_new_token(lwa_params)
|
||||||
|
@ -133,7 +134,7 @@ class Auth:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
response_json = await response.json()
|
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"]
|
access_token: str = response_json["access_token"]
|
||||||
refresh_token: str = response_json["refresh_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
|
# we add PRESET_MODE_NA if a fan / humidifier has only one preset_mode
|
||||||
PRESET_MODE_NA = "-"
|
PRESET_MODE_NA = "-"
|
||||||
|
|
||||||
|
STORAGE_ACCESS_TOKEN = "access_token"
|
||||||
|
STORAGE_REFRESH_TOKEN = "refresh_token"
|
||||||
|
|
||||||
|
|
||||||
class Cause:
|
class Cause:
|
||||||
"""Possible causes for property changes.
|
"""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.
|
Async friendly.
|
||||||
"""
|
"""
|
||||||
auth_code: str = directive.payload["grant"]["code"]
|
auth_code: str = directive.payload["grant"]["code"]
|
||||||
_LOGGER.debug("AcceptGrant code: %s", auth_code)
|
|
||||||
|
|
||||||
if config.supports_auth:
|
if config.supports_auth:
|
||||||
await config.async_accept_grant(auth_code)
|
await config.async_accept_grant(auth_code)
|
||||||
|
|
|
@ -25,6 +25,7 @@ from .const import (
|
||||||
CONF_LOCALE,
|
CONF_LOCALE,
|
||||||
EVENT_ALEXA_SMART_HOME,
|
EVENT_ALEXA_SMART_HOME,
|
||||||
)
|
)
|
||||||
|
from .diagnostics import async_redact_auth_data
|
||||||
from .errors import AlexaBridgeUnreachableError, AlexaError
|
from .errors import AlexaBridgeUnreachableError, AlexaError
|
||||||
from .handlers import HANDLERS
|
from .handlers import HANDLERS
|
||||||
from .state_report import AlexaDirective
|
from .state_report import AlexaDirective
|
||||||
|
@ -149,12 +150,21 @@ class SmartHomeView(HomeAssistantView):
|
||||||
user: User = request["hass_user"]
|
user: User = request["hass_user"]
|
||||||
message: dict[str, Any] = await request.json()
|
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(
|
response = await async_handle_message(
|
||||||
hass, self.smart_home_config, message, context=core.Context(user_id=user.id)
|
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)
|
return b"" if response is None else self.json(response)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
Cause,
|
Cause,
|
||||||
)
|
)
|
||||||
|
from .diagnostics import async_redact_auth_data
|
||||||
from .entities import ENTITY_ADAPTERS, AlexaEntity, generate_alexa_id
|
from .entities import ENTITY_ADAPTERS, AlexaEntity, generate_alexa_id
|
||||||
from .errors import AlexaInvalidEndpointError, NoTokenAvailable, RequireRelink
|
from .errors import AlexaInvalidEndpointError, NoTokenAvailable, RequireRelink
|
||||||
|
|
||||||
|
@ -43,6 +44,8 @@ if TYPE_CHECKING:
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
|
|
||||||
|
TO_REDACT = {"correlationToken", "token"}
|
||||||
|
|
||||||
|
|
||||||
class AlexaDirective:
|
class AlexaDirective:
|
||||||
"""An incoming Alexa directive."""
|
"""An incoming Alexa directive."""
|
||||||
|
@ -379,7 +382,9 @@ async def async_send_changereport_message(
|
||||||
response_text = await response.text()
|
response_text = await response.text()
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
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)
|
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||||
|
|
||||||
if response.status == HTTPStatus.ACCEPTED:
|
if response.status == HTTPStatus.ACCEPTED:
|
||||||
|
@ -533,7 +538,9 @@ async def async_send_doorbell_event_message(
|
||||||
response_text = await response.text()
|
response_text = await response.text()
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
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)
|
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||||
|
|
||||||
if response.status == HTTPStatus.ACCEPTED:
|
if response.status == HTTPStatus.ACCEPTED:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Test Smart Home HTTP endpoints."""
|
"""Test Smart Home HTTP endpoints."""
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -44,11 +45,16 @@ async def do_http_discovery(config, hass, hass_client):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_http_api(
|
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:
|
) -> None:
|
||||||
"""With `smart_home:` HTTP API is exposed."""
|
"""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 = await do_http_discovery(config, hass, hass_client)
|
||||||
response_data = await response.json()
|
response_data = await response.json()
|
||||||
|
assert "'correlationToken': '**REDACTED**'" in caplog.text
|
||||||
|
|
||||||
# Here we're testing just the HTTP view glue -- details of discovery are
|
# Here we're testing just the HTTP view glue -- details of discovery are
|
||||||
# covered in other tests.
|
# covered in other tests.
|
||||||
|
@ -61,5 +67,4 @@ async def test_http_api_disabled(
|
||||||
"""Without `smart_home:`, the HTTP API is disabled."""
|
"""Without `smart_home:`, the HTTP API is disabled."""
|
||||||
config = {"alexa": {}}
|
config = {"alexa": {}}
|
||||||
response = await do_http_discovery(config, hass, hass_client)
|
response = await do_http_discovery(config, hass, hass_client)
|
||||||
|
|
||||||
assert response.status == HTTPStatus.NOT_FOUND
|
assert response.status == HTTPStatus.NOT_FOUND
|
||||||
|
|
Loading…
Add table
Reference in a new issue