async def test_refresh_token_failure(http_request, http_client, create_json_response): auth_lost = MagicMock() http_client.set_auth_lost_callback(auth_lost) http_request.return_value = create_json_response({"access_token": "token"}) await http_client.authenticate({}) http_request.assert_called_once() http_request.reset_mock() http_request.side_effect = [AccessDenied(), AccessDenied()] with pytest.raises(AccessDenied): await http_client.get("http://test.com") assert http_request.call_count == 2 auth_lost.assert_called_once_with()
async def test_servers_cache_failure(client, protocol_client, backend_client, servers_cache): servers_cache.get.return_value = async_raise(AccessDenied()) await client.run() servers_cache.get.assert_called_once_with() backend_client.get_authentication_data.assert_not_called() protocol_client.authenticate.assert_not_called() protocol_client.run.assert_not_called()
def handle_exception(): """ Context manager translating network related exceptions to custom :mod:`~galaxy.api.errors`. """ try: yield except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ServerDisconnectedError: raise BackendNotAvailable() except aiohttp.ClientConnectionError: raise NetworkError() except aiohttp.ContentTypeError as error: raise UnknownBackendResponse(error.message) except aiohttp.ClientResponseError as error: if error.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired(error.message) if error.status == HTTPStatus.FORBIDDEN: raise AccessDenied(error.message) if error.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable(error.message) if error.status == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests(error.message) if error.status >= 500: raise BackendError(error.message) if error.status >= 400: logger.warning("Got status %d while performing %s request for %s", error.status, error.request_info.method, str(error.request_info.url)) raise UnknownError(error.message) except aiohttp.ClientError as e: logger.exception("Caught exception while performing request") raise UnknownError(repr(e))
async def request(self, method, url, *args, **kwargs): try: response = await self._session.request(method, url, *args, **kwargs) except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ServerDisconnectedError: raise BackendNotAvailable() except aiohttp.ClientConnectionError: raise NetworkError() except aiohttp.ContentTypeError: raise UnknownBackendResponse() except aiohttp.ClientError: logging.exception( "Caught exception while running {} request for {}".format( method, url)) raise UnknownError() if response.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests() if response.status >= 500: raise BackendError() if response.status >= 400: logging.warning( "Got status {} while running {} request for {}".format( response.status, method, url)) raise UnknownError() return response
async def test_servers_cache_failure(client, protocol_client, backend_client, websocket_list): websocket_list.get.return_value = async_raise(AccessDenied()) with pytest.raises(AccessDenied): await client.run() websocket_list.get.assert_called_once_with(0) backend_client.get_authentication_data.assert_not_called() protocol_client.authenticate.assert_not_called() protocol_client.run.assert_not_called()
async def get(self, *args, **kwargs): if not self._access_token: raise AccessDenied("No access token") try: return await self._authorized_get(*args, **kwargs) except (AuthenticationRequired, AccessDenied): # Origin backend returns 403 when the auth token expires await self._refresh_token() return await self._authorized_get(*args, **kwargs)
def handle_status_code(status_code): if status_code == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if status_code == HTTPStatus.FORBIDDEN: raise AccessDenied() if status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if status_code >= 500: raise BackendError() if status_code >= 400: raise UnknownError()
async def _refresh_token(self): try: await self._get_access_token() except (BackendNotAvailable, BackendTimeout, BackendError, NetworkError): logging.warning("Failed to refresh token for independent reasons") raise except Exception: logging.exception("Failed to refresh token") self._access_token = None if self._auth_lost_callback: self._auth_lost_callback() raise AccessDenied()
async def do_request(self, method, url, data=None, json=True, headers=None, ignore_failure=False): loop = asyncio.get_event_loop() if not headers: headers = self._authentication_client.session.headers try: if data is None: data = {} params = { "method": method, "url": url, "data": data, "timeout": self._authentication_client.timeout, "headers": headers } try: response = await loop.run_in_executor( None, functools.partial( self._authentication_client.session.request, **params)) except requests.Timeout: raise BackendTimeout() if not ignore_failure: if response.status_code == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status_code == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status_code >= 500: raise BackendError() if response.status_code >= 400: raise UnknownError() if json: return response.json() else: return response except Exception as e: log.exception( f"Request exception: {str(e)}; url: {url}, method: {method}, data: {data}, headers: {headers}" ) raise
async def _do_request(self, method, url, *args, **kwargs): loop = asyncio.get_running_loop() r = await loop.run_in_executor(None, functools.partial(self.session.request, method, url, *args, **kwargs)) log.info(f"{r.status_code}: response from endpoint {url}") if r.status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN): raise AccessDenied() if r.status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if r.status_code >= 500: raise BackendError() if r.status_code >= 400: raise UnknownError() j = r.json() # all ubi endpoints return jsons return j
async def authenticate(self, stored_credentials=None): if not stored_credentials: self.create_task(self._steam_client.run(), "Run WebSocketClient") return next_step_response(START_URI.LOGIN, END_URI.LOGIN_FINISHED) if stored_credentials.get("cookies", []): logger.error( 'Old http login flow is not unsupported. Please reconnect the plugin' ) raise AccessDenied() self._user_info_cache.from_dict(stored_credentials) if 'games' in self.persistent_cache: self._games_cache.loads(self.persistent_cache['games']) steam_run_task = self.create_task(self._steam_client.run(), "Run WebSocketClient") connection_timeout = 30 try: await asyncio.wait_for(self._user_info_cache.initialized.wait(), connection_timeout) except asyncio.TimeoutError: try: self.raise_websocket_errors() except BackendError as e: logging.info( f"Unable to keep connection with steam backend {repr(e)}") raise except InvalidCredentials: logging.info("Invalid credentials during authentication") raise except Exception as e: logging.info( f"Internal websocket exception caught during auth {repr(e)}" ) raise else: logging.info( f"Failed to initialize connection with steam client within {connection_timeout} seconds" ) raise BackendTimeout() finally: await self.cancel_task(steam_run_task) self.store_credentials(self._user_info_cache.to_dict()) return Authentication(self._user_info_cache.steam_id, self._user_info_cache.persona_name)
async def test_refresh_access_token_success(http_request, http_client, create_json_response): http_request.side_effect = [ create_json_response({"access_token": "token"}) ] await http_client.authenticate({}) assert http_request.call_count == 1 http_request.reset_mock() http_request.side_effect = [ AccessDenied(), create_json_response({"access_token": "new_token"}), create_json_response({}) ] await http_client.get("http://test.com") assert http_request.call_count == 3 headers = http_request.call_args_list[2][1]["headers"] assert headers.get("AuthToken") == "new_token"
async def _authenticated_request(self, method, url, data=None, json=True, headers=None, ignore_failure=False): try: return await self.do_request(method, url, data, json, headers, ignore_failure) except: try: await self.refresh_cookies() self._authentication_client.refresh_credentials() except Exception: self._plugin.lost_authentication() raise AccessDenied() return await self.do_request(method, url, data, json, headers, ignore_failure)
async def _request(self, method, *args, **kwargs): try: response = await self._session.request(method, *args, **kwargs) except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ClientConnectionError: raise NetworkError() logging.debug(f"Request response status: {response.status}") if response.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status >= 500: raise BackendError() if response.status >= 400: raise UnknownError() return response
def _auth_lost(self): if self._auth_lost_callback: self._auth_lost_callback() raise AccessDenied()