From cea636fddd768d4d1a08563ab54c9ab74a1d2dd7 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 6b36e0fe7e478..ae11ff72ae83b 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 d7469e8326509da56c81ee046b09bf24ee5490f0 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 | 9 +++++++++ src/libsync/owncloudpropagator_p.h | 4 ++-- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index ebe1c1af525f6..31467ec3f4bf0 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 ae11ff72ae83b..66282a067a293 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 7ec4da78cbebb..950ea2eba2cc7 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" @@ -128,6 +129,14 @@ void SyncStatusSummary::clearFolderErrors() void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) { if (_accountState && !_accountState->isConnected()) { + setSyncing(false); + setTotalFiles(0); + setSyncStatusString(tr("Offline")); + setSyncStatusDetailString(""); + if (_accountState->state() == AccountState::NeedToSignTermsOfService) { + setSyncStatusDetailString(tr("You need to accept the terms of service")); + } + setSyncIcon(Theme::instance()->folderOffline()); return; } diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index beb150648db87..95e383493ce67 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 a01efaf37c6b62ab686eed2d6b3e3942768151e7 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 | 46 ++++++++++++++++---- src/gui/owncloudsetupwizard.h | 7 ++- src/gui/wizard/owncloudadvancedsetuppage.cpp | 12 +++++ src/gui/wizard/owncloudadvancedsetuppage.h | 2 + 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 31467ec3f4bf0..61ba6ee447510 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 66282a067a293..15d74139864cd 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,19 @@ #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" +#include +#include +#include +#include +#include +#include + namespace OCC { OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent) @@ -377,7 +378,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 +424,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 +484,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 +745,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 d7288ee6c8b77..fab85d9bbb4fe 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 slots: 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 bb51e447438d0..e3fb2b8e62b2e 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,17 @@ void OwncloudAdvancedSetupPage::slotQuotaRetrieved(const QVariantMap &result) updateStatus(); } +void OwncloudAdvancedSetupPage::slotQuotaRetrievedWithError(QNetworkReply *reply) +{ + if (reply && reply->error() == QNetworkReply::ContentAccessDenied) { + + } + _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 2c0f23bb42b37..d01ebe4d953d6 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 7972310675b68206962d734c02d810f54222fbca 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 4bd77fabad294..5bb37c6ce905c 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 e03b9a50faba5..a2f23f6ef2631 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 9368b3d162800..4fb364b8f4fb2 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 da614b92d7e5c..7f950db06493f 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 @@ public slots: [[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 f5d7ba530f436..7c771492052da 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 0000000000000..5a7ec7a7ad9ef --- /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 0000000000000..86765e2fb970f --- /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 18acab0c44e7f..a3587d1d8e3b0 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 ca309692001cd5fb14052579d58fb57248ba1100 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 | 14 +- src/gui/wizard/webviewpage.cpp | 8 +- 13 files changed, 608 insertions(+), 56 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 5bb37c6ce905c..d999868509a2a 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 15d74139864cd..2105ef6848f42 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" @@ -378,12 +377,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(); @@ -422,34 +429,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"); } @@ -484,14 +482,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; @@ -745,17 +735,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 fab85d9bbb4fe..b3ad88a8f9680 100644 --- a/src/gui/owncloudsetupwizard.h +++ b/src/gui/owncloudsetupwizard.h @@ -45,6 +45,7 @@ class OwncloudSetupWizard : public QObject /** 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 slots: 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 a2f23f6ef2631..8eef42fab0f4b 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 ed514b72a459c..436b46b3da871 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 4fb364b8f4fb2..3a209312bd0b7 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 7f950db06493f..df8e8c6fe10b8 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -69,6 +69,7 @@ class OwncloudWizard : public QWizard [[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 @@ public slots: 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 0000000000000..a5f625bccbf1b --- /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 0000000000000..d718e4cf5bd88 --- /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; + 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 0000000000000..be52d44b11ddc --- /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 5a7ec7a7ad9ef..f5b577628150c 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 86765e2fb970f..2312d98668116 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 @@ class TermsOfServiceWizardPage : public QWizardPage [[nodiscard]] int nextId() const override; [[nodiscard]] bool isComplete() const override; +public Q_SLOTS: + void slotPollNow(); + Q_SIGNALS: - void connectToOCUrl(const QString &); void pollNow(); - -private Q_SLOTS: - void slotPollNow(); - void termsOfServiceChecked(); + 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 a3587d1d8e3b0..0d47cb23ee42a 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 e25c778f7ba450ecac7bb7350118fbb7fe4442f2 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 579b053a0dcaa..0a5a9f52765e5 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 e2725c12acc1a45dcaa658435c623b07d0ffbe99 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 b482c5586dab7..fa106f96ccf2f 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 b24ae990df338..01260f275c1de 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 3f2170ab0748e..2d56f01210595 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 @@ class User : public QObject [[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 0e08fb20aa4f204a4c59987ceea6a63617a16322 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 d167f76f87ec3..a59fed2731750 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 de4311dbe6059..0d52f77873bf1 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -127,6 +127,8 @@ class AccountState : public QObject, public QSharedData bool isConnected() const; + bool needsToSignTermsOfService() const; + /** Returns a new settings object for this account, already in the right groups. */ std::unique_ptr settings();