Only refresh token on unauthorized response from request

This commit is contained in:
Kevin Fronczak
2020-05-27 23:05:51 +00:00
parent 6a5256e9b1
commit 9e124ce5cc
5 changed files with 60 additions and 18 deletions
+32 -14
View File
@@ -5,7 +5,6 @@ from requests import Request, Session, exceptions
from blinkpy import api from blinkpy import api
from blinkpy.helpers import util from blinkpy.helpers import util
from blinkpy.helpers.constants import BLINK_URL, LOGIN_ENDPOINT from blinkpy.helpers.constants import BLINK_URL, LOGIN_ENDPOINT
from blinkpy.helpers import errors as ERROR
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -95,7 +94,10 @@ class Auth:
self.token = self.login_response["authtoken"]["authtoken"] self.token = self.login_response["authtoken"]["authtoken"]
self.client_id = self.login_response["client"]["id"] self.client_id = self.login_response["client"]["id"]
self.account_id = self.login_response["account"]["id"] self.account_id = self.login_response["account"]["id"]
except KeyError: except LoginError:
_LOGGER.error("Login endpoint failed. Try again later.")
raise TokenRefreshFailed
except (TypeError, KeyError):
_LOGGER.error("Malformed login response: %s", self.login_response) _LOGGER.error("Malformed login response: %s", self.login_response)
raise TokenRefreshFailed raise TokenRefreshFailed
return True return True
@@ -110,11 +112,12 @@ class Auth:
"""Check for valid response.""" """Check for valid response."""
if not json_resp: if not json_resp:
return response return response
try: try:
json_data = response.json() if response.status_code in [101, 401]:
if json_data["code"] in ERROR.BLINK_ERRORS: raise UnauthorizedError
if response.status_code == 404:
raise exceptions.ConnectionError raise exceptions.ConnectionError
json_data = response.json()
except KeyError: except KeyError:
pass pass
except (AttributeError, ValueError): except (AttributeError, ValueError):
@@ -141,31 +144,42 @@ class Auth:
:param reqtype: Can be 'get' or 'post' (default: 'get') :param reqtype: Can be 'get' or 'post' (default: 'get')
:param stream: Stream response? True/FALSE :param stream: Stream response? True/FALSE
:param json_resp: Return JSON response? TRUE/False :param json_resp: Return JSON response? TRUE/False
:param is_retry: Is this a retry attempt? True/FALSE :param is_retry: Is this part of a re-auth attempt? True/FALSE
""" """
req = self.prepare_request(url, headers, data, reqtype) req = self.prepare_request(url, headers, data, reqtype)
try: try:
response = self.session.send(req, stream=stream) response = self.session.send(req, stream=stream)
return self.validate_response(response, json_resp) return self.validate_response(response, json_resp)
except (exceptions.ConnectionError, exceptions.Timeout):
except (exceptions.ConnectionError, exceptions.Timeout, TokenRefreshFailed): _LOGGER.error(
"Connection error. Endpoint %s possibly down or throttled. %s: %s",
url,
response.status_code,
response.reason,
)
except BlinkBadResponse:
_LOGGER.error(
"Expected json response from %s, but received: %s: %s",
url,
response.status_code,
response.reason,
)
except UnauthorizedError:
try: try:
if not is_retry: if not is_retry:
self.refresh_token() self.refresh_token()
return self.query( return self.query(
url=url, url=url,
data=data, data=data,
headers=headers, headers=self.header,
reqtype=reqtype, reqtype=reqtype,
stream=stream, stream=stream,
json_resp=json_resp, json_resp=json_resp,
is_retry=True, is_retry=True,
) )
except (TokenRefreshFailed, LoginError): _LOGGER.error("Unable to access %s after token refresh.", url)
_LOGGER.error("Endpoint %s failed. Unable to refresh login tokens", url) except TokenRefreshFailed:
except BlinkBadResponse: _LOGGER.error("Unable to refresh token.")
_LOGGER.error("Expected json response, but received: %s", response)
_LOGGER.error("Endpoint %s failed", url)
return None return None
def send_auth_key(self, blink, key): def send_auth_key(self, blink, key):
@@ -200,3 +214,7 @@ class LoginError(Exception):
class BlinkBadResponse(Exception): class BlinkBadResponse(Exception):
"""Class to throw bad json response exception.""" """Class to throw bad json response exception."""
class UnauthorizedError(Exception):
"""Class to throw an unauthorized access error."""
-1
View File
@@ -78,7 +78,6 @@ class Blink:
""" """
if self.check_if_ok_to_update() or force: if self.check_if_ok_to_update() or force:
if not self.available: if not self.available:
self.auth.refresh_token()
self.setup_post_verify() self.setup_post_verify()
for sync_name, sync_module in self.sync.items(): for sync_name, sync_module in self.sync.items():
+1 -1
View File
@@ -12,4 +12,4 @@ AUTH_TOKEN = (
) )
REQUEST = (4, "Cannot perform request (get/post type incorrect)") REQUEST = (4, "Cannot perform request (get/post type incorrect)")
BLINK_ERRORS = [101, 400, 404] BLINK_ERRORS = [400, 404]
+1
View File
@@ -9,6 +9,7 @@ class MockResponse:
self.json_data = json_data self.json_data = json_data
self.status_code = status_code self.status_code = status_code
self.raw_data = raw_data self.raw_data = raw_data
self.reason = "foobar"
def json(self): def json(self):
"""Return json data from get_request.""" """Return json data from get_request."""
+26 -2
View File
@@ -3,7 +3,13 @@
import unittest import unittest
from unittest import mock from unittest import mock
from requests import exceptions from requests import exceptions
from blinkpy.auth import Auth, LoginError, TokenRefreshFailed, BlinkBadResponse from blinkpy.auth import (
Auth,
LoginError,
TokenRefreshFailed,
BlinkBadResponse,
UnauthorizedError,
)
import blinkpy.helpers.constants as const import blinkpy.helpers.constants as const
import tests.mock_responses as mresp import tests.mock_responses as mresp
@@ -90,6 +96,10 @@ class TestAuth(unittest.TestCase):
with self.assertRaises(exceptions.ConnectionError): with self.assertRaises(exceptions.ConnectionError):
self.auth.validate_response(fake_resp, True) self.auth.validate_response(fake_resp, True)
fake_resp = mresp.MockResponse({"code": 101}, 401)
with self.assertRaises(UnauthorizedError):
self.auth.validate_response(fake_resp, True)
def test_good_response_code(self): def test_good_response_code(self):
"""Check good response code from server.""" """Check good response code from server."""
fake_resp = mresp.MockResponse({"foo": "bar"}, 200) fake_resp = mresp.MockResponse({"foo": "bar"}, 200)
@@ -133,6 +143,8 @@ class TestAuth(unittest.TestCase):
mock_req.return_value = fake_resp mock_req.return_value = fake_resp
with self.assertRaises(LoginError): with self.assertRaises(LoginError):
self.auth.login() self.auth.login()
with self.assertRaises(TokenRefreshFailed):
self.auth.refresh_token()
@mock.patch("blinkpy.auth.Auth.login") @mock.patch("blinkpy.auth.Auth.login")
def test_refresh_token(self, mock_login): def test_refresh_token(self, mock_login):
@@ -197,10 +209,22 @@ class TestAuth(unittest.TestCase):
def test_query_retry(self, mock_refresh, mock_validate): def test_query_retry(self, mock_refresh, mock_validate):
"""Check handling of request retry.""" """Check handling of request retry."""
self.auth.session = MockSession() self.auth.session = MockSession()
mock_validate.side_effect = [TokenRefreshFailed, "foobar"] mock_validate.side_effect = [UnauthorizedError, "foobar"]
mock_refresh.return_value = True mock_refresh.return_value = True
self.assertEqual(self.auth.query(url="http://example.com"), "foobar") self.assertEqual(self.auth.query(url="http://example.com"), "foobar")
@mock.patch("blinkpy.auth.Auth.validate_response")
@mock.patch("blinkpy.auth.Auth.refresh_token")
def test_query_retry_failed(self, mock_refresh, mock_validate):
"""Check handling of failed retry request."""
self.auth.seession = MockSession()
mock_validate.side_effect = [UnauthorizedError, BlinkBadResponse]
mock_refresh.return_value = True
self.assertEqual(self.auth.query(url="http://example.com"), None)
mock_validate.side_effect = [UnauthorizedError, TokenRefreshFailed]
self.assertEqual(self.auth.query(url="http://example.com"), None)
class MockSession: class MockSession:
"""Object to mock a session.""" """Object to mock a session."""