Merge pull request #7895 from nextcloud/backport/7856/stable-3.16

[stable-3.16] improve ToS handling
This commit is contained in:
Matthieu Gallien 2025-02-20 12:10:36 +01:00 committed by GitHub
commit cfd1b988ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 816 additions and 29 deletions

View File

@ -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,10 @@ 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
wizard/owncloudwizardcommon.cpp
wizard/owncloudwizard.h

View File

@ -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 {

View File

@ -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();

View File

@ -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<QSettings> settings();

View File

@ -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;
}
}
}
@ -383,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) {

View File

@ -13,13 +13,6 @@
* for more details.
*/
#include <QAbstractButton>
#include <QtCore>
#include <QProcess>
#include <QMessageBox>
#include <QDesktopServices>
#include <QApplication>
#include "accessmanager.h"
#include "account.h"
#include "accountmanager.h"
@ -31,6 +24,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"
@ -39,6 +33,17 @@
#include "creds/abstractcredentials.h"
#include "creds/dummycredentials.h"
#ifdef BUILD_FILE_PROVIDER_MODULE
#include "gui/macOS/fileprovider.h"
#endif
#include <QAbstractButton>
#include <QtCore>
#include <QProcess>
#include <QMessageBox>
#include <QDesktopServices>
#include <QApplication>
namespace OCC {
OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent)
@ -358,8 +363,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();
});
@ -377,7 +381,22 @@ void OwncloudSetupWizard::testOwnCloudConnect()
job->setFollowRedirects(false);
job->setProperties(QList<QByteArray>() << "getlastmodified");
connect(job, &PropfindJob::result, _ocWizard, &OwncloudWizard::successfulStep);
connect(job, &PropfindJob::finishedWithError, this, &OwncloudSetupWizard::slotAuthError);
connect(job, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) -> void {
if (reply && reply->error() == QNetworkReply::ContentAccessDenied) {
// 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();
}
@ -414,14 +433,15 @@ 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::ContentNotFoundError) {
_ocWizard->successfulStep();
return;
// Provide messages for other errors, such as invalid credentials.
} else if (reply->error() != QNetworkReply::NoError) {
// 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, "
"<a href=\"%1\">click here</a> to access the service with your browser.")
@ -430,8 +450,8 @@ void OwncloudSetupWizard::slotAuthError()
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");
}

View File

@ -30,6 +30,7 @@
namespace OCC {
class AccountState;
class TermsOfServiceChecker;
class OwncloudWizard;
@ -44,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);
@ -80,7 +82,7 @@ private:
AccountState *applyAccountChanges();
bool checkDowngradeAdvised(QNetworkReply *reply);
OwncloudWizard *_ocWizard;
OwncloudWizard *_ocWizard = nullptr;
QString _initLocalFolder;
QString _remoteFolder;
};

View File

@ -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()
}
}

View File

@ -14,6 +14,7 @@
#include "syncstatussummary.h"
#include "accountfwd.h"
#include "accountstate.h"
#include "folderman.h"
#include "navigationpanehelper.h"
#include "networkjobs.h"

View File

@ -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
{

View File

@ -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;

View File

@ -104,6 +104,12 @@ void Flow2AuthCredsPage::slotFlow2AuthResult(Flow2Auth::Result r, const QString
int Flow2AuthCredsPage::nextId() const
{
const auto ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
if (ocWizard->needsToAcceptTermsOfService()) {
return WizardCommon::Page_TermsOfService;
}
return WizardCommon::Page_AdvancedSetup;
}

View File

@ -176,6 +176,7 @@ void OwncloudAdvancedSetupPage::initializePage()
quotaJob->setProperties(QList<QByteArray>() << "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();

View File

@ -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);

View File

@ -153,6 +153,12 @@ bool OwncloudHttpCredsPage::validatePage()
int OwncloudHttpCredsPage::nextId() const
{
const auto ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
if (ocWizard->needsToAcceptTermsOfService()) {
return WizardCommon::Page_TermsOfService;
}
return WizardCommon::Page_AdvancedSetup;
}

View File

@ -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"
@ -53,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))
@ -71,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()) {
@ -116,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();
@ -216,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();
@ -239,9 +248,12 @@ void OwncloudWizard::setRemoteFolder(const QString &remoteFolder)
void OwncloudWizard::successfulStep()
{
const int id(currentId());
const WizardCommon::Pages id{static_cast<WizardCommon::Pages>(currentId())};
switch (id) {
case WizardCommon::Page_Welcome:
break;
case WizardCommon::Page_HttpCreds:
_httpCredsPage->setConnected();
break;
@ -258,6 +270,10 @@ void OwncloudWizard::successfulStep()
break;
#endif // WITH_WEBENGINE
case WizardCommon::Page_TermsOfService:
// nothing to do here
break;
case WizardCommon::Page_AdvancedSetup:
_advancedSetupPage->directoriesCreated();
break;
@ -284,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;
}
}
@ -328,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 });
@ -351,7 +371,13 @@ void OwncloudWizard::slotCurrentPageChanged(int id)
void OwncloudWizard::displayError(const QString &msg, bool retryHTTPonly)
{
switch (currentId()) {
switch (static_cast<WizardCommon::Pages>(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;

View File

@ -33,6 +33,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWizard)
class WelcomePage;
class OwncloudSetupPage;
class OwncloudHttpCredsPage;
class TermsOfServiceWizardPage;
class OwncloudAdvancedSetupPage;
class OwncloudWizardResultPage;
class AbstractCredentials;
@ -68,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;
@ -123,14 +125,15 @@ private:
[[nodiscard]] QList<QSize> 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;
@ -138,6 +141,8 @@ private:
bool _useFlow2 = ConfigFile().forceLoginV2();
bool _needsToAcceptTermsOfService = false;
friend class OwncloudSetupWizard;
};

View File

@ -50,6 +50,7 @@ namespace WizardCommon {
#ifdef WITH_WEBENGINE
Page_WebView,
#endif // WITH_WEBENGINE
Page_TermsOfService,
Page_AdvancedSetup,
};

View File

@ -0,0 +1,194 @@
/*
* Copyright (C) by Jyrki Gadinger <nilsding@nilsding.org>
*
* 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 <QClipboard>
#include <QDesktopServices>
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

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) by Jyrki Gadinger <nilsding@nilsding.org>
*
* 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 <QWidget>
#include <QTimer>
#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

View File

@ -0,0 +1,248 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TermsOfServiceCheckWidget</class>
<widget class="QWidget" name="TermsOfServiceCheckWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>597</width>
<height>387</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>280</height>
</size>
</property>
<property name="windowTitle">
<string>Terms of Service</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="logoLabel">
<property name="text">
<string>Logo</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>32</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Switch to your browser to accept the terms of service</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string notr="true">Status</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>32</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="progressLayout"/>
</item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>64</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="copyLinkButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string notr="true">copyLinkButton</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="openLinkButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string notr="true">openLinkButton</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>60</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) by Matthieu Gallien <matthieu.gallien@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "termsofservicewizardpage.h"
#include "account.h"
#include "owncloudsetupwizard.h"
#include "wizard/owncloudwizard.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/termsofservicecheckwidget.h"
#include "connectionvalidator.h"
#include <QVBoxLayout>
#include <QDesktopServices>
namespace OCC {
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<OwncloudWizard *>(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
{
return WizardCommon::Page_AdvancedSetup;
}
bool OCC::TermsOfServiceWizardPage::isComplete() const
{
return false;
}
void TermsOfServiceWizardPage::slotPollNow()
{
if (!_termsOfServiceChecker) {
return;
}
_termsOfServiceChecker->start();
}
void TermsOfServiceWizardPage::termsOfServiceChecked()
{
if (_termsOfServiceChecker && _termsOfServiceChecker->needToSign()) {
_termsOfServiceCheckWidget->termsNotAcceptedYet();
return;
}
_ocWizard->successfulStep();
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) by Matthieu Gallien <matthieu.gallien@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef TERMSOFSERVICEWIZARDPAGE_H
#define TERMSOFSERVICEWIZARDPAGE_H
#include <QWizardPage>
class QVBoxLayout;
namespace OCC {
class OwncloudWizard;
class TermsOfServiceChecker;
class TermsOfServiceCheckWidget;
class TermsOfServiceWizardPage : public QWizardPage
{
Q_OBJECT
public:
TermsOfServiceWizardPage();
void initializePage() override;
void cleanupPage() override;
[[nodiscard]] int nextId() const override;
[[nodiscard]] bool isComplete() const override;
public Q_SLOTS:
void slotPollNow();
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
#endif // TERMSOFSERVICEWIZARDPAGE_H

View File

@ -92,6 +92,12 @@ void WebViewPage::cleanupPage()
}
int WebViewPage::nextId() const {
const auto ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
if (ocWizard->needsToAcceptTermsOfService()) {
return WizardCommon::Page_TermsOfService;
}
return WizardCommon::Page_AdvancedSetup;
}

View File

@ -69,8 +69,8 @@ inline QPair<QByteArray, QByteArray> 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 {};
}