From 29c7be6b6aee5c052d22509243e1d9e484482f18 Mon Sep 17 00:00:00 2001 From: Russell Martin Date: Fri, 15 Nov 2024 21:45:26 -0500 Subject: [PATCH] Avoid infinite recursion when client is banned --- src/qbittorrentapi/auth.py | 13 ++++++++++--- src/qbittorrentapi/request.py | 5 +++++ tests/test_auth.py | 3 +-- tests/test_request.py | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/qbittorrentapi/auth.py b/src/qbittorrentapi/auth.py index 56ebc4010..c7c2d2d4f 100644 --- a/src/qbittorrentapi/auth.py +++ b/src/qbittorrentapi/auth.py @@ -6,9 +6,12 @@ from requests import Response -from qbittorrentapi import Version +from qbittorrentapi._version_support import Version from qbittorrentapi.definitions import APIKwargsT, APINames, ClientCache -from qbittorrentapi.exceptions import LoginFailed, UnsupportedQbittorrentVersion +from qbittorrentapi.exceptions import ( + LoginFailed, + UnsupportedQbittorrentVersion, +) from qbittorrentapi.request import Request if TYPE_CHECKING: @@ -132,7 +135,11 @@ def _session_cookie(self, cookie_name: str = "SID") -> str | None: def auth_log_out(self, **kwargs: APIKwargsT) -> None: """End session with qBittorrent.""" - self._post(_name=APINames.Authorization, _method="logout", **kwargs) + # Originally, if log out failed authentication, the client would re-authenticate + # and then log out of that session. With the change to avoid retrying failed + # auth calls, only attempt to log out if the current authentication is valid. + if self.is_logged_in: + self._post(_name=APINames.Authorization, _method="logout", **kwargs) def __enter__(self) -> Client: self.auth_log_in() diff --git a/src/qbittorrentapi/request.py b/src/qbittorrentapi/request.py index 9aa790223..c7b79d788 100644 --- a/src/qbittorrentapi/request.py +++ b/src/qbittorrentapi/request.py @@ -604,6 +604,11 @@ def _auth_request( **kwargs, ) except HTTP403Error: + # Do not retry auth endpoints for 403. If an auth endpoint is returning + # 403, then trying again won't work because it is likely the credentials + # are no longer valid. Furthermore, it leads to infinite recursion. + if api_namespace == APINames.Authorization: + raise logger.debug("Login may have expired...attempting new login") self.auth_log_in( # type: ignore[attr-defined] requests_args=requests_args, diff --git a/tests/test_auth.py b/tests/test_auth.py index 3daccc023..a4cd95fff 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -53,9 +53,8 @@ def test_is_logged_in_bad_client(): client.auth_log_in() assert client.is_logged_in is False - with pytest.raises(APIConnectionError): - client.auth_log_out() assert client.is_logged_in is False + client.auth_log_out() # does nothing if not logged in def test_session_cookie(app_version): diff --git a/tests/test_request.py b/tests/test_request.py index 2cfba4594..582017f5f 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -10,6 +10,7 @@ from qbittorrentapi import APINames, Client, exceptions from qbittorrentapi._version_support import v from qbittorrentapi.definitions import Dictionary, List +from qbittorrentapi.exceptions import Forbidden403Error from qbittorrentapi.request import Request from qbittorrentapi.torrents import TorrentDictionary, TorrentInfoList from tests.conftest import IS_QBT_DEV @@ -65,6 +66,23 @@ def test_log_in_via_auth(): client_bad.auth_log_in(username="asdf", password="asdfasdf") +def test_forbidden_when_banned(client, monkeypatch): + monkeypatch.setattr( + client, + "_request_manager", + MagicMock( + side_effect=Forbidden403Error, + spec=client._request_manager, + ), + ) + + with pytest.raises(Forbidden403Error): + client.auth.log_in() + + with pytest.raises(Forbidden403Error): + _ = client.app.version + + @pytest.mark.parametrize( "hostname", (