From 780f85e898ebaedc3e7e96570d1a3f159a3d7ea5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 2 Mar 2018 09:57:03 -0800 Subject: [PATCH 1/3] Handle expired refresh token better --- homeassistant/components/cloud/const.py | 6 ++++++ homeassistant/components/cloud/iot.py | 16 +++++++++++++--- tests/components/cloud/test_iot.py | 18 ++++++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index b13ec6d1e45d1..650ad047dad97 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -16,3 +16,9 @@ It looks like your Home Assistant Cloud subscription has expired. Please check your [account page](/config/cloud/account) to continue using the service. """ + +MESSAGE_AUTH_FAIL = """ +You have been logged out of Home Assistant Cloud because we have been unable +to verify your credentials. Please [log in](/config/cloud) again to continue +using the service. +""" \ No newline at end of file diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index 3220fc372f7ff..f1a30d2a4d444 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -10,7 +10,7 @@ from homeassistant.util.decorator import Registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import auth_api -from .const import MESSAGE_EXPIRATION +from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) @@ -97,13 +97,23 @@ def _handle_connection(self): try: yield from hass.async_add_job(auth_api.check_token, self.cloud) + except auth_api.Unauthenticated as err: + _LOGGER.error('Unable to refresh token: %s', err) + + hass.components.persistent_notification.async_create( + MESSAGE_AUTH_FAIL, 'Home Assistant Cloud', + 'cloud_subscription_expired') + + # Don't await it because it will cancel this task + hass.async_add_job(self.cloud.logout()) + return except auth_api.CloudError as err: - _LOGGER.warning("Unable to connect: %s", err) + _LOGGER.warning("Unable to refresh token: %s", err) return if self.cloud.subscription_expired: hass.components.persistent_notification.async_create( - MESSAGE_EXPIRATION, 'Subscription expired', + MESSAGE_EXPIRATION, 'Home Assistant Cloud', 'cloud_subscription_expired') self.close_requested = True return diff --git a/tests/components/cloud/test_iot.py b/tests/components/cloud/test_iot.py index 3eec350b2cba7..d6a26ee37e0e9 100644 --- a/tests/components/cloud/test_iot.py +++ b/tests/components/cloud/test_iot.py @@ -6,7 +6,7 @@ import pytest from homeassistant.setup import async_setup_component -from homeassistant.components.cloud import iot, auth_api +from homeassistant.components.cloud import Cloud, iot, auth_api, MODE_DEV from tests.components.alexa import test_smart_home as test_alexa from tests.common import mock_coro @@ -202,7 +202,7 @@ def test_cloud_check_token_raising(mock_client, caplog, mock_cloud): yield from conn.connect() - assert 'Unable to connect: BLA' in caplog.text + assert 'Unable to refresh token: BLA' in caplog.text @asyncio.coroutine @@ -348,3 +348,17 @@ def test_handler_google_actions(hass): assert device['name']['name'] == 'Config name' assert device['name']['nicknames'] == ['Config alias'] assert device['type'] == 'action.devices.types.LIGHT' + + +async def test_refresh_token_expired(hass): + """Test handling Unauthenticated error raised if refresh token expired.""" + cloud = Cloud(hass, MODE_DEV, None, None) + + with patch('homeassistant.components.cloud.auth_api.check_token', + side_effect=auth_api.Unauthenticated) as mock_check_token, \ + patch.object(hass.components.persistent_notification, + 'async_create') as mock_create: + await cloud.iot.connect() + + assert len(mock_check_token.mock_calls) == 1 + assert len(mock_create.mock_calls) == 1 From 5cf74d0fcb1fa3fb877217c60548ff4c9dd3ab1a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 2 Mar 2018 10:00:40 -0800 Subject: [PATCH 2/3] Retry less aggressive --- homeassistant/components/cloud/iot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index f1a30d2a4d444..91fbc85df6bfe 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -77,9 +77,9 @@ def _handle_hass_stop(event): self.tries += 1 try: - # Sleep 0, 5, 10, 15 ... 30 seconds between retries + # Sleep 2^tries seconds between retries self.retry_task = hass.async_add_job(asyncio.sleep( - min(30, (self.tries - 1) * 5), loop=hass.loop)) + 2**min(9, self.tries), loop=hass.loop)) yield from self.retry_task self.retry_task = None except asyncio.CancelledError: From 0a086305ae9c0203d35278b39c0a03d213a75162 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 2 Mar 2018 10:03:41 -0800 Subject: [PATCH 3/3] Newline --- homeassistant/components/cloud/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index 650ad047dad97..99075d3d02d07 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -21,4 +21,4 @@ You have been logged out of Home Assistant Cloud because we have been unable to verify your credentials. Please [log in](/config/cloud) again to continue using the service. -""" \ No newline at end of file +"""