From c06b31fa3aa19d4a37ff2ddab063c84274df1621 Mon Sep 17 00:00:00 2001 From: FriendsOfGalaxy Date: Mon, 18 May 2020 09:59:12 +0200 Subject: [PATCH] version 0.30 --- requirements/dev.txt | 2 +- src/http_client.py | 13 ++++++++++++- src/version.py | 5 ++++- tests/async_mock.py | 8 ++++++++ tests/test_authentication.py | 1 + tests/test_http_client.py | 27 +++++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/test_http_client.py diff --git a/requirements/dev.txt b/requirements/dev.txt index a27fb70..27f05c4 100755 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -7,4 +7,4 @@ pytest-pythonpath==0.7.3 pytest-asyncio==0.10.0 pytest-mock==1.10.3 cryptography==2.5 -pip-tools==4.5.0 \ No newline at end of file +pip-tools==5.1.2 \ No newline at end of file diff --git a/src/http_client.py b/src/http_client.py index 8a5d10d..dd5bb55 100755 --- a/src/http_client.py +++ b/src/http_client.py @@ -1,5 +1,6 @@ import aiohttp import logging +import asyncio from urllib.parse import parse_qsl, urlsplit @@ -94,6 +95,7 @@ def __init__(self, auth_lost_callback, store_credentials_callback): self._refresh_token = None self._auth_lost_callback = auth_lost_callback self._store_credentials_callback = store_credentials_callback + self.can_refresh = asyncio.Event() super().__init__() @property @@ -145,11 +147,18 @@ async def get_access_token(self, refresh_token=None, url=OAUTH_TOKEN_URL, cookie async def authenticate(self, refresh_token): self._refresh_token = refresh_token - self._access_token = await self.get_access_token(self._refresh_token) + try: + self._access_token = await self.get_access_token(self._refresh_token) + finally: + self.can_refresh.set() if not self._access_token: raise UnknownBackendResponse("Empty access token") async def _refresh_access_token(self): + if not self.can_refresh.is_set(): + await self.can_refresh.wait() + return + self.can_refresh.clear() try: self._access_token = await self.get_access_token(self._refresh_token) if not self._access_token: @@ -162,6 +171,8 @@ async def _refresh_access_token(self): if self._auth_lost_callback: self._auth_lost_callback() raise AuthenticationRequired() + finally: + self.can_refresh.set() async def request(self, method, *args, **kwargs): if not self._access_token: diff --git a/src/version.py b/src/version.py index b1861d2..25278a5 100755 --- a/src/version.py +++ b/src/version.py @@ -1,6 +1,9 @@ -__version__ = "0.29" +__version__ = "0.30" __changelog__ = { + "0.30":""" + - Only allow one token refresh at a time + """, "0.29": """ - Fix broken authentication due to change on psn side """, diff --git a/tests/async_mock.py b/tests/async_mock.py index ebc3682..bae4158 100644 --- a/tests/async_mock.py +++ b/tests/async_mock.py @@ -1,7 +1,15 @@ from unittest.mock import MagicMock +import asyncio + class AsyncMock(MagicMock): async def __call__(self, *args, **kwargs): # pylint: disable=useless-super-delegation return super(AsyncMock, self).__call__(*args, **kwargs) + +class AsyncMockDelayed(MagicMock): + async def __call__(self, *args, **kwargs): + await asyncio.sleep(0.1) + # pylint: disable=useless-super-delegation + return super(AsyncMockDelayed, self).__call__(*args, **kwargs) diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 96b3f74..670e078 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -173,3 +173,4 @@ async def test_failed_to_refresh_access_token( await authenticated_psn_client.async_get_own_user_info() get_access_token.assert_called_once_with(npsso) + diff --git a/tests/test_http_client.py b/tests/test_http_client.py new file mode 100644 index 0000000..c2b2209 --- /dev/null +++ b/tests/test_http_client.py @@ -0,0 +1,27 @@ +import asyncio +import pytest +from unittest.mock import Mock +from galaxy.api.errors import AuthenticationRequired +from http_client import AuthenticatedHttpClient +from tests.async_mock import AsyncMockDelayed, AsyncMock + + +@pytest.mark.asyncio +async def test_multiple_refreshing(): + EXPIRED_TOKEN = "old access token" + REFRESHED_TOKEN = "refreshed access token" + def raise_on_old_token(*args, **kwargs): + if http_client._access_token == EXPIRED_TOKEN: + raise AuthenticationRequired() + return 'ok' + http_client = AuthenticatedHttpClient(Mock, Mock) + http_client.can_refresh.set() + http_client._access_token = EXPIRED_TOKEN + http_client.get_access_token = AsyncMockDelayed(return_value=REFRESHED_TOKEN) + http_client._oauth_request = AsyncMock(side_effect=raise_on_old_token) + responses = await asyncio.gather( + http_client.request('url'), + http_client.request('url'), + ) + for i in responses: + assert i == 'ok' \ No newline at end of file