From 7c277c8fbe8a424d069fca4ce5c06db038a21c9e Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 6 Dec 2024 16:13:16 +0100 Subject: [PATCH] OAuth: Prevent logout when refreshing token (#11984) Prevent the user being logged out when the network disappears during OAuth token refresh. --- src/libsync/creds/httpcredentials.cpp | 60 ++++++++++++++++++--------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/libsync/creds/httpcredentials.cpp b/src/libsync/creds/httpcredentials.cpp index cc447a631b0..37eb725d16e 100644 --- a/src/libsync/creds/httpcredentials.cpp +++ b/src/libsync/creds/httpcredentials.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ Q_LOGGING_CATEGORY(lcHttpCredentials, "sync.credentials.http", QtInfoMsg) namespace { constexpr int TokenRefreshMaxRetries = 3; +constexpr std::chrono::seconds TokenRefreshDefaultTimeout = 30s; constexpr int CredentialVersion = 1; const char authenticationFailedC[] = "owncloud-authentication-failed"; @@ -275,29 +277,47 @@ bool HttpCredentials::refreshAccessTokenInternal(int tokenRefreshRetriesCount) _oAuthJob = new AccountBasedOAuth(_account->sharedFromThis(), _account->accessManager()); connect(_oAuthJob, &AccountBasedOAuth::refreshError, this, [tokenRefreshRetriesCount, this](QNetworkReply::NetworkError error, const QString &) { _oAuthJob->deleteLater(); + + auto networkUnavailable = []() { + if (auto qni = QNetworkInformation::instance()) { + if (qni->reachability() == QNetworkInformation::Reachability::Disconnected) { + return true; + } + } + + return false; + }; + int nextTry = tokenRefreshRetriesCount + 1; std::chrono::seconds timeout = {}; - switch (error) { - case QNetworkReply::ContentNotFoundError: - // 404: bigip f5? - timeout = 0s; - break; - case QNetworkReply::HostNotFoundError: - [[fallthrough]]; - case QNetworkReply::TimeoutError: - [[fallthrough]]; - // Qt reports OperationCanceledError if the request timed out - case QNetworkReply::OperationCanceledError: - [[fallthrough]]; - case QNetworkReply::TemporaryNetworkFailureError: - [[fallthrough]]; - // VPN not ready? - case QNetworkReply::ConnectionRefusedError: + + if (networkUnavailable()) { nextTry = 0; - [[fallthrough]]; - default: - timeout = 30s; + timeout = TokenRefreshDefaultTimeout; + } else { + switch (error) { + case QNetworkReply::ContentNotFoundError: + // 404: bigip f5? + timeout = 0s; + break; + case QNetworkReply::HostNotFoundError: + [[fallthrough]]; + case QNetworkReply::TimeoutError: + [[fallthrough]]; + // Qt reports OperationCanceledError if the request timed out + case QNetworkReply::OperationCanceledError: + [[fallthrough]]; + case QNetworkReply::TemporaryNetworkFailureError: + [[fallthrough]]; + // VPN not ready? + case QNetworkReply::ConnectionRefusedError: + nextTry = 0; + [[fallthrough]]; + default: + timeout = TokenRefreshDefaultTimeout; + } } + if (nextTry >= TokenRefreshMaxRetries) { qCWarning(lcHttpCredentials) << "Too many failed refreshes" << nextTry << "-> log out"; forgetSensitiveData(); @@ -336,6 +356,8 @@ bool HttpCredentials::refreshAccessTokenInternal(int tokenRefreshRetriesCount) void HttpCredentials::invalidateToken() { + qCWarning(lcHttpCredentials) << "Invalidating the credentials"; + if (!_password.isEmpty()) { _previousPassword = _password; }