From 520ba0a82e2da3665f8f09a167c5eddaf69cedcf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 20 Jan 2022 14:02:47 -0800 Subject: [PATCH] Introduce new redact helper (#64579) --- .../components/airvisual/diagnostics.py | 41 ++++++---------- .../components/ambient_station/diagnostics.py | 45 ++++++------------ .../components/diagnostics/__init__.py | 3 +- homeassistant/components/diagnostics/util.py | 28 +++++++++++ .../evil_genius_labs/diagnostics.py | 10 ++-- .../components/netatmo/diagnostics.py | 47 +++++++++---------- .../components/renault/diagnostics.py | 33 ++++--------- .../components/airvisual/test_diagnostics.py | 16 ++++--- .../ambient_station/test_diagnostics.py | 13 ++--- tests/components/netatmo/test_diagnostics.py | 25 +++++----- tests/components/renault/test_diagnostics.py | 13 ++--- 11 files changed, 129 insertions(+), 145 deletions(-) create mode 100644 homeassistant/components/diagnostics/util.py diff --git a/homeassistant/components/airvisual/diagnostics.py b/homeassistant/components/airvisual/diagnostics.py index 7fe882311f3..94cf5f1899d 100644 --- a/homeassistant/components/airvisual/diagnostics.py +++ b/homeassistant/components/airvisual/diagnostics.py @@ -1,40 +1,27 @@ """Diagnostics support for AirVisual.""" from __future__ import annotations -from types import MappingProxyType from typing import Any -from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_STATE -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import CONF_CITY, CONF_COUNTRY, DOMAIN CONF_COORDINATES = "coordinates" - -@callback -def _async_redact_data(data: MappingProxyType | dict) -> dict[str, Any]: - """Redact sensitive data in a dict.""" - redacted = {**data} - - for key, value in redacted.items(): - if key in ( - CONF_API_KEY, - CONF_CITY, - CONF_COORDINATES, - CONF_COUNTRY, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_STATE, - ): - redacted[key] = REDACTED - elif isinstance(value, dict): - redacted[key] = _async_redact_data(value) - - return redacted +TO_REDACT = { + CONF_API_KEY, + CONF_CITY, + CONF_COORDINATES, + CONF_COUNTRY, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_STATE, +} async def async_get_config_entry_diagnostics( @@ -46,8 +33,8 @@ async def async_get_config_entry_diagnostics( return { "entry": { "title": entry.title, - "data": _async_redact_data(entry.data), - "options": _async_redact_data(entry.options), + "data": async_redact_data(entry.data, TO_REDACT), + "options": async_redact_data(entry.options, TO_REDACT), }, - "data": _async_redact_data(coordinator.data["data"]), + "data": async_redact_data(coordinator.data["data"], TO_REDACT), } diff --git a/homeassistant/components/ambient_station/diagnostics.py b/homeassistant/components/ambient_station/diagnostics.py index f62e1e9d423..cb9c7adab18 100644 --- a/homeassistant/components/ambient_station/diagnostics.py +++ b/homeassistant/components/ambient_station/diagnostics.py @@ -1,13 +1,12 @@ """Diagnostics support for Ambient PWS.""" from __future__ import annotations -from types import MappingProxyType from typing import Any -from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from . import AmbientStation from .const import CONF_APP_KEY, DOMAIN @@ -20,31 +19,17 @@ CONF_MAC_ADDRESS = "mac_address" CONF_MAC_ADDRESS_CAMEL = "macAddress" CONF_TZ = "tz" - -@callback -def _async_redact_data(data: MappingProxyType | dict) -> dict[str, Any]: - """Redact sensitive data in a dict.""" - redacted = {**data} - - for key, value in redacted.items(): - if key in ( - CONF_API_KEY, - CONF_API_KEY_CAMEL, - CONF_APP_KEY, - CONF_APP_KEY_CAMEL, - CONF_DEVICE_ID_CAMEL, - CONF_LOCATION, - CONF_MAC_ADDRESS, - CONF_MAC_ADDRESS_CAMEL, - CONF_TZ, - ): - redacted[key] = REDACTED - elif isinstance(value, dict): - redacted[key] = _async_redact_data(value) - elif isinstance(value, list): - redacted[key] = [_async_redact_data(item) for item in value] - - return redacted +TO_REDACT = { + CONF_API_KEY, + CONF_API_KEY_CAMEL, + CONF_APP_KEY, + CONF_APP_KEY_CAMEL, + CONF_DEVICE_ID_CAMEL, + CONF_LOCATION, + CONF_MAC_ADDRESS, + CONF_MAC_ADDRESS_CAMEL, + CONF_TZ, +} async def async_get_config_entry_diagnostics( @@ -56,7 +41,7 @@ async def async_get_config_entry_diagnostics( return { "entry": { "title": entry.title, - "data": _async_redact_data(entry.data), + "data": async_redact_data(entry.data, TO_REDACT), }, - "stations": _async_redact_data(ambient.stations), + "stations": async_redact_data(ambient.stations, TO_REDACT), } diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index e5ba98b70c3..cb56dc79f03 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -22,8 +22,9 @@ from homeassistant.util.json import ( ) from .const import DOMAIN, REDACTED, DiagnosticsSubType, DiagnosticsType +from .util import async_redact_data -__all__ = ["REDACTED"] +__all__ = ["REDACTED", "async_redact_data"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py new file mode 100644 index 00000000000..d849dc052e4 --- /dev/null +++ b/homeassistant/components/diagnostics/util.py @@ -0,0 +1,28 @@ +"""Diagnostic utilities.""" +from __future__ import annotations + +from collections.abc import Iterable, Mapping +from typing import Any + +from homeassistant.core import callback + +from .const import REDACTED + + +@callback +def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict[str, Any]: + """Redact sensitive data in a dict.""" + if not isinstance(data, (Mapping, list)): + return data + + redacted = {**data} + + for key, value in redacted.items(): + if key in to_redact: + redacted[key] = REDACTED + elif isinstance(value, dict): + redacted[key] = async_redact_data(value, to_redact) + elif isinstance(value, list): + redacted[key] = [async_redact_data(item, to_redact) for item in value] + + return redacted diff --git a/homeassistant/components/evil_genius_labs/diagnostics.py b/homeassistant/components/evil_genius_labs/diagnostics.py index fce462a7f84..c269f699c11 100644 --- a/homeassistant/components/evil_genius_labs/diagnostics.py +++ b/homeassistant/components/evil_genius_labs/diagnostics.py @@ -1,13 +1,15 @@ """Diagnostics support for Evil Genius Labs.""" from __future__ import annotations -from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from . import EvilGeniusUpdateCoordinator from .const import DOMAIN +TO_REDACT = {"wiFiSsidDefault", "wiFiSSID"} + async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry @@ -16,10 +18,6 @@ async def async_get_config_entry_diagnostics( coordinator: EvilGeniusUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] return { - "info": { - **coordinator.info, - "wiFiSsidDefault": REDACTED, - "wiFiSSID": REDACTED, - }, + "info": async_redact_data(coordinator.info, TO_REDACT), "data": coordinator.data, } diff --git a/homeassistant/components/netatmo/diagnostics.py b/homeassistant/components/netatmo/diagnostics.py index 5d9754ea69c..5bf9c340295 100644 --- a/homeassistant/components/netatmo/diagnostics.py +++ b/homeassistant/components/netatmo/diagnostics.py @@ -1,13 +1,25 @@ """Diagnostics support for Netatmo.""" from __future__ import annotations -from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from .const import DATA_HANDLER, DOMAIN from .data_handler import NetatmoDataHandler +TO_REDACT = { + "access_token", + "refresh_token", + "restricted_access_token", + "restricted_refresh_token", + "webhook_id", + "lat_ne", + "lat_sw", + "lon_ne", + "lon_sw", +} + async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry @@ -17,28 +29,13 @@ async def async_get_config_entry_diagnostics( DATA_HANDLER ] - diagnostics_data = { - "info": { - **config_entry.as_dict(), - "webhook_registered": data_handler.webhook, - }, - "data": data_handler.data, + return { + "info": async_redact_data( + { + **config_entry.as_dict(), + "webhook_registered": data_handler.webhook, + }, + TO_REDACT, + ), + "data": async_redact_data(data_handler.data, TO_REDACT), } - - if "token" in diagnostics_data["info"]["data"]: - diagnostics_data["info"]["data"]["token"]["access_token"] = REDACTED - diagnostics_data["info"]["data"]["token"]["refresh_token"] = REDACTED - diagnostics_data["info"]["data"]["token"]["restricted_access_token"] = REDACTED - diagnostics_data["info"]["data"]["token"]["restricted_refresh_token"] = REDACTED - - if "webhook_id" in diagnostics_data["info"]["data"]: - diagnostics_data["info"]["data"]["webhook_id"] = REDACTED - - if "weather_areas" in diagnostics_data["info"].get("options", {}): - for area in diagnostics_data["info"]["options"]["weather_areas"]: - for attr in ("lat_ne", "lat_sw", "lon_ne", "lon_sw"): - diagnostics_data["info"]["options"]["weather_areas"][area][ - attr - ] = REDACTED - - return diagnostics_data diff --git a/homeassistant/components/renault/diagnostics.py b/homeassistant/components/renault/diagnostics.py index a0c94f3b3ae..f2a4e0f7cba 100644 --- a/homeassistant/components/renault/diagnostics.py +++ b/homeassistant/components/renault/diagnostics.py @@ -1,42 +1,25 @@ """Diagnostics support for Renault.""" from __future__ import annotations -from types import MappingProxyType from typing import Any -from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry from . import RenaultHub from .const import CONF_KAMEREON_ACCOUNT_ID, DOMAIN -TO_REDACT = ( +TO_REDACT = { CONF_KAMEREON_ACCOUNT_ID, CONF_PASSWORD, CONF_USERNAME, "radioCode", "registrationNumber", "vin", -) - - -@callback -def _async_redact_data(data: MappingProxyType | dict) -> dict[str, Any]: - """Redact sensitive data in a dict.""" - redacted = {**data} - - for key, value in redacted.items(): - if key in TO_REDACT: - redacted[key] = REDACTED - elif isinstance(value, dict): - redacted[key] = _async_redact_data(value) - elif isinstance(value, list): - redacted[key] = [_async_redact_data(item) for item in value] - - return redacted +} async def async_get_config_entry_diagnostics( @@ -48,10 +31,10 @@ async def async_get_config_entry_diagnostics( return { "entry": { "title": entry.title, - "data": _async_redact_data(entry.data), + "data": async_redact_data(entry.data, TO_REDACT), }, "vehicles": [ - _async_redact_data(vehicle.details.raw_data) + async_redact_data(vehicle.details.raw_data, TO_REDACT) for vehicle in renault_hub.vehicles.values() ], } @@ -65,5 +48,7 @@ async def async_get_device_diagnostics( vin = next(iter(device.identifiers))[1] return { - "details": _async_redact_data(renault_hub.vehicles[vin].details.raw_data), + "details": async_redact_data( + renault_hub.vehicles[vin].details.raw_data, TO_REDACT + ), } diff --git a/tests/components/airvisual/test_diagnostics.py b/tests/components/airvisual/test_diagnostics.py index 287df6ac74d..5b68644bb7e 100644 --- a/tests/components/airvisual/test_diagnostics.py +++ b/tests/components/airvisual/test_diagnostics.py @@ -1,4 +1,6 @@ """Test AirVisual diagnostics.""" +from homeassistant.components.diagnostics import REDACTED + from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -8,18 +10,18 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_airvisua "entry": { "title": "Mock Title", "data": { - "api_key": "**REDACTED**", + "api_key": REDACTED, "integration_type": "Geographical Location by Latitude/Longitude", - "latitude": "**REDACTED**", - "longitude": "**REDACTED**", + "latitude": REDACTED, + "longitude": REDACTED, }, "options": { "show_on_map": True, }, }, "data": { - "city": "**REDACTED**", - "country": "**REDACTED**", + "city": REDACTED, + "country": REDACTED, "current": { "weather": { "ts": "2021-09-03T21:00:00.000Z", @@ -39,9 +41,9 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_airvisua }, }, "location": { - "coordinates": "**REDACTED**", + "coordinates": REDACTED, "type": "Point", }, - "state": "**REDACTED**", + "state": REDACTED, }, } diff --git a/tests/components/ambient_station/test_diagnostics.py b/tests/components/ambient_station/test_diagnostics.py index 2295dad64d9..63d5fcff7a1 100644 --- a/tests/components/ambient_station/test_diagnostics.py +++ b/tests/components/ambient_station/test_diagnostics.py @@ -1,5 +1,6 @@ """Test Ambient PWS diagnostics.""" from homeassistant.components.ambient_station import DOMAIN +from homeassistant.components.diagnostics import REDACTED from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -12,14 +13,14 @@ async def test_entry_diagnostics( ambient.stations = station_data assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "entry": { - "data": {"api_key": "**REDACTED**", "app_key": "**REDACTED**"}, + "data": {"api_key": REDACTED, "app_key": REDACTED}, "title": "Mock Title", }, "stations": { "devices": [ { - "apiKey": "**REDACTED**", - "info": {"location": "**REDACTED**", "name": "Side Yard"}, + "apiKey": REDACTED, + "info": {"location": REDACTED, "name": "Side Yard"}, "lastData": { "baromabsin": 25.016, "baromrelin": 29.953, @@ -27,7 +28,7 @@ async def test_entry_diagnostics( "dailyrainin": 0, "date": "2022-01-19T22:38:00.000Z", "dateutc": 1642631880000, - "deviceId": "**REDACTED**", + "deviceId": REDACTED, "dewPoint": 17.75, "dewPointin": 37, "eventrainin": 0, @@ -43,14 +44,14 @@ async def test_entry_diagnostics( "tempf": 21, "tempinf": 70.9, "totalrainin": 35.398, - "tz": "**REDACTED**", + "tz": REDACTED, "uv": 0, "weeklyrainin": 0, "winddir": 25, "windgustmph": 1.1, "windspeedmph": 0.2, }, - "macAddress": "**REDACTED**", + "macAddress": REDACTED, } ], "method": "subscribe", diff --git a/tests/components/netatmo/test_diagnostics.py b/tests/components/netatmo/test_diagnostics.py index 62e99cba8b2..fc9d6d410e7 100644 --- a/tests/components/netatmo/test_diagnostics.py +++ b/tests/components/netatmo/test_diagnostics.py @@ -1,6 +1,7 @@ """Test the Netatmo diagnostics.""" from unittest.mock import AsyncMock, patch +from homeassistant.components.diagnostics import REDACTED from homeassistant.setup import async_setup_component from .common import fake_post_request @@ -34,11 +35,9 @@ async def test_entry_diagnostics(hass, hass_client, config_entry): "data": { "auth_implementation": "cloud", "token": { - "access_token": "**REDACTED**", - "restricted_access_token": "**REDACTED**", + "access_token": REDACTED, "expires_in": 60, - "refresh_token": "**REDACTED**", - "restricted_refresh_token": "**REDACTED**", + "refresh_token": REDACTED, "scope": [ "read_station", "read_camera", @@ -54,7 +53,7 @@ async def test_entry_diagnostics(hass, hass_client, config_entry): ], "type": "Bearer", }, - "webhook_id": "**REDACTED**", + "webhook_id": REDACTED, }, "disabled_by": None, "domain": "netatmo", @@ -62,19 +61,19 @@ async def test_entry_diagnostics(hass, hass_client, config_entry): "weather_areas": { "Home avg": { "area_name": "Home avg", - "lat_ne": "**REDACTED**", - "lat_sw": "**REDACTED**", - "lon_ne": "**REDACTED**", - "lon_sw": "**REDACTED**", + "lat_ne": REDACTED, + "lat_sw": REDACTED, + "lon_ne": REDACTED, + "lon_sw": REDACTED, "mode": "avg", "show_on_map": False, }, "Home max": { "area_name": "Home max", - "lat_ne": "**REDACTED**", - "lat_sw": "**REDACTED**", - "lon_ne": "**REDACTED**", - "lon_sw": "**REDACTED**", + "lat_ne": REDACTED, + "lat_sw": REDACTED, + "lon_ne": REDACTED, + "lon_sw": REDACTED, "mode": "max", "show_on_map": True, }, diff --git a/tests/components/renault/test_diagnostics.py b/tests/components/renault/test_diagnostics.py index dd3ab369f05..e3ea6254237 100644 --- a/tests/components/renault/test_diagnostics.py +++ b/tests/components/renault/test_diagnostics.py @@ -1,6 +1,7 @@ """Test Renault diagnostics.""" import pytest +from homeassistant.components.diagnostics import REDACTED from homeassistant.components.renault import DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -14,7 +15,7 @@ from tests.components.diagnostics import ( pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") VEHICLE_DETAILS = { - "vin": "**REDACTED**", + "vin": REDACTED, "registrationDate": "2017-08-01", "firstRegistrationDate": "2017-08-01", "engineType": "5AQ", @@ -52,7 +53,7 @@ VEHICLE_DETAILS = { }, "version": {"code": "INT MB 10R"}, "energy": {"code": "ELEC", "label": "ELECTRIQUE", "group": "019"}, - "registrationNumber": "**REDACTED**", + "registrationNumber": REDACTED, "vcd": "SYTINC/SKTPOU/SAND41/FDIU1/SSESM/MAPSUP/SSCALL/SAND88/SAND90/SQKDRO/SDIFPA/FACBA2/PRLEX1/SSRCAR/CABDO2/TCU0G2/SWALBO/EVTEC1/STANDA/X10/B10/EA2/MB/ELEC/DG/TEMP/TR4X2/RV/ABS/CAREG/LAC/VT003/CPE/RET03/SPROJA/RALU16/CEAVRH/AIRBA1/SERIE/DRA/DRAP08/HARM02/ATAR/TERQG/SFBANA/KM/DPRPN/AVREPL/SSDECA/ASRESP/RDAR02/ALEVA/CACBL2/SOP02C/CTHAB2/TRNOR/LVAVIP/LVAREL/SASURV/KTGREP/SGSCHA/APL03/ALOUCC/CMAR3P/NAV3G5/RAD37A/BVEL/AUTAUG/RNORM/ISOFIX/EQPEUR/HRGM01/SDPCLV/TLFRAN/SPRODI/SAN613/SSAPEX/GENEV1/ELC1/SANCML/PE2012/PHAS1/SAN913/045KWH/BT4AR1/VEC153/X101VE/NBT017/5AQ", "assets": [ { @@ -130,7 +131,7 @@ VEHICLE_DETAILS = { "deliveryDate": "2017-08-11", "retrievedFromDhs": False, "engineEnergyType": "ELEC", - "radioCode": "**REDACTED**", + "radioCode": REDACTED, } @@ -146,10 +147,10 @@ async def test_entry_diagnostics( assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "entry": { "data": { - "kamereon_account_id": "**REDACTED**", + "kamereon_account_id": REDACTED, "locale": "fr_FR", - "password": "**REDACTED**", - "username": "**REDACTED**", + "password": REDACTED, + "username": REDACTED, }, "title": "Mock Title", },