diff --git a/custom_components/panasonic_cc/manifest.json b/custom_components/panasonic_cc/manifest.json index f8726e3..659e10f 100644 --- a/custom_components/panasonic_cc/manifest.json +++ b/custom_components/panasonic_cc/manifest.json @@ -2,7 +2,7 @@ "domain": "panasonic_cc", "name": "Panasonic Comfort Cloud", "after_dependencies": ["http"], - "version": "2024.6.4", + "version": "1.0.42", "config_flow": true, "documentation": "https://github.com/sockless-coding/panasonic_cc/", "dependencies": [], diff --git a/custom_components/panasonic_cc/pcomfortcloud/apiclient.py b/custom_components/panasonic_cc/pcomfortcloud/apiclient.py index 64334d8..25f54f3 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/apiclient.py +++ b/custom_components/panasonic_cc/pcomfortcloud/apiclient.py @@ -245,27 +245,27 @@ def _read_parameters(self, parameters=dict()): def _get_group_url(self): return '{base_url}/device/group'.format( - base_url=panasonicsession.PanasonicSession.BASE_PATH_ACC + base_url=constants.BASE_PATH_ACC ) def _get_device_status_url(self, guid): return '{base_url}/deviceStatus/{guid}'.format( - base_url=panasonicsession.PanasonicSession.BASE_PATH_ACC, + base_url=constants.BASE_PATH_ACC, guid=re.sub('(?i)2f', 'f', quote_plus(guid)) ) def _get_device_status_now_url(self, guid): return '{base_url}/deviceStatus/now/{guid}'.format( - base_url=panasonicsession.PanasonicSession.BASE_PATH_ACC, + base_url=constants.BASE_PATH_ACC, guid=re.sub('(?i)2f', 'f', quote_plus(guid)) ) def _get_device_status_control_url(self): return '{base_url}/deviceStatus/control'.format( - base_url=panasonicsession.PanasonicSession.BASE_PATH_ACC + base_url=constants.BASE_PATH_ACC ) def _get_device_history_url(self): return '{base_url}/deviceHistoryData'.format( - base_url=panasonicsession.PanasonicSession.BASE_PATH_ACC, + base_url=constants.BASE_PATH_ACC, ) diff --git a/custom_components/panasonic_cc/pcomfortcloud/constants.py b/custom_components/panasonic_cc/pcomfortcloud/constants.py index f572e3a..b3e3073 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/constants.py +++ b/custom_components/panasonic_cc/pcomfortcloud/constants.py @@ -79,4 +79,12 @@ class NanoeMode(Enum): SETTING_SCOPE = "scope" SETTING_VERSION = "android_version" SETTING_VERSION_DATE = "android_version_date" -SETTING_CLIENT_ID = "clientId" \ No newline at end of file +SETTING_CLIENT_ID = "clientId" + +APP_CLIENT_ID = "Xmy6xIYIitMxngjB2rHvlm6HSDNnaMJx" +AUTH_0_CLIENT = "eyJuYW1lIjoiQXV0aDAuQW5kcm9pZCIsImVudiI6eyJhbmRyb2lkIjoiMzAifSwidmVyc2lvbiI6IjIuOS4zIn0=" +REDIRECT_URI = "panasonic-iot-cfc://authglb.digital.panasonic.com/android/com.panasonic.ACCsmart/callback" +BASE_PATH_AUTH = "https://authglb.digital.panasonic.com" +BASE_PATH_ACC = "https://accsmart.panasonic.com" +AUTH_API_USER_AGENT = "okhttp/4.10.0" +AUTH_BROWSER_USER_AGENT = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36" \ No newline at end of file diff --git a/custom_components/panasonic_cc/pcomfortcloud/panasonicauthentication.py b/custom_components/panasonic_cc/pcomfortcloud/panasonicauthentication.py new file mode 100644 index 0000000..600b45d --- /dev/null +++ b/custom_components/panasonic_cc/pcomfortcloud/panasonicauthentication.py @@ -0,0 +1,246 @@ +import aiohttp +import base64 +import hashlib +import logging +import random +import string +import urllib +import datetime +import time +import json + +from bs4 import BeautifulSoup + +from .panasonicsettings import PanasonicSettings +from .ccappversion import CCAppVersion +from .panasonicrequestheader import PanasonicRequestHeader +from . import exceptions +from .constants import (APP_CLIENT_ID, AUTH_0_CLIENT, BASE_PATH_ACC, BASE_PATH_AUTH, REDIRECT_URI, AUTH_API_USER_AGENT, AUTH_BROWSER_USER_AGENT) + +_LOGGER = logging.getLogger(__name__) + +def generate_random_string(length): + return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length)) + +def check_response(response: aiohttp.ClientResponse, function_description, expected_status): + + if response.status != expected_status: + raise exceptions.ResponseError( + f"({function_description}: Expected status code {expected_status}, received: {response.status}: " + + f"{response.text}" + ) + +def get_querystring_parameter_from_header_entry_url(response: aiohttp.ClientResponse, header_entry, querystring_parameter): + header_entry_value = response.headers[header_entry] + parsed_url = urllib.parse.urlparse(header_entry_value) + params = urllib.parse.parse_qs(parsed_url.query) + return params.get(querystring_parameter, [None])[0] + +class PanasonicAuthentication: + + def __init__(self, client: aiohttp.ClientSession, settings: PanasonicSettings, app_version:CCAppVersion): + self._client = client + self._settings = settings + self._app_version = app_version + + async def authenticate(self, username: str, password: str): + + # generate initial state and code_challenge + code_verifier = generate_random_string(43) + + code_challenge = base64.urlsafe_b64encode( + hashlib.sha256( + code_verifier.encode('utf-8') + ).digest()).split('='.encode('utf-8'))[0].decode('utf-8') + + authorization_response = await self._authorize(code_challenge) + authorization_redirect = authorization_response.headers['Location'] + # check if the user can skip the authentication workflows - in that case, + # the location is directly pointing to the redirect url with the "code" + # query parameter included + if authorization_redirect.startswith(REDIRECT_URI): + code = get_querystring_parameter_from_header_entry_url( + authorization_response, 'Location', 'code') + else: + code = await self._login(authorization_response, username, password) + + await self._request_new_token(code, code_verifier) + await self._retrieve_client_acc() + + async def refresh_token(self): + _LOGGER.debug("Refreshing token") + # do before, so that timestamp is older rather than newer + now = datetime.datetime.now() + unix_time_token_received = time.mktime(now.timetuple()) + + response = await self._client.post( + f'{BASE_PATH_AUTH}/oauth/token', + headers={ + "Auth0-Client": AUTH_0_CLIENT, + "user-agent": AUTH_API_USER_AGENT, + }, + json={ + "scope": self._settings.scope, + "client_id": APP_CLIENT_ID, + "refresh_token": self._settings.refresh_token, + "grant_type": "refresh_token" + }, + allow_redirects=False) + check_response(response, 'refresh_token', 200) + token_response = json.loads(await response.text()) + self._set_token(token_response, unix_time_token_received) + + + async def _authorize(self, challenge) -> aiohttp.ClientResponse: + # -------------------------------------------------------------------- + # AUTHORIZE + # -------------------------------------------------------------------- + state = generate_random_string(20) + + response = await self._client.get( + f'{BASE_PATH_AUTH}/authorize', + headers={ + "user-agent": AUTH_API_USER_AGENT, + }, + params={ + "scope": "openid offline_access comfortcloud.control a2w.control", + "audience": f"https://digital.panasonic.com/{APP_CLIENT_ID}/api/v1/", + "protocol": "oauth2", + "response_type": "code", + "code_challenge": challenge, + "code_challenge_method": "S256", + "auth0Client": AUTH_0_CLIENT, + "client_id": APP_CLIENT_ID, + "redirect_uri": REDIRECT_URI, + "state": state, + }, + allow_redirects=False) + check_response(response, 'authorize', 302) + return response + + + async def _login(self, authorization_response: aiohttp.ClientResponse, username, password): + _LOGGER.debug("Trying to log in") + state = get_querystring_parameter_from_header_entry_url( + authorization_response, 'Location', 'state') + location = authorization_response.headers['Location'] + response = await self._client.get( + f"{BASE_PATH_AUTH}/{location}", + allow_redirects=False) + check_response(response, 'authorize_redirect', 200) + + # get the "_csrf" cookie + csrf = response.cookies['_csrf'] + + # ------------------------------------------------------------------- + # LOGIN + # ------------------------------------------------------------------- + + response = await self._client.post( + f'{BASE_PATH_AUTH}/usernamepassword/login', + headers={ + "Auth0-Client": AUTH_0_CLIENT, + "user-agent": AUTH_API_USER_AGENT, + }, + json={ + "client_id": APP_CLIENT_ID, + "redirect_uri": REDIRECT_URI, + "tenant": "pdpauthglb-a1", + "response_type": "code", + "scope": "openid offline_access comfortcloud.control a2w.control", + "audience": f"https://digital.panasonic.com/{APP_CLIENT_ID}/api/v1/", + "_csrf": csrf, + "state": state, + "_intstate": "deprecated", + "username": username, + "password": password, + "lang": "en", + "connection": "PanasonicID-Authentication" + }, + allow_redirects=False) + check_response(response, 'login', 200) + + # ------------------------------------------------------------------- + # CALLBACK + # ------------------------------------------------------------------- + + # get wa, wresult, wctx from body + soup = BeautifulSoup(await response.text(), "html.parser") + input_lines = soup.find_all("input", {"type": "hidden"}) + parameters = dict() + for input_line in input_lines: + parameters[input_line.get("name")] = input_line.get("value") + + + response = await self._client.post( + url=f"{BASE_PATH_AUTH}/login/callback", + data=parameters, + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": AUTH_BROWSER_USER_AGENT, + }, + allow_redirects=False) + check_response(response, 'login_callback', 302) + + # ------------------------------------------------------------------ + # FOLLOW REDIRECT + # ------------------------------------------------------------------ + + location = response.headers['Location'] + + response = await self._client.get( + f"{BASE_PATH_AUTH}/{location}", + allow_redirects=False) + check_response(response, 'login_redirect', 302) + + return get_querystring_parameter_from_header_entry_url( + response, 'Location', 'code') + + async def _request_new_token(self, code, code_verifier): + _LOGGER.debug("Requesting a new token") + # do before, so that timestamp is older rather than newer + now = datetime.datetime.now() + unix_time_token_received = time.mktime(now.timetuple()) + + response = await self._client.post( + f'{BASE_PATH_AUTH}/oauth/token', + headers={ + "Auth0-Client": AUTH_0_CLIENT, + "user-agent": AUTH_API_USER_AGENT, + }, + json={ + "scope": "openid", + "client_id": APP_CLIENT_ID, + "grant_type": "authorization_code", + "code": code, + "redirect_uri": REDIRECT_URI, + "code_verifier": code_verifier + }, + allow_redirects=False) + check_response(response, 'get_token', 200) + + token_response = json.loads(await response.text()) + self._set_token(token_response, unix_time_token_received) + + def _set_token(self, token_response, unix_time_token_received): + self._settings.set_token( + token_response["access_token"], + token_response["refresh_token"], + unix_time_token_received + token_response["expires_in"], + token_response["scope"]) + + async def _retrieve_client_acc(self): + # ------------------------------------------------------------------ + # RETRIEVE ACC_CLIENT_ID + # ------------------------------------------------------------------ + response = await self._client.post( + f'{BASE_PATH_ACC}/auth/v2/login', + headers = await PanasonicRequestHeader.get(self._settings, self._app_version), + json={ + "language": 0 + }) + check_response(response, 'get_acc_client_id', 200) + + json_body = json.loads(await response.text()) + self._settings.clientId = json_body["clientId"] + diff --git a/custom_components/panasonic_cc/pcomfortcloud/panasonicrequestheader.py b/custom_components/panasonic_cc/pcomfortcloud/panasonicrequestheader.py new file mode 100644 index 0000000..d1643e9 --- /dev/null +++ b/custom_components/panasonic_cc/pcomfortcloud/panasonicrequestheader.py @@ -0,0 +1,30 @@ +import datetime +import random +import string + +from .panasonicsettings import PanasonicSettings +from .ccappversion import CCAppVersion + +class PanasonicRequestHeader: + + @staticmethod + async def get(settings: PanasonicSettings, app_version: CCAppVersion): + now = datetime.datetime.now() + timestamp = now.strftime("%Y-%m-%d %H:%M:%S") + headers={ + "Content-Type": "application/json;charset=utf-8", + "User-Agent": "G-RAC", + "X-APP-NAME": "Comfort Cloud", + "X-APP-TIMESTAMP": timestamp, + "X-APP-TYPE": "1", + "X-APP-VERSION": await app_version.get(), + "X-CFC-API-KEY": PanasonicRequestHeader._get_api_key(), + "X-User-Authorization-V2": "Bearer " + settings.access_token + } + if (settings.clientId): + headers["X-Client-Id"] = settings.clientId + return headers + + @staticmethod + def _get_api_key(): + return ''.join(random.choice(string.hexdigits) for _ in range(128)) \ No newline at end of file diff --git a/custom_components/panasonic_cc/pcomfortcloud/panasonicsession.py b/custom_components/panasonic_cc/pcomfortcloud/panasonicsession.py index 618a765..45b74c2 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/panasonicsession.py +++ b/custom_components/panasonic_cc/pcomfortcloud/panasonicsession.py @@ -1,30 +1,21 @@ -import base64 -import datetime -import hashlib import json import os -import random -import string -import time -import urllib import aiohttp import logging -import requests -from bs4 import BeautifulSoup +import aiohttp.client_exceptions +import aiohttp.http_exceptions +import aiohttp.web_exceptions from . import exceptions from .panasonicsettings import PanasonicSettings from .ccappversion import CCAppVersion +from .panasonicauthentication import PanasonicAuthentication +from .panasonicrequestheader import PanasonicRequestHeader +from .constants import BASE_PATH_ACC _LOGGER = logging.getLogger(__name__) -def generate_random_string(length): - return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length)) - - -def generate_random_string_hex(length): - return ''.join(random.choice(string.hexdigits) for _ in range(length)) def check_response(response: aiohttp.ClientResponse, function_description, expected_status): @@ -36,21 +27,7 @@ def check_response(response: aiohttp.ClientResponse, function_description, expec ) -def get_querystring_parameter_from_header_entry_url(response: aiohttp.ClientResponse, header_entry, querystring_parameter): - header_entry_value = response.headers[header_entry] - parsed_url = urllib.parse.urlparse(header_entry_value) - params = urllib.parse.parse_qs(parsed_url.query) - return params.get(querystring_parameter, [None])[0] - - class PanasonicSession: - APP_CLIENT_ID = "Xmy6xIYIitMxngjB2rHvlm6HSDNnaMJx" - AUTH_0_CLIENT = "eyJuYW1lIjoiQXV0aDAuQW5kcm9pZCIsImVudiI6eyJhbmRyb2lkIjoiMzAifSwidmVyc2lvbiI6IjIuOS4zIn0=" - REDIRECT_URI = "panasonic-iot-cfc://authglb.digital.panasonic.com/android/com.panasonic.ACCsmart/callback" - BASE_PATH_AUTH = "https://authglb.digital.panasonic.com" - BASE_PATH_ACC = "https://accsmart.panasonic.com" - X_APP_VERSION = "1.20.0" - # token: # - access_token # - refresh_token @@ -65,204 +42,25 @@ def __init__(self, username, password, client: aiohttp.ClientSession, settingsFi self._password = password self._client = client self._settings = PanasonicSettings(os.path.expanduser(settingsFileName)) - self._appVersion = CCAppVersion(client, self._settings) + self._app_version = CCAppVersion(client, self._settings) + self._authentication = PanasonicAuthentication(client, self._settings, self._app_version) self._raw = raw async def start_session(self): + _LOGGER.debug("Starting Session") if (not self._settings.has_refresh_token): - await self._get_new_token() + await self._authentication.authenticate(self._username, self._password) if (not self._settings.is_access_token_valid): - await self._refresh_token() - - - async def _get_new_token(self): - - - # generate initial state and code_challenge - state = generate_random_string(20) - code_verifier = generate_random_string(43) - - code_challenge = base64.urlsafe_b64encode( - hashlib.sha256( - code_verifier.encode('utf-8') - ).digest()).split('='.encode('utf-8'))[0].decode('utf-8') - - # -------------------------------------------------------------------- - # AUTHORIZE - # -------------------------------------------------------------------- - - response = await self._client.get( - f'{PanasonicSession.BASE_PATH_AUTH}/authorize', - headers={ - "user-agent": "okhttp/4.10.0", - }, - params={ - "scope": "openid offline_access comfortcloud.control a2w.control", - "audience": f"https://digital.panasonic.com/{PanasonicSession.APP_CLIENT_ID}/api/v1/", - "protocol": "oauth2", - "response_type": "code", - "code_challenge": code_challenge, - "code_challenge_method": "S256", - "auth0Client": PanasonicSession.AUTH_0_CLIENT, - "client_id": PanasonicSession.APP_CLIENT_ID, - "redirect_uri": PanasonicSession.REDIRECT_URI, - "state": state, - }, - allow_redirects=False) - check_response(response, 'authorize', 302) + await self._authentication.refresh_token() - # ------------------------------------------------------------------- - # FOLLOW REDIRECT - # ------------------------------------------------------------------- - location = response.headers['Location'] - state = get_querystring_parameter_from_header_entry_url( - response, 'Location', 'state') - - # check if the user can skip the authentication workflows - in that case, - # the location is directly pointing to the redirect url with the "code" - # query parameter included - if not location.startswith(PanasonicSession.REDIRECT_URI): - - response = await self._client.get( - f"{PanasonicSession.BASE_PATH_AUTH}/{location}", - allow_redirects=False) - check_response(response, 'authorize_redirect', 200) - - # get the "_csrf" cookie - csrf = response.cookies['_csrf'] - - # ------------------------------------------------------------------- - # LOGIN - # ------------------------------------------------------------------- - - response = await self._client.post( - f'{PanasonicSession.BASE_PATH_AUTH}/usernamepassword/login', - headers={ - "Auth0-Client": PanasonicSession.AUTH_0_CLIENT, - "user-agent": "okhttp/4.10.0", - }, - json={ - "client_id": PanasonicSession.APP_CLIENT_ID, - "redirect_uri": PanasonicSession.REDIRECT_URI, - "tenant": "pdpauthglb-a1", - "response_type": "code", - "scope": "openid offline_access comfortcloud.control a2w.control", - "audience": f"https://digital.panasonic.com/{PanasonicSession.APP_CLIENT_ID}/api/v1/", - "_csrf": csrf, - "state": state, - "_intstate": "deprecated", - "username": self._username, - "password": self._password, - "lang": "en", - "connection": "PanasonicID-Authentication" - }, - allow_redirects=False) - check_response(response, 'login', 200) - - # ------------------------------------------------------------------- - # CALLBACK - # ------------------------------------------------------------------- - - # get wa, wresult, wctx from body - soup = BeautifulSoup(await response.text(), "html.parser") - input_lines = soup.find_all("input", {"type": "hidden"}) - parameters = dict() - for input_line in input_lines: - parameters[input_line.get("name")] = input_line.get("value") - - user_agent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 " - user_agent += "(KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36" - - response = await self._client.post( - url=f"{PanasonicSession.BASE_PATH_AUTH}/login/callback", - data=parameters, - headers={ - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": user_agent, - }, - allow_redirects=False) - check_response(response, 'login_callback', 302) - # ------------------------------------------------------------------ - # FOLLOW REDIRECT - # ------------------------------------------------------------------ - - location = response.headers['Location'] - - response = await self._client.get( - f"{PanasonicSession.BASE_PATH_AUTH}/{location}", - allow_redirects=False) - check_response(response, 'login_redirect', 302) - - # ------------------------------------------------------------------ - # GET TOKEN - # ------------------------------------------------------------------ - - code = get_querystring_parameter_from_header_entry_url( - response, 'Location', 'code') - - # do before, so that timestamp is older rather than newer - now = datetime.datetime.now() - unix_time_token_received = time.mktime(now.timetuple()) - - response = await self._client.post( - f'{PanasonicSession.BASE_PATH_AUTH}/oauth/token', - headers={ - "Auth0-Client": PanasonicSession.AUTH_0_CLIENT, - "user-agent": "okhttp/4.10.0", - }, - json={ - "scope": "openid", - "client_id": PanasonicSession.APP_CLIENT_ID, - "grant_type": "authorization_code", - "code": code, - "redirect_uri": PanasonicSession.REDIRECT_URI, - "code_verifier": code_verifier - }, - allow_redirects=False) - check_response(response, 'get_token', 200) - - token_response = json.loads(await response.text()) - - # ------------------------------------------------------------------ - # RETRIEVE ACC_CLIENT_ID - # ------------------------------------------------------------------ - now = datetime.datetime.now() - timestamp = now.strftime("%Y-%m-%d %H:%M:%S") - response = await self._client.post( - f'{PanasonicSession.BASE_PATH_ACC}/auth/v2/login', - headers={ - "Content-Type": "application/json;charset=utf-8", - "User-Agent": "G-RAC", - "X-APP-NAME": "Comfort Cloud", - "X-APP-TIMESTAMP": timestamp, - "X-APP-TYPE": "1", - "X-APP-VERSION": PanasonicSession.X_APP_VERSION, - "X-CFC-API-KEY": generate_random_string_hex(128), - "X-User-Authorization-V2": "Bearer " + token_response["access_token"] - }, - json={ - "language": 0 - }) - check_response(response, 'get_acc_client_id', 200) - - json_body = json.loads(await response.text()) - acc_client_id = json_body["clientId"] - - self._settings.clientId = acc_client_id - self._settings.set_token( - token_response["access_token"], - token_response["refresh_token"], - unix_time_token_received + token_response["expires_in"], - token_response["scope"]) - - async def stop_session(self): + _LOGGER.debug("Stopping Session") response = await self._client.post( - f"{PanasonicSession.BASE_PATH_ACC}/auth/v2/logout", - headers=await self._get_header_for_api_calls() + f"{BASE_PATH_ACC}/auth/v2/logout", + headers = await PanasonicRequestHeader.get(self._settings, self._app_version) ) check_response(response, "logout", 200) if json.loads(await response.text())["result"] != 0: @@ -273,63 +71,6 @@ async def stop_session(self): except FileNotFoundError: pass - async def _refresh_token(self): - # do before, so that timestamp is older rather than newer - now = datetime.datetime.now() - unix_time_token_received = time.mktime(now.timetuple()) - - response = await self._client.post( - f'{PanasonicSession.BASE_PATH_AUTH}/oauth/token', - headers={ - "Auth0-Client": PanasonicSession.AUTH_0_CLIENT, - "user-agent": "okhttp/4.10.0", - }, - json={ - "scope": self._settings.scope, - "client_id": PanasonicSession.APP_CLIENT_ID, - "refresh_token": self._settings.refresh_token, - "grant_type": "refresh_token" - }, - allow_redirects=False) - check_response(response, 'refresh_token', 200) - token_response = json.loads(await response.text()) - - self._settings.set_token( - token_response["access_token"], - token_response["refresh_token"], - unix_time_token_received + token_response["expires_in"], - token_response["scope"]) - - - - - async def _get_header_for_api_calls(self): - now = datetime.datetime.now() - timestamp = now.strftime("%Y-%m-%d %H:%M:%S") - _LOGGER.debug(f"Header ClientId: {self._settings.clientId}") - _LOGGER.debug(f"Header Token: {self._settings.access_token}") - return { - "Content-Type": "application/json;charset=utf-8", - "X-APP-NAME": "Comfort Cloud", - "User-Agent": "G-RAC", - "X-APP-TIMESTAMP": timestamp, - "X-APP-TYPE": "1", - "X-APP-VERSION": await self._appVersion.get(), - # Seems to work by either setting X-CFC-API-KEY to 0 or to a 128-long hex string - # "X-CFC-API-KEY": "0", - "X-CFC-API-KEY": generate_random_string_hex(128), - "X-Client-Id": self._settings.clientId, - "X-User-Authorization-V2": "Bearer " + self._settings.access_token - } - - async def _get_user_info(self): - response = await self._client.get( - f'{PanasonicSession.BASE_PATH_AUTH}/userinfo', - headers={ - "Auth0-Client": self.AUTH_0_CLIENT, - "Authorization": "Bearer " + self._settings.access_token - }) - check_response(response, 'userinfo', 200) async def execute_post(self, url, json_data, function_description, expected_status_code): await self._ensure_valid_token() @@ -337,15 +78,20 @@ async def execute_post(self, url, json_data, function_description, expected_stat try: response = await self._client.post( url, - json=json_data, - headers= await self._get_header_for_api_calls() + json = json_data, + headers = await PanasonicRequestHeader.get(self._settings, self._app_version) ) - except requests.exceptions.RequestException as ex: + except (aiohttp.client_exceptions.ClientError, + aiohttp.http_exceptions.HttpProcessingError, + aiohttp.web_exceptions.HTTPError) as ex: raise exceptions.RequestError(ex) + self._print_response_if_raw_is_set(response, function_description) check_response(response, function_description, expected_status_code) - return json.loads(await response.text()) + response_text = await response.text() + _LOGGER.debug("POST url: %s, data: %s, response: %s", url, json_data, response_text) + return json.loads(response_text) async def execute_get(self, url, function_description, expected_status_code): await self._ensure_valid_token() @@ -353,14 +99,18 @@ async def execute_get(self, url, function_description, expected_status_code): try: response = await self._client.get( url, - headers=await self._get_header_for_api_calls() + headers = await PanasonicRequestHeader.get(self._settings, self._app_version) ) - except requests.exceptions.RequestException as ex: + except (aiohttp.client_exceptions.ClientError, + aiohttp.http_exceptions.HttpProcessingError, + aiohttp.web_exceptions.HTTPError) as ex: raise exceptions.RequestError(ex) self._print_response_if_raw_is_set(response, function_description) check_response(response, function_description, expected_status_code) - return json.loads(await response.text()) + response_text = await response.text() + _LOGGER.debug("GET url: %s, response: %s", url, response_text) + return json.loads(response_text) def _print_response_if_raw_is_set(self, response, function_description): if self._raw: @@ -380,4 +130,4 @@ def _print_response_if_raw_is_set(self, response, function_description): async def _ensure_valid_token(self): if self._settings.is_access_token_valid: return - await self._refresh_token() + await self._authentication.refresh_token() diff --git a/custom_components/panasonic_cc/pcomfortcloud/panasonicsettings.py b/custom_components/panasonic_cc/pcomfortcloud/panasonicsettings.py index b9c3745..c8fa2e9 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/panasonicsettings.py +++ b/custom_components/panasonic_cc/pcomfortcloud/panasonicsettings.py @@ -5,6 +5,7 @@ import base64 import aiofiles import asyncio +import logging from datetime import date from packaging import version @@ -21,6 +22,8 @@ MAX_VERSION_AGE ) +_LOGGER = logging.getLogger(__name__) + class PanasonicSettings: def __init__(self, fileName): @@ -37,6 +40,7 @@ def __init__(self, fileName): async def _load(self): if not os.path.exists(self._fileName): + _LOGGER.info("Settings file '%s' was not found", self._fileName) return try: async with aiofiles.open(self._fileName) as json_file: @@ -48,7 +52,9 @@ async def _load(self): self._refresh_token = data[SETTING_REFRESH_TOKEN] self._clientId = data[SETTING_CLIENT_ID] self._scope = data[SETTING_SCOPE] + _LOGGER.debug("Loaded settings from '%s'", self._fileName) except: + _LOGGER.debug("Failed to loaded settings from '%s'", self._fileName) pass def _save(self): @@ -67,6 +73,7 @@ def _save(self): async def _do_save(self, data): async with aiofiles.open(self._fileName, 'w') as outfile: await outfile.write(json.dumps(data)) + _LOGGER.debug("Saved settings to '%s'", self._fileName) @property def version(self):