From a61dde026cb5e3ab5ae06f3af9ccebd247756546 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 17 Feb 2025 10:51:03 +0100 Subject: [PATCH 1/8] using multi-arg is better than many arg calls in QString class Signed-off-by: Matthieu Gallien Signed-off-by: Jyrki Gadinger --- src/gui/owncloudsetupwizard.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index 6b36e0fe7e..ae11ff72ae 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -358,8 +358,7 @@ void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url) _ocWizard->setField(QLatin1String("OCUrl"), url); _ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2 …") - .arg(Theme::instance()->appNameGUI()) - .arg(url)); + .arg(Theme::instance()->appNameGUI(), url)); testOwnCloudConnect(); }); From b2e4b18c2e62e28c27a00318be588b96896c0bec Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Wed, 12 Feb 2025 12:33:20 +0100 Subject: [PATCH 2/8] wizard: display error message when ToS haven't been accepted yet Signed-off-by: Jyrki Gadinger --- src/gui/connectionvalidator.cpp | 9 ++++++++- src/gui/owncloudsetupwizard.cpp | 8 ++++++++ src/gui/tray/syncstatussummary.cpp | 1 + src/libsync/owncloudpropagator_p.h | 4 ++-- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index ebe1c1af52..31467ec3f4 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -221,13 +221,20 @@ void ConnectionValidator::slotAuthFailed(QNetworkReply *reply) stat = CredentialsWrong; } else if (reply->error() != QNetworkReply::NoError) { - _errors << job->errorStringParsingBody(); + QByteArray body; + _errors << job->errorStringParsingBody(&body); const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (httpStatus == 503) { _errors.clear(); stat = ServiceUnavailable; + } else if (httpStatus == 403) { + const auto davException = job->errorStringParsingBodyException(body); + if (davException == QStringLiteral(R"(OCA\TermsOfService\TermsNotSignedException)")) { + qCInfo(lcConnectionValidator) << "The terms of service need to be signed"; + stat = NeedToSignTermsOfService; + } } } diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index ae11ff72ae..66282a067a 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -31,6 +31,7 @@ #include "networkjobs.h" #include "owncloudgui.h" #include "owncloudsetupwizard.h" +#include "owncloudpropagator_p.h" #include "sslerrordialog.h" #include "wizard/owncloudwizard.h" #include "wizard/owncloudwizardcommon.h" @@ -421,10 +422,17 @@ void OwncloudSetupWizard::slotAuthError() // Provide messages for other errors, such as invalid credentials. } else if (reply->error() != QNetworkReply::NoError) { + auto davException = OCC::getExceptionFromReply(reply); + if (!_ocWizard->account()->credentials()->stillValid(reply)) { errorMsg = tr("Access forbidden by server. To verify that you have proper access, " "click here to access the service with your browser.") .arg(Utility::escape(_ocWizard->account()->url().toString())); + } else if (!davException.first.isEmpty() && davException.first == QStringLiteral(R"(OCA\TermsOfService\TermsNotSignedException)")) { + qCInfo(lcWizard) << "Terms of service not accepted yet!"; + // TODO: it would be cool to display a new wizard page containing the terms of service + errorMsg = tr("Please accept the Terms of Service with your browser and try again.") + .arg(Utility::escape(_ocWizard->account()->url().toString())); } else { errorMsg = job->errorStringParsingBody(); } diff --git a/src/gui/tray/syncstatussummary.cpp b/src/gui/tray/syncstatussummary.cpp index cd31daac96..0e5d43c921 100644 --- a/src/gui/tray/syncstatussummary.cpp +++ b/src/gui/tray/syncstatussummary.cpp @@ -14,6 +14,7 @@ #include "syncstatussummary.h" #include "accountfwd.h" +#include "accountstate.h" #include "folderman.h" #include "navigationpanehelper.h" #include "networkjobs.h" diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index beb150648d..95e383493c 100644 --- a/src/libsync/owncloudpropagator_p.h +++ b/src/libsync/owncloudpropagator_p.h @@ -69,8 +69,8 @@ inline QPair getExceptionFromReply(QNetworkReply * const return {}; } const auto httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // only for BadRequest and UnsupportedMediaType - if (httpStatusCode != 400 && httpStatusCode != 415) { + // only for BadRequest, Forbidden, and UnsupportedMediaType + if (!(httpStatusCode == 400 || httpStatusCode == 403 || httpStatusCode == 415)) { return {}; } From 1723fa86a51e42f025aafb192afe215e7176e156 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 17 Feb 2025 09:14:05 +0100 Subject: [PATCH 3/8] detect the need to sign the terms of service during login web flow v2 should avoid being blocked by terms_of_service blocking WebDAV access Signed-off-by: Matthieu Gallien Signed-off-by: Jyrki Gadinger --- src/gui/connectionvalidator.cpp | 2 +- src/gui/owncloudsetupwizard.cpp | 50 ++++++++++++++++---- src/gui/owncloudsetupwizard.h | 7 ++- src/gui/wizard/owncloudadvancedsetuppage.cpp | 10 ++++ src/gui/wizard/owncloudadvancedsetuppage.h | 2 + 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 31467ec3f4..61ba6ee447 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -390,8 +390,8 @@ void TermsOfServiceChecker::slotServerTermsOfServiceRecieved(const QJsonDocument if (reply.object().contains("ocs")) { const auto needToSign = !reply.object().value("ocs").toObject().value("data").toObject().value("hasSigned").toBool(false); if (needToSign != _needToSign) { - qCInfo(lcConnectionValidator) << "_needToSign" << (_needToSign ? "need to sign" : "no need to sign"); _needToSign = needToSign; + qCInfo(lcConnectionValidator) << "_needToSign" << (_needToSign ? "need to sign" : "no need to sign"); emit needToSignChanged(); } } else if (_needToSign) { diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index 66282a067a..8f10fc52f6 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -13,13 +13,6 @@ * for more details. */ -#include -#include -#include -#include -#include -#include - #include "accessmanager.h" #include "account.h" #include "accountmanager.h" @@ -35,11 +28,23 @@ #include "sslerrordialog.h" #include "wizard/owncloudwizard.h" #include "wizard/owncloudwizardcommon.h" +#include "connectionvalidator.h" #include "creds/credentialsfactory.h" #include "creds/abstractcredentials.h" #include "creds/dummycredentials.h" +#ifdef BUILD_FILE_PROVIDER_MODULE +#include "gui/macOS/fileprovider.h" +#endif + +#include +#include +#include +#include +#include +#include + namespace OCC { OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent) @@ -377,7 +382,14 @@ void OwncloudSetupWizard::testOwnCloudConnect() job->setFollowRedirects(false); job->setProperties(QList() << "getlastmodified"); connect(job, &PropfindJob::result, _ocWizard, &OwncloudWizard::successfulStep); - connect(job, &PropfindJob::finishedWithError, this, &OwncloudSetupWizard::slotAuthError); + connect(job, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) { + if (reply && reply->error() == QNetworkReply::ContentAccessDenied) { + testTermsOfService(); + } else { + slotAuthError(); + } + }); + job->start(); } @@ -416,6 +428,9 @@ void OwncloudSetupWizard::slotAuthError() // A 404 is actually a success: we were authorized to know that the folder does // not exist. It will be created later... + } else if (reply->error() == QNetworkReply::ContentAccessDenied) { + testTermsOfService(); + return; } else if (reply->error() == QNetworkReply::ContentNotFoundError) { _ocWizard->successfulStep(); return; @@ -473,6 +488,14 @@ bool OwncloudSetupWizard::checkDowngradeAdvised(QNetworkReply *reply) return true; } +void OwncloudSetupWizard::testTermsOfService() +{ + _termsOfServiceChecker = new TermsOfServiceChecker{_ocWizard->account(), this}; + + connect(_termsOfServiceChecker, &TermsOfServiceChecker::done, this, &OwncloudSetupWizard::termsOfServiceChecked); + _termsOfServiceChecker->start(); +} + void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString &localFolder, const QString &remoteFolder) { qCInfo(lcWizard) << "Setup local sync folder for new oC connection " << localFolder; @@ -726,6 +749,17 @@ void OwncloudSetupWizard::slotSkipFolderConfiguration() emit ownCloudWizardDone(QDialog::Accepted); } +void OwncloudSetupWizard::termsOfServiceChecked() +{ + if (_termsOfServiceChecker && _termsOfServiceChecker->needToSign()) { + QDesktopServices::openUrl(_ocWizard->account()->url()); + } else { + _ocWizard->successfulStep(); + delete _termsOfServiceChecker; + _termsOfServiceChecker = nullptr; + } +} + AccountState *OwncloudSetupWizard::applyAccountChanges() { AccountPtr newAccount = _ocWizard->account(); diff --git a/src/gui/owncloudsetupwizard.h b/src/gui/owncloudsetupwizard.h index d7288ee6c8..fab85d9bbb 100644 --- a/src/gui/owncloudsetupwizard.h +++ b/src/gui/owncloudsetupwizard.h @@ -30,6 +30,7 @@ namespace OCC { class AccountState; +class TermsOfServiceChecker; class OwncloudWizard; @@ -69,6 +70,8 @@ private slots: void slotAssistantFinished(int); void slotSkipFolderConfiguration(); + void termsOfServiceChecked(); + private: explicit OwncloudSetupWizard(QObject *parent = nullptr); ~OwncloudSetupWizard() override; @@ -79,8 +82,10 @@ private: bool ensureStartFromScratch(const QString &localFolder); AccountState *applyAccountChanges(); bool checkDowngradeAdvised(QNetworkReply *reply); + void testTermsOfService(); - OwncloudWizard *_ocWizard; + TermsOfServiceChecker *_termsOfServiceChecker = nullptr; + OwncloudWizard *_ocWizard = nullptr; QString _initLocalFolder; QString _remoteFolder; }; diff --git a/src/gui/wizard/owncloudadvancedsetuppage.cpp b/src/gui/wizard/owncloudadvancedsetuppage.cpp index bb51e44743..872f53f7f5 100644 --- a/src/gui/wizard/owncloudadvancedsetuppage.cpp +++ b/src/gui/wizard/owncloudadvancedsetuppage.cpp @@ -176,6 +176,7 @@ void OwncloudAdvancedSetupPage::initializePage() quotaJob->setProperties(QList() << "http://owncloud.org/ns:size"); connect(quotaJob, &PropfindJob::result, this, &OwncloudAdvancedSetupPage::slotQuotaRetrieved); + connect(quotaJob, &PropfindJob::finishedWithError, this, &OwncloudAdvancedSetupPage::slotQuotaRetrievedWithError); quotaJob->start(); @@ -547,6 +548,15 @@ void OwncloudAdvancedSetupPage::slotQuotaRetrieved(const QVariantMap &result) updateStatus(); } +void OwncloudAdvancedSetupPage::slotQuotaRetrievedWithError(QNetworkReply *reply) +{ + Q_UNUSED(reply) + _rSize = -1; + _ui.lSyncEverythingSizeLabel->setText({}); + + updateStatus(); +} + qint64 OwncloudAdvancedSetupPage::availableLocalSpace() const { QString localDir = localFolder(); diff --git a/src/gui/wizard/owncloudadvancedsetuppage.h b/src/gui/wizard/owncloudadvancedsetuppage.h index 2c0f23bb42..d01ebe4d95 100644 --- a/src/gui/wizard/owncloudadvancedsetuppage.h +++ b/src/gui/wizard/owncloudadvancedsetuppage.h @@ -23,6 +23,7 @@ #include "elidedlabel.h" class QProgressIndicator; +class QNetworkReply; namespace OCC { @@ -63,6 +64,7 @@ private slots: void slotSelectiveSyncClicked(); void slotVirtualFileSyncClicked(); void slotQuotaRetrieved(const QVariantMap &result); + void slotQuotaRetrievedWithError(QNetworkReply *reply); private: void setRadioChecked(QRadioButton *radio); From 88400b1ef753aa691fcc9ce6fe7a774163f2e170 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 17 Feb 2025 11:49:35 +0100 Subject: [PATCH 4/8] add a wizard page to check terms of service should enable proper polling while the user check and signs the terms of service via teh web browser Signed-off-by: Matthieu Gallien Signed-off-by: Jyrki Gadinger --- src/gui/CMakeLists.txt | 2 + src/gui/wizard/flow2authcredspage.cpp | 2 +- src/gui/wizard/owncloudwizard.cpp | 18 +++++- src/gui/wizard/owncloudwizard.h | 14 ++-- src/gui/wizard/owncloudwizardcommon.h | 1 + src/gui/wizard/termsofservicewizardpage.cpp | 71 +++++++++++++++++++++ src/gui/wizard/termsofservicewizardpage.h | 54 ++++++++++++++++ src/gui/wizard/webviewpage.cpp | 2 +- 8 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 src/gui/wizard/termsofservicewizardpage.cpp create mode 100644 src/gui/wizard/termsofservicewizardpage.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4bd77fabad..5bb37c6ce9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -240,6 +240,8 @@ set(client_SRCS wizard/flow2authwidget.cpp wizard/owncloudsetuppage.h wizard/owncloudsetuppage.cpp + wizard/termsofservicewizardpage.h + wizard/termsofservicewizardpage.cpp wizard/owncloudwizardcommon.h wizard/owncloudwizardcommon.cpp wizard/owncloudwizard.h diff --git a/src/gui/wizard/flow2authcredspage.cpp b/src/gui/wizard/flow2authcredspage.cpp index e03b9a50fa..a2f23f6ef2 100644 --- a/src/gui/wizard/flow2authcredspage.cpp +++ b/src/gui/wizard/flow2authcredspage.cpp @@ -104,7 +104,7 @@ void Flow2AuthCredsPage::slotFlow2AuthResult(Flow2Auth::Result r, const QString int Flow2AuthCredsPage::nextId() const { - return WizardCommon::Page_AdvancedSetup; + return WizardCommon::Page_TermsOfService; } void Flow2AuthCredsPage::setConnected() diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp index 9368b3d162..4fb364b8f4 100644 --- a/src/gui/wizard/owncloudwizard.cpp +++ b/src/gui/wizard/owncloudwizard.cpp @@ -27,6 +27,7 @@ #include "wizard/welcomepage.h" #include "wizard/owncloudsetuppage.h" #include "wizard/owncloudhttpcredspage.h" +#include "wizard/termsofservicewizardpage.h" #include "wizard/owncloudadvancedsetuppage.h" #include "wizard/webviewpage.h" #include "wizard/flow2authcredspage.h" @@ -239,9 +240,12 @@ void OwncloudWizard::setRemoteFolder(const QString &remoteFolder) void OwncloudWizard::successfulStep() { - const int id(currentId()); + const WizardCommon::Pages id{static_cast(currentId())}; switch (id) { + case WizardCommon::Page_Welcome: + break; + case WizardCommon::Page_HttpCreds: _httpCredsPage->setConnected(); break; @@ -258,6 +262,10 @@ void OwncloudWizard::successfulStep() break; #endif // WITH_WEBENGINE + case WizardCommon::Page_TermsOfService: + _termsOfServicePage->initializePage(); + break; + case WizardCommon::Page_AdvancedSetup: _advancedSetupPage->directoriesCreated(); break; @@ -351,7 +359,13 @@ void OwncloudWizard::slotCurrentPageChanged(int id) void OwncloudWizard::displayError(const QString &msg, bool retryHTTPonly) { - switch (currentId()) { + switch (static_cast(currentId())) { + case WizardCommon::Page_Welcome: + case WizardCommon::Page_Flow2AuthCreds: + case WizardCommon::Page_WebView: + case WizardCommon::Page_TermsOfService: + break; + case WizardCommon::Page_ServerSetup: _setupPage->setErrorString(msg, retryHTTPonly); break; diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h index da614b92d7..7f950db064 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -33,6 +33,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWizard) class WelcomePage; class OwncloudSetupPage; class OwncloudHttpCredsPage; +class TermsOfServiceWizardPage; class OwncloudAdvancedSetupPage; class OwncloudWizardResultPage; class AbstractCredentials; @@ -123,14 +124,15 @@ private: [[nodiscard]] QList calculateWizardPageSizes() const; AccountPtr _account; - WelcomePage *_welcomePage; - OwncloudSetupPage *_setupPage; - OwncloudHttpCredsPage *_httpCredsPage; - Flow2AuthCredsPage *_flow2CredsPage; - OwncloudAdvancedSetupPage *_advancedSetupPage; + WelcomePage *_welcomePage = nullptr; + OwncloudSetupPage *_setupPage = nullptr; + OwncloudHttpCredsPage *_httpCredsPage = nullptr; + Flow2AuthCredsPage *_flow2CredsPage = nullptr; + TermsOfServiceWizardPage *_termsOfServicePage = nullptr; + OwncloudAdvancedSetupPage *_advancedSetupPage = nullptr; OwncloudWizardResultPage *_resultPage = nullptr; AbstractCredentialsWizardPage *_credentialsPage = nullptr; - WebViewPage *_webViewPage = nullptr; + WebViewPage*_webViewPage = nullptr; QStringList _setupLog; diff --git a/src/gui/wizard/owncloudwizardcommon.h b/src/gui/wizard/owncloudwizardcommon.h index f5d7ba530f..7c77149205 100644 --- a/src/gui/wizard/owncloudwizardcommon.h +++ b/src/gui/wizard/owncloudwizardcommon.h @@ -50,6 +50,7 @@ namespace WizardCommon { #ifdef WITH_WEBENGINE Page_WebView, #endif // WITH_WEBENGINE + Page_TermsOfService, Page_AdvancedSetup, }; diff --git a/src/gui/wizard/termsofservicewizardpage.cpp b/src/gui/wizard/termsofservicewizardpage.cpp new file mode 100644 index 0000000000..5a7ec7a7ad --- /dev/null +++ b/src/gui/wizard/termsofservicewizardpage.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * 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 "termsofservicewizardpage.h" + +#include "account.h" +#include "owncloudsetupwizard.h" +#include "wizard/owncloudwizard.h" +#include "wizard/owncloudwizardcommon.h" +#include "connectionvalidator.h" + +#include +#include + +namespace OCC { + +OCC::TermsOfServiceWizardPage::TermsOfServiceWizardPage() + : QWizardPage() +{ + _layout = new QVBoxLayout(this); +} + +void OCC::TermsOfServiceWizardPage::initializePage() +{ +} + +void OCC::TermsOfServiceWizardPage::cleanupPage() +{ +} + +int OCC::TermsOfServiceWizardPage::nextId() const +{ + return WizardCommon::Page_AdvancedSetup; +} + +bool OCC::TermsOfServiceWizardPage::isComplete() const +{ + return false; +} + +void TermsOfServiceWizardPage::slotPollNow() +{ + _termsOfServiceChecker = new TermsOfServiceChecker{_ocWizard->account(), this}; + + connect(_termsOfServiceChecker, &TermsOfServiceChecker::done, this, &TermsOfServiceWizardPage::termsOfServiceChecked); + _termsOfServiceChecker->start(); +} + +void TermsOfServiceWizardPage::termsOfServiceChecked() +{ + if (_termsOfServiceChecker && _termsOfServiceChecker->needToSign()) { + QDesktopServices::openUrl(_ocWizard->account()->url()); + } else { + _ocWizard->successfulStep(); + delete _termsOfServiceChecker; + _termsOfServiceChecker = nullptr; + } +} + +} diff --git a/src/gui/wizard/termsofservicewizardpage.h b/src/gui/wizard/termsofservicewizardpage.h new file mode 100644 index 0000000000..86765e2fb9 --- /dev/null +++ b/src/gui/wizard/termsofservicewizardpage.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) by Matthieu Gallien + * + * 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. + */ + +#ifndef TERMSOFSERVICEWIZARDPAGE_H +#define TERMSOFSERVICEWIZARDPAGE_H + +#include + +class QVBoxLayout; + +namespace OCC { + +class OwncloudWizard; +class TermsOfServiceChecker; + +class TermsOfServiceWizardPage : public QWizardPage +{ + Q_OBJECT +public: + TermsOfServiceWizardPage(); + + void initializePage() override; + void cleanupPage() override; + [[nodiscard]] int nextId() const override; + [[nodiscard]] bool isComplete() const override; + +Q_SIGNALS: + void connectToOCUrl(const QString &); + void pollNow(); + +private Q_SLOTS: + void slotPollNow(); + void termsOfServiceChecked(); + +private: + QVBoxLayout *_layout = nullptr; + OwncloudWizard *_ocWizard = nullptr; + TermsOfServiceChecker *_termsOfServiceChecker = nullptr; +}; + +} // namespace OCC + +#endif // TERMSOFSERVICEWIZARDPAGE_H diff --git a/src/gui/wizard/webviewpage.cpp b/src/gui/wizard/webviewpage.cpp index 18acab0c44..a3587d1d8e 100644 --- a/src/gui/wizard/webviewpage.cpp +++ b/src/gui/wizard/webviewpage.cpp @@ -92,7 +92,7 @@ void WebViewPage::cleanupPage() } int WebViewPage::nextId() const { - return WizardCommon::Page_AdvancedSetup; + return WizardCommon::Page_TermsOfService; } bool WebViewPage::isComplete() const { From 34ace3ef27a29daed6a619df6734795b533c941d Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Mon, 17 Feb 2025 18:01:08 +0100 Subject: [PATCH 5/8] implement terms of service check wizard page this is only shown if the TOS haven't been accepted yet! for now this only opens the browser, similar to the Flow2Auth page the `TermsOfServiceCheckWidget` is a combination of the `Flow2Auth` and the `Flow2AuthWidget` classes -- in the future we ideally display the required TOS directly in the wizard Signed-off-by: Jyrki Gadinger --- src/gui/CMakeLists.txt | 3 + src/gui/owncloudsetupwizard.cpp | 51 ++-- src/gui/owncloudsetupwizard.h | 5 +- src/gui/wizard/flow2authcredspage.cpp | 8 +- src/gui/wizard/owncloudhttpcredspage.cpp | 6 + src/gui/wizard/owncloudwizard.cpp | 16 +- src/gui/wizard/owncloudwizard.h | 3 + src/gui/wizard/termsofservicecheckwidget.cpp | 194 +++++++++++++++ src/gui/wizard/termsofservicecheckwidget.h | 72 ++++++ src/gui/wizard/termsofservicecheckwidget.ui | 248 +++++++++++++++++++ src/gui/wizard/termsofservicewizardpage.cpp | 36 ++- src/gui/wizard/termsofservicewizardpage.h | 16 +- src/gui/wizard/webviewpage.cpp | 8 +- 13 files changed, 609 insertions(+), 57 deletions(-) create mode 100644 src/gui/wizard/termsofservicecheckwidget.cpp create mode 100644 src/gui/wizard/termsofservicecheckwidget.h create mode 100644 src/gui/wizard/termsofservicecheckwidget.ui diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5bb37c6ce9..d999868509 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -43,6 +43,7 @@ set(client_UI_SRCS wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui wizard/owncloudsetupnocredspage.ui + wizard/termsofservicecheckwidget.ui wizard/webview.ui wizard/welcomepage.ui ) @@ -240,6 +241,8 @@ set(client_SRCS wizard/flow2authwidget.cpp wizard/owncloudsetuppage.h wizard/owncloudsetuppage.cpp + wizard/termsofservicecheckwidget.h + wizard/termsofservicecheckwidget.cpp wizard/termsofservicewizardpage.h wizard/termsofservicewizardpage.cpp wizard/owncloudwizardcommon.h diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index 8f10fc52f6..0916ffb738 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -28,7 +28,6 @@ #include "sslerrordialog.h" #include "wizard/owncloudwizard.h" #include "wizard/owncloudwizardcommon.h" -#include "connectionvalidator.h" #include "creds/credentialsfactory.h" #include "creds/abstractcredentials.h" @@ -382,12 +381,20 @@ void OwncloudSetupWizard::testOwnCloudConnect() job->setFollowRedirects(false); job->setProperties(QList() << "getlastmodified"); connect(job, &PropfindJob::result, _ocWizard, &OwncloudWizard::successfulStep); - connect(job, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) { + connect(job, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) -> void { if (reply && reply->error() == QNetworkReply::ContentAccessDenied) { - testTermsOfService(); - } else { - slotAuthError(); + // A 403 might indicate that the terms of service need to be signed. + // catch this special case here, fall back to the standard error handler if it's not TOS-related + auto davException = OCC::getExceptionFromReply(reply); + if (!davException.first.isEmpty() && davException.first == QStringLiteral(R"(OCA\TermsOfService\TermsNotSignedException)")) { + // authentication was successful, but the user hasn't signed the terms of service yet. Prompt for that in the next step + qCInfo(lcWizard) << "Terms of service not accepted yet! Will prompt the user in the next step"; + _ocWizard->_needsToAcceptTermsOfService = true; + _ocWizard->successfulStep(); + return; + } } + slotAuthError(); }); job->start(); @@ -426,34 +433,25 @@ void OwncloudSetupWizard::slotAuthError() "\"%1\". The URL is bad, the server is misconfigured.") .arg(Utility::escape(redirectUrl.toString())); + } else if (reply->error() == QNetworkReply::ContentNotFoundError) { // A 404 is actually a success: we were authorized to know that the folder does // not exist. It will be created later... - } else if (reply->error() == QNetworkReply::ContentAccessDenied) { - testTermsOfService(); - return; - } else if (reply->error() == QNetworkReply::ContentNotFoundError) { _ocWizard->successfulStep(); return; - // Provide messages for other errors, such as invalid credentials. } else if (reply->error() != QNetworkReply::NoError) { - auto davException = OCC::getExceptionFromReply(reply); + // Provide messages for other errors, such as invalid credentials. if (!_ocWizard->account()->credentials()->stillValid(reply)) { errorMsg = tr("Access forbidden by server. To verify that you have proper access, " "click here to access the service with your browser.") .arg(Utility::escape(_ocWizard->account()->url().toString())); - } else if (!davException.first.isEmpty() && davException.first == QStringLiteral(R"(OCA\TermsOfService\TermsNotSignedException)")) { - qCInfo(lcWizard) << "Terms of service not accepted yet!"; - // TODO: it would be cool to display a new wizard page containing the terms of service - errorMsg = tr("Please accept the Terms of Service with your browser and try again.") - .arg(Utility::escape(_ocWizard->account()->url().toString())); } else { errorMsg = job->errorStringParsingBody(); } - // Something else went wrong, maybe the response was 200 but with invalid data. } else { + // Something else went wrong, maybe the response was 200 but with invalid data. errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); } @@ -488,14 +486,6 @@ bool OwncloudSetupWizard::checkDowngradeAdvised(QNetworkReply *reply) return true; } -void OwncloudSetupWizard::testTermsOfService() -{ - _termsOfServiceChecker = new TermsOfServiceChecker{_ocWizard->account(), this}; - - connect(_termsOfServiceChecker, &TermsOfServiceChecker::done, this, &OwncloudSetupWizard::termsOfServiceChecked); - _termsOfServiceChecker->start(); -} - void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString &localFolder, const QString &remoteFolder) { qCInfo(lcWizard) << "Setup local sync folder for new oC connection " << localFolder; @@ -749,17 +739,6 @@ void OwncloudSetupWizard::slotSkipFolderConfiguration() emit ownCloudWizardDone(QDialog::Accepted); } -void OwncloudSetupWizard::termsOfServiceChecked() -{ - if (_termsOfServiceChecker && _termsOfServiceChecker->needToSign()) { - QDesktopServices::openUrl(_ocWizard->account()->url()); - } else { - _ocWizard->successfulStep(); - delete _termsOfServiceChecker; - _termsOfServiceChecker = nullptr; - } -} - AccountState *OwncloudSetupWizard::applyAccountChanges() { AccountPtr newAccount = _ocWizard->account(); diff --git a/src/gui/owncloudsetupwizard.h b/src/gui/owncloudsetupwizard.h index fab85d9bbb..b3ad88a8f9 100644 --- a/src/gui/owncloudsetupwizard.h +++ b/src/gui/owncloudsetupwizard.h @@ -45,6 +45,7 @@ public: /** Run the wizard */ static void runWizard(QObject *obj, const char *amember, QWidget *parent = nullptr); static bool bringWizardToFrontIfVisible(); + signals: // overall dialog close signal. void ownCloudWizardDone(int); @@ -70,8 +71,6 @@ private slots: void slotAssistantFinished(int); void slotSkipFolderConfiguration(); - void termsOfServiceChecked(); - private: explicit OwncloudSetupWizard(QObject *parent = nullptr); ~OwncloudSetupWizard() override; @@ -82,9 +81,7 @@ private: bool ensureStartFromScratch(const QString &localFolder); AccountState *applyAccountChanges(); bool checkDowngradeAdvised(QNetworkReply *reply); - void testTermsOfService(); - TermsOfServiceChecker *_termsOfServiceChecker = nullptr; OwncloudWizard *_ocWizard = nullptr; QString _initLocalFolder; QString _remoteFolder; diff --git a/src/gui/wizard/flow2authcredspage.cpp b/src/gui/wizard/flow2authcredspage.cpp index a2f23f6ef2..8eef42fab0 100644 --- a/src/gui/wizard/flow2authcredspage.cpp +++ b/src/gui/wizard/flow2authcredspage.cpp @@ -104,7 +104,13 @@ void Flow2AuthCredsPage::slotFlow2AuthResult(Flow2Auth::Result r, const QString int Flow2AuthCredsPage::nextId() const { - return WizardCommon::Page_TermsOfService; + const auto ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + if (ocWizard->needsToAcceptTermsOfService()) { + return WizardCommon::Page_TermsOfService; + } + + return WizardCommon::Page_AdvancedSetup; } void Flow2AuthCredsPage::setConnected() diff --git a/src/gui/wizard/owncloudhttpcredspage.cpp b/src/gui/wizard/owncloudhttpcredspage.cpp index ed514b72a4..436b46b3da 100644 --- a/src/gui/wizard/owncloudhttpcredspage.cpp +++ b/src/gui/wizard/owncloudhttpcredspage.cpp @@ -153,6 +153,12 @@ bool OwncloudHttpCredsPage::validatePage() int OwncloudHttpCredsPage::nextId() const { + const auto ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + if (ocWizard->needsToAcceptTermsOfService()) { + return WizardCommon::Page_TermsOfService; + } + return WizardCommon::Page_AdvancedSetup; } diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp index 4fb364b8f4..3a209312bd 100644 --- a/src/gui/wizard/owncloudwizard.cpp +++ b/src/gui/wizard/owncloudwizard.cpp @@ -54,6 +54,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) , _setupPage(new OwncloudSetupPage(this)) , _httpCredsPage(new OwncloudHttpCredsPage(this)) , _flow2CredsPage(new Flow2AuthCredsPage) + , _termsOfServicePage(new TermsOfServiceWizardPage) , _advancedSetupPage(new OwncloudAdvancedSetupPage(this)) #ifdef WITH_WEBENGINE , _webViewPage(new WebViewPage(this)) @@ -72,6 +73,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) setPage(WizardCommon::Page_ServerSetup, _setupPage); setPage(WizardCommon::Page_HttpCreds, _httpCredsPage); setPage(WizardCommon::Page_Flow2AuthCreds, _flow2CredsPage); + setPage(WizardCommon::Page_TermsOfService, _termsOfServicePage); setPage(WizardCommon::Page_AdvancedSetup, _advancedSetupPage); #ifdef WITH_WEBENGINE if (!useFlow2()) { @@ -117,6 +119,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) connect(this, &OwncloudWizard::styleChanged, _setupPage, &OwncloudSetupPage::slotStyleChanged); connect(this, &OwncloudWizard::styleChanged, _advancedSetupPage, &OwncloudAdvancedSetupPage::slotStyleChanged); connect(this, &OwncloudWizard::styleChanged, _flow2CredsPage, &Flow2AuthCredsPage::slotStyleChanged); + connect(this, &OwncloudWizard::styleChanged, _termsOfServicePage, &TermsOfServiceWizardPage::styleChanged); customizeStyle(); @@ -217,6 +220,11 @@ bool OwncloudWizard::isConfirmBigFolderChecked() const return _advancedSetupPage->isConfirmBigFolderChecked(); } +bool OwncloudWizard::needsToAcceptTermsOfService() const +{ + return _needsToAcceptTermsOfService; +} + QString OwncloudWizard::ocUrl() const { QString url = field("OCUrl").toString().simplified(); @@ -263,7 +271,7 @@ void OwncloudWizard::successfulStep() #endif // WITH_WEBENGINE case WizardCommon::Page_TermsOfService: - _termsOfServicePage->initializePage(); + // nothing to do here break; case WizardCommon::Page_AdvancedSetup: @@ -292,6 +300,9 @@ void OwncloudWizard::slotCustomButtonClicked(const int which) } else if (which == WizardButton::CustomButton2) { // Because QWizard doesn't have a way of directly going to a specific page (!!!) restart(); + + // in case the wizard had been cancelled at a page where the need for signing the TOS got checked: + _needsToAcceptTermsOfService = false; } } @@ -336,7 +347,8 @@ void OwncloudWizard::slotCurrentPageChanged(int id) #ifdef WITH_WEBENGINE id == WizardCommon::Page_WebView || #endif // WITH_WEBENGINE - id == WizardCommon::Page_Flow2AuthCreds) { + id == WizardCommon::Page_Flow2AuthCreds || + id == WizardCommon::Page_TermsOfService) { setButtonLayout({ QWizard::BackButton, QWizard::Stretch }); } else if (id == WizardCommon::Page_AdvancedSetup) { setButtonLayout({ QWizard::CustomButton2, QWizard::Stretch, QWizard::CustomButton1, QWizard::FinishButton }); diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h index 7f950db064..df8e8c6fe1 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -69,6 +69,7 @@ public: [[nodiscard]] bool useFlow2() const; [[nodiscard]] bool useVirtualFileSync() const; [[nodiscard]] bool isConfirmBigFolderChecked() const; + [[nodiscard]] bool needsToAcceptTermsOfService() const; void displayError(const QString &, bool retryHTTPonly); [[nodiscard]] AbstractCredentials *getCredentials() const; @@ -140,6 +141,8 @@ private: bool _useFlow2 = ConfigFile().forceLoginV2(); + bool _needsToAcceptTermsOfService = false; + friend class OwncloudSetupWizard; }; diff --git a/src/gui/wizard/termsofservicecheckwidget.cpp b/src/gui/wizard/termsofservicecheckwidget.cpp new file mode 100644 index 0000000000..a5f625bccb --- /dev/null +++ b/src/gui/wizard/termsofservicecheckwidget.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) by Jyrki Gadinger + * + * 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 "termsofservicecheckwidget.h" + +#include "wizard/owncloudwizardcommon.h" +#include "theme.h" +#include "configfile.h" + +#include "QProgressIndicator.h" + +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcTosCheckWidget, "nextcloud.gui.wizard.termsofservicecheckwidget", QtInfoMsg) + + +TermsOfServiceCheckWidget::TermsOfServiceCheckWidget(QWidget *parent) + : QWidget(parent) + , _progressIndicator(new QProgressIndicator(this)) +{ + _pollTimer.setInterval(1000); + QObject::connect(&_pollTimer, &QTimer::timeout, this, &TermsOfServiceCheckWidget::slotPollTimerTimeout); + + _ui.setupUi(this); + + connect(_ui.openLinkButton, &QPushButton::clicked, this, &TermsOfServiceCheckWidget::slotOpenBrowser); + connect(_ui.copyLinkButton, &QPushButton::clicked, this, &TermsOfServiceCheckWidget::slotCopyLinkToClipboard); + + auto sizePolicy = _progressIndicator->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden(true); + _progressIndicator->setSizePolicy(sizePolicy); + + _ui.progressLayout->addWidget(_progressIndicator); + stopSpinner(false); + + customizeStyle(); +} + +TermsOfServiceCheckWidget::~TermsOfServiceCheckWidget() { +} + +void TermsOfServiceCheckWidget::start() +{ + ConfigFile cfg; + std::chrono::milliseconds polltime = cfg.remotePollInterval(); + qCInfo(lcTosCheckWidget) << "setting remote poll timer interval to" << polltime.count() << "msec"; + _secondsInterval = (polltime.count() / 1000); + _secondsLeft = _secondsInterval; + + _pollTimer.start(); + // open browser when the wizard page is shown + slotOpenBrowser(); +} + +void TermsOfServiceCheckWidget::setUrl(const QUrl &url) +{ + _url = url; +} + +void TermsOfServiceCheckWidget::termsNotAcceptedYet() +{ + _secondsLeft = _secondsInterval; + _isBusy = false; + statusChanged(Status::statusPollCountdown); +} + +void TermsOfServiceCheckWidget::setLogo() +{ + const auto backgroundColor = palette().window().color(); + const auto logoIconFileName = Theme::instance()->isBranded() ? Theme::hidpiFileName("external.png", backgroundColor) + : Theme::hidpiFileName(":/client/theme/colored/external.png"); + _ui.logoLabel->setPixmap(logoIconFileName); +} + +void TermsOfServiceCheckWidget::slotStyleChanged() +{ + customizeStyle(); +} + +void TermsOfServiceCheckWidget::slotPollTimerTimeout() +{ + if (_isBusy) { + return; + } + + _isBusy = true; + + _secondsLeft--; + if (_secondsLeft > 0) { + statusChanged(Status::statusPollCountdown); + _isBusy = false; + return; + } + + statusChanged(Status::statusPollNow); + Q_EMIT pollNow(); +} + +void TermsOfServiceCheckWidget::slotOpenBrowser() +{ + QDesktopServices::openUrl(_url); +} + +void TermsOfServiceCheckWidget::slotCopyLinkToClipboard() +{ + statusChanged(Status::statusCopyLinkToClipboard); + QApplication::clipboard()->setText(_url.toString(QUrl::FullyEncoded)); +} + +void TermsOfServiceCheckWidget::statusChanged(Status status) +{ + switch (status) + { + case statusPollCountdown: + if (_statusUpdateSkipCount > 0) { + _statusUpdateSkipCount--; + return; + } + + _ui.statusLabel->setText(tr("Waiting for terms to be accepted") + QStringLiteral("… (%1)").arg(_secondsLeft)); + stopSpinner(true); + return; + + case statusPollNow: + _statusUpdateSkipCount = 0; + _ui.statusLabel->setText(tr("Polling") + QStringLiteral("…")); + startSpinner(); + return; + + case statusCopyLinkToClipboard: + _statusUpdateSkipCount = 3; + _ui.statusLabel->setText(tr("Link copied to clipboard.")); + stopSpinner(true); + return; + } +} + +void TermsOfServiceCheckWidget::startSpinner() +{ + _ui.progressLayout->setEnabled(true); + _ui.statusLabel->setVisible(true); + _progressIndicator->setVisible(true); + _progressIndicator->startAnimation(); + + _ui.openLinkButton->setEnabled(false); + _ui.copyLinkButton->setEnabled(false); +} + +void TermsOfServiceCheckWidget::stopSpinner(bool showStatusLabel) +{ + _ui.progressLayout->setEnabled(false); + _ui.statusLabel->setVisible(showStatusLabel); + _progressIndicator->setVisible(false); + _progressIndicator->stopAnimation(); + + _ui.openLinkButton->setEnabled(_statusUpdateSkipCount == 0); + _ui.copyLinkButton->setEnabled(_statusUpdateSkipCount == 0); +} + +void TermsOfServiceCheckWidget::customizeStyle() +{ + setLogo(); + + if (_progressIndicator) { + const auto isDarkBackground = Theme::isDarkColor(palette().window().color()); + if (isDarkBackground) { + _progressIndicator->setColor(Qt::white); + } else { + _progressIndicator->setColor(Qt::black); + } + } + + _ui.openLinkButton->setText(tr("Open Browser")); + + _ui.copyLinkButton->setText(tr("Copy Link")); + + WizardCommon::customizeHintLabel(_ui.statusLabel); +} + +} // namespace OCC diff --git a/src/gui/wizard/termsofservicecheckwidget.h b/src/gui/wizard/termsofservicecheckwidget.h new file mode 100644 index 0000000000..1c28149d34 --- /dev/null +++ b/src/gui/wizard/termsofservicecheckwidget.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) by Jyrki Gadinger + * + * 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 +#include + +#include "ui_termsofservicecheckwidget.h" + +class QProgressIndicator; + +namespace OCC { + +class TermsOfServiceCheckWidget : public QWidget +{ + Q_OBJECT +public: + enum Status { + statusPollCountdown = 1, + statusPollNow, + statusCopyLinkToClipboard, + }; + + TermsOfServiceCheckWidget(QWidget *parent = nullptr); + ~TermsOfServiceCheckWidget() override; + + void start(); + void setUrl(const QUrl &url); + void termsNotAcceptedYet(); + +public Q_SLOTS: + void slotStyleChanged(); + +Q_SIGNALS: + void pollNow(); + +private Q_SLOTS: + void slotPollTimerTimeout(); + void slotOpenBrowser(); + void slotCopyLinkToClipboard(); + +private: + Ui_TermsOfServiceCheckWidget _ui{}; + QTimer _pollTimer; + QProgressIndicator *_progressIndicator = nullptr; + int _statusUpdateSkipCount = 0; + qint64 _secondsLeft = 0LL; + qint64 _secondsInterval = 0LL; + bool _isBusy = false; + QUrl _url; + + void statusChanged(Status status); + void startSpinner(); + void stopSpinner(bool showStatusLabel); + void customizeStyle(); + void setLogo(); + +}; + +} // namespace OCC diff --git a/src/gui/wizard/termsofservicecheckwidget.ui b/src/gui/wizard/termsofservicecheckwidget.ui new file mode 100644 index 0000000000..be52d44b11 --- /dev/null +++ b/src/gui/wizard/termsofservicecheckwidget.ui @@ -0,0 +1,248 @@ + + + TermsOfServiceCheckWidget + + + + 0 + 0 + 597 + 387 + + + + + 0 + 0 + + + + + 500 + 280 + + + + Terms of Service + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + Logo + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Qt::Orientation::Vertical + + + + 20 + 32 + + + + + + + + + 12 + true + + + + Switch to your browser to accept the terms of service + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Expanding + + + + 20 + 8 + + + + + + + + Status + + + Qt::AlignmentFlag::AlignCenter + + + 0 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 32 + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 64 + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 9 + + + + copyLinkButton + + + true + + + false + + + + + + + + 0 + 0 + + + + + 9 + + + + openLinkButton + + + true + + + false + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Expanding + + + + 20 + 60 + + + + + + + + + diff --git a/src/gui/wizard/termsofservicewizardpage.cpp b/src/gui/wizard/termsofservicewizardpage.cpp index 5a7ec7a7ad..f5b5776281 100644 --- a/src/gui/wizard/termsofservicewizardpage.cpp +++ b/src/gui/wizard/termsofservicewizardpage.cpp @@ -18,6 +18,7 @@ #include "owncloudsetupwizard.h" #include "wizard/owncloudwizard.h" #include "wizard/owncloudwizardcommon.h" +#include "wizard/termsofservicecheckwidget.h" #include "connectionvalidator.h" #include @@ -29,14 +30,35 @@ OCC::TermsOfServiceWizardPage::TermsOfServiceWizardPage() : QWizardPage() { _layout = new QVBoxLayout(this); + + _termsOfServiceCheckWidget = new TermsOfServiceCheckWidget; + _layout->addWidget(_termsOfServiceCheckWidget); + + connect(this, &TermsOfServiceWizardPage::styleChanged, _termsOfServiceCheckWidget, &TermsOfServiceCheckWidget::slotStyleChanged); + connect(_termsOfServiceCheckWidget, &TermsOfServiceCheckWidget::pollNow, this, &TermsOfServiceWizardPage::slotPollNow); } void OCC::TermsOfServiceWizardPage::initializePage() { + _ocWizard = qobject_cast(wizard()); + Q_ASSERT(_ocWizard); + + _termsOfServiceChecker = new TermsOfServiceChecker{_ocWizard->account(), this}; + connect(_termsOfServiceChecker, &TermsOfServiceChecker::done, this, &TermsOfServiceWizardPage::termsOfServiceChecked); + + _termsOfServiceCheckWidget->setUrl(_ocWizard->account()->url()); + _termsOfServiceCheckWidget->slotStyleChanged(); + _termsOfServiceCheckWidget->start(); + + connect(_ocWizard, &OwncloudWizard::onActivate, this, &TermsOfServiceWizardPage::slotPollNow); } void OCC::TermsOfServiceWizardPage::cleanupPage() { + disconnect(_ocWizard, &OwncloudWizard::onActivate, this, &TermsOfServiceWizardPage::slotPollNow); + + _termsOfServiceChecker->deleteLater(); + _termsOfServiceChecker = nullptr; } int OCC::TermsOfServiceWizardPage::nextId() const @@ -51,21 +73,21 @@ bool OCC::TermsOfServiceWizardPage::isComplete() const void TermsOfServiceWizardPage::slotPollNow() { - _termsOfServiceChecker = new TermsOfServiceChecker{_ocWizard->account(), this}; + if (!_termsOfServiceChecker) { + return; + } - connect(_termsOfServiceChecker, &TermsOfServiceChecker::done, this, &TermsOfServiceWizardPage::termsOfServiceChecked); _termsOfServiceChecker->start(); } void TermsOfServiceWizardPage::termsOfServiceChecked() { if (_termsOfServiceChecker && _termsOfServiceChecker->needToSign()) { - QDesktopServices::openUrl(_ocWizard->account()->url()); - } else { - _ocWizard->successfulStep(); - delete _termsOfServiceChecker; - _termsOfServiceChecker = nullptr; + _termsOfServiceCheckWidget->termsNotAcceptedYet(); + return; } + _ocWizard->successfulStep(); } } + diff --git a/src/gui/wizard/termsofservicewizardpage.h b/src/gui/wizard/termsofservicewizardpage.h index 86765e2fb9..2312d98668 100644 --- a/src/gui/wizard/termsofservicewizardpage.h +++ b/src/gui/wizard/termsofservicewizardpage.h @@ -23,6 +23,7 @@ namespace OCC { class OwncloudWizard; class TermsOfServiceChecker; +class TermsOfServiceCheckWidget; class TermsOfServiceWizardPage : public QWizardPage { @@ -35,18 +36,21 @@ public: [[nodiscard]] int nextId() const override; [[nodiscard]] bool isComplete() const override; -Q_SIGNALS: - void connectToOCUrl(const QString &); - void pollNow(); - -private Q_SLOTS: +public Q_SLOTS: void slotPollNow(); - void termsOfServiceChecked(); + +Q_SIGNALS: + void pollNow(); + void styleChanged(); private: QVBoxLayout *_layout = nullptr; OwncloudWizard *_ocWizard = nullptr; TermsOfServiceChecker *_termsOfServiceChecker = nullptr; + TermsOfServiceCheckWidget *_termsOfServiceCheckWidget = nullptr; + +private Q_SLOTS: + void termsOfServiceChecked(); }; } // namespace OCC diff --git a/src/gui/wizard/webviewpage.cpp b/src/gui/wizard/webviewpage.cpp index a3587d1d8e..0d47cb23ee 100644 --- a/src/gui/wizard/webviewpage.cpp +++ b/src/gui/wizard/webviewpage.cpp @@ -92,7 +92,13 @@ void WebViewPage::cleanupPage() } int WebViewPage::nextId() const { - return WizardCommon::Page_TermsOfService; + const auto ocWizard = qobject_cast(wizard()); + Q_ASSERT(ocWizard); + if (ocWizard->needsToAcceptTermsOfService()) { + return WizardCommon::Page_TermsOfService; + } + + return WizardCommon::Page_AdvancedSetup; } bool WebViewPage::isComplete() const { From c320a0ae311f06a1f668a0fba5b331cf1c46c69d Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Tue, 18 Feb 2025 11:53:13 +0100 Subject: [PATCH 6/8] add link to server if TOS needs to be signed Signed-off-by: Jyrki Gadinger --- src/gui/accountsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 579b053a0d..0a5a9f5276 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -1346,7 +1346,7 @@ void AccountSettings::slotAccountStateChanged() Q_UNREACHABLE(); break; case AccountState::NeedToSignTermsOfService: - showConnectionLabel(tr("You need to accept the terms of service")); + showConnectionLabel(tr("You need to accept the terms of service at %1.").arg(server)); break; } } else { From f19a79a24d5e36c901289809127d48beacf5a801 Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Tue, 18 Feb 2025 11:53:38 +0100 Subject: [PATCH 7/8] add a button to open the browser if TOS need to be signed Signed-off-by: Jyrki Gadinger --- src/gui/tray/SyncStatus.qml | 11 +++++++++++ src/gui/tray/usermodel.cpp | 5 +++++ src/gui/tray/usermodel.h | 2 ++ 3 files changed, 18 insertions(+) diff --git a/src/gui/tray/SyncStatus.qml b/src/gui/tray/SyncStatus.qml index b482c5586d..fa106f96cc 100644 --- a/src/gui/tray/SyncStatus.qml +++ b/src/gui/tray/SyncStatus.qml @@ -119,4 +119,15 @@ RowLayout { enabled: visible onClicked: NC.Systray.createResolveConflictsDialog(activityModel.allConflicts); } + + Button { + Layout.rightMargin: Style.trayHorizontalMargin + + text: qsTr("Open browser") + + visible: NC.UserModel.currentUser.needsToSignTermsOfService + enabled: visible + + onClicked: NC.UserModel.openCurrentAccountServer() + } } diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index b24ae990df..01260f275c 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -1130,6 +1130,11 @@ bool User::isConnected() const return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); } +bool User::needsToSignTermsOfService() const +{ + return _account->connectionStatus() == AccountState::ConnectionStatus::NeedToSignTermsOfService; +} + bool User::isDesktopNotificationsAllowed() const { diff --git a/src/gui/tray/usermodel.h b/src/gui/tray/usermodel.h index 3f2170ab07..2d56f01210 100644 --- a/src/gui/tray/usermodel.h +++ b/src/gui/tray/usermodel.h @@ -61,6 +61,7 @@ class User : public QObject Q_PROPERTY(QString featuredAppAccessibleName READ featuredAppAccessibleName NOTIFY featuredAppChanged) Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged) Q_PROPERTY(bool isConnected READ isConnected NOTIFY accountStateChanged) + Q_PROPERTY(bool needsToSignTermsOfService READ needsToSignTermsOfService NOTIFY accountStateChanged) Q_PROPERTY(UnifiedSearchResultsListModel* unifiedSearchResultsListModel READ getUnifiedSearchResultsListModel CONSTANT) Q_PROPERTY(QVariantList groupFolders READ groupFolders NOTIFY groupFoldersChanged) @@ -71,6 +72,7 @@ public: [[nodiscard]] AccountStatePtr accountState() const; [[nodiscard]] bool isConnected() const; + [[nodiscard]] bool needsToSignTermsOfService() const; [[nodiscard]] bool isCurrentUser() const; void setCurrentUser(const bool &isCurrent); [[nodiscard]] Folder *getFolder() const; From 85715efe7017300e8735d6a6691758dee0ca9846 Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Tue, 18 Feb 2025 13:12:38 +0100 Subject: [PATCH 8/8] check for need of signing TOS during connectivity check Signed-off-by: Jyrki Gadinger --- src/gui/accountstate.cpp | 7 ++++++- src/gui/accountstate.h | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index d167f76f87..a59fed2731 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -222,6 +222,11 @@ bool AccountState::isConnected() const return _state == Connected; } +bool AccountState::needsToSignTermsOfService() const +{ + return _state == NeedToSignTermsOfService; +} + void AccountState::tagLastSuccessfullETagRequest(const QDateTime &tp) { _timeOfLastETagCheck = tp; @@ -316,7 +321,7 @@ void AccountState::checkConnectivity() _connectionErrors.clear(); connect(conValidator, &ConnectionValidator::connectionResult, this, &AccountState::slotConnectionValidatorResult); - if (isConnected()) { + if (isConnected() || needsToSignTermsOfService()) { // Use a small authed propfind as a minimal ping when we're // already connected. conValidator->checkAuthentication(); diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index de4311dbe6..0d52f77873 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -127,6 +127,8 @@ public: bool isConnected() const; + bool needsToSignTermsOfService() const; + /** Returns a new settings object for this account, already in the right groups. */ std::unique_ptr settings();