diff options
author | Fabian Müller <fmueller@owncloud.com> | 2022-05-13 09:52:07 +0300 |
---|---|---|
committer | Hannah von Reth <vonreth@kde.org> | 2022-05-13 14:08:30 +0300 |
commit | b2daa0004d32dd4691dc54a81cd9b9f56c50d230 (patch) | |
tree | c027acaf56f94f6921afc0c44d6b93849322b97c /src/libsync | |
parent | c7fa763aa85fccc4c38856e86d690349a069add1 (diff) |
Make the CheckServer job independent of the account
The job needs to run on an independent QNAM
Diffstat (limited to 'src/libsync')
-rw-r--r-- | src/libsync/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/libsync/creds/oauth.cpp | 23 | ||||
-rw-r--r-- | src/libsync/networkjobs.cpp | 154 | ||||
-rw-r--r-- | src/libsync/networkjobs.h | 75 | ||||
-rw-r--r-- | src/libsync/networkjobs/checkserverjobfactory.cpp | 139 | ||||
-rw-r--r-- | src/libsync/networkjobs/checkserverjobfactory.h | 54 |
6 files changed, 204 insertions, 242 deletions
diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 8e73a2c1f..c837ee7a7 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -52,6 +52,7 @@ set(libsync_SRCS creds/credentialscommon.cpp creds/oauth.cpp + networkjobs/checkserverjobfactory.cpp networkjobs/jsonjob.cpp abstractcorejob.cpp diff --git a/src/libsync/creds/oauth.cpp b/src/libsync/creds/oauth.cpp index 4d97df5a6..5a6330913 100644 --- a/src/libsync/creds/oauth.cpp +++ b/src/libsync/creds/oauth.cpp @@ -20,6 +20,7 @@ #include "creds/httpcredentials.h" #include "creds/jobs/determineuserjobfactory.h" #include "networkjobs.h" +#include "networkjobs/checkserverjobfactory.h" #include "theme.h" #include <QApplication> @@ -597,23 +598,19 @@ void AccountBasedOAuth::startAuthentication() void AccountBasedOAuth::fetchWellKnown() { - auto *checkServerJob = new CheckServerJob(_account->sharedFromThis(), this); - checkServerJob->setClearCookies(true); - checkServerJob->setTimeout(defaultTimeout()); + auto *checkServerJob = CheckServerJobFactory(_networkAccessManager, this).startJob(_serverUrl); - connect(checkServerJob, &CheckServerJob::instanceNotFound, this, [this](QNetworkReply *reply) { - if (_isRefreshingToken) { - Q_EMIT refreshError(reply->error(), reply->errorString()); + connect(checkServerJob, &CoreJob::finished, this, [checkServerJob, this]() { + if (checkServerJob->success()) { + OAuth::fetchWellKnown(); } else { - Q_EMIT result(Error); + if (_isRefreshingToken) { + Q_EMIT refreshError(checkServerJob->reply()->error(), checkServerJob->errorMessage()); + } else { + Q_EMIT result(Error); + } } }); - - connect(checkServerJob, &CheckServerJob::instanceFound, this, [this](const QUrl &url, const QJsonObject &info) { - OAuth::fetchWellKnown(); - }); - - checkServerJob->start(); } void AccountBasedOAuth::dynamicRegistrationDataReceived(const QVariantMap &dynamicRegistrationData) diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 86d8d4e36..a689f7635 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -47,7 +47,6 @@ namespace OCC { Q_LOGGING_CATEGORY(lcEtagJob, "sync.networkjob.etag", QtInfoMsg) Q_LOGGING_CATEGORY(lcLsColJob, "sync.networkjob.lscol", QtInfoMsg) -Q_LOGGING_CATEGORY(lcCheckServerJob, "sync.networkjob.checkserver", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropfindJob, "sync.networkjob.propfind", QtInfoMsg) Q_LOGGING_CATEGORY(lcAvatarJob, "sync.networkjob.avatar", QtInfoMsg) Q_LOGGING_CATEGORY(lcMkColJob, "sync.networkjob.mkcol", QtInfoMsg) @@ -396,159 +395,6 @@ const QHash<QString, qint64> &LsColJob::sizes() const /*********************************************************************************************/ -CheckServerJob::CheckServerJob(AccountPtr account, QObject *parent) - : AbstractNetworkJob(account, account->url(), QStringLiteral("status.php"), parent) -{ - setIgnoreCredentialFailure(true); - setAuthenticationJob(true); - - connect(this, &CheckServerJob::networkError, this, [this] { - if (timedOut()) { - Q_EMIT timeout(url()); - } - }); -} - -void CheckServerJob::start() -{ - _serverUrl = baseUrl(); - QNetworkRequest req; - // don't authenticate the request to a possibly external service - req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true); - req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - req.setRawHeader(QByteArrayLiteral("OC-Connection-Validator"), QByteArrayLiteral("desktop")); - req.setMaximumRedirectsAllowed(_maxRedirectsAllowed); - if (_clearCookies && Theme::instance()->connectionValidatorClearCookies()) { - _account->clearCookieJar(); - } - sendRequest("GET", req); - AbstractNetworkJob::start(); -} - -void CheckServerJob::setClearCookies(bool clearCookies) -{ - _clearCookies = clearCookies; -} - -QString CheckServerJob::version(const QJsonObject &info) -{ - return info.value(QLatin1String("version")).toString() + QLatin1Char('-') + info.value(QLatin1String("productname")).toString(); -} - -QString CheckServerJob::versionString(const QJsonObject &info) -{ - return info.value(QLatin1String("versionstring")).toString(); -} - -bool CheckServerJob::installed(const QJsonObject &info) -{ - return info.value(QLatin1String("installed")).toBool(); -} - -static void mergeSslConfigurationForSslButton(const QSslConfiguration &config, AccountPtr account) -{ - if (config.peerCertificateChain().length() > 0) { - const auto certs = config.peerCertificateChain(); - account->_peerCertificateChain = { certs.cbegin(), certs.cend() }; - } - if (!config.sessionCipher().isNull()) { - account->_sessionCipher = config.sessionCipher(); - } - if (config.sessionTicket().length() > 0) { - account->_sessionTicket = config.sessionTicket(); - } -} - -void CheckServerJob::encryptedSlot() -{ - mergeSslConfigurationForSslButton(reply()->sslConfiguration(), account()); -} - -void CheckServerJob::newReplyHook(QNetworkReply *reply) -{ - connect(reply, &QNetworkReply::metaDataChanged, this, &CheckServerJob::metaDataChangedSlot); - connect(reply, &QNetworkReply::encrypted, this, &CheckServerJob::encryptedSlot); - connect(reply, &QNetworkReply::sslErrors, this, &CheckServerJob::sslErrors); - connect(reply, &QNetworkReply::redirected, this, [reply, this] { - const auto code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (code == 302 || code == 307) { - _redirectDistinct = false; - } - }); -} - -int CheckServerJob::maxRedirectsAllowed() const -{ - return _maxRedirectsAllowed; -} - -void CheckServerJob::setMaxRedirectsAllowed(int maxRedirectsAllowed) -{ - _maxRedirectsAllowed = maxRedirectsAllowed; -} - -void CheckServerJob::metaDataChangedSlot() -{ - mergeSslConfigurationForSslButton(reply()->sslConfiguration(), account()); -} - -bool CheckServerJob::finished() -{ - const QUrl targetUrl = reply()->url().adjusted(QUrl::RemoveFilename); - if (targetUrl.scheme() == QLatin1String("https") - && reply()->sslConfiguration().sessionTicket().isEmpty() - && reply()->error() == QNetworkReply::NoError) { - qCWarning(lcCheckServerJob) << "No SSL session identifier / session ticket is used, this might impact sync performance negatively."; - } - if (_serverUrl != targetUrl) { - if (_redirectDistinct) { - _serverUrl = targetUrl; - } else { - if (_firstTry) { - qCWarning(lcCheckServerJob) << "Server might have moved, retry"; - _firstTry = false; - _redirectDistinct = true; - start(); - return false; - } else { - qCWarning(lcCheckServerJob) << "We got a temporary moved server aborting"; - emit instanceNotFound(reply()); - return true; - } - } - } - - mergeSslConfigurationForSslButton(reply()->sslConfiguration(), account()); - - const int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (reply()->error() == QNetworkReply::TooManyRedirectsError) { - qCWarning(lcCheckServerJob) << "error:" << reply()->errorString(); - emit instanceNotFound(reply()); - } else if (httpStatus != 200 || reply()->bytesAvailable() == 0) { - qCWarning(lcCheckServerJob) << "error: status.php replied " << httpStatus; - emit instanceNotFound(reply()); - } else { - const QByteArray body = reply()->peek(4 * 1024); - QJsonParseError error; - auto status = QJsonDocument::fromJson(body, &error); - // empty or invalid response - if (error.error != QJsonParseError::NoError || status.isNull()) { - qCWarning(lcCheckServerJob) << "status.php from server is not valid JSON!" << body << reply()->request().url() << error.errorString(); - } - - qCInfo(lcCheckServerJob) << "status.php returns: " << status << " " << reply()->error() << " Reply: " << reply(); - if (status.object().contains(QStringLiteral("installed"))) { - emit instanceFound(_serverUrl, status.object()); - } else { - qCWarning(lcCheckServerJob) << "No proper answer on " << reply()->url(); - emit instanceNotFound(reply()); - } - } - return true; -} - -/*********************************************************************************************/ - void PropfindJob::start() { diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index 24f770940..4ecfbc07d 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -194,81 +194,6 @@ private: }; /** - * @brief The CheckServerJob class - * @ingroup libsync - */ -class OWNCLOUDSYNC_EXPORT CheckServerJob : public AbstractNetworkJob -{ - Q_OBJECT -public: - explicit CheckServerJob(AccountPtr account, QObject *parent = nullptr); - void start() override; - - static QString version(const QJsonObject &info); - static QString versionString(const QJsonObject &info); - static bool installed(const QJsonObject &info); - - int maxRedirectsAllowed() const; - void setMaxRedirectsAllowed(int maxRedirectsAllowed); - - /** Whether to clear the cookies before we start the job - * This option also depends on Theme::instance()->connectionValidatorClearCookies() - */ - void setClearCookies(bool clearCookies); - -signals: - /** Emitted when a status.php was successfully read. - * - * \a url see _serverStatusUrl (does not include "/status.php") - * \a info The status.php reply information - */ - void instanceFound(const QUrl &url, const QJsonObject &info); - - /** Emitted on invalid status.php reply. - * - * \a reply is never null - */ - void instanceNotFound(QNetworkReply *reply); - - /** A timeout occurred. - * - * \a url The specific url where the timeout happened. - */ - void timeout(const QUrl &url); - - void sslErrors(const QList<QSslError> &errors); - -private: - bool finished() override; -private slots: - virtual void metaDataChangedSlot(); - virtual void encryptedSlot(); - -protected: - void newReplyHook(QNetworkReply *) override; - -private: - bool _clearCookies = false; - - /** The permanent-redirect adjusted account url. - * - * Note that temporary redirects or a permanent redirect behind a temporary - * one do not affect this url. - */ - QUrl _serverUrl; - - int _maxRedirectsAllowed = 5; - - /** we only got permanent redirects */ - bool _redirectDistinct = true; - /** only retry once, the first try might give us needed cookies - the second is supposed to succeed - */ - bool _firstTry = true; -}; - - -/** * @brief The RequestEtagJob class */ class OWNCLOUDSYNC_EXPORT RequestEtagJob : public AbstractNetworkJob diff --git a/src/libsync/networkjobs/checkserverjobfactory.cpp b/src/libsync/networkjobs/checkserverjobfactory.cpp new file mode 100644 index 000000000..a83692d46 --- /dev/null +++ b/src/libsync/networkjobs/checkserverjobfactory.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) Fabian Müller <fmueller@owncloud.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "checkserverjobfactory.h" +#include "common/utility.h" +#include "creds/httpcredentials.h" +#include <QJsonParseError> + +namespace { + +// FIXME: this is not a permanent solution, eventually we want to replace the job factories with job classes so we can store such information there +class CheckServerCoreJob : OCC::CoreJob +{ + friend OCC::CheckServerJobFactory; + +private: + // doesn't concern users of the job factory + // we just need a place to maintain these variables, but the factory is likely deleted before the job has finished + bool _redirectDistinct; + bool _firstTry; +}; + +} + +namespace OCC { + +Q_LOGGING_CATEGORY(lcCheckServerJob, "sync.checkserverjob", QtInfoMsg) + +CheckServerJobResult::CheckServerJobResult(const QJsonObject &statusObject, const QUrl &serverUrl) + : _statusObject(statusObject) + , _serverUrl(serverUrl) +{ +} + +QJsonObject CheckServerJobResult::statusObject() const +{ + return _statusObject; +} + +QUrl CheckServerJobResult::serverUrl() const +{ + return _serverUrl; +} + +CoreJob *CheckServerJobFactory::startJob(const QUrl &url) +{ + // the custom job class is used to store some state we need to maintain until the job has finished + auto job = new CheckServerCoreJob; + + auto req = makeRequest(Utility::concatUrlPath(url, QStringLiteral("status.php"))); + + req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + req.setRawHeader(QByteArrayLiteral("OC-Connection-Validator"), QByteArrayLiteral("desktop")); + req.setMaximumRedirectsAllowed(_maxRedirectsAllowed); + + auto *reply = nam()->get(req); + + connect(reply, &QNetworkReply::redirected, job, [reply, job] { + const auto code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (code == 302 || code == 307) { + job->_redirectDistinct = false; + } + }); + + connect(reply, &QNetworkReply::finished, job, [url, reply, job] { + reply->deleteLater(); + + // need a mutable copy + auto serverUrl = url; + + const QUrl targetUrl = reply->url().adjusted(QUrl::RemoveFilename); + + // TODO: still needed? + if (targetUrl.scheme() == QLatin1String("https") + && reply->sslConfiguration().sessionTicket().isEmpty() + && reply->error() == QNetworkReply::NoError) { + qCWarning(lcCheckServerJob) << "No SSL session identifier / session ticket is used, this might impact sync performance negatively."; + } + + if (serverUrl != targetUrl) { + if (job->_redirectDistinct) { + serverUrl = targetUrl; + } else { + if (job->_firstTry) { + qCWarning(lcCheckServerJob) << "Server might have moved, retry"; + job->_firstTry = false; + job->_redirectDistinct = true; + + // FIXME + } else { + qCWarning(lcCheckServerJob) << "We got a temporary moved server aborting"; + setJobError(job, QStringLiteral("Illegal redirect by server"), reply); + } + } + } + + const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (reply->error() == QNetworkReply::TooManyRedirectsError) { + qCWarning(lcCheckServerJob) << "error:" << reply->errorString(); + setJobError(job, reply->errorString(), reply); + } else if (httpStatus != 200 || reply->bytesAvailable() == 0) { + qCWarning(lcCheckServerJob) << "error: status.php replied " << httpStatus; + setJobError(job, QStringLiteral("Invalid HTTP status code received for status.php: %1").arg(httpStatus), reply); + } else { + const QByteArray body = reply->peek(4 * 1024); + QJsonParseError error; + auto status = QJsonDocument::fromJson(body, &error); + // empty or invalid response + if (error.error != QJsonParseError::NoError || status.isNull()) { + qCWarning(lcCheckServerJob) << "status.php from server is not valid JSON!" << body << reply->request().url() << error.errorString(); + } + + qCInfo(lcCheckServerJob) << "status.php returns: " << status << " " << reply->error() << " Reply: " << reply; + + if (status.object().contains(QStringLiteral("installed"))) { + CheckServerJobResult result(status.object(), serverUrl); + setJobResult(job, QVariant::fromValue(result)); + } else { + qCWarning(lcCheckServerJob) << "No proper answer on " << reply->url(); + setJobError(job, QStringLiteral("Did not receive expected reply from server"), reply); + } + } + }); + + return job; +} + +} // OCC diff --git a/src/libsync/networkjobs/checkserverjobfactory.h b/src/libsync/networkjobs/checkserverjobfactory.h new file mode 100644 index 000000000..e3ffff2a2 --- /dev/null +++ b/src/libsync/networkjobs/checkserverjobfactory.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) Fabian Müller <fmueller@owncloud.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include "abstractcorejob.h" + +#include <QJsonObject> + +namespace OCC { + +class OWNCLOUDSYNC_EXPORT CheckServerJobResult +{ + +public: + CheckServerJobResult() = default; + CheckServerJobResult(const QJsonObject &statusObject, const QUrl &serverUrl); + + QJsonObject statusObject() const; + QUrl serverUrl() const; + +private: + const QJsonObject _statusObject; + const QUrl _serverUrl; +}; + + +class OWNCLOUDSYNC_EXPORT CheckServerJobFactory : public AbstractCoreJobFactory +{ + Q_OBJECT + +public: + using AbstractCoreJobFactory::AbstractCoreJobFactory; + + CoreJob *startJob(const QUrl &url) override; + +private: + int _maxRedirectsAllowed = 5; +}; + +} // OCC + +Q_DECLARE_METATYPE(OCC::CheckServerJobResult) |