Refresh the august access token when needed (#31735)

* Refresh the august access token
when needed.

Currently august will stop working when the token
expires about every six month.

This resolves issue #23788

* Make refresh_access_token_if_needed private since we do not want additional callers

* Add init
This commit is contained in:
J. Nick Koston 2020-02-12 00:13:54 -06:00 committed by GitHub
parent 891f13eefe
commit f5be9ef7fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 4 deletions

View File

@ -35,6 +35,7 @@ homeassistant/components/arest/* @fabaff
homeassistant/components/asuswrt/* @kennedyshead
homeassistant/components/aten_pe/* @mtdcr
homeassistant/components/atome/* @baqs
homeassistant/components/august/* @bdraco
homeassistant/components/aurora_abb_powerone/* @davet2001
homeassistant/components/auth/* @home-assistant/core
homeassistant/components/automatic/* @armills

View File

@ -136,7 +136,7 @@ def setup_august(hass, config, api, authenticator):
if DOMAIN in _CONFIGURING:
hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN))
hass.data[DATA_AUGUST] = AugustData(hass, api, authentication.access_token)
hass.data[DATA_AUGUST] = AugustData(hass, api, authentication, authenticator)
for component in AUGUST_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
@ -193,11 +193,14 @@ def setup(hass, config):
class AugustData:
"""August data object."""
def __init__(self, hass, api, access_token):
def __init__(self, hass, api, authentication, authenticator):
"""Init August data object."""
self._hass = hass
self._api = api
self._access_token = access_token
self._authenticator = authenticator
self._access_token = authentication.access_token
self._access_token_expires = authentication.access_token_expires
self._doorbells = self._api.get_doorbells(self._access_token) or []
self._locks = self._api.get_operable_locks(self._access_token) or []
self._house_ids = set()
@ -227,6 +230,21 @@ class AugustData:
"""Return a list of locks."""
return self._locks
def _refresh_access_token_if_needed(self):
"""Refresh the august access token if needed."""
if self._authenticator.should_refresh():
refreshed_authentication = self._authenticator.refresh_access_token(
force=False
)
_LOGGER.info(
"Refreshed august access token. The old token expired at %s, and the new token expires at %s",
self._access_token_expires,
refreshed_authentication.access_token_expires,
)
self._access_token = refreshed_authentication.access_token
self._access_token_expires = refreshed_authentication.access_token_expires
def get_device_activities(self, device_id, *activity_types):
"""Return a list of activities."""
_LOGGER.debug("Getting device activities")
@ -245,6 +263,17 @@ class AugustData:
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
"""Update data object with latest from August API."""
# This is the only place we refresh the api token
# in order to avoid multiple threads from doing it at the same time
# since there will only be one activity refresh at a time
#
# In the future when this module is converted to async we should
# use a lock to prevent all api calls while the token
# is being refreshed as this is a better solution
#
self._refresh_access_token_if_needed()
_LOGGER.debug("Start retrieving device activities")
for house_id in self.house_ids:
_LOGGER.debug("Updating device activity for house id %s", house_id)

View File

@ -4,5 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"requirements": ["py-august==0.11.0"],
"dependencies": ["configurator"],
"codeowners": []
"codeowners": ["@bdraco"]
}

View File

@ -389,6 +389,9 @@ pure-python-adb==0.2.2.dev0
# homeassistant.components.pushbullet
pushbullet.py==0.11.0
# homeassistant.components.august
py-august==0.11.0
# homeassistant.components.canary
py-canary==0.5.0

View File

@ -0,0 +1 @@
"""Tests for the august component."""

View File

@ -0,0 +1,44 @@
"""The tests for the august platform."""
from unittest.mock import MagicMock, PropertyMock
from homeassistant.components import august
def _mock_august_authenticator():
authenticator = MagicMock(name="august.authenticator")
authenticator.should_refresh = MagicMock(
name="august.authenticator.should_refresh", return_value=0
)
authenticator.refresh_access_token = MagicMock(
name="august.authenticator.refresh_access_token"
)
return authenticator
def _mock_august_authentication(token_text, token_timestamp):
authentication = MagicMock(name="august.authentication")
type(authentication).access_token = PropertyMock(return_value=token_text)
type(authentication).access_token_expires = PropertyMock(
return_value=token_timestamp
)
return authentication
def test__refresh_access_token():
"""Set up things to be run when tests are started."""
authentication = _mock_august_authentication("original_token", 1234)
authenticator = _mock_august_authenticator()
data = august.AugustData(
MagicMock(name="hass"), MagicMock(name="api"), authentication, authenticator
)
data._refresh_access_token_if_needed()
authenticator.refresh_access_token.assert_not_called()
authenticator.should_refresh.return_value = 1
authenticator.refresh_access_token.return_value = _mock_august_authentication(
"new_token", 5678
)
data._refresh_access_token_if_needed()
authenticator.refresh_access_token.assert_called()
assert data._access_token == "new_token"
assert data._access_token_expires == 5678