Introduce new redact helper (#64579)

This commit is contained in:
Paulus Schoutsen 2022-01-20 14:02:47 -08:00 committed by GitHub
parent 53aed22d5c
commit 520ba0a82e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 129 additions and 145 deletions

View File

@ -1,27 +1,19 @@
"""Diagnostics support for AirVisual.""" """Diagnostics support for AirVisual."""
from __future__ import annotations from __future__ import annotations
from types import MappingProxyType
from typing import Any 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.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_STATE 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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import CONF_CITY, CONF_COUNTRY, DOMAIN from .const import CONF_CITY, CONF_COUNTRY, DOMAIN
CONF_COORDINATES = "coordinates" CONF_COORDINATES = "coordinates"
TO_REDACT = {
@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,
CONF_CITY, CONF_CITY,
CONF_COORDINATES, CONF_COORDINATES,
@ -29,12 +21,7 @@ def _async_redact_data(data: MappingProxyType | dict) -> dict[str, Any]:
CONF_LATITUDE, CONF_LATITUDE,
CONF_LONGITUDE, CONF_LONGITUDE,
CONF_STATE, CONF_STATE,
): }
redacted[key] = REDACTED
elif isinstance(value, dict):
redacted[key] = _async_redact_data(value)
return redacted
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
@ -46,8 +33,8 @@ async def async_get_config_entry_diagnostics(
return { return {
"entry": { "entry": {
"title": entry.title, "title": entry.title,
"data": _async_redact_data(entry.data), "data": async_redact_data(entry.data, TO_REDACT),
"options": _async_redact_data(entry.options), "options": async_redact_data(entry.options, TO_REDACT),
}, },
"data": _async_redact_data(coordinator.data["data"]), "data": async_redact_data(coordinator.data["data"], TO_REDACT),
} }

View File

@ -1,13 +1,12 @@
"""Diagnostics support for Ambient PWS.""" """Diagnostics support for Ambient PWS."""
from __future__ import annotations from __future__ import annotations
from types import MappingProxyType
from typing import Any 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.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from . import AmbientStation from . import AmbientStation
from .const import CONF_APP_KEY, DOMAIN from .const import CONF_APP_KEY, DOMAIN
@ -20,14 +19,7 @@ CONF_MAC_ADDRESS = "mac_address"
CONF_MAC_ADDRESS_CAMEL = "macAddress" CONF_MAC_ADDRESS_CAMEL = "macAddress"
CONF_TZ = "tz" CONF_TZ = "tz"
TO_REDACT = {
@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,
CONF_API_KEY_CAMEL, CONF_API_KEY_CAMEL,
CONF_APP_KEY, CONF_APP_KEY,
@ -37,14 +29,7 @@ def _async_redact_data(data: MappingProxyType | dict) -> dict[str, Any]:
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_MAC_ADDRESS_CAMEL, CONF_MAC_ADDRESS_CAMEL,
CONF_TZ, 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
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
@ -56,7 +41,7 @@ async def async_get_config_entry_diagnostics(
return { return {
"entry": { "entry": {
"title": entry.title, "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),
} }

View File

@ -22,8 +22,9 @@ from homeassistant.util.json import (
) )
from .const import DOMAIN, REDACTED, DiagnosticsSubType, DiagnosticsType from .const import DOMAIN, REDACTED, DiagnosticsSubType, DiagnosticsType
from .util import async_redact_data
__all__ = ["REDACTED"] __all__ = ["REDACTED", "async_redact_data"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -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

View File

@ -1,13 +1,15 @@
"""Diagnostics support for Evil Genius Labs.""" """Diagnostics support for Evil Genius Labs."""
from __future__ import annotations 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.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import EvilGeniusUpdateCoordinator from . import EvilGeniusUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
TO_REDACT = {"wiFiSsidDefault", "wiFiSSID"}
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry 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] coordinator: EvilGeniusUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
return { return {
"info": { "info": async_redact_data(coordinator.info, TO_REDACT),
**coordinator.info,
"wiFiSsidDefault": REDACTED,
"wiFiSSID": REDACTED,
},
"data": coordinator.data, "data": coordinator.data,
} }

View File

@ -1,13 +1,25 @@
"""Diagnostics support for Netatmo.""" """Diagnostics support for Netatmo."""
from __future__ import annotations 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.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DATA_HANDLER, DOMAIN from .const import DATA_HANDLER, DOMAIN
from .data_handler import NetatmoDataHandler 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( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry hass: HomeAssistant, config_entry: ConfigEntry
@ -17,28 +29,13 @@ async def async_get_config_entry_diagnostics(
DATA_HANDLER DATA_HANDLER
] ]
diagnostics_data = { return {
"info": { "info": async_redact_data(
{
**config_entry.as_dict(), **config_entry.as_dict(),
"webhook_registered": data_handler.webhook, "webhook_registered": data_handler.webhook,
}, },
"data": data_handler.data, 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

View File

@ -1,42 +1,25 @@
"""Diagnostics support for Renault.""" """Diagnostics support for Renault."""
from __future__ import annotations from __future__ import annotations
from types import MappingProxyType
from typing import Any 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.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME 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 homeassistant.helpers.device_registry import DeviceEntry
from . import RenaultHub from . import RenaultHub
from .const import CONF_KAMEREON_ACCOUNT_ID, DOMAIN from .const import CONF_KAMEREON_ACCOUNT_ID, DOMAIN
TO_REDACT = ( TO_REDACT = {
CONF_KAMEREON_ACCOUNT_ID, CONF_KAMEREON_ACCOUNT_ID,
CONF_PASSWORD, CONF_PASSWORD,
CONF_USERNAME, CONF_USERNAME,
"radioCode", "radioCode",
"registrationNumber", "registrationNumber",
"vin", "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( async def async_get_config_entry_diagnostics(
@ -48,10 +31,10 @@ async def async_get_config_entry_diagnostics(
return { return {
"entry": { "entry": {
"title": entry.title, "title": entry.title,
"data": _async_redact_data(entry.data), "data": async_redact_data(entry.data, TO_REDACT),
}, },
"vehicles": [ "vehicles": [
_async_redact_data(vehicle.details.raw_data) async_redact_data(vehicle.details.raw_data, TO_REDACT)
for vehicle in renault_hub.vehicles.values() for vehicle in renault_hub.vehicles.values()
], ],
} }
@ -65,5 +48,7 @@ async def async_get_device_diagnostics(
vin = next(iter(device.identifiers))[1] vin = next(iter(device.identifiers))[1]
return { 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
),
} }

View File

@ -1,4 +1,6 @@
"""Test AirVisual diagnostics.""" """Test AirVisual diagnostics."""
from homeassistant.components.diagnostics import REDACTED
from tests.components.diagnostics import get_diagnostics_for_config_entry 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": { "entry": {
"title": "Mock Title", "title": "Mock Title",
"data": { "data": {
"api_key": "**REDACTED**", "api_key": REDACTED,
"integration_type": "Geographical Location by Latitude/Longitude", "integration_type": "Geographical Location by Latitude/Longitude",
"latitude": "**REDACTED**", "latitude": REDACTED,
"longitude": "**REDACTED**", "longitude": REDACTED,
}, },
"options": { "options": {
"show_on_map": True, "show_on_map": True,
}, },
}, },
"data": { "data": {
"city": "**REDACTED**", "city": REDACTED,
"country": "**REDACTED**", "country": REDACTED,
"current": { "current": {
"weather": { "weather": {
"ts": "2021-09-03T21:00:00.000Z", "ts": "2021-09-03T21:00:00.000Z",
@ -39,9 +41,9 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_airvisua
}, },
}, },
"location": { "location": {
"coordinates": "**REDACTED**", "coordinates": REDACTED,
"type": "Point", "type": "Point",
}, },
"state": "**REDACTED**", "state": REDACTED,
}, },
} }

View File

@ -1,5 +1,6 @@
"""Test Ambient PWS diagnostics.""" """Test Ambient PWS diagnostics."""
from homeassistant.components.ambient_station import DOMAIN from homeassistant.components.ambient_station import DOMAIN
from homeassistant.components.diagnostics import REDACTED
from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.components.diagnostics import get_diagnostics_for_config_entry
@ -12,14 +13,14 @@ async def test_entry_diagnostics(
ambient.stations = station_data ambient.stations = station_data
assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {
"entry": { "entry": {
"data": {"api_key": "**REDACTED**", "app_key": "**REDACTED**"}, "data": {"api_key": REDACTED, "app_key": REDACTED},
"title": "Mock Title", "title": "Mock Title",
}, },
"stations": { "stations": {
"devices": [ "devices": [
{ {
"apiKey": "**REDACTED**", "apiKey": REDACTED,
"info": {"location": "**REDACTED**", "name": "Side Yard"}, "info": {"location": REDACTED, "name": "Side Yard"},
"lastData": { "lastData": {
"baromabsin": 25.016, "baromabsin": 25.016,
"baromrelin": 29.953, "baromrelin": 29.953,
@ -27,7 +28,7 @@ async def test_entry_diagnostics(
"dailyrainin": 0, "dailyrainin": 0,
"date": "2022-01-19T22:38:00.000Z", "date": "2022-01-19T22:38:00.000Z",
"dateutc": 1642631880000, "dateutc": 1642631880000,
"deviceId": "**REDACTED**", "deviceId": REDACTED,
"dewPoint": 17.75, "dewPoint": 17.75,
"dewPointin": 37, "dewPointin": 37,
"eventrainin": 0, "eventrainin": 0,
@ -43,14 +44,14 @@ async def test_entry_diagnostics(
"tempf": 21, "tempf": 21,
"tempinf": 70.9, "tempinf": 70.9,
"totalrainin": 35.398, "totalrainin": 35.398,
"tz": "**REDACTED**", "tz": REDACTED,
"uv": 0, "uv": 0,
"weeklyrainin": 0, "weeklyrainin": 0,
"winddir": 25, "winddir": 25,
"windgustmph": 1.1, "windgustmph": 1.1,
"windspeedmph": 0.2, "windspeedmph": 0.2,
}, },
"macAddress": "**REDACTED**", "macAddress": REDACTED,
} }
], ],
"method": "subscribe", "method": "subscribe",

View File

@ -1,6 +1,7 @@
"""Test the Netatmo diagnostics.""" """Test the Netatmo diagnostics."""
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from homeassistant.components.diagnostics import REDACTED
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .common import fake_post_request from .common import fake_post_request
@ -34,11 +35,9 @@ async def test_entry_diagnostics(hass, hass_client, config_entry):
"data": { "data": {
"auth_implementation": "cloud", "auth_implementation": "cloud",
"token": { "token": {
"access_token": "**REDACTED**", "access_token": REDACTED,
"restricted_access_token": "**REDACTED**",
"expires_in": 60, "expires_in": 60,
"refresh_token": "**REDACTED**", "refresh_token": REDACTED,
"restricted_refresh_token": "**REDACTED**",
"scope": [ "scope": [
"read_station", "read_station",
"read_camera", "read_camera",
@ -54,7 +53,7 @@ async def test_entry_diagnostics(hass, hass_client, config_entry):
], ],
"type": "Bearer", "type": "Bearer",
}, },
"webhook_id": "**REDACTED**", "webhook_id": REDACTED,
}, },
"disabled_by": None, "disabled_by": None,
"domain": "netatmo", "domain": "netatmo",
@ -62,19 +61,19 @@ async def test_entry_diagnostics(hass, hass_client, config_entry):
"weather_areas": { "weather_areas": {
"Home avg": { "Home avg": {
"area_name": "Home avg", "area_name": "Home avg",
"lat_ne": "**REDACTED**", "lat_ne": REDACTED,
"lat_sw": "**REDACTED**", "lat_sw": REDACTED,
"lon_ne": "**REDACTED**", "lon_ne": REDACTED,
"lon_sw": "**REDACTED**", "lon_sw": REDACTED,
"mode": "avg", "mode": "avg",
"show_on_map": False, "show_on_map": False,
}, },
"Home max": { "Home max": {
"area_name": "Home max", "area_name": "Home max",
"lat_ne": "**REDACTED**", "lat_ne": REDACTED,
"lat_sw": "**REDACTED**", "lat_sw": REDACTED,
"lon_ne": "**REDACTED**", "lon_ne": REDACTED,
"lon_sw": "**REDACTED**", "lon_sw": REDACTED,
"mode": "max", "mode": "max",
"show_on_map": True, "show_on_map": True,
}, },

View File

@ -1,6 +1,7 @@
"""Test Renault diagnostics.""" """Test Renault diagnostics."""
import pytest import pytest
from homeassistant.components.diagnostics import REDACTED
from homeassistant.components.renault import DOMAIN from homeassistant.components.renault import DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -14,7 +15,7 @@ from tests.components.diagnostics import (
pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles")
VEHICLE_DETAILS = { VEHICLE_DETAILS = {
"vin": "**REDACTED**", "vin": REDACTED,
"registrationDate": "2017-08-01", "registrationDate": "2017-08-01",
"firstRegistrationDate": "2017-08-01", "firstRegistrationDate": "2017-08-01",
"engineType": "5AQ", "engineType": "5AQ",
@ -52,7 +53,7 @@ VEHICLE_DETAILS = {
}, },
"version": {"code": "INT MB 10R"}, "version": {"code": "INT MB 10R"},
"energy": {"code": "ELEC", "label": "ELECTRIQUE", "group": "019"}, "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", "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": [ "assets": [
{ {
@ -130,7 +131,7 @@ VEHICLE_DETAILS = {
"deliveryDate": "2017-08-11", "deliveryDate": "2017-08-11",
"retrievedFromDhs": False, "retrievedFromDhs": False,
"engineEnergyType": "ELEC", "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) == { assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {
"entry": { "entry": {
"data": { "data": {
"kamereon_account_id": "**REDACTED**", "kamereon_account_id": REDACTED,
"locale": "fr_FR", "locale": "fr_FR",
"password": "**REDACTED**", "password": REDACTED,
"username": "**REDACTED**", "username": REDACTED,
}, },
"title": "Mock Title", "title": "Mock Title",
}, },