diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 0fb5970adb..5db93a6402 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -39,6 +39,7 @@ set(client_UI_SRCS wizard/owncloudhttpcredspage.ui wizard/owncloudoauthcredspage.ui wizard/flow2authcredspage.ui + wizard/flow2authwidget.ui wizard/owncloudsetupnocredspage.ui wizard/owncloudwizardresultpage.ui wizard/webview.ui @@ -114,6 +115,7 @@ set(client_SRCS wizard/owncloudhttpcredspage.cpp wizard/owncloudoauthcredspage.cpp wizard/flow2authcredspage.cpp + wizard/flow2authwidget.cpp wizard/owncloudsetuppage.cpp wizard/owncloudwizardcommon.cpp wizard/owncloudwizard.cpp diff --git a/src/gui/creds/flow2auth.cpp b/src/gui/creds/flow2auth.cpp index 76ff7c4ac2..e570e01d48 100644 --- a/src/gui/creds/flow2auth.cpp +++ b/src/gui/creds/flow2auth.cpp @@ -34,9 +34,9 @@ Flow2Auth::~Flow2Auth() void Flow2Auth::start() { - // Note: All startup code is in openBrowser() to allow reinitiate a new request with - // fresh tokens. Opening the same pollEndpoint link twice triggers an expiration - // message by the server (security, intended design). + // Note: All startup code is in openBrowser() to allow reinitiate a new request with + // fresh tokens. Opening the same pollEndpoint link twice triggers an expiration + // message by the server (security, intended design). openBrowser(); } @@ -91,6 +91,7 @@ void Flow2Auth::openBrowser() _pollEndpoint = pollEndpoint; + // Start polling ConfigFile cfg; std::chrono::milliseconds polltime = cfg.remotePollInterval(); qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec"; @@ -99,10 +100,10 @@ void Flow2Auth::openBrowser() _pollTimer.start(); - if (!QDesktopServices::openUrl(authorisationLink())) { - // TODO: Ask the user to copy and open the link instead of failing here! - - // We cannot open the browser, then we claim we don't support OAuth. + // Try to open Browser + if (!QDesktopServices::openUrl(authorisationLink())) { + // We cannot open the browser, then we claim we don't support Flow2Auth. + // Our UI callee should ask the user to copy and open the link. emit result(NotSupported, QString()); } }); @@ -110,7 +111,7 @@ void Flow2Auth::openBrowser() void Flow2Auth::slotPollTimerTimeout() { - _pollTimer.stop(); + _pollTimer.stop(); // Step 2: Poll QNetworkRequest req; @@ -122,6 +123,7 @@ void Flow2Auth::slotPollTimerTimeout() auto job = _account->sendRequest("POST", _pollEndpoint, req, requestBody); job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec())); + QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) { auto jsonData = reply->readAll(); QJsonParseError jsonParseError; @@ -149,15 +151,25 @@ void Flow2Auth::slotPollTimerTimeout() } qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason; - _pollTimer.start(); + // Forget sensitive data + appPassword.clear(); + loginName.clear(); + + // Failed: poll again + _pollTimer.start(); return; } - qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << " on server: " << serverUrl; - - _account->setUrl(serverUrl); + // Success + qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString(); - emit result(LoggedIn, loginName, appPassword); + _account->setUrl(serverUrl); + + emit result(LoggedIn, loginName, appPassword); + + // Forget sensitive data + appPassword.clear(); + loginName.clear(); }); } diff --git a/src/gui/creds/flow2auth.h b/src/gui/creds/flow2auth.h index 188865a0da..b53834a115 100644 --- a/src/gui/creds/flow2auth.h +++ b/src/gui/creds/flow2auth.h @@ -49,10 +49,9 @@ public: signals: /** * The state has changed. - * when logged in, token has the value of the token. + * when logged in, appPassword has the value of the app password. */ - // TODO: Remove refreshToken - void result(Flow2Auth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString()); + void result(Flow2Auth::Result result, const QString &user = QString(), const QString &appPassword = QString()); private slots: void slotPollTimerTimeout(); diff --git a/src/gui/creds/webflowcredentials.cpp b/src/gui/creds/webflowcredentials.cpp index 70a709470f..ecc0e61d93 100644 --- a/src/gui/creds/webflowcredentials.cpp +++ b/src/gui/creds/webflowcredentials.cpp @@ -114,12 +114,17 @@ void WebFlowCredentials::fetchFromKeychain() { } void WebFlowCredentials::askFromUser() { - _askDialog = new WebFlowCredentialsDialog(); + // LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic + bool useFlow2 = (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0)); - QUrl url = _account->url(); - QString path = url.path() + "/index.php/login/flow"; - url.setPath(path); - _askDialog->setUrl(url); + _askDialog = new WebFlowCredentialsDialog(_account, useFlow2); + + if (!useFlow2) { + QUrl url = _account->url(); + QString path = url.path() + "/index.php/login/flow"; + url.setPath(path); + _askDialog->setUrl(url); + } QString msg = tr("You have been logged out of %1 as user %2. Please login again") .arg(_account->displayName(), _user); @@ -142,10 +147,12 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, .arg(_user); _askDialog->setError(msg); - QUrl url = _account->url(); - QString path = url.path() + "/index.php/login/flow"; - url.setPath(path); - _askDialog->setUrl(url); + if (!_askDialog->isUsingFlow2()) { + QUrl url = _account->url(); + QString path = url.path() + "/index.php/login/flow"; + url.setPath(path); + _askDialog->setUrl(url); + } return; } diff --git a/src/gui/creds/webflowcredentialsdialog.cpp b/src/gui/creds/webflowcredentialsdialog.cpp index 2d22ba06e3..30a327afa2 100644 --- a/src/gui/creds/webflowcredentialsdialog.cpp +++ b/src/gui/creds/webflowcredentialsdialog.cpp @@ -3,13 +3,21 @@ #include #include +#include "theme.h" +#include "wizard/owncloudwizardcommon.h" #include "wizard/webview.h" +#include "wizard/flow2authwidget.h" namespace OCC { -WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent) - : QDialog(parent) +WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent) + : QDialog(parent), + _useFlow2(useFlow2), + _flow2AuthWidget(nullptr), + _webView(nullptr) { + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + _layout = new QVBoxLayout(this); //QString msg = tr("You have been logged out of %1 as user %2, please login again") @@ -17,28 +25,44 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent) _infoLabel = new QLabel(); _layout->addWidget(_infoLabel); - _webView = new WebView(); - _layout->addWidget(_webView); + if (_useFlow2) { + _flow2AuthWidget = new Flow2AuthWidget(account); + _layout->addWidget(_flow2AuthWidget); + + connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched); + } else { + _webView = new WebView(); + _layout->addWidget(_webView); + + connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched); + } _errorLabel = new QLabel(); _errorLabel->hide(); _layout->addWidget(_errorLabel); - setLayout(_layout); + Theme *theme = Theme::instance(); + WizardCommon::initErrorLabel(_errorLabel); - connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched); + setLayout(_layout); } void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) { Q_UNUSED(e); - // Force calling WebView::~WebView() earlier so that _profile and _page are - // deleted in the correct order. - delete _webView; + if (_webView) { + // Force calling WebView::~WebView() earlier so that _profile and _page are + // deleted in the correct order. + delete _webView; + } + + if (_flow2AuthWidget) + delete _flow2AuthWidget; } void WebFlowCredentialsDialog::setUrl(const QUrl &url) { - _webView->setUrl(url); + if (_webView) + _webView->setUrl(url); } void WebFlowCredentialsDialog::setInfo(const QString &msg) { @@ -46,6 +70,11 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) { } void WebFlowCredentialsDialog::setError(const QString &error) { + if (_useFlow2 && _flow2AuthWidget) { + _flow2AuthWidget->setError(error); + return; + } + if (error.isEmpty()) { _errorLabel->hide(); } else { diff --git a/src/gui/creds/webflowcredentialsdialog.h b/src/gui/creds/webflowcredentialsdialog.h index 9849ee3a46..4e1f21c0dd 100644 --- a/src/gui/creds/webflowcredentialsdialog.h +++ b/src/gui/creds/webflowcredentialsdialog.h @@ -4,23 +4,30 @@ #include #include +#include "accountfwd.h" + class QLabel; class QVBoxLayout; namespace OCC { class WebView; +class Flow2AuthWidget; class WebFlowCredentialsDialog : public QDialog { Q_OBJECT public: - WebFlowCredentialsDialog(QWidget *parent = nullptr); + WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent = nullptr); void setUrl(const QUrl &url); void setInfo(const QString &msg); void setError(const QString &error); + const bool isUsingFlow2() const { + return _useFlow2; + } + protected: void closeEvent(QCloseEvent * e) override; @@ -28,7 +35,11 @@ signals: void urlCatched(const QString user, const QString pass, const QString host); private: + bool _useFlow2; + + Flow2AuthWidget *_flow2AuthWidget; WebView *_webView; + QLabel *_errorLabel; QLabel *_infoLabel; QVBoxLayout *_layout; diff --git a/src/gui/wizard/flow2authcredspage.cpp b/src/gui/wizard/flow2authcredspage.cpp index e5914a10b1..91e5415bb8 100644 --- a/src/gui/wizard/flow2authcredspage.cpp +++ b/src/gui/wizard/flow2authcredspage.cpp @@ -81,17 +81,25 @@ void OCC::Flow2AuthCredsPage::cleanupPage() // The next or back button was activated, show the wizard again wizard()->show(); _asyncAuth.reset(); + + // Forget sensitive data + _appPassword.clear(); + _user.clear(); } void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &user, - const QString &token, const QString &refreshToken) + const QString &appPassword) { switch (r) { case Flow2Auth::NotSupported: { - /* Flow2Auth not supported (can't open browser), fallback to HTTP credentials */ - OwncloudWizard *ocWizard = qobject_cast(wizard()); + /* Flow2Auth not supported (can't open browser) */ + _ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser.")); + _ui.errorLabel->show(); + + /* Don't fallback to HTTP credentials */ + /*OwncloudWizard *ocWizard = qobject_cast(wizard()); ocWizard->back(); - ocWizard->setAuthType(DetermineAuthTypeJob::Basic); + ocWizard->setAuthType(DetermineAuthTypeJob::Basic);*/ break; } case Flow2Auth::Error: @@ -100,9 +108,8 @@ void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &use wizard()->show(); break; case Flow2Auth::LoggedIn: { - _token = token; _user = user; - _refreshToken = refreshToken; + _appPassword = appPassword; OwncloudWizard *ocWizard = qobject_cast(wizard()); Q_ASSERT(ocWizard); emit connectToOCUrl(ocWizard->account()->url().toString()); @@ -125,7 +132,7 @@ AbstractCredentials *Flow2AuthCredsPage::getCredentials() const { OwncloudWizard *ocWizard = qobject_cast(wizard()); Q_ASSERT(ocWizard); - return new HttpCredentialsGui(_user, _token, _refreshToken, + return new HttpCredentialsGui(_user, _appPassword, QString(), ocWizard->_clientSslCertificate, ocWizard->_clientSslKey); } diff --git a/src/gui/wizard/flow2authcredspage.h b/src/gui/wizard/flow2authcredspage.h index 85f1a0a9fc..bffcf68b3f 100644 --- a/src/gui/wizard/flow2authcredspage.h +++ b/src/gui/wizard/flow2authcredspage.h @@ -46,16 +46,14 @@ public: bool isComplete() const override; public Q_SLOTS: - void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &token, - const QString &reniewToken); + void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword); signals: void connectToOCUrl(const QString &); public: QString _user; - QString _token; - QString _refreshToken; + QString _appPassword; QScopedPointer _asyncAuth; Ui_Flow2AuthCredsPage _ui; }; diff --git a/src/gui/wizard/flow2authwidget.cpp b/src/gui/wizard/flow2authwidget.cpp new file mode 100644 index 0000000000..300b2e83d8 --- /dev/null +++ b/src/gui/wizard/flow2authwidget.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) by Michael Schuster + * + * 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 "flow2authwidget.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/utility.h" +#include "account.h" +#include "theme.h" +#include "wizard/owncloudwizardcommon.h" + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "gui.wizard.flow2authwidget", QtInfoMsg) + + +Flow2AuthWidget::Flow2AuthWidget(Account *account, QWidget *parent) + : QWidget(parent), + _account(account), + _ui() +{ + _ui.setupUi(this); + + Theme *theme = Theme::instance(); + _ui.topLabel->hide(); + _ui.bottomLabel->hide(); + QVariant variant = theme->customMedia(Theme::oCSetupTop); + WizardCommon::setupCustomMedia(variant, _ui.topLabel); + variant = theme->customMedia(Theme::oCSetupBottom); + WizardCommon::setupCustomMedia(variant, _ui.bottomLabel); + + WizardCommon::initErrorLabel(_ui.errorLabel); + + connect(_ui.openLinkButton, &QCommandLinkButton::clicked, [this] { + _ui.errorLabel->hide(); + if (_asyncAuth) + _asyncAuth->openBrowser(); + }); + _ui.openLinkButton->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) { + auto menu = new QMenu(_ui.openLinkButton); + menu->addAction(tr("Copy link to clipboard"), this, [this] { + if (_asyncAuth) + QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded)); + }); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(_ui.openLinkButton->mapToGlobal(pos)); + }); + + _asyncAuth.reset(new Flow2Auth(_account, this)); + connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthWidget::asyncAuthResult, Qt::QueuedConnection); + _asyncAuth->start(); +} + +void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user, + const QString &appPassword) +{ + switch (r) { + case Flow2Auth::NotSupported: + /* Flow2Auth can't open browser */ + _ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser.")); + _ui.errorLabel->show(); + break; + case Flow2Auth::Error: + /* Error while getting the access token. (Timeout, or the server did not accept our client credentials */ + _ui.errorLabel->show(); + break; + case Flow2Auth::LoggedIn: { + _user = user; + _appPassword = appPassword; + emit urlCatched(_user, _appPassword, QString()); + break; + } + } +} + +void Flow2AuthWidget::setError(const QString &error) { + if (error.isEmpty()) { + _ui.errorLabel->hide(); + } else { + _ui.errorLabel->setText(error); + _ui.errorLabel->show(); + } +} + +Flow2AuthWidget::~Flow2AuthWidget() { + _asyncAuth.reset(); + + // Forget sensitive data + _appPassword.clear(); + _user.clear(); +} + +} diff --git a/src/gui/wizard/flow2authwidget.h b/src/gui/wizard/flow2authwidget.h new file mode 100644 index 0000000000..cf04d91935 --- /dev/null +++ b/src/gui/wizard/flow2authwidget.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) by Michael Schuster + * + * 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 FLOW2AUTHWIDGET_H +#define FLOW2AUTHWIDGET_H + +#include +#include + +#include "creds/flow2auth.h" + +#include "ui_flow2authwidget.h" + +namespace OCC { + +class Flow2AuthWidget : public QWidget +{ + Q_OBJECT +public: + Flow2AuthWidget(Account *account, QWidget *parent = nullptr); + virtual ~Flow2AuthWidget(); + + void setError(const QString &error); + +public Q_SLOTS: + void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword); + +signals: + void urlCatched(const QString user, const QString pass, const QString host); + +private: + Account *_account; + QString _user; + QString _appPassword; + QScopedPointer _asyncAuth; + Ui_Flow2AuthWidget _ui; +}; + +} + +#endif // FLOW2AUTHWIDGET_H diff --git a/src/gui/wizard/flow2authwidget.ui b/src/gui/wizard/flow2authwidget.ui new file mode 100644 index 0000000000..e73ae6a1d6 --- /dev/null +++ b/src/gui/wizard/flow2authwidget.ui @@ -0,0 +1,99 @@ + + + Flow2AuthWidget + + + + 0 + 0 + 500 + 280 + + + + + 0 + 0 + + + + + 500 + 280 + + + + Form + + + + + + TextLabel + + + Qt::RichText + + + Qt::AlignCenter + + + true + + + + + + + Please switch to your browser to proceed. + + + true + + + + + + + An error occurred while connecting. Please try again. + + + Qt::PlainText + + + + + + + Re-open Browser (or right-click to copy link) + + + + + + + Qt::Vertical + + + + 20 + 127 + + + + + + + + TextLabel + + + Qt::RichText + + + + + + + +