Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions homeassistant/components/websocket_api/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from homeassistant.core import Context, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, Unauthorized
from homeassistant.helpers.http import current_request
from homeassistant.helpers.redact import async_redact_data
from homeassistant.util.json import JsonValueType

from . import const, messages
Expand All @@ -32,6 +33,15 @@
"current_connection", default=None
)

REDACT_KEYS = {
"access_token",
"password",
"api_password",
"refresh_token",
"token",
"auth_token",
}

type MessageHandler = Callable[[HomeAssistant, ActiveConnection, dict[str, Any]], None]
type BinaryHandler = Callable[[HomeAssistant, ActiveConnection, bytes], None]

Expand Down Expand Up @@ -201,6 +211,7 @@ def async_handle(self, msg: JsonValueType) -> None:
or type(type_) is not str
)
):
msg = async_redact_data(msg, REDACT_KEYS)
self.logger.error("Received invalid command: %s", msg)
id_ = msg.get("id") if isinstance(msg, dict) else 0
Comment thread
ch604 marked this conversation as resolved.
self.send_message(
Expand Down Expand Up @@ -264,6 +275,7 @@ def _connect_closed_error(
self, msg: bytes | str | dict[str, Any] | Callable[[], str]
) -> None:
"""Send a message when the connection is closed."""
msg = async_redact_data(msg, REDACT_KEYS)
self.logger.debug("Tried to send message %s on closed connection", msg)

@callback
Expand All @@ -277,6 +289,8 @@ def async_handle_exception(self, msg: dict[str, Any], err: Exception) -> None:
translation_key: str | None = None
translation_placeholders: dict[str, Any] | None = None

msg = async_redact_data(msg, REDACT_KEYS)

if isinstance(err, Unauthorized):
code = const.ERR_UNAUTHORIZED
err_message = "Unauthorized"
Expand Down
44 changes: 44 additions & 0 deletions tests/components/websocket_api/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.redact import REDACTED

from tests.common import MockUser

Expand Down Expand Up @@ -137,3 +138,46 @@ async def test_binary_handler_registration() -> None:
# Verify we reuse an unsubscribed prefix
prefix, unsub = connection.async_register_binary_handler(None)
assert prefix == 15


async def test_credential_redaction(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test credential redaction."""
send_messages = []
user = MockUser()
refresh_token = Mock()
hass.data[DOMAIN] = {}
test_input = ["valid detail information", "secretpassword", "api-token-12345"]

connection = websocket_api.ActiveConnection(
logging.getLogger(__name__),
hass,
send_messages.append,
user,
refresh_token,
remote=None,
)

msg = {
"id": 5,
"detail": test_input[0],
"password": test_input[1],
"token": test_input[2],
}
connection.async_handle_exception(msg, vol.Invalid("bad input"))

assert len(send_messages) == 1
error_message = send_messages[0]["error"]["message"]
assert test_input[0] in error_message
assert test_input[1] not in error_message
assert test_input[2] not in error_message
assert REDACTED in error_message

msg = {"type": "auth", "access_token": test_input[2]}
connection.async_handle(msg)

assert len(send_messages) == 2
assert send_messages[1]["error"]["message"] == "Message incorrectly formatted."
assert test_input[2] not in caplog.text
assert REDACTED in caplog.text