From 1b46575f29fb78bee09ca0630facc54009d91714 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 23 Jan 2022 22:52:08 +0100 Subject: [PATCH] Add diagnostics support to Axis integration (#64637) * Add diagnostics support to Axis integration * Remove system info * Redact sensitive information * Store whole config entry * Redact username * Apply suggestions from code review Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> --- homeassistant/components/axis/diagnostics.py | 45 ++++++++ tests/components/axis/test_diagnostics.py | 102 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 homeassistant/components/axis/diagnostics.py create mode 100644 tests/components/axis/test_diagnostics.py diff --git a/homeassistant/components/axis/diagnostics.py b/homeassistant/components/axis/diagnostics.py new file mode 100644 index 00000000000..be3da8daf9e --- /dev/null +++ b/homeassistant/components/axis/diagnostics.py @@ -0,0 +1,45 @@ +"""Diagnostics support for Axis.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_MAC, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import DOMAIN as AXIS_DOMAIN + +REDACT_CONFIG = {CONF_MAC, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME} +REDACT_BASIC_DEVICE_INFO = {"SerialNumber", "SocSerialNumber"} +REDACT_VAPIX_PARAMS = {"root.Network", "System.SerialNumber"} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + device = hass.data[AXIS_DOMAIN][config_entry.unique_id] + diag: dict[str, Any] = {} + + diag["config"] = async_redact_data(config_entry.as_dict(), REDACT_CONFIG) + + if device.api.vapix.api_discovery: + diag["api_discovery"] = [ + {"id": api.id, "name": api.name, "version": api.version} + for api in device.api.vapix.api_discovery.values() + ] + + if device.api.vapix.basic_device_info: + diag["basic_device_info"] = async_redact_data( + {attr.id: attr.raw for attr in device.api.vapix.basic_device_info.values()}, + REDACT_BASIC_DEVICE_INFO, + ) + + if device.api.vapix.params: + diag["params"] = async_redact_data( + {param.id: param.raw for param in device.api.vapix.params.values()}, + REDACT_VAPIX_PARAMS, + ) + + return diag diff --git a/tests/components/axis/test_diagnostics.py b/tests/components/axis/test_diagnostics.py new file mode 100644 index 00000000000..4f43f1d42ff --- /dev/null +++ b/tests/components/axis/test_diagnostics.py @@ -0,0 +1,102 @@ +"""Test Axis diagnostics.""" + +from copy import deepcopy +from unittest.mock import patch + +from homeassistant.components.diagnostics import REDACTED + +from .test_device import ( + API_DISCOVERY_BASIC_DEVICE_INFO, + API_DISCOVERY_RESPONSE, + setup_axis_integration, +) + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics(hass, hass_client): + """Test config entry diagnostics.""" + api_discovery = deepcopy(API_DISCOVERY_RESPONSE) + api_discovery["data"]["apiList"].append(API_DISCOVERY_BASIC_DEVICE_INFO) + + with patch.dict(API_DISCOVERY_RESPONSE, api_discovery): + config_entry = await setup_axis_integration(hass) + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "config": { + "entry_id": config_entry.entry_id, + "version": 3, + "domain": "axis", + "title": "Mock Title", + "data": { + "host": "1.2.3.4", + "username": REDACTED, + "password": REDACTED, + "port": 80, + "model": "model", + "name": "name", + }, + "options": {"events": True}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "user", + "unique_id": REDACTED, + "disabled_by": None, + }, + "api_discovery": [ + { + "id": "api-discovery", + "name": "API Discovery Service", + "version": "1.0", + }, + { + "id": "param-cgi", + "name": "Legacy Parameter Handling", + "version": "1.0", + }, + { + "id": "basic-device-info", + "name": "Basic Device Information", + "version": "1.1", + }, + ], + "basic_device_info": { + "ProdNbr": "M1065-LW", + "ProdType": "Network Camera", + "SerialNumber": REDACTED, + "Version": "9.80.1", + }, + "params": { + "root.IOPort": { + "I0.Configurable": "no", + "I0.Direction": "input", + "I0.Input.Name": "PIR sensor", + "I0.Input.Trig": "closed", + }, + "root.Input": {"NbrOfInputs": "1"}, + "root.Output": {"NbrOfOutputs": "0"}, + "root.Properties": { + "API.HTTP.Version": "3", + "API.Metadata.Metadata": "yes", + "API.Metadata.Version": "1.0", + "EmbeddedDevelopment.Version": "2.16", + "Firmware.BuildDate": "Feb 15 2019 09:42", + "Firmware.BuildNumber": "26", + "Firmware.Version": "9.10.1", + "Image.Format": "jpeg,mjpeg,h264", + "Image.NbrOfViews": "2", + "Image.Resolution": "1920x1080,1280x960,1280x720,1024x768,1024x576,800x600,640x480,640x360,352x240,320x240", + "Image.Rotation": "0,180", + "System.SerialNumber": REDACTED, + }, + "root.StreamProfile": { + "MaxGroups": "26", + "S0.Description": "profile_1_description", + "S0.Name": "profile_1", + "S0.Parameters": "videocodec=h264", + "S1.Description": "profile_2_description", + "S1.Name": "profile_2", + "S1.Parameters": "videocodec=h265", + }, + }, + }