mirror of
https://github.com/python-kasa/python-kasa.git
synced 2026-11-06 03:47:22 +00:00
Keep the session token out of the HTTP client's debug log (#1835)
Camera logins over `SslAesTransport` send their requests to `/stok=<token>/ds`, and `HttpClient` logs every URL it posts to at debug, so the session token ends up in the debug logs people paste into issues. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import builtins
|
import builtins
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import ssl
|
import ssl
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -22,6 +23,9 @@ from .json import loads as json_loads
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# The session token in /stok=<token>/ds URLs, e.g. after a camera's secure login
|
||||||
|
_SESSION_TOKEN = re.compile(r"(stok=)[^/?#]+")
|
||||||
|
|
||||||
|
|
||||||
def get_cookie_jar() -> aiohttp.CookieJar:
|
def get_cookie_jar() -> aiohttp.CookieJar:
|
||||||
"""Return a new cookie jar with the correct options for device communication."""
|
"""Return a new cookie jar with the correct options for device communication."""
|
||||||
@@ -86,7 +90,8 @@ class HttpClient:
|
|||||||
)
|
)
|
||||||
await asyncio.sleep(sleep)
|
await asyncio.sleep(sleep)
|
||||||
|
|
||||||
_LOGGER.debug("Posting to %s", url)
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
|
_LOGGER.debug("Posting to %s", _SESSION_TOKEN.sub(r"\1REDACTED", str(url)))
|
||||||
response_data = None
|
response_data = None
|
||||||
self._last_url = url
|
self._last_url = url
|
||||||
self.client.cookie_jar.clear()
|
self.client.cookie_jar.clear()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import builtins
|
import builtins
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -114,3 +115,37 @@ async def test_httpclient_errors(
|
|||||||
assert mock_response.call_count == 1
|
assert mock_response.call_count == 1
|
||||||
else:
|
else:
|
||||||
assert conn.call_count == 1
|
assert conn.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
class _OkResponse:
|
||||||
|
status = 200
|
||||||
|
connection = None
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_t, exc_v, exc_tb) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def read(self) -> bytes:
|
||||||
|
return b"{}"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_httpclient_debug_log_hides_session_token(
|
||||||
|
mocker: MockerFixture, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""The /stok=<token>/ds session token of a camera login is not logged."""
|
||||||
|
|
||||||
|
async def _post(url, *_, **__):
|
||||||
|
return _OkResponse()
|
||||||
|
|
||||||
|
mocker.patch.object(aiohttp.ClientSession, "post", side_effect=_post)
|
||||||
|
client = HttpClient(DeviceConfig("127.0.0.1"))
|
||||||
|
|
||||||
|
with caplog.at_level(logging.DEBUG, logger="kasa.httpclient"):
|
||||||
|
await client.post(URL("https://127.0.0.1/stok=S3cr3tT0k3n/ds"), data=b"x")
|
||||||
|
await client.post(URL("http://127.0.0.1/app/handshake1"), data=b"x")
|
||||||
|
|
||||||
|
assert "S3cr3tT0k3n" not in caplog.text
|
||||||
|
assert "Posting to https://127.0.0.1/stok=REDACTED/ds" in caplog.text
|
||||||
|
assert "Posting to http://127.0.0.1/app/handshake1" in caplog.text
|
||||||
|
|||||||
Reference in New Issue
Block a user