From a8f3d341b4ac3b53503973b356fc25cc918b7a5d Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 18 Jan 2022 21:28:57 +0100 Subject: [PATCH 01/13] Add local support to models --- pyoverkiz/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyoverkiz/models.py b/pyoverkiz/models.py index 5ff4d949..751b63c3 100644 --- a/pyoverkiz/models.py +++ b/pyoverkiz/models.py @@ -436,7 +436,8 @@ def __init__( self.protocol_type = protocol_type self.name = EventName(name) self.failure_type_code = ( - None if failure_type_code is None else FailureType(failure_type_code) + None if failure_type_code is None else FailureType( + failure_type_code) ) @@ -542,7 +543,8 @@ def __init__( self.time_reliable = time_reliable self.connectivity = Connectivity(**connectivity) self.up_to_date = up_to_date - self.update_status = UpdateBoxStatus(update_status) if update_status else None + self.update_status = UpdateBoxStatus( + update_status) if update_status else None self.sync_in_progress = sync_in_progress self.partners = [Partner(**p) for p in partners] if partners else [] self.type = GatewayType(type) if type else None From 74a7101afdfcf75216087a0d15496d6920d60a2c Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 19 Jan 2022 11:09:24 +0100 Subject: [PATCH 02/13] Add local event tests --- tests/test_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 3284d368..9b4c8bc3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -33,7 +33,11 @@ async def test_get_devices_basic(self, client): "fixture_name, event_length", [ ("events.json", 16), + << << << < HEAD ("local_events.json", 3), + == == == = + # ("local_events.json", 3), + >>>>>> > c921bed(Add local event tests) ], ) @pytest.mark.asyncio From bf9f6e4c6e57982a26d99be119b2d33689805fc9 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 4 Apr 2022 15:49:34 +0000 Subject: [PATCH 03/13] Fix local event listener --- pyoverkiz/models.py | 6 ++---- tests/test_client.py | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pyoverkiz/models.py b/pyoverkiz/models.py index 751b63c3..5ff4d949 100644 --- a/pyoverkiz/models.py +++ b/pyoverkiz/models.py @@ -436,8 +436,7 @@ def __init__( self.protocol_type = protocol_type self.name = EventName(name) self.failure_type_code = ( - None if failure_type_code is None else FailureType( - failure_type_code) + None if failure_type_code is None else FailureType(failure_type_code) ) @@ -543,8 +542,7 @@ def __init__( self.time_reliable = time_reliable self.connectivity = Connectivity(**connectivity) self.up_to_date = up_to_date - self.update_status = UpdateBoxStatus( - update_status) if update_status else None + self.update_status = UpdateBoxStatus(update_status) if update_status else None self.sync_in_progress = sync_in_progress self.partners = [Partner(**p) for p in partners] if partners else [] self.type = GatewayType(type) if type else None diff --git a/tests/test_client.py b/tests/test_client.py index 9b4c8bc3..3284d368 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -33,11 +33,7 @@ async def test_get_devices_basic(self, client): "fixture_name, event_length", [ ("events.json", 16), - << << << < HEAD ("local_events.json", 3), - == == == = - # ("local_events.json", 3), - >>>>>> > c921bed(Add local event tests) ], ) @pytest.mark.asyncio From 4b56f772749925ee5a0c8ee932052961401ec3e9 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 4 Apr 2022 16:14:52 +0000 Subject: [PATCH 04/13] Add local API support --- pyoverkiz/client.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index bea24303..8fffdbf9 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -94,12 +94,15 @@ class OverkizClient: _refresh_token: str | None = None _expires_in: datetime.datetime | None = None _access_token: str | None = None + _local_access_token: str | None = None + _is_local: bool = False def __init__( self, username: str, password: str, server: OverkizServer, + token: str | None = None, session: ClientSession | None = None, ) -> None: """ @@ -122,6 +125,10 @@ def __init__( self.session = session if session else ClientSession() + if "/enduser-mobile-web/1/enduserAPI/" in server.endpoint: + self._is_local = True + self._local_access_token = token + async def __aenter__(self) -> OverkizClient: return self @@ -148,6 +155,13 @@ async def login( Authenticate and create an API session allowing access to the other operations. Caller must provide one of [userId+userPassword, userId+ssoToken, accessToken, jwt] """ + # Local authentication + # TODO check which endpoint can be used to validate the token! + if self._is_local: + if register_event_listener: + await self.register_event_listener() + return True + # Somfy TaHoma authentication using access_token if self.server == SUPPORTED_SERVERS["somfy_europe"]: await self.somfy_tahoma_get_access_token() @@ -717,6 +731,10 @@ async def __get(self, path: str) -> Any: if self._access_token: headers["Authorization"] = f"Bearer {self._access_token}" + # TODO check what is the definitive header + if self._local_access_token: + headers["X-Auth-Token"] = f"Bearer {self._access_token}" + async with self.session.get( f"{self.server.endpoint}{path}", headers=headers ) as response: @@ -733,6 +751,10 @@ async def __post( await self._refresh_token_if_expired() headers["Authorization"] = f"Bearer {self._access_token}" + # TODO check what is the definitive header + if self._local_access_token: + headers["X-Auth-Token"] = f"Bearer {self._access_token}" + async with self.session.post( f"{self.server.endpoint}{path}", data=data, json=payload, headers=headers ) as response: @@ -748,6 +770,10 @@ async def __delete(self, path: str) -> None: if self._access_token: headers["Authorization"] = f"Bearer {self._access_token}" + # TODO check what is the definitive header + if self._local_access_token: + headers["X-Auth-Token"] = f"Bearer {self._access_token}" + async with self.session.delete( f"{self.server.endpoint}{path}", headers=headers ) as response: From 431ea9ba1c26773488b9cc6835123ed511bec6cd Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 7 Apr 2022 12:03:46 +0000 Subject: [PATCH 05/13] Add first start to verify local cert --- pyoverkiz/client.py | 6 ++++++ pyoverkiz/overkiz-root-ca-2048.crt | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 pyoverkiz/overkiz-root-ca-2048.crt diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index 8fffdbf9..1dcf1578 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -129,6 +129,12 @@ def __init__( self._is_local = True self._local_access_token = token + # TODO To avoid security issue, add the following authority to + # your HTTPS client trust store: https://ca.overkiz.com/overkiz-root-ca-2048.crt + # sslcontext = ssl.create_default_context( + # cafile="overkiz-root-ca-2048.crt" + # ) + async def __aenter__(self) -> OverkizClient: return self diff --git a/pyoverkiz/overkiz-root-ca-2048.crt b/pyoverkiz/overkiz-root-ca-2048.crt new file mode 100644 index 00000000..9d9adef8 --- /dev/null +++ b/pyoverkiz/overkiz-root-ca-2048.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPDCCAiSgAwIBAgIJAOvswclbF4QnMA0GCSqGSIb3DQEBCwUAMEsxEDAOBgNV +BAoTB092ZXJraXoxEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD092ZXJraXog +Um9vdCBDQTELMAkGA1UEBhMCRlIwHhcNMTYwNDI3MTI1ODE1WhcNMzYwNDI3MTI1 +ODE1WjBLMRAwDgYDVQQKEwdPdmVya2l6MRAwDgYDVQQLEwdSb290IENBMRgwFgYD +VQQDEw9PdmVya2l6IFJvb3QgQ0ExCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAsszRfbcNCEoD9ZfzTbXfuMK8CrXGqBR/ZOCk4guN +aRKqZ/rLQm3V6Q+dlJ/8qle5J3KN5bZmqT4qXKHwJsaOiLfPyAptSM6vuIlls2N+ +UsKkv3m5+gTyLaSGMS4wh2GoOCa21V9t5wYUQnoFaQByVNyl+kkrWLpKw5gQasU0 +xkVsjAVKgkkb3puBl7sZgiSoz97I9U9JUkg3spH0I84CZRI+JejioDHvkZEyf83j ++QFxSTV/hZkUwUY/X0zt2dTZuliCTePeCdANryo6+9TbBp98j/SB1s59FcO8NSSK +sV07rTFlM9/soko2/J0aTtXHE86wFq7vfFVZzZxsQpIBbwIDAQABoyMwITAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA +GU7xUWlZZEVnEK0k3Z1FoRl9xA7cLOiDVCQ5qQDFfQgGpMtXv1PKsQNZ6T6tZN3d +bdzsqcQXtLhXknz6aGBZNR4g6liQhVuCaiyURaI+LM2KuSZnbixs3+1SPBvxHrJh +/gOsxctxq+0DALnOK9qbGl6N5DtjM/EC5Qve71c+UVTEcJjJ3L2S1Ne+PxDOJuUC +JsOLUk96G+uLn6CQB5Wu8fYrkWAjF3yrxkCoZCqOvVrnbL77vXmz2mlqNHSJt3Ur +ndWJLVvrRFKdSG6WiNCh/Q+ARQAorN60JD9x8+IyXRGvlZl7KVeRduE2rjZuom7h +QLMnmaF+oFW5mnhh9gu6Gg== +-----END CERTIFICATE----- From 5a56ccb107e355b32265e6b72576b05622aeab63 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 7 Apr 2022 11:47:37 +0000 Subject: [PATCH 06/13] Implement all local endpoints --- pyoverkiz/client.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index 1dcf1578..63e7e691 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -246,7 +246,8 @@ async def refresh_token(self) -> None: return if not self._refresh_token: - raise ValueError("No refresh token provided. Login method must be used.") + raise ValueError( + "No refresh token provided. Login method must be used.") # &grant_type=refresh_token&refresh_token=REFRESH_TOKEN # Request access token @@ -302,7 +303,8 @@ async def cozytouch_login(self) -> str: # {'error': 'invalid_grant', # 'error_description': 'Provided Authorization Grant is invalid.'} if "error" in token and token["error"] == "invalid_grant": - raise CozyTouchBadCredentialsException(token["error_description"]) + raise CozyTouchBadCredentialsException( + token["error_description"]) if "token_type" not in token: raise CozyTouchServiceException("No CozyTouch token provided.") @@ -479,7 +481,8 @@ async def get_execution_history(self) -> list[HistoryExecution]: List execution history """ response = await self.__get("history/executions") - execution_history = [HistoryExecution(**h) for h in humps.decamelize(response)] + execution_history = [HistoryExecution( + **h) for h in humps.decamelize(response)] return execution_history @@ -796,7 +799,8 @@ async def check_response(response: ClientResponse) -> None: except JSONDecodeError as error: result = await response.text() if "Server is down for maintenance" in result: - raise MaintenanceException("Server is down for maintenance") from error + raise MaintenanceException( + "Server is down for maintenance") from error raise Exception( f"Unknown error while requesting {response.url}. {response.status} - {result}" ) from error From 21a255dd7235a69999efc075e539d724ad73acc2 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 7 Apr 2022 12:26:41 +0000 Subject: [PATCH 07/13] Local fixesak --- pyoverkiz/client.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index 63e7e691..c320b00c 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -3,6 +3,8 @@ import asyncio import datetime +import os +import ssl import urllib.parse from json import JSONDecodeError from types import TracebackType @@ -94,8 +96,8 @@ class OverkizClient: _refresh_token: str | None = None _expires_in: datetime.datetime | None = None _access_token: str | None = None - _local_access_token: str | None = None _is_local: bool = False + _ssl_context: ssl.SSLContext | None = None def __init__( self, @@ -127,13 +129,14 @@ def __init__( if "/enduser-mobile-web/1/enduserAPI/" in server.endpoint: self._is_local = True - self._local_access_token = token + self._access_token = token - # TODO To avoid security issue, add the following authority to + # To avoid security issue, add the following authority to # your HTTPS client trust store: https://ca.overkiz.com/overkiz-root-ca-2048.crt - # sslcontext = ssl.create_default_context( - # cafile="overkiz-root-ca-2048.crt" - # ) + self._ssl_context = ssl.create_default_context( + cafile=os.path.dirname(os.path.realpath(__file__)) + + "/overkiz-root-ca-2048.crt" + ) async def __aenter__(self) -> OverkizClient: return self @@ -740,12 +743,10 @@ async def __get(self, path: str) -> Any: if self._access_token: headers["Authorization"] = f"Bearer {self._access_token}" - # TODO check what is the definitive header - if self._local_access_token: - headers["X-Auth-Token"] = f"Bearer {self._access_token}" - async with self.session.get( - f"{self.server.endpoint}{path}", headers=headers + f"{self.server.endpoint}{path}", + headers=headers, + ssl_context=self._ssl_context, ) as response: await self.check_response(response) return await response.json() @@ -760,10 +761,6 @@ async def __post( await self._refresh_token_if_expired() headers["Authorization"] = f"Bearer {self._access_token}" - # TODO check what is the definitive header - if self._local_access_token: - headers["X-Auth-Token"] = f"Bearer {self._access_token}" - async with self.session.post( f"{self.server.endpoint}{path}", data=data, json=payload, headers=headers ) as response: @@ -779,10 +776,6 @@ async def __delete(self, path: str) -> None: if self._access_token: headers["Authorization"] = f"Bearer {self._access_token}" - # TODO check what is the definitive header - if self._local_access_token: - headers["X-Auth-Token"] = f"Bearer {self._access_token}" - async with self.session.delete( f"{self.server.endpoint}{path}", headers=headers ) as response: From 88f976912622ab6b8ce1622acfc165ff0a2f6090 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 7 Apr 2022 14:34:24 +0200 Subject: [PATCH 08/13] Add extra test --- tests/fixtures/setup/setup_local_tahoma.json | 1544 ++++++++++++++++++ tests/test_client.py | 1 + 2 files changed, 1545 insertions(+) create mode 100644 tests/fixtures/setup/setup_local_tahoma.json diff --git a/tests/fixtures/setup/setup_local_tahoma.json b/tests/fixtures/setup/setup_local_tahoma.json new file mode 100644 index 00000000..006fb33c --- /dev/null +++ b/tests/fixtures/setup/setup_local_tahoma.json @@ -0,0 +1,1544 @@ +{ + "gateways": [ + { + "connectivity": { + "status": "OK", + "protocolVersion": "2022.1.4-26" + }, + "gatewayId": "****-****-3293" + } + ], + "devices": [ + { + "deviceURL": "io://****-****-3293/5770898", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 1, + "name": "core:Memorized1PositionState", + "value": 80 + }, + { + "type": 1, + "name": "core:TargetClosureState", + "value": 0 + }, + { + "type": 3, + "name": "core:StatusState", + "value": "available" + }, + { + "type": 3, + "name": "core:DiscreteRSSILevelState", + "value": "normal" + }, + { + "type": 1, + "name": "core:RSSILevelState", + "value": 70 + }, + { + "type": 11, + "name": "core:ManufacturerSettingsState", + "value": { + "current_position": 0 + } + }, + { + "type": 1, + "name": "core:ClosureState", + "value": 0 + }, + { + "type": 3, + "name": "core:OpenClosedState", + "value": "open" + }, + { + "type": 1, + "name": "core:DeploymentState", + "value": 0 + }, + { + "type": 6, + "name": "core:MovingState", + "value": false + }, + { + "type": 3, + "name": "core:NameState", + "value": "** **" + } + ], + "label": "** **", + "subsystemId": 0, + "attributes": [ + { + "type": 3, + "name": "core:Manufacturer", + "value": "Somfy" + }, + { + "type": 3, + "name": "core:FirmwareRevision", + "value": "5104761X04" + } + ], + "enabled": true, + "controllableName": "io:VerticalExteriorAwningIOComponent", + "definition": { + "states": [ + { + "name": "core:StatusState" + }, + { + "name": "core:NameState" + }, + { + "name": "core:AdditionalStatusState" + }, + { + "name": "core:TargetClosureState" + }, + { + "name": "core:SecuredPositionState" + }, + { + "name": "core:ManufacturerSettingsState" + }, + { + "name": "core:ClosureState" + }, + { + "name": "core:OpenClosedState" + }, + { + "name": "core:DeploymentState" + }, + { + "name": "core:MovingState" + }, + { + "name": "core:DiscreteRSSILevelState" + }, + { + "name": "core:RSSILevelState" + }, + { + "name": "core:Memorized1PositionState" + } + ], + "widgetName": "PositionableScreen", + "type": "ACTUATOR", + "commands": [ + { + "commandName": "stop", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setDeployment", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "delayedStopIdentify", + "paramsSig": "p1" + }, + { + "nparams": 2, + "commandName": "runManufacturerSettingsCommand", + "paramsSig": "p1,p2" + }, + { + "commandName": "down", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setClosure", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllers", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setConfigState", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "pairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "advancedRefresh", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "refreshMemorized1Position", + "nparams": 0 + }, + { + "commandName": "undeploy", + "nparams": 0 + }, + { + "commandName": "up", + "nparams": 0 + }, + { + "commandName": "startIdentify", + "nparams": 0 + }, + { + "commandName": "stopIdentify", + "nparams": 0 + }, + { + "commandName": "open", + "nparams": 0 + }, + { + "commandName": "deploy", + "nparams": 0 + }, + { + "commandName": "keepOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "sendIOKey", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setMemorized1Position", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "wink", + "paramsSig": "p1" + }, + { + "commandName": "close", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setName", + "paramsSig": "p1" + }, + { + "commandName": "identify", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setPosition", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "unpairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "setSecuredPosition", + "paramsSig": "p1" + }, + { + "commandName": "my", + "nparams": 0 + }, + { + "commandName": "getName", + "nparams": 0 + } + ], + "uiClass": "ExteriorScreen" + } + }, + { + "deviceURL": "io://****-****-3293/3541212", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 3, + "name": "core:StatusState", + "value": "available" + }, + { + "type": 3, + "name": "core:DiscreteRSSILevelState", + "value": "normal" + }, + { + "type": 1, + "name": "core:RSSILevelState", + "value": 72 + }, + { + "type": 1, + "name": "core:Memorized1PositionState", + "value": 89 + }, + { + "type": 1, + "name": "core:TargetClosureState", + "value": 0 + }, + { + "type": 3, + "name": "core:NameState", + "value": "** **" + }, + { + "type": 11, + "name": "core:ManufacturerSettingsState", + "value": { + "current_position": 0 + } + }, + { + "type": 1, + "name": "core:ClosureState", + "value": 0 + }, + { + "type": 3, + "name": "core:OpenClosedState", + "value": "open" + }, + { + "type": 1, + "name": "core:DeploymentState", + "value": 0 + }, + { + "type": 6, + "name": "core:MovingState", + "value": false + } + ], + "label": "** **", + "subsystemId": 0, + "attributes": [ + { + "type": 3, + "name": "core:Manufacturer", + "value": "Somfy" + }, + { + "type": 3, + "name": "core:FirmwareRevision", + "value": "5121525A07" + } + ], + "enabled": true, + "controllableName": "io:VerticalExteriorAwningIOComponent", + "definition": { + "states": [ + { + "name": "core:ManufacturerSettingsState" + }, + { + "name": "core:DiscreteRSSILevelState" + }, + { + "name": "core:RSSILevelState" + }, + { + "name": "core:Memorized1PositionState" + }, + { + "name": "core:TargetClosureState" + }, + { + "name": "core:SecuredPositionState" + }, + { + "name": "core:ClosureState" + }, + { + "name": "core:OpenClosedState" + }, + { + "name": "core:DeploymentState" + }, + { + "name": "core:MovingState" + }, + { + "name": "core:AdditionalStatusState" + }, + { + "name": "core:NameState" + }, + { + "name": "core:StatusState" + } + ], + "widgetName": "PositionableScreen", + "type": "ACTUATOR", + "commands": [ + { + "commandName": "stop", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setDeployment", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "delayedStopIdentify", + "paramsSig": "p1" + }, + { + "nparams": 2, + "commandName": "runManufacturerSettingsCommand", + "paramsSig": "p1,p2" + }, + { + "commandName": "down", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setClosure", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllers", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setConfigState", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "pairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "advancedRefresh", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "refreshMemorized1Position", + "nparams": 0 + }, + { + "commandName": "undeploy", + "nparams": 0 + }, + { + "commandName": "up", + "nparams": 0 + }, + { + "commandName": "startIdentify", + "nparams": 0 + }, + { + "commandName": "stopIdentify", + "nparams": 0 + }, + { + "commandName": "open", + "nparams": 0 + }, + { + "commandName": "deploy", + "nparams": 0 + }, + { + "commandName": "keepOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "sendIOKey", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setMemorized1Position", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "wink", + "paramsSig": "p1" + }, + { + "commandName": "close", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setName", + "paramsSig": "p1" + }, + { + "commandName": "identify", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setPosition", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "unpairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "setSecuredPosition", + "paramsSig": "p1" + }, + { + "commandName": "my", + "nparams": 0 + }, + { + "commandName": "getName", + "nparams": 0 + } + ], + "uiClass": "ExteriorScreen" + } + }, + { + "deviceURL": "io://****-****-3293/7614902", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 3, + "name": "core:NameState", + "value": "** **" + }, + { + "type": 3, + "name": "core:SlatsOpenClosedState", + "value": "closed" + }, + { + "type": 1, + "name": "core:SlatsOrientationState", + "value": 100 + }, + { + "type": 1, + "name": "core:SlateOrientationState", + "value": 100 + }, + { + "type": 3, + "name": "core:DiscreteRSSILevelState", + "value": "normal" + }, + { + "type": 1, + "name": "core:RSSILevelState", + "value": 58 + }, + { + "type": 3, + "name": "core:StatusState", + "value": "available" + } + ], + "label": "** **", + "subsystemId": 0, + "attributes": [ + { + "type": 3, + "name": "core:Manufacturer", + "value": "Somfy" + }, + { + "type": 3, + "name": "core:FirmwareRevision", + "value": "5133756X72" + } + ], + "enabled": true, + "controllableName": "io:SimpleBioclimaticPergolaIOComponent", + "definition": { + "states": [ + { + "name": "core:AdditionalStatusState" + }, + { + "name": "core:DiscreteRSSILevelState" + }, + { + "name": "core:RSSILevelState" + }, + { + "name": "core:NameState" + }, + { + "name": "core:StatusState" + }, + { + "name": "core:ManufacturerSettingsState" + }, + { + "name": "core:SlatsOpenClosedState" + }, + { + "name": "core:SlatsOrientationState" + }, + { + "name": "core:SlateOrientationState" + }, + { + "name": "core:Memorized1OrientationState" + }, + { + "name": "core:SecuredOrientationState" + } + ], + "widgetName": "BioclimaticPergola", + "type": "ACTUATOR", + "commands": [ + { + "commandName": "stop", + "nparams": 0 + }, + { + "commandName": "closeSlats", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setSecuredOrientation", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllers", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setConfigState", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "pairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "commandName": "unpairAllOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setOrientation", + "paramsSig": "p1" + }, + { + "commandName": "refreshMemorized1Orientation", + "nparams": 0 + }, + { + "commandName": "startIdentify", + "nparams": 0 + }, + { + "commandName": "stopIdentify", + "nparams": 0 + }, + { + "commandName": "sendIOKey", + "nparams": 0 + }, + { + "commandName": "openSlats", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "wink", + "paramsSig": "p1" + }, + { + "commandName": "getName", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "unpairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "advancedRefresh", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "setName", + "paramsSig": "p1" + }, + { + "commandName": "identify", + "nparams": 0 + }, + { + "commandName": "keepOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "delayedStopIdentify", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "setMemorized1Orientation", + "paramsSig": "p1" + }, + { + "commandName": "my", + "nparams": 0 + }, + { + "nparams": 2, + "commandName": "runManufacturerSettingsCommand", + "paramsSig": "p1,p2" + } + ], + "uiClass": "Pergola" + } + }, + { + "deviceURL": "io://****-****-3293/16483707", + "available": true, + "synced": true, + "type": 5, + "states": [], + "label": "** *(**)*", + "subsystemId": 0, + "attributes": [ + { + "type": 3, + "name": "core:Manufacturer", + "value": "Somfy" + } + ], + "enabled": true, + "controllableName": "io:StackComponent", + "definition": { + "states": [], + "widgetName": "IOStack", + "type": "PROTOCOL_GATEWAY", + "commands": [ + { + "nparams": 1, + "commandName": "discoverActuators", + "paramsSig": "p1" + }, + { + "commandName": "joinNetwork", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "advancedSomfyDiscover", + "paramsSig": "p1" + }, + { + "commandName": "resetNetworkSecurity", + "nparams": 0 + }, + { + "commandName": "shareNetwork", + "nparams": 0 + }, + { + "nparams": 0, + "commandName": "discover1WayController", + "paramsSig": "*p1,*p2" + }, + { + "nparams": 1, + "commandName": "discoverSensors", + "paramsSig": "p1" + }, + { + "commandName": "discoverSomfyUnsetActuators", + "nparams": 0 + } + ], + "uiClass": "ProtocolGateway" + } + }, + { + "deviceURL": "internal://****-****-3293/alarm/0", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 3, + "name": "core:NameState", + "value": "** **" + }, + { + "type": 3, + "name": "internal:IntrusionDetectedState", + "value": "notDetected" + }, + { + "type": 3, + "name": "internal:CurrentAlarmModeState", + "value": "off" + }, + { + "type": 3, + "name": "internal:TargetAlarmModeState", + "value": "off" + }, + { + "type": 1, + "name": "internal:AlarmDelayState", + "value": 30 + } + ], + "label": "**", + "subsystemId": 0, + "attributes": [], + "enabled": true, + "controllableName": "internal:TSKAlarmComponent", + "definition": { + "states": [ + { + "name": "internal:TargetAlarmModeState" + }, + { + "name": "internal:AlarmDelayState" + }, + { + "name": "core:NameState" + }, + { + "name": "internal:IntrusionDetectedState" + }, + { + "name": "internal:CurrentAlarmModeState" + } + ], + "widgetName": "TSKAlarmController", + "type": "ACTUATOR", + "commands": [ + { + "commandName": "arm", + "nparams": 0 + }, + { + "commandName": "alarmOn", + "nparams": 0 + }, + { + "commandName": "disarm", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setTargetAlarmMode", + "paramsSig": "p1" + }, + { + "commandName": "on", + "nparams": 0 + }, + { + "commandName": "refreshAlarmDelay", + "nparams": 0 + }, + { + "commandName": "getName", + "nparams": 0 + }, + { + "commandName": "off", + "nparams": 0 + }, + { + "commandName": "alarmPartial2", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setName", + "paramsSig": "p1" + }, + { + "commandName": "alarmOff", + "nparams": 0 + }, + { + "commandName": "alarmPartial1", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setIntrusionDetected", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "setAlarmDelay", + "paramsSig": "p1" + }, + { + "commandName": "refreshCurrentAlarmMode", + "nparams": 0 + }, + { + "commandName": "refreshIntrusionDetected", + "nparams": 0 + } + ], + "uiClass": "Alarm" + } + }, + { + "deviceURL": "io://****-****-3293/5353900", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 1, + "name": "core:TargetClosureState", + "value": 0 + }, + { + "type": 6, + "name": "core:MovingState", + "value": false + }, + { + "type": 3, + "name": "core:StatusState", + "value": "available" + }, + { + "type": 3, + "name": "core:DiscreteRSSILevelState", + "value": "low" + }, + { + "type": 1, + "name": "core:RSSILevelState", + "value": 30 + }, + { + "type": 1, + "name": "core:Memorized1PositionState", + "value": 90 + }, + { + "type": 3, + "name": "core:NameState", + "value": "** **" + }, + { + "type": 11, + "name": "core:ManufacturerSettingsState", + "value": { + "current_position": 0 + } + }, + { + "type": 1, + "name": "core:ClosureState", + "value": 0 + }, + { + "type": 3, + "name": "core:OpenClosedState", + "value": "open" + }, + { + "type": 1, + "name": "core:DeploymentState", + "value": 0 + } + ], + "label": "** **", + "subsystemId": 0, + "attributes": [ + { + "type": 3, + "name": "core:Manufacturer", + "value": "Somfy" + }, + { + "type": 3, + "name": "core:FirmwareRevision", + "value": "5121525A07" + } + ], + "enabled": true, + "controllableName": "io:VerticalExteriorAwningIOComponent", + "definition": { + "states": [ + { + "name": "core:ManufacturerSettingsState" + }, + { + "name": "core:DiscreteRSSILevelState" + }, + { + "name": "core:RSSILevelState" + }, + { + "name": "core:Memorized1PositionState" + }, + { + "name": "core:TargetClosureState" + }, + { + "name": "core:SecuredPositionState" + }, + { + "name": "core:ClosureState" + }, + { + "name": "core:OpenClosedState" + }, + { + "name": "core:DeploymentState" + }, + { + "name": "core:MovingState" + }, + { + "name": "core:AdditionalStatusState" + }, + { + "name": "core:NameState" + }, + { + "name": "core:StatusState" + } + ], + "widgetName": "PositionableScreen", + "type": "ACTUATOR", + "commands": [ + { + "commandName": "stop", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setDeployment", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "delayedStopIdentify", + "paramsSig": "p1" + }, + { + "nparams": 2, + "commandName": "runManufacturerSettingsCommand", + "paramsSig": "p1,p2" + }, + { + "commandName": "down", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setClosure", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllers", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setConfigState", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "pairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "advancedRefresh", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "refreshMemorized1Position", + "nparams": 0 + }, + { + "commandName": "undeploy", + "nparams": 0 + }, + { + "commandName": "up", + "nparams": 0 + }, + { + "commandName": "startIdentify", + "nparams": 0 + }, + { + "commandName": "stopIdentify", + "nparams": 0 + }, + { + "commandName": "open", + "nparams": 0 + }, + { + "commandName": "deploy", + "nparams": 0 + }, + { + "commandName": "keepOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "sendIOKey", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setMemorized1Position", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "wink", + "paramsSig": "p1" + }, + { + "commandName": "close", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setName", + "paramsSig": "p1" + }, + { + "commandName": "identify", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setPosition", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "unpairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "nparams": 1, + "commandName": "setSecuredPosition", + "paramsSig": "p1" + }, + { + "commandName": "my", + "nparams": 0 + }, + { + "commandName": "getName", + "nparams": 0 + } + ], + "uiClass": "ExteriorScreen" + } + }, + { + "deviceURL": "internal://****-****-3293/pod/0", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 3, + "name": "core:NameState", + "value": "**" + }, + { + "type": 3, + "name": "core:CountryCodeState", + "value": "NL" + }, + { + "type": 1, + "name": "internal:LightingLedPodModeState", + "value": 1 + }, + { + "type": 3, + "name": "internal:BatteryStatusState", + "value": "no" + }, + { + "type": 3, + "name": "core:LocalIPv4AddressState", + "value": "192.168.11.36" + }, + { + "type": 3, + "name": "core:ConnectivityState", + "value": "online" + } + ], + "label": "** **", + "subsystemId": 0, + "attributes": [], + "enabled": true, + "controllableName": "internal:PodV2Component", + "definition": { + "states": [ + { + "name": "core:ConnectivityState" + }, + { + "name": "core:LocalIPv4AddressState" + }, + { + "name": "core:CountryCodeState" + }, + { + "name": "internal:LightingLedPodModeState" + }, + { + "name": "core:CyclicButtonState" + }, + { + "name": "core:NameState" + }, + { + "name": "internal:BatteryStatusState" + } + ], + "widgetName": "Pod", + "type": "ACTUATOR", + "commands": [ + { + "commandName": "deactivateCalendar", + "nparams": 0 + }, + { + "commandName": "refreshPodMode", + "nparams": 0 + }, + { + "commandName": "getName", + "nparams": 0 + }, + { + "commandName": "setPodLedOff", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setCalendar", + "paramsSig": "p1" + }, + { + "commandName": "update", + "nparams": 0 + }, + { + "commandName": "setPodLedOn", + "nparams": 0 + }, + { + "commandName": "refreshBatteryStatus", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setLightingLedPodMode", + "paramsSig": "p1" + }, + { + "commandName": "activateCalendar", + "nparams": 0 + }, + { + "commandName": "refreshUpdateStatus", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setCountryCode", + "paramsSig": "p1" + } + ], + "uiClass": "Pod" + } + }, + { + "deviceURL": "io://****-****-3293/5046564", + "available": true, + "synced": true, + "type": 1, + "states": [ + { + "type": 1, + "name": "core:LightIntensityState", + "value": 0 + }, + { + "type": 3, + "name": "core:OnOffState", + "value": "off" + }, + { + "type": 1, + "name": "core:Memorized1PositionState", + "value": 65 + }, + { + "type": 3, + "name": "core:NameState", + "value": "** **" + }, + { + "type": 3, + "name": "core:StatusState", + "value": "available" + }, + { + "type": 3, + "name": "core:DiscreteRSSILevelState", + "value": "normal" + }, + { + "type": 1, + "name": "core:RSSILevelState", + "value": 60 + } + ], + "label": "** **", + "subsystemId": 0, + "attributes": [ + { + "type": 3, + "name": "core:Manufacturer", + "value": "Somfy" + }, + { + "type": 3, + "name": "core:FirmwareRevision", + "value": "5133033A02" + } + ], + "enabled": true, + "controllableName": "io:DimmableLightIOComponent", + "definition": { + "states": [ + { + "name": "core:AdditionalStatusState" + }, + { + "name": "core:DiscreteRSSILevelState" + }, + { + "name": "core:RSSILevelState" + }, + { + "name": "core:NameState" + }, + { + "name": "core:StatusState" + }, + { + "name": "core:ManufacturerSettingsState" + }, + { + "name": "core:LightIntensityState" + }, + { + "name": "core:OnOffState" + }, + { + "name": "core:Memorized1PositionState" + }, + { + "name": "core:SecuredPositionState" + } + ], + "widgetName": "DimmerLight", + "type": "ACTUATOR", + "commands": [ + { + "nparams": 1, + "commandName": "delayedStopIdentify", + "paramsSig": "p1" + }, + { + "nparams": 2, + "commandName": "runManufacturerSettingsCommand", + "paramsSig": "p1,p2" + }, + { + "commandName": "on", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "onWithTimer", + "paramsSig": "p1" + }, + { + "commandName": "unpairAllOneWayControllers", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setConfigState", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "pairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "commandName": "unpairAllOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "sendIOKey", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "wink", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "unpairOneWayController", + "paramsSig": "p1,*p2" + }, + { + "commandName": "startIdentify", + "nparams": 0 + }, + { + "commandName": "stopIdentify", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setSecuredPosition", + "paramsSig": "p1" + }, + { + "commandName": "my", + "nparams": 0 + }, + { + "commandName": "keepOneWayControllersAndDeleteNode", + "nparams": 0 + }, + { + "commandName": "getName", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setMemorized1Position", + "paramsSig": "p1" + }, + { + "commandName": "refreshMemorized1Position", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "advancedRefresh", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "setName", + "paramsSig": "p1" + }, + { + "commandName": "identify", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setPosition", + "paramsSig": "p1" + }, + { + "nparams": 1, + "commandName": "setOnOff", + "paramsSig": "p1" + }, + { + "commandName": "off", + "nparams": 0 + }, + { + "nparams": 1, + "commandName": "setIntensity", + "paramsSig": "p1" + }, + { + "nparams": 2, + "commandName": "setIntensityWithTimer", + "paramsSig": "p1,p2,*p3" + } + ], + "uiClass": "Light" + } + } + ] +} diff --git a/tests/test_client.py b/tests/test_client.py index 3284d368..9b4bd2be 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -129,6 +129,7 @@ async def test_fetch_events_casting(self, client, fixture_name: str): ("setup_tahoma_siren_rtd.json", 31), ("setup_tahoma_be.json", 15), ("setup_local.json", 3), + ("setup_local_tahoma.json", 8), ], ) @pytest.mark.asyncio From 9031763d712ce7abbcb082ae0caa9c5ee0f5b8c5 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 7 Apr 2022 15:32:45 +0200 Subject: [PATCH 09/13] Add Unknown Object exception --- pyoverkiz/client.py | 31 ++++++++++++++++++++++++++----- pyoverkiz/exceptions.py | 4 ++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index c320b00c..b7e92350 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -51,6 +51,7 @@ TooManyExecutionsException, TooManyRequestsException, UnknownUserException, + UnknownObjectException, ) from pyoverkiz.models import ( Command, @@ -137,6 +138,7 @@ def __init__( cafile=os.path.dirname(os.path.realpath(__file__)) + "/overkiz-root-ca-2048.crt" ) + self._ssl_context = None async def __aenter__(self) -> OverkizClient: return self @@ -165,10 +167,13 @@ async def login( Caller must provide one of [userId+userPassword, userId+ssoToken, accessToken, jwt] """ # Local authentication - # TODO check which endpoint can be used to validate the token! if self._is_local: if register_event_listener: await self.register_event_listener() + else: + # Call a simple endpoint to verify if our token is correct + await self.get_api_version() + return True # Somfy TaHoma authentication using access_token @@ -594,6 +599,15 @@ async def get_current_executions(self) -> list[Execution]: return executions + @backoff.on_exception( + backoff.expo, NotAuthenticatedException, max_tries=2, on_backoff=relogin + ) + async def get_api_version(self) -> str: + """Get the API version (local only)""" + response = await self.__get("apiVersion") + + return cast(str, response["protocolVersion"]) + @backoff.on_exception(backoff.expo, TooManyExecutionsException, max_tries=10) @backoff.on_exception( backoff.expo, NotAuthenticatedException, max_tries=2, on_backoff=relogin @@ -670,7 +684,7 @@ async def generate_local_token(self, gateway_id: str) -> str: Generates a new token Access scope : Full enduser API access (enduser/*) """ - response = await self.__get(f"/config/{gateway_id}/local/tokens/generate") + response = await self.__get(f"config/{gateway_id}/local/tokens/generate") return cast(str, response["token"]) @@ -688,7 +702,7 @@ async def activate_local_token( Access scope : Full enduser API access (enduser/*) """ response = await self.__post( - f"/config/{gateway_id}/local/tokens", + f"config/{gateway_id}/local/tokens", {"label": label, "token": token, "scope": scope}, ) @@ -707,7 +721,7 @@ async def get_local_tokens( Get all gateway tokens with the given scope Access scope : Full enduser API access (enduser/*) """ - response = await self.__get(f"/config/{gateway_id}/local/tokens/{scope}") + response = await self.__get(f"config/{gateway_id}/local/tokens/{scope}") local_tokens = [LocalToken(**lt) for lt in humps.decamelize(response)] return local_tokens @@ -723,7 +737,7 @@ async def delete_local_token(self, gateway_id: str, uuid: str) -> bool: Delete a token Access scope : Full enduser API access (enduser/*) """ - await self.__delete(f"/config/{gateway_id}/local/tokens/{uuid}") + await self.__delete(f"config/{gateway_id}/local/tokens/{uuid}") return True @@ -853,8 +867,15 @@ async def check_response(response: ClientResponse) -> None: if "Not such token with UUID: " in message: raise NotSuchTokenException(message) + +<< << << < HEAD if "Unknown user :" in message: raise UnknownUserException(message) +== == == = + # {"error":"Unknown object.","errorCode":"UNSPECIFIED_ERROR"} + if message == "Unknown object.": + raise UnknownObjectException(message) +>>>>>> > 562d327(Add Unknown Object exception) raise Exception(message if message else result) diff --git a/pyoverkiz/exceptions.py b/pyoverkiz/exceptions.py index a323de8d..a3f5c8ff 100644 --- a/pyoverkiz/exceptions.py +++ b/pyoverkiz/exceptions.py @@ -62,6 +62,10 @@ class UnknownUserException(BaseOverkizException): pass +class UnknownObjectException(BaseOverkizException): + pass + + # Nexity class NexityBadCredentialsException(BadCredentialsException): pass From 3c3f24a17d470f5b030dafa9875befc2abe5ff41 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 7 Apr 2022 14:34:18 +0000 Subject: [PATCH 10/13] Bugfix --- pyoverkiz/client.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index b7e92350..85708f00 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -172,7 +172,7 @@ async def login( await self.register_event_listener() else: # Call a simple endpoint to verify if our token is correct - await self.get_api_version() + await self.get_gateways() return True @@ -254,8 +254,7 @@ async def refresh_token(self) -> None: return if not self._refresh_token: - raise ValueError( - "No refresh token provided. Login method must be used.") + raise ValueError("No refresh token provided. Login method must be used.") # &grant_type=refresh_token&refresh_token=REFRESH_TOKEN # Request access token @@ -311,8 +310,7 @@ async def cozytouch_login(self) -> str: # {'error': 'invalid_grant', # 'error_description': 'Provided Authorization Grant is invalid.'} if "error" in token and token["error"] == "invalid_grant": - raise CozyTouchBadCredentialsException( - token["error_description"]) + raise CozyTouchBadCredentialsException(token["error_description"]) if "token_type" not in token: raise CozyTouchServiceException("No CozyTouch token provided.") @@ -489,8 +487,7 @@ async def get_execution_history(self) -> list[HistoryExecution]: List execution history """ response = await self.__get("history/executions") - execution_history = [HistoryExecution( - **h) for h in humps.decamelize(response)] + execution_history = [HistoryExecution(**h) for h in humps.decamelize(response)] return execution_history @@ -806,8 +803,7 @@ async def check_response(response: ClientResponse) -> None: except JSONDecodeError as error: result = await response.text() if "Server is down for maintenance" in result: - raise MaintenanceException( - "Server is down for maintenance") from error + raise MaintenanceException("Server is down for maintenance") from error raise Exception( f"Unknown error while requesting {response.url}. {response.status} - {result}" ) from error From 3e2fad21628d9b4141074e1894409a2a7301df49 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 11 Apr 2022 10:42:31 +0000 Subject: [PATCH 11/13] Remove SSL check for now --- pyoverkiz/client.py | 19 ++----------------- pyoverkiz/overkiz-root-ca-2048.crt | 20 -------------------- 2 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 pyoverkiz/overkiz-root-ca-2048.crt diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index 85708f00..c14402bf 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -3,8 +3,6 @@ import asyncio import datetime -import os -import ssl import urllib.parse from json import JSONDecodeError from types import TracebackType @@ -50,8 +48,8 @@ TooManyConcurrentRequestsException, TooManyExecutionsException, TooManyRequestsException, - UnknownUserException, UnknownObjectException, + UnknownUserException, ) from pyoverkiz.models import ( Command, @@ -98,7 +96,6 @@ class OverkizClient: _expires_in: datetime.datetime | None = None _access_token: str | None = None _is_local: bool = False - _ssl_context: ssl.SSLContext | None = None def __init__( self, @@ -132,14 +129,6 @@ def __init__( self._is_local = True self._access_token = token - # To avoid security issue, add the following authority to - # your HTTPS client trust store: https://ca.overkiz.com/overkiz-root-ca-2048.crt - self._ssl_context = ssl.create_default_context( - cafile=os.path.dirname(os.path.realpath(__file__)) - + "/overkiz-root-ca-2048.crt" - ) - self._ssl_context = None - async def __aenter__(self) -> OverkizClient: return self @@ -757,7 +746,6 @@ async def __get(self, path: str) -> Any: async with self.session.get( f"{self.server.endpoint}{path}", headers=headers, - ssl_context=self._ssl_context, ) as response: await self.check_response(response) return await response.json() @@ -863,15 +851,12 @@ async def check_response(response: ClientResponse) -> None: if "Not such token with UUID: " in message: raise NotSuchTokenException(message) - -<< << << < HEAD if "Unknown user :" in message: raise UnknownUserException(message) -== == == = + # {"error":"Unknown object.","errorCode":"UNSPECIFIED_ERROR"} if message == "Unknown object.": raise UnknownObjectException(message) ->>>>>> > 562d327(Add Unknown Object exception) raise Exception(message if message else result) diff --git a/pyoverkiz/overkiz-root-ca-2048.crt b/pyoverkiz/overkiz-root-ca-2048.crt deleted file mode 100644 index 9d9adef8..00000000 --- a/pyoverkiz/overkiz-root-ca-2048.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDPDCCAiSgAwIBAgIJAOvswclbF4QnMA0GCSqGSIb3DQEBCwUAMEsxEDAOBgNV -BAoTB092ZXJraXoxEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD092ZXJraXog -Um9vdCBDQTELMAkGA1UEBhMCRlIwHhcNMTYwNDI3MTI1ODE1WhcNMzYwNDI3MTI1 -ODE1WjBLMRAwDgYDVQQKEwdPdmVya2l6MRAwDgYDVQQLEwdSb290IENBMRgwFgYD -VQQDEw9PdmVya2l6IFJvb3QgQ0ExCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAsszRfbcNCEoD9ZfzTbXfuMK8CrXGqBR/ZOCk4guN -aRKqZ/rLQm3V6Q+dlJ/8qle5J3KN5bZmqT4qXKHwJsaOiLfPyAptSM6vuIlls2N+ -UsKkv3m5+gTyLaSGMS4wh2GoOCa21V9t5wYUQnoFaQByVNyl+kkrWLpKw5gQasU0 -xkVsjAVKgkkb3puBl7sZgiSoz97I9U9JUkg3spH0I84CZRI+JejioDHvkZEyf83j -+QFxSTV/hZkUwUY/X0zt2dTZuliCTePeCdANryo6+9TbBp98j/SB1s59FcO8NSSK -sV07rTFlM9/soko2/J0aTtXHE86wFq7vfFVZzZxsQpIBbwIDAQABoyMwITAPBgNV -HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA -GU7xUWlZZEVnEK0k3Z1FoRl9xA7cLOiDVCQ5qQDFfQgGpMtXv1PKsQNZ6T6tZN3d -bdzsqcQXtLhXknz6aGBZNR4g6liQhVuCaiyURaI+LM2KuSZnbixs3+1SPBvxHrJh -/gOsxctxq+0DALnOK9qbGl6N5DtjM/EC5Qve71c+UVTEcJjJ3L2S1Ne+PxDOJuUC -JsOLUk96G+uLn6CQB5Wu8fYrkWAjF3yrxkCoZCqOvVrnbL77vXmz2mlqNHSJt3Ur -ndWJLVvrRFKdSG6WiNCh/Q+ARQAorN60JD9x8+IyXRGvlZl7KVeRduE2rjZuom7h -QLMnmaF+oFW5mnhh9gu6Gg== ------END CERTIFICATE----- From 414c1ed1cb954ef531423041d5d5aad9b4272d77 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 11 Apr 2022 11:40:52 +0000 Subject: [PATCH 12/13] Add AccessDeniedToGatewayException --- pyoverkiz/client.py | 5 +++++ pyoverkiz/exceptions.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index c14402bf..8144a3e1 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -28,6 +28,7 @@ SUPPORTED_SERVERS, ) from pyoverkiz.exceptions import ( + AccessDeniedToGatewayException, BadCredentialsException, CozyTouchBadCredentialsException, CozyTouchServiceException, @@ -858,6 +859,10 @@ async def check_response(response: ClientResponse) -> None: if message == "Unknown object.": raise UnknownObjectException(message) + # {'errorCode': 'RESOURCE_ACCESS_DENIED', 'error': 'Access denied to gateway #1234-5678-1234 for action ADD_TOKEN'} + if "Access denied to gateway" in message: + raise AccessDeniedToGatewayException(message) + raise Exception(message if message else result) async def _refresh_token_if_expired(self) -> None: diff --git a/pyoverkiz/exceptions.py b/pyoverkiz/exceptions.py index a3f5c8ff..14bed408 100644 --- a/pyoverkiz/exceptions.py +++ b/pyoverkiz/exceptions.py @@ -66,6 +66,10 @@ class UnknownObjectException(BaseOverkizException): pass +class AccessDeniedToGatewayException(BaseOverkizException): + pass + + # Nexity class NexityBadCredentialsException(BadCredentialsException): pass From cfa2abc08e52eae553b25a4d369f0c720f8b1bdc Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 11 Apr 2022 12:26:55 +0000 Subject: [PATCH 13/13] Simplify local check --- pyoverkiz/client.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index 8144a3e1..b6c74589 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -96,7 +96,6 @@ class OverkizClient: _refresh_token: str | None = None _expires_in: datetime.datetime | None = None _access_token: str | None = None - _is_local: bool = False def __init__( self, @@ -118,6 +117,7 @@ def __init__( self.username = username self.password = password self.server = server + self._access_token = token self.setup: Setup | None = None self.devices: list[Device] = [] @@ -126,10 +126,6 @@ def __init__( self.session = session if session else ClientSession() - if "/enduser-mobile-web/1/enduserAPI/" in server.endpoint: - self._is_local = True - self._access_token = token - async def __aenter__(self) -> OverkizClient: return self @@ -157,7 +153,7 @@ async def login( Caller must provide one of [userId+userPassword, userId+ssoToken, accessToken, jwt] """ # Local authentication - if self._is_local: + if "/enduser-mobile-web/1/enduserAPI/" in self.server.endpoint: if register_event_listener: await self.register_event_listener() else: