From 490e8d68b2665141c3b589ab1d3f73f63a36ca65 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Thu, 1 Feb 2024 20:28:39 +0100 Subject: [PATCH 1/2] Fix port-override for aes&klap transports --- kasa/aestransport.py | 5 ++--- kasa/cli.py | 1 + kasa/httpclient.py | 4 ++++ kasa/klaptransport.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/kasa/aestransport.py b/kasa/aestransport.py index f784390bf..c4668b0a4 100644 --- a/kasa/aestransport.py +++ b/kasa/aestransport.py @@ -102,7 +102,7 @@ def __init__( self._session_cookie: Optional[Dict[str, str]] = None self._key_pair: Optional[KeyPair] = None - self._app_url = URL(f"http://{self._host}/app") + self._app_url = URL(f"http://{self._host}:{self._port}/app") self._token_url: Optional[URL] = None _LOGGER.debug("Created AES transport for %s", self._host) @@ -257,7 +257,6 @@ async def perform_handshake(self) -> None: self._session_expire_at = None self._session_cookie = None - url = f"http://{self._host}/app" # Device needs the content length or it will response with 500 headers = { **self.COMMON_HEADERS, @@ -266,7 +265,7 @@ async def perform_handshake(self) -> None: http_client = self._http_client status_code, resp_dict = await http_client.post( - url, + self._app_url, json=self._generate_key_pair_payload(), headers=headers, cookies_dict=self._session_cookie, diff --git a/kasa/cli.py b/kasa/cli.py index 42b13b9bb..3906fdcba 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -334,6 +334,7 @@ def _nop_echo(*args, **kwargs): ) config = DeviceConfig( host=host, + port_override=port, credentials=credentials, credentials_hash=credentials_hash, timeout=timeout, diff --git a/kasa/httpclient.py b/kasa/httpclient.py index 659ebdcfd..607efc7f9 100644 --- a/kasa/httpclient.py +++ b/kasa/httpclient.py @@ -1,5 +1,6 @@ """Module for HttpClientSession class.""" import asyncio +import logging from typing import Any, Dict, Optional, Tuple, Union import aiohttp @@ -13,6 +14,8 @@ ) from .json import loads as json_loads +_LOGGER = logging.getLogger(__name__) + def get_cookie_jar() -> aiohttp.CookieJar: """Return a new cookie jar with the correct options for device communication.""" @@ -54,6 +57,7 @@ async def post( If the request is provided via the json parameter json will be returned. """ + _LOGGER.debug("Posting to %s", url) response_data = None self._last_url = url self.client.cookie_jar.clear() diff --git a/kasa/klaptransport.py b/kasa/klaptransport.py index 0e585f2cd..265650d3c 100644 --- a/kasa/klaptransport.py +++ b/kasa/klaptransport.py @@ -121,7 +121,7 @@ def __init__( self._session_cookie: Optional[Dict[str, Any]] = None _LOGGER.debug("Created KLAP transport for %s", self._host) - self._app_url = URL(f"http://{self._host}/app") + self._app_url = URL(f"http://{self._host}:{self._port}/app") self._request_url = self._app_url / "request" @property From fbb000d6d9041e98d861a1f2f1dbd0982b19055e Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Fri, 2 Feb 2024 20:07:41 +0100 Subject: [PATCH 2/2] Add tests for port override --- kasa/tests/test_aestransport.py | 13 ++++++++++++- kasa/tests/test_klapprotocol.py | 27 +++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/kasa/tests/test_aestransport.py b/kasa/tests/test_aestransport.py index 5d590b7fc..a692ba9be 100644 --- a/kasa/tests/test_aestransport.py +++ b/kasa/tests/test_aestransport.py @@ -209,6 +209,17 @@ async def test_passthrough_errors(mocker, error_code): await transport.send(json_dumps(request)) +async def test_port_override(): + """Test that port override sets the app_url.""" + host = "127.0.0.1" + config = DeviceConfig( + host, credentials=Credentials("foo", "bar"), port_override=12345 + ) + transport = AesTransport(config=config) + + assert str(transport._app_url) == "http://127.0.0.1:12345/app" + + class MockAesDevice: class _mock_response: def __init__(self, status, json: dict): @@ -256,7 +267,7 @@ async def _post(self, url: URL, json: Dict[str, Any]): elif json["method"] == "login_device": return await self._return_login_response(url, json) else: - assert str(url) == f"http://{self.host}/app?token={self.token}" + assert str(url) == f"http://{self.host}:80/app?token={self.token}" return await self._return_send_response(url, json) async def _return_handshake_response(self, url: URL, json: Dict[str, Any]): diff --git a/kasa/tests/test_klapprotocol.py b/kasa/tests/test_klapprotocol.py index 1e007b930..fa25439e6 100644 --- a/kasa/tests/test_klapprotocol.py +++ b/kasa/tests/test_klapprotocol.py @@ -323,14 +323,14 @@ async def test_handshake( async def _return_handshake_response(url: URL, params=None, data=None, *_, **__): nonlocal client_seed, server_seed, device_auth_hash - if str(url) == "http://127.0.0.1/app/handshake1": + if str(url) == "http://127.0.0.1:80/app/handshake1": client_seed = data seed_auth_hash = _sha256( seed_auth_hash_calc1(client_seed, server_seed, device_auth_hash) ) return _mock_response(200, server_seed + seed_auth_hash) - elif str(url) == "http://127.0.0.1/app/handshake2": + elif str(url) == "http://127.0.0.1:80/app/handshake2": seed_auth_hash = _sha256( seed_auth_hash_calc2(client_seed, server_seed, device_auth_hash) ) @@ -367,14 +367,14 @@ async def test_query(mocker): async def _return_response(url: URL, params=None, data=None, *_, **__): nonlocal client_seed, server_seed, device_auth_hash, seq - if str(url) == "http://127.0.0.1/app/handshake1": + if str(url) == "http://127.0.0.1:80/app/handshake1": client_seed = data client_seed_auth_hash = _sha256(data + device_auth_hash) return _mock_response(200, server_seed + client_seed_auth_hash) - elif str(url) == "http://127.0.0.1/app/handshake2": + elif str(url) == "http://127.0.0.1:80/app/handshake2": return _mock_response(200, b"") - elif str(url) == "http://127.0.0.1/app/request": + elif str(url) == "http://127.0.0.1:80/app/request": encryption_session = KlapEncryptionSession( protocol._transport._encryption_session.local_seed, protocol._transport._encryption_session.remote_seed, @@ -419,16 +419,16 @@ async def test_authentication_failures(mocker, response_status, expectation): async def _return_response(url: URL, params=None, data=None, *_, **__): nonlocal client_seed, server_seed, device_auth_hash, response_status - if str(url) == "http://127.0.0.1/app/handshake1": + if str(url) == "http://127.0.0.1:80/app/handshake1": client_seed = data client_seed_auth_hash = _sha256(data + device_auth_hash) return _mock_response( response_status[0], server_seed + client_seed_auth_hash ) - elif str(url) == "http://127.0.0.1/app/handshake2": + elif str(url) == "http://127.0.0.1:80/app/handshake2": return _mock_response(response_status[1], b"") - elif str(url) == "http://127.0.0.1/app/request": + elif str(url) == "http://127.0.0.1:80/app/request": return _mock_response(response_status[2], b"") mocker.patch.object(aiohttp.ClientSession, "post", side_effect=_return_response) @@ -438,3 +438,14 @@ async def _return_response(url: URL, params=None, data=None, *_, **__): with expectation: await protocol.query({}) + + +async def test_port_override(): + """Test that port override sets the app_url.""" + host = "127.0.0.1" + config = DeviceConfig( + host, credentials=Credentials("foo", "bar"), port_override=12345 + ) + transport = KlapTransport(config=config) + + assert str(transport._app_url) == "http://127.0.0.1:12345/app"