Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/owncloud/client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/connectionvalidator.cpp')
-rw-r--r--src/gui/connectionvalidator.cpp336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp
new file mode 100644
index 000000000..3f702903d
--- /dev/null
+++ b/src/gui/connectionvalidator.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) by Klaas Freitag <freitag@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 <QJsonDocument>
+#include <QJsonObject>
+#include <QLoggingCategory>
+#include <QNetworkReply>
+#include <QNetworkProxyFactory>
+#include <QXmlStreamReader>
+
+#include "connectionvalidator.h"
+#include "account.h"
+#include "networkjobs.h"
+#include "clientproxy.h"
+#include <creds/abstractcredentials.h>
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcConnectionValidator, "sync.connectionvalidator", QtInfoMsg)
+
+// Make sure the timeout for this job is less than how often we get called
+// This makes sure we get tried often enough without "ConnectionValidator already running"
+static qint64 timeoutToUseMsec = qMax(1000, ConnectionValidator::DefaultCallingIntervalMsec - 5 * 1000);
+
+ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
+ : QObject(parent)
+ , _account(account)
+ , _isCheckingServerAndAuth(false)
+{
+}
+
+void ConnectionValidator::checkServerAndAuth()
+{
+ if (!_account) {
+ _errors << tr("No ownCloud account configured");
+ reportResult(NotConfigured);
+ return;
+ }
+ qCDebug(lcConnectionValidator) << "Checking server and authentication";
+
+ _isCheckingServerAndAuth = true;
+
+ // Lookup system proxy in a thread https://github.com/owncloud/client/issues/2993
+ if (ClientProxy::isUsingSystemDefault()) {
+ qCDebug(lcConnectionValidator) << "Trying to look up system proxy";
+ ClientProxy::lookupSystemProxyAsync(_account->url(),
+ this, SLOT(systemProxyLookupDone(QNetworkProxy)));
+ } else {
+ // We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings)
+ _account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy));
+ // use a queued invocation so we're as asynchronous as with the other code path
+ QMetaObject::invokeMethod(this, "slotCheckServerAndAuth", Qt::QueuedConnection);
+ }
+}
+
+void ConnectionValidator::systemProxyLookupDone(const QNetworkProxy &proxy)
+{
+ if (!_account) {
+ qCWarning(lcConnectionValidator) << "Bailing out, Account had been deleted";
+ return;
+ }
+
+ if (proxy.type() != QNetworkProxy::NoProxy) {
+ qCInfo(lcConnectionValidator) << "Setting QNAM proxy to be system proxy" << printQNetworkProxy(proxy);
+ } else {
+ qCInfo(lcConnectionValidator) << "No system proxy set by OS";
+ }
+ _account->networkAccessManager()->setProxy(proxy);
+
+ slotCheckServerAndAuth();
+}
+
+// The actual check
+void ConnectionValidator::slotCheckServerAndAuth()
+{
+ CheckServerJob *checkJob = new CheckServerJob(_account, this);
+ checkJob->setTimeout(timeoutToUseMsec);
+ checkJob->setIgnoreCredentialFailure(true);
+ connect(checkJob, &CheckServerJob::instanceFound, this, &ConnectionValidator::slotStatusFound);
+ connect(checkJob, &CheckServerJob::instanceNotFound, this, &ConnectionValidator::slotNoStatusFound);
+ connect(checkJob, &CheckServerJob::timeout, this, &ConnectionValidator::slotJobTimeout);
+ checkJob->start();
+}
+
+void ConnectionValidator::slotStatusFound(const QUrl &url, const QJsonObject &info)
+{
+ // Newer servers don't disclose any version in status.php anymore
+ // https://github.com/owncloud/core/pull/27473/files
+ // so this string can be empty.
+ QString serverVersion = CheckServerJob::version(info);
+
+ // status.php was found.
+ qCInfo(lcConnectionValidator) << "** Application: ownCloud found: "
+ << url << " with version "
+ << CheckServerJob::versionString(info)
+ << "(" << serverVersion << ")";
+
+ // Update server url in case of redirection
+ if (_account->url() != url) {
+ qCInfo(lcConnectionValidator()) << "status.php was redirected to" << url.toString();
+ _account->setUrl(url);
+ _account->wantsAccountSaved(_account.data());
+ }
+
+ if (!serverVersion.isEmpty() && !setAndCheckServerVersion(serverVersion)) {
+ return;
+ }
+
+ // Check for maintenance mode: Servers send "true", so go through QVariant
+ // to parse it correctly.
+ if (info["maintenance"].toVariant().toBool()) {
+ reportResult(MaintenanceMode);
+ return;
+ }
+
+ // now check the authentication
+ QTimer::singleShot(0, this, &ConnectionValidator::checkAuthentication);
+}
+
+// status.php could not be loaded (network or server issue!).
+void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply)
+{
+ auto job = qobject_cast<CheckServerJob *>(sender());
+ qCWarning(lcConnectionValidator) << reply->error() << job->errorString() << reply->peek(1024);
+ if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
+ reportResult(SslError);
+ return;
+ }
+
+ if (!_account->credentials()->stillValid(reply)) {
+ // Note: Why would this happen on a status.php request?
+ _errors.append(tr("Authentication error: Either username or password are wrong."));
+ } else {
+ //_errors.append(tr("Unable to connect to %1").arg(_account->url().toString()));
+ _errors.append(job->errorString());
+ }
+ reportResult(StatusNotFound);
+}
+
+void ConnectionValidator::slotJobTimeout(const QUrl &url)
+{
+ Q_UNUSED(url);
+ //_errors.append(tr("Unable to connect to %1").arg(url.toString()));
+ _errors.append(tr("timeout"));
+ reportResult(Timeout);
+}
+
+
+void ConnectionValidator::checkAuthentication()
+{
+ AbstractCredentials *creds = _account->credentials();
+
+ if (!creds->ready()) {
+ reportResult(CredentialsNotReady);
+ return;
+ }
+
+ // simply GET the webdav root, will fail if credentials are wrong.
+ // continue in slotAuthCheck here :-)
+ qCDebug(lcConnectionValidator) << "# Check whether authenticated propfind works.";
+ PropfindJob *job = new PropfindJob(_account, "/", this);
+ job->setTimeout(timeoutToUseMsec);
+ job->setProperties(QList<QByteArray>() << "getlastmodified");
+ connect(job, &PropfindJob::result, this, &ConnectionValidator::slotAuthSuccess);
+ connect(job, &PropfindJob::finishedWithError, this, &ConnectionValidator::slotAuthFailed);
+ job->start();
+}
+
+void ConnectionValidator::slotAuthFailed(QNetworkReply *reply)
+{
+ auto job = qobject_cast<PropfindJob *>(sender());
+ Status stat = Timeout;
+
+ if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
+ _errors << job->errorStringParsingBody();
+ stat = SslError;
+
+ } else if (reply->error() == QNetworkReply::AuthenticationRequiredError
+ || !_account->credentials()->stillValid(reply)) {
+ qCWarning(lcConnectionValidator) << "******** Password is wrong!" << reply->error() << job->errorString();
+ _errors << tr("The provided credentials are not correct");
+ stat = CredentialsWrong;
+
+ } else if (reply->error() != QNetworkReply::NoError) {
+ _errors << job->errorStringParsingBody();
+
+ const int httpStatus =
+ reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ if (httpStatus == 503) {
+ _errors.clear();
+ stat = ServiceUnavailable;
+ }
+ }
+
+ reportResult(stat);
+}
+
+void ConnectionValidator::slotAuthSuccess()
+{
+ _errors.clear();
+ if (!_isCheckingServerAndAuth) {
+ reportResult(Connected);
+ return;
+ }
+ checkServerCapabilities();
+}
+
+void ConnectionValidator::checkServerCapabilities()
+{
+ // The main flow now needs the capabilities
+ JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/capabilities"), this);
+ job->setTimeout(timeoutToUseMsec);
+ QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotCapabilitiesRecieved);
+ job->start();
+
+ // And we'll retrieve the ocs config in parallel
+ // note that 'this' might be destroyed before the job finishes, so intentionally not parented
+ auto configJob = new JsonApiJob(_account, QLatin1String("ocs/v1.php/config"));
+ configJob->setTimeout(timeoutToUseMsec);
+ auto account = _account; // capturing account by value will make it live long enough
+ QObject::connect(configJob, &JsonApiJob::jsonReceived, _account.data(),
+ [=](const QJsonDocument &json) {
+ ocsConfigReceived(json, account);
+ });
+ configJob->start();
+}
+
+void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
+{
+ auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
+ qCInfo(lcConnectionValidator) << "Server capabilities" << caps;
+ _account->setCapabilities(caps.toVariantMap());
+
+ // New servers also report the version in the capabilities
+ QString serverVersion = caps["core"].toObject()["status"].toObject()["version"].toString();
+ if (!serverVersion.isEmpty() && !setAndCheckServerVersion(serverVersion)) {
+ return;
+ }
+
+ fetchUser();
+}
+
+void ConnectionValidator::ocsConfigReceived(const QJsonDocument &json, AccountPtr account)
+{
+ QString host = json.object().value("ocs").toObject().value("data").toObject().value("host").toString();
+ if (host.isEmpty()) {
+ qCWarning(lcConnectionValidator) << "Could not extract 'host' from ocs config reply";
+ return;
+ }
+ qCInfo(lcConnectionValidator) << "Determined user-visible host to be" << host;
+ account->setUserVisibleHost(host);
+}
+
+void ConnectionValidator::fetchUser()
+{
+ JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
+ job->setTimeout(timeoutToUseMsec);
+ QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotUserFetched);
+ job->start();
+}
+
+bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
+{
+ qCInfo(lcConnectionValidator) << _account->url() << "has server version" << version;
+ _account->setServerVersion(version);
+
+ // We cannot deal with servers < 7.0.0
+ if (_account->serverVersionInt()
+ && _account->serverVersionInt() < Account::makeServerVersion(7, 0, 0)) {
+ _errors.append(tr("The configured server for this client is too old"));
+ _errors.append(tr("Please update to the latest server and restart the client."));
+ reportResult(ServerVersionMismatch);
+ return false;
+ }
+ // We attempt to work with servers >= 7.0.0 but warn users.
+ // Check usages of Account::serverVersionUnsupported() for details.
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
+ // Record that the server supports HTTP/2
+ // Actual decision if we should use HTTP/2 is done in AccessManager::createRequest
+ if (auto job = qobject_cast<AbstractNetworkJob *>(sender())) {
+ if (auto reply = job->reply()) {
+ _account->setHttp2Supported(
+ reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool());
+ }
+ }
+#endif
+ return true;
+}
+
+void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
+{
+ QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
+ if (!user.isEmpty()) {
+ _account->setDavUser(user);
+ }
+ QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString();
+ if (!displayName.isEmpty()) {
+ _account->setDavDisplayName(displayName);
+ }
+#ifndef TOKEN_AUTH_ONLY
+ AvatarJob *job = new AvatarJob(_account, _account->davUser(), 128, this);
+ job->setTimeout(20 * 1000);
+ QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
+ job->start();
+#else
+ reportResult(Connected);
+#endif
+}
+
+#ifndef TOKEN_AUTH_ONLY
+void ConnectionValidator::slotAvatarImage(const QImage &img)
+{
+ _account->setAvatar(img);
+ reportResult(Connected);
+}
+#endif
+
+void ConnectionValidator::reportResult(Status status)
+{
+ emit connectionResult(status, _errors);
+ deleteLater();
+}
+
+} // namespace OCC