diff --git a/blinkpy/auth.py b/blinkpy/auth.py index 2f07048..b379406 100644 --- a/blinkpy/auth.py +++ b/blinkpy/auth.py @@ -5,7 +5,6 @@ from requests import Request, Session, exceptions from blinkpy import api from blinkpy.helpers import util from blinkpy.helpers.constants import BLINK_URL, LOGIN_ENDPOINT -from blinkpy.helpers import errors as ERROR _LOGGER = logging.getLogger(__name__) @@ -95,7 +94,10 @@ class Auth: self.token = self.login_response["authtoken"]["authtoken"] self.client_id = self.login_response["client"]["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) raise TokenRefreshFailed return True @@ -110,11 +112,12 @@ class Auth: """Check for valid response.""" if not json_resp: return response - try: - json_data = response.json() - if json_data["code"] in ERROR.BLINK_ERRORS: + if response.status_code in [101, 401]: + raise UnauthorizedError + if response.status_code == 404: raise exceptions.ConnectionError + json_data = response.json() except KeyError: pass except (AttributeError, ValueError): @@ -141,31 +144,42 @@ class Auth: :param reqtype: Can be 'get' or 'post' (default: 'get') :param stream: Stream 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) try: response = self.session.send(req, stream=stream) return self.validate_response(response, json_resp) - - except (exceptions.ConnectionError, exceptions.Timeout, TokenRefreshFailed): + except (exceptions.ConnectionError, exceptions.Timeout): + _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: if not is_retry: self.refresh_token() return self.query( url=url, data=data, - headers=headers, + headers=self.header, reqtype=reqtype, stream=stream, json_resp=json_resp, is_retry=True, ) - except (TokenRefreshFailed, LoginError): - _LOGGER.error("Endpoint %s failed. Unable to refresh login tokens", url) - except BlinkBadResponse: - _LOGGER.error("Expected json response, but received: %s", response) - _LOGGER.error("Endpoint %s failed", url) + _LOGGER.error("Unable to access %s after token refresh.", url) + except TokenRefreshFailed: + _LOGGER.error("Unable to refresh token.") return None def send_auth_key(self, blink, key): @@ -200,3 +214,7 @@ class LoginError(Exception): class BlinkBadResponse(Exception): """Class to throw bad json response exception.""" + + +class UnauthorizedError(Exception): + """Class to throw an unauthorized access error.""" diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 671b380..1d6d0f5 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -78,7 +78,6 @@ class Blink: """ if self.check_if_ok_to_update() or force: if not self.available: - self.auth.refresh_token() self.setup_post_verify() for sync_name, sync_module in self.sync.items(): diff --git a/blinkpy/helpers/errors.py b/blinkpy/helpers/errors.py index 3f39086..e701f2c 100644 --- a/blinkpy/helpers/errors.py +++ b/blinkpy/helpers/errors.py @@ -12,4 +12,4 @@ AUTH_TOKEN = ( ) REQUEST = (4, "Cannot perform request (get/post type incorrect)") -BLINK_ERRORS = [101, 400, 404] +BLINK_ERRORS = [400, 404] diff --git a/tests/mock_responses.py b/tests/mock_responses.py index 3981926..a789e80 100644 --- a/tests/mock_responses.py +++ b/tests/mock_responses.py @@ -9,6 +9,7 @@ class MockResponse: self.json_data = json_data self.status_code = status_code self.raw_data = raw_data + self.reason = "foobar" def json(self): """Return json data from get_request.""" diff --git a/tests/test_auth.py b/tests/test_auth.py index 8086267..8ee18ce 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -3,7 +3,13 @@ import unittest from unittest import mock 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 tests.mock_responses as mresp @@ -90,6 +96,10 @@ class TestAuth(unittest.TestCase): with self.assertRaises(exceptions.ConnectionError): 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): """Check good response code from server.""" fake_resp = mresp.MockResponse({"foo": "bar"}, 200) @@ -133,6 +143,8 @@ class TestAuth(unittest.TestCase): mock_req.return_value = fake_resp with self.assertRaises(LoginError): self.auth.login() + with self.assertRaises(TokenRefreshFailed): + self.auth.refresh_token() @mock.patch("blinkpy.auth.Auth.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): """Check handling of request retry.""" self.auth.session = MockSession() - mock_validate.side_effect = [TokenRefreshFailed, "foobar"] + mock_validate.side_effect = [UnauthorizedError, "foobar"] mock_refresh.return_value = True 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: """Object to mock a session."""