72 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			72 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			Python
		
	
	
"""Helpers to redact sensitive data."""
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
from collections.abc import Callable, Iterable, Mapping
 | 
						|
from typing import Any, cast, overload
 | 
						|
 | 
						|
from homeassistant.core import callback
 | 
						|
 | 
						|
REDACTED = "**REDACTED**"
 | 
						|
 | 
						|
 | 
						|
def partial_redact(
 | 
						|
    x: str | Any, unmasked_prefix: int = 4, unmasked_suffix: int = 4
 | 
						|
) -> str:
 | 
						|
    """Mask part of a string with *."""
 | 
						|
    if not isinstance(x, str):
 | 
						|
        return REDACTED
 | 
						|
 | 
						|
    unmasked = unmasked_prefix + unmasked_suffix
 | 
						|
    if len(x) < unmasked * 2:
 | 
						|
        return REDACTED
 | 
						|
 | 
						|
    if not unmasked_prefix and not unmasked_suffix:
 | 
						|
        return REDACTED
 | 
						|
 | 
						|
    suffix = x[-unmasked_suffix:] if unmasked_suffix else ""
 | 
						|
    return f"{x[:unmasked_prefix]}***{suffix}"
 | 
						|
 | 
						|
 | 
						|
@overload
 | 
						|
def async_redact_data[_ValueT](
 | 
						|
    data: Mapping, to_redact: Iterable[Any] | Mapping[Any, Callable[[_ValueT], _ValueT]]
 | 
						|
) -> dict: ...
 | 
						|
 | 
						|
 | 
						|
@overload
 | 
						|
def async_redact_data[_T, _ValueT](
 | 
						|
    data: _T, to_redact: Iterable[Any] | Mapping[Any, Callable[[_ValueT], _ValueT]]
 | 
						|
) -> _T: ...
 | 
						|
 | 
						|
 | 
						|
@callback
 | 
						|
def async_redact_data[_T, _ValueT](
 | 
						|
    data: _T, to_redact: Iterable[Any] | Mapping[Any, Callable[[_ValueT], _ValueT]]
 | 
						|
) -> _T:
 | 
						|
    """Redact sensitive data in a dict."""
 | 
						|
    if not isinstance(data, (Mapping, list)):
 | 
						|
        return data
 | 
						|
 | 
						|
    if isinstance(data, list):
 | 
						|
        return cast(_T, [async_redact_data(val, to_redact) for val in data])
 | 
						|
 | 
						|
    redacted = {**data}
 | 
						|
 | 
						|
    for key, value in redacted.items():
 | 
						|
        if value is None:
 | 
						|
            continue
 | 
						|
        if isinstance(value, str) and not value:
 | 
						|
            continue
 | 
						|
        if key in to_redact:
 | 
						|
            if isinstance(to_redact, Mapping):
 | 
						|
                redacted[key] = to_redact[key](value)
 | 
						|
            else:
 | 
						|
                redacted[key] = REDACTED
 | 
						|
        elif isinstance(value, Mapping):
 | 
						|
            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 cast(_T, redacted)
 |