Production-readiness pass: security hardening, performance improvements, new services (send_message, set_repeat, refresh_library), diagnostics, reauth flow, image proxy, per-instance device IDs, exponential WS reconnect backoff, ID validation, stale device cleanup, and supporting integration plumbing. Three rounds of independent code review applied. See RELEASE_NOTES.md for the full changelog.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
"""Diagnostics support for Emby Media Player."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from dataclasses import asdict
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import EmbyConfigEntry
|
||||
from .const import CONF_API_KEY
|
||||
|
||||
TO_REDACT_ENTRY = {CONF_API_KEY}
|
||||
# Session-level fields that could allow Emby command injection if leaked
|
||||
# alongside the API key.
|
||||
TO_REDACT_SESSION = {"session_id", "device_id", "user_id"}
|
||||
|
||||
|
||||
def _stable_token(value: str) -> str:
|
||||
"""Stable, irreversible token for a session identifier.
|
||||
|
||||
The first 10 chars of an MD5 are enough to correlate entries in a single
|
||||
diagnostics dump without exposing the real Emby ID.
|
||||
"""
|
||||
return "sid-" + hashlib.md5(value.encode("utf-8")).hexdigest()[:10] # noqa: S324
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: EmbyConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
runtime = entry.runtime_data
|
||||
coordinator = runtime.coordinator
|
||||
|
||||
sessions_dump: dict[str, Any] = {}
|
||||
if coordinator.data:
|
||||
for sid, session in coordinator.data.items():
|
||||
session_dict = asdict(session)
|
||||
# Convert datetimes to isoformat for JSON friendliness.
|
||||
for key, value in list(session_dict.items()):
|
||||
if hasattr(value, "isoformat"):
|
||||
session_dict[key] = value.isoformat()
|
||||
# Nested play_state.updated_at may also be a datetime.
|
||||
if isinstance(value, dict):
|
||||
for sub_k, sub_v in list(value.items()):
|
||||
if hasattr(sub_v, "isoformat"):
|
||||
value[sub_k] = sub_v.isoformat()
|
||||
sessions_dump[_stable_token(sid)] = async_redact_data(
|
||||
session_dict, TO_REDACT_SESSION
|
||||
)
|
||||
|
||||
return {
|
||||
"entry": {
|
||||
"title": entry.title,
|
||||
"data": async_redact_data(dict(entry.data), TO_REDACT_ENTRY),
|
||||
"options": dict(entry.options),
|
||||
"unique_id": entry.unique_id,
|
||||
},
|
||||
"server_id": runtime.server_id,
|
||||
"websocket_connected": coordinator.websocket_connected,
|
||||
"last_update_success": coordinator.last_update_success,
|
||||
"sessions": sessions_dump,
|
||||
}
|
||||
Reference in New Issue
Block a user