mirror of
https://github.com/python-kasa/python-kasa.git
synced 2026-10-06 21:03:50 +00:00
SslAesTransport: handshake again when a camera answers 401 to passthrough (#1771)
Some checks failed
CI / Perform Lint Checks (3.14) (push) Has been cancelled
CI / Python 3.11 on macos-latest (push) Has been cancelled
CI / Python 3.12 on macos-latest (push) Has been cancelled
CI / Python 3.13 on macos-latest (push) Has been cancelled
CI / Python 3.14 on macos-latest (push) Has been cancelled
CI / Python 3.11 on ubuntu-latest (push) Has been cancelled
CI / Python 3.12 on ubuntu-latest (push) Has been cancelled
CI / Python 3.13 on ubuntu-latest (push) Has been cancelled
CI / Python 3.14 on ubuntu-latest (push) Has been cancelled
CI / Python 3.11 on windows-latest (push) Has been cancelled
CI / Python 3.12 on windows-latest (push) Has been cancelled
CI / Python 3.13 on windows-latest (push) Has been cancelled
CI / Python 3.14 on windows-latest (push) Has been cancelled
CodeQL Checks / Analyze (python) (push) Has been cancelled
Stale / stale (push) Has been cancelled
Some checks failed
CI / Perform Lint Checks (3.14) (push) Has been cancelled
CI / Python 3.11 on macos-latest (push) Has been cancelled
CI / Python 3.12 on macos-latest (push) Has been cancelled
CI / Python 3.13 on macos-latest (push) Has been cancelled
CI / Python 3.14 on macos-latest (push) Has been cancelled
CI / Python 3.11 on ubuntu-latest (push) Has been cancelled
CI / Python 3.12 on ubuntu-latest (push) Has been cancelled
CI / Python 3.13 on ubuntu-latest (push) Has been cancelled
CI / Python 3.14 on ubuntu-latest (push) Has been cancelled
CI / Python 3.11 on windows-latest (push) Has been cancelled
CI / Python 3.12 on windows-latest (push) Has been cancelled
CI / Python 3.13 on windows-latest (push) Has been cancelled
CI / Python 3.14 on windows-latest (push) Has been cancelled
CodeQL Checks / Analyze (python) (push) Has been cancelled
Stale / stale (push) Has been cancelled
Some cameras (C220 and C100 on the 1.4.4 line in those reports) drop the session about every ten minutes and answer the next `securePassthrough` with HTTP 401 and `-40421` in the body. With this change a 401 on passthrough marks the transport as needing a handshake and raises `_RetryableError`, so the protocol logs in again and retries the same request. --------- Co-authored-by: freeKC <7538438+freeKC@users.noreply.github.com>
This commit is contained in:
@@ -274,6 +274,20 @@ class SslAesTransport(BaseTransport):
|
||||
_LOGGER.debug(msg)
|
||||
raise _RetryableError(msg)
|
||||
|
||||
# Some devices answer 401 when the session has expired and they
|
||||
# require a new handshake: reauthenticate and retry the request.
|
||||
if status_code == 401:
|
||||
_LOGGER.debug(
|
||||
"Device %s replied with status 401 to passthrough, "
|
||||
"session expired, handshake required",
|
||||
self._host,
|
||||
)
|
||||
self._state = TransportState.HANDSHAKE_REQUIRED
|
||||
raise _RetryableError(
|
||||
f"{self._host} responded with status 401 to passthrough, "
|
||||
"session expired"
|
||||
)
|
||||
|
||||
if status_code != 200:
|
||||
raise KasaException(
|
||||
f"{self._host} responded with an unexpected "
|
||||
|
||||
@@ -797,3 +797,24 @@ class MockSslAesDevice:
|
||||
|
||||
def put_next_response(self, request: dict | bytes) -> None:
|
||||
self._next_responses.append(request)
|
||||
|
||||
|
||||
async def test_passthrough_401_requires_new_handshake(mocker):
|
||||
"""A 401 on passthrough means the session expired: retryable, new handshake."""
|
||||
host = "127.0.0.1"
|
||||
mock_ssl_aes_device = MockSslAesDevice(host)
|
||||
mocker.patch.object(
|
||||
aiohttp.ClientSession, "post", side_effect=mock_ssl_aes_device.post
|
||||
)
|
||||
transport = SslAesTransport(
|
||||
config=DeviceConfig(host, credentials=Credentials(MOCK_USER, MOCK_PWD))
|
||||
)
|
||||
request = {"method": "getDeviceInfo", "params": None}
|
||||
|
||||
await transport.perform_handshake()
|
||||
assert transport._state is TransportState.ESTABLISHED
|
||||
|
||||
mock_ssl_aes_device.status_code = 401
|
||||
with pytest.raises(_RetryableError, match="session expired"):
|
||||
await transport.send(json_dumps(request))
|
||||
assert transport._state is TransportState.HANDSHAKE_REQUIRED
|
||||
|
||||
Reference in New Issue
Block a user