mirror of
https://github.com/python-kasa/python-kasa.git
synced 2026-02-25 20:30:05 +00:00
Add support for tapo login_version 3 in sslaestransport (#1638)
Updates to get_default_credentials and DEFAULT_CREDENTIALS for handling a new default password for encryption_type 3 in TAPOCAMERA devices that use encryption_type 3. This adds support for devices like TC40.
This commit is contained in:
@@ -201,7 +201,7 @@ The following devices have been tested and confirmed as working. If your device
|
|||||||
- **Wall Switches**: S210, S220, S500, S500D, S505, S505D, TS15
|
- **Wall Switches**: S210, S220, S500, S500D, S505, S505D, TS15
|
||||||
- **Bulbs**: L430C, L430P, L510B, L510E, L530B, L530E, L535E, L630
|
- **Bulbs**: L430C, L430P, L510B, L510E, L530B, L530E, L535E, L630
|
||||||
- **Light Strips**: L900-10, L900-5, L920-5, L930-5
|
- **Light Strips**: L900-10, L900-5, L920-5, L930-5
|
||||||
- **Cameras**: C100, C110, C210, C220, C225, C325WB, C460, C520WS, C720, TC65, TC70
|
- **Cameras**: C100, C110, C210, C220, C225, C325WB, C460, C520WS, C720, TC40, TC65, TC70
|
||||||
- **Doorbells and chimes**: D100C, D130, D230
|
- **Doorbells and chimes**: D100C, D130, D230
|
||||||
- **Vacuums**: RV20 Max Plus, RV30 Max
|
- **Vacuums**: RV20 Max Plus, RV30 Max
|
||||||
- **Hubs**: H100, H200
|
- **Hubs**: H100, H200
|
||||||
|
|||||||
@@ -320,6 +320,8 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
|
|||||||
- Hardware: 1.0 (US) / Firmware: 1.2.8
|
- Hardware: 1.0 (US) / Firmware: 1.2.8
|
||||||
- **C720**
|
- **C720**
|
||||||
- Hardware: 1.0 (US) / Firmware: 1.2.3
|
- Hardware: 1.0 (US) / Firmware: 1.2.3
|
||||||
|
- **TC40**
|
||||||
|
- Hardware: 2.0 (EU) / Firmware: 1.0.4
|
||||||
- **TC65**
|
- **TC65**
|
||||||
- Hardware: 1.0 / Firmware: 1.3.9
|
- Hardware: 1.0 / Firmware: 1.3.9
|
||||||
- **TC70**
|
- **TC70**
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ class Credentials:
|
|||||||
password: str = field(default="", repr=False)
|
password: str = field(default="", repr=False)
|
||||||
|
|
||||||
|
|
||||||
def get_default_credentials(tuple: tuple[str, str]) -> Credentials:
|
def get_default_credentials(crdentials: tuple[str, str]) -> Credentials:
|
||||||
"""Return decoded default credentials."""
|
"""Return decoded default credentials."""
|
||||||
un = base64.b64decode(tuple[0].encode()).decode()
|
un = base64.b64decode(crdentials[0].encode()).decode()
|
||||||
pw = base64.b64decode(tuple[1].encode()).decode()
|
pw = base64.b64decode(crdentials[1].encode()).decode()
|
||||||
return Credentials(un, pw)
|
return Credentials(un, pw)
|
||||||
|
|
||||||
|
|
||||||
@@ -28,4 +28,5 @@ DEFAULT_CREDENTIALS = {
|
|||||||
"KASACAMERA": ("YWRtaW4=", "MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM="),
|
"KASACAMERA": ("YWRtaW4=", "MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM="),
|
||||||
"TAPO": ("dGVzdEB0cC1saW5rLm5ldA==", "dGVzdA=="),
|
"TAPO": ("dGVzdEB0cC1saW5rLm5ldA==", "dGVzdA=="),
|
||||||
"TAPOCAMERA": ("YWRtaW4=", "YWRtaW4="),
|
"TAPOCAMERA": ("YWRtaW4=", "YWRtaW4="),
|
||||||
|
"TAPOCAMERA_LV3": ("YWRtaW4=", "VFBMMDc1NTI2NDYwNjAz"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,12 @@ class SslAesTransport(BaseTransport):
|
|||||||
not self._credentials or self._credentials.username is None
|
not self._credentials or self._credentials.username is None
|
||||||
) and not self._credentials_hash:
|
) and not self._credentials_hash:
|
||||||
self._credentials = Credentials()
|
self._credentials = Credentials()
|
||||||
|
if self._login_version == 3:
|
||||||
|
_default_credentials = DEFAULT_CREDENTIALS["TAPOCAMERA_LV3"]
|
||||||
|
else:
|
||||||
|
_default_credentials = DEFAULT_CREDENTIALS["TAPOCAMERA"]
|
||||||
self._default_credentials: Credentials = get_default_credentials(
|
self._default_credentials: Credentials = get_default_credentials(
|
||||||
DEFAULT_CREDENTIALS["TAPOCAMERA"]
|
_default_credentials
|
||||||
)
|
)
|
||||||
self._http_client: HttpClient = HttpClient(config)
|
self._http_client: HttpClient = HttpClient(config)
|
||||||
|
|
||||||
|
|||||||
1037
tests/fixtures/smartcam/TC40(EU)_2.0_1.0.4.json
vendored
Normal file
1037
tests/fixtures/smartcam/TC40(EU)_2.0_1.0.4.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
from contextlib import nullcontext as does_not_raise
|
from contextlib import nullcontext as does_not_raise
|
||||||
@@ -12,7 +13,12 @@ import pytest
|
|||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from kasa.credentials import DEFAULT_CREDENTIALS, Credentials, get_default_credentials
|
from kasa.credentials import DEFAULT_CREDENTIALS, Credentials, get_default_credentials
|
||||||
from kasa.deviceconfig import DeviceConfig
|
from kasa.deviceconfig import (
|
||||||
|
DeviceConfig,
|
||||||
|
DeviceConnectionParameters,
|
||||||
|
DeviceEncryptionType,
|
||||||
|
DeviceFamily,
|
||||||
|
)
|
||||||
from kasa.exceptions import (
|
from kasa.exceptions import (
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
DeviceError,
|
DeviceError,
|
||||||
@@ -393,6 +399,53 @@ async def test_port_override():
|
|||||||
assert str(transport._app_url) == f"https://127.0.0.1:{port_override}"
|
assert str(transport._app_url) == f"https://127.0.0.1:{port_override}"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("login_version", "expected_password_b64"),
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
3,
|
||||||
|
"VFBMMDc1NTI2NDYwNjAz", # noqa: S105
|
||||||
|
id="version-3-uses-lv3-credentials",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
2,
|
||||||
|
"YWRtaW4=", # noqa: S105
|
||||||
|
id="version-2-uses-tapocamera-credentials",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
None,
|
||||||
|
"YWRtaW4=", # noqa: S105
|
||||||
|
id="no-version-uses-tapocamera-credentials",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_login_version_default_credentials(
|
||||||
|
mocker, login_version, expected_password_b64
|
||||||
|
):
|
||||||
|
"""Test that login_version=3 uses TAPOCAMERA_LV3 credentials while other versions use TAPOCAMERA."""
|
||||||
|
host = "127.0.0.1"
|
||||||
|
tapo_family = DeviceFamily.SmartIpCamera
|
||||||
|
aes_type = DeviceEncryptionType.Aes
|
||||||
|
mock_ssl_aes_device = MockSslAesDevice(host)
|
||||||
|
mocker.patch.object(
|
||||||
|
aiohttp.ClientSession, "post", side_effect=mock_ssl_aes_device.post
|
||||||
|
)
|
||||||
|
|
||||||
|
config = DeviceConfig(
|
||||||
|
host,
|
||||||
|
credentials=Credentials("foo", "bar"),
|
||||||
|
connection_type=DeviceConnectionParameters(
|
||||||
|
tapo_family, aes_type, login_version=login_version
|
||||||
|
),
|
||||||
|
)
|
||||||
|
transport = SslAesTransport(config=config)
|
||||||
|
assert transport._default_credentials.username == "admin"
|
||||||
|
password_b64 = base64.b64encode(
|
||||||
|
transport._default_credentials.password.encode()
|
||||||
|
).decode()
|
||||||
|
assert password_b64 == expected_password_b64
|
||||||
|
|
||||||
|
|
||||||
class MockSslAesDevice:
|
class MockSslAesDevice:
|
||||||
BAD_USER_RESP = {
|
BAD_USER_RESP = {
|
||||||
"error_code": SmartErrorCode.SESSION_EXPIRED.value,
|
"error_code": SmartErrorCode.SESSION_EXPIRED.value,
|
||||||
|
|||||||
Reference in New Issue
Block a user