Redact more credentials in stream URL query params (#82089)

Co-authored-by: Allen Porter <allen.porter@gmail.com>
This commit is contained in:
uvjustin 2022-11-15 12:42:40 +08:00 committed by GitHub
parent f96a4e2d0f
commit 6bab63fb0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 34 additions and 16 deletions

View File

@ -20,7 +20,6 @@ import asyncio
from collections.abc import Callable, Mapping from collections.abc import Callable, Mapping
import copy import copy
import logging import logging
import re
import secrets import secrets
import threading import threading
import time import time
@ -28,6 +27,7 @@ from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, cast from typing import TYPE_CHECKING, Any, Final, cast
import voluptuous as vol import voluptuous as vol
from yarl import URL
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
@ -91,17 +91,18 @@ __all__ = [
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
STREAM_SOURCE_REDACT_PATTERN = [
(re.compile(r"//.*:.*@"), "//****:****@"),
(re.compile(r"\?auth=.*"), "?auth=****"),
]
def redact_credentials(url: str) -> str:
def redact_credentials(data: str) -> str:
"""Redact credentials from string data.""" """Redact credentials from string data."""
for (pattern, repl) in STREAM_SOURCE_REDACT_PATTERN: yurl = URL(url)
data = pattern.sub(repl, data) if yurl.user is not None:
return data yurl = yurl.with_user("****")
if yurl.password is not None:
yurl = yurl.with_password("****")
redacted_query_params = dict.fromkeys(
{"auth", "user", "password"} & yurl.query.keys(), "****"
)
return str(yurl.update_query(redacted_query_params))
def create_stream( def create_stream(

View File

@ -727,11 +727,29 @@ async def test_update_stream_source(hass):
await stream.stop() await stream.stop()
async def test_worker_log(hass, caplog): test_worker_log_cases = (
("https://abcd:efgh@foo.bar", "https://****:****@foo.bar"),
(
"https://foo.bar/baz?user=abcd&password=efgh",
"https://foo.bar/baz?user=****&password=****",
),
(
"https://foo.bar/baz?param1=abcd&param2=efgh",
"https://foo.bar/baz?param1=abcd&param2=efgh",
),
(
"https://foo.bar/baz?param1=abcd&password=efgh",
"https://foo.bar/baz?param1=abcd&password=****",
),
)
@pytest.mark.parametrize("stream_url, redacted_url", test_worker_log_cases)
async def test_worker_log(hass, caplog, stream_url, redacted_url):
"""Test that the worker logs the url without username and password.""" """Test that the worker logs the url without username and password."""
stream = Stream( stream = Stream(
hass, hass,
"https://abcd:efgh@foo.bar", stream_url,
{}, {},
hass.data[DOMAIN][ATTR_SETTINGS], hass.data[DOMAIN][ATTR_SETTINGS],
dynamic_stream_settings(), dynamic_stream_settings(),
@ -740,13 +758,12 @@ async def test_worker_log(hass, caplog):
with patch("av.open") as av_open, pytest.raises(StreamWorkerError) as err: with patch("av.open") as av_open, pytest.raises(StreamWorkerError) as err:
av_open.side_effect = av.error.InvalidDataError(-2, "error") av_open.side_effect = av.error.InvalidDataError(-2, "error")
run_worker(hass, stream, "https://abcd:efgh@foo.bar") run_worker(hass, stream, stream_url)
await hass.async_block_till_done() await hass.async_block_till_done()
assert ( assert (
str(err.value) str(err.value) == f"Error opening stream (ERRORTYPE_-2, error) {redacted_url}"
== "Error opening stream (ERRORTYPE_-2, error) https://****:****@foo.bar"
) )
assert "https://abcd:efgh@foo.bar" not in caplog.text assert stream_url not in caplog.text
@pytest.fixture @pytest.fixture