From 19e024456821c8d3490de37f0f33a6da3319118a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20M=C3=BCller?= Date: Wed, 15 Jun 2022 00:23:14 +0200 Subject: [PATCH] Add Webfinger support to new wizard This commit adds support Webfinger lookups in the wizard. This way, enterprise customers who allow authorization against a variety of OpenID Connect servers can set up a Webfinger server to have the client use the right IdP. The commit includes @TheOneRing's the Webfinger implementation from #9366. --- src/gui/newwizard/CMakeLists.txt | 5 + src/gui/newwizard/enums.cpp | 2 + src/gui/newwizard/enums.h | 2 + .../pages/basiccredentialssetupwizardpage.cpp | 8 + .../pages/basiccredentialssetupwizardpage.h | 2 + .../pages/webfingersetupwizardpage.cpp | 54 +++++++ .../pages/webfingersetupwizardpage.h | 41 +++++ .../pages/webfingersetupwizardpage.ui | 140 ++++++++++++++++++ .../newwizard/setupwizardaccountbuilder.cpp | 20 +++ src/gui/newwizard/setupwizardaccountbuilder.h | 23 ++- src/gui/newwizard/setupwizardcontroller.cpp | 42 +++++- .../basiccredentialssetupwizardstate.cpp | 6 +- .../oauthcredentialssetupwizardstate.cpp | 5 +- .../states/serverurlsetupwizardstate.cpp | 38 +++-- .../states/webfingersetupwizardstate.cpp | 75 ++++++++++ .../states/webfingersetupwizardstate.h | 33 +++++ src/libsync/CMakeLists.txt | 1 + src/libsync/creds/webfinger.cpp | 95 ++++++++++++ src/libsync/creds/webfinger.h | 46 ++++++ src/libsync/theme.cpp | 5 + src/libsync/theme.h | 5 + src/resources/wizard/style.qss | 1 + 22 files changed, 626 insertions(+), 23 deletions(-) create mode 100644 src/gui/newwizard/pages/webfingersetupwizardpage.cpp create mode 100644 src/gui/newwizard/pages/webfingersetupwizardpage.h create mode 100644 src/gui/newwizard/pages/webfingersetupwizardpage.ui create mode 100644 src/gui/newwizard/states/webfingersetupwizardstate.cpp create mode 100644 src/gui/newwizard/states/webfingersetupwizardstate.h create mode 100644 src/libsync/creds/webfinger.cpp create mode 100644 src/libsync/creds/webfinger.h diff --git a/src/gui/newwizard/CMakeLists.txt b/src/gui/newwizard/CMakeLists.txt index a643953952b..f8055318b97 100644 --- a/src/gui/newwizard/CMakeLists.txt +++ b/src/gui/newwizard/CMakeLists.txt @@ -8,6 +8,10 @@ add_library(newwizard STATIC pages/serverurlsetupwizardpage.h pages/serverurlsetupwizardpage.cpp + pages/webfingersetupwizardpage.ui + pages/webfingersetupwizardpage.h + pages/webfingersetupwizardpage.cpp + pages/basiccredentialssetupwizardpage.ui pages/basiccredentialssetupwizardpage.h pages/basiccredentialssetupwizardpage.cpp @@ -43,6 +47,7 @@ add_library(newwizard STATIC states/abstractsetupwizardstate.cpp states/serverurlsetupwizardstate.cpp + states/webfingersetupwizardstate.cpp states/basiccredentialssetupwizardstate.cpp states/oauthcredentialssetupwizardstate.cpp states/accountconfiguredsetupwizardstate.cpp diff --git a/src/gui/newwizard/enums.cpp b/src/gui/newwizard/enums.cpp index 8795887de01..60dff0c2224 100644 --- a/src/gui/newwizard/enums.cpp +++ b/src/gui/newwizard/enums.cpp @@ -29,6 +29,8 @@ QString OCC::Utility::enumToDisplayName(SetupWizardState state) switch (state) { case SetupWizardState::ServerUrlState: return QApplication::translate(contextC, "Server URL"); + case SetupWizardState::WebFingerState: + return QApplication::translate(contextC, "WebFinger"); case SetupWizardState::CredentialsState: return QApplication::translate(contextC, "Credentials"); case SetupWizardState::AccountConfiguredState: diff --git a/src/gui/newwizard/enums.h b/src/gui/newwizard/enums.h index a6995b1b2fe..2b2d3ab3f9b 100644 --- a/src/gui/newwizard/enums.h +++ b/src/gui/newwizard/enums.h @@ -26,6 +26,8 @@ enum class SetupWizardState { ServerUrlState, FirstState = ServerUrlState, + WebFingerState, + CredentialsState, AccountConfiguredState, diff --git a/src/gui/newwizard/pages/basiccredentialssetupwizardpage.cpp b/src/gui/newwizard/pages/basiccredentialssetupwizardpage.cpp index a220b74e26c..f87ffa62218 100644 --- a/src/gui/newwizard/pages/basiccredentialssetupwizardpage.cpp +++ b/src/gui/newwizard/pages/basiccredentialssetupwizardpage.cpp @@ -44,6 +44,14 @@ BasicCredentialsSetupWizardPage::BasicCredentialsSetupWizardPage(const QUrl &ser _ui->appPasswordLabel->setText(tr("Click here to set up an app password.").arg(appPasswordUrl, linkColor)); } +BasicCredentialsSetupWizardPage *BasicCredentialsSetupWizardPage::createForWebFinger(const QUrl &serverUrl, const QString &username) +{ + auto page = new BasicCredentialsSetupWizardPage(serverUrl); + page->_ui->usernameLineEdit->setText(username); + page->_ui->usernameLineEdit->setEnabled(false); + return page; +} + QString BasicCredentialsSetupWizardPage::username() const { return _ui->usernameLineEdit->text(); diff --git a/src/gui/newwizard/pages/basiccredentialssetupwizardpage.h b/src/gui/newwizard/pages/basiccredentialssetupwizardpage.h index 9a34f44a4a8..bcb08f9c9d4 100644 --- a/src/gui/newwizard/pages/basiccredentialssetupwizardpage.h +++ b/src/gui/newwizard/pages/basiccredentialssetupwizardpage.h @@ -30,6 +30,8 @@ class BasicCredentialsSetupWizardPage : public AbstractSetupWizardPage BasicCredentialsSetupWizardPage(const QUrl &serverUrl); ~BasicCredentialsSetupWizardPage() noexcept override; + static BasicCredentialsSetupWizardPage *createForWebFinger(const QUrl &serverUrl, const QString &username); + QString username() const; QString password() const; diff --git a/src/gui/newwizard/pages/webfingersetupwizardpage.cpp b/src/gui/newwizard/pages/webfingersetupwizardpage.cpp new file mode 100644 index 00000000000..7cea2137454 --- /dev/null +++ b/src/gui/newwizard/pages/webfingersetupwizardpage.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "webfingersetupwizardpage.h" +#include "ui_webfingersetupwizardpage.h" + +#include "theme.h" + +namespace OCC::Wizard { + +WebFingerSetupWizardPage::WebFingerSetupWizardPage(const QUrl &serverUrl) + : _ui(new ::Ui::WebFingerSetupWizardPage) +{ + _ui->setupUi(this); + + _ui->urlLabel->setText(tr("Connecting to %1").arg(serverUrl.toString(), Theme::instance()->wizardHeaderTitleColor().name())); + + connect(this, &AbstractSetupWizardPage::pageDisplayed, this, [this]() { + _ui->usernameLineEdit->setFocus(); + }); + + _ui->usernameLabel->setText(Utility::enumToDisplayName(Theme::instance()->userIDType())); + + if (!Theme::instance()->userIDHint().isEmpty()) { + _ui->usernameLineEdit->setPlaceholderText(Theme::instance()->userIDHint()); + } +} + +QString WebFingerSetupWizardPage::username() const +{ + return _ui->usernameLineEdit->text(); +} + +WebFingerSetupWizardPage::~WebFingerSetupWizardPage() +{ + delete _ui; +} + +bool WebFingerSetupWizardPage::validateInput() +{ + return !(username().isEmpty()); +} +} diff --git a/src/gui/newwizard/pages/webfingersetupwizardpage.h b/src/gui/newwizard/pages/webfingersetupwizardpage.h new file mode 100644 index 00000000000..a6b251e620f --- /dev/null +++ b/src/gui/newwizard/pages/webfingersetupwizardpage.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "abstractsetupwizardpage.h" + +namespace Ui { +class WebFingerSetupWizardPage; +} + +namespace OCC::Wizard { + +class WebFingerSetupWizardPage : public AbstractSetupWizardPage +{ + Q_OBJECT + +public: + WebFingerSetupWizardPage(const QUrl &serverUrl); + ~WebFingerSetupWizardPage() noexcept override; + + QString username() const; + + bool validateInput() override; + +private: + ::Ui::WebFingerSetupWizardPage *_ui; +}; + +} diff --git a/src/gui/newwizard/pages/webfingersetupwizardpage.ui b/src/gui/newwizard/pages/webfingersetupwizardpage.ui new file mode 100644 index 00000000000..15c848bf5ee --- /dev/null +++ b/src/gui/newwizard/pages/webfingersetupwizardpage.ui @@ -0,0 +1,140 @@ + + + WebFingerSetupWizardPage + + + + 0 + 0 + 629 + 434 + + + + Form + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + Please enter your username: + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Username + + + + + + + + 200 + 0 + + + + Username + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <server URL> (placeholder) + + + Qt::AlignCenter + + + + + + + + diff --git a/src/gui/newwizard/setupwizardaccountbuilder.cpp b/src/gui/newwizard/setupwizardaccountbuilder.cpp index a585b3f55ee..e31e6d4999f 100644 --- a/src/gui/newwizard/setupwizardaccountbuilder.cpp +++ b/src/gui/newwizard/setupwizardaccountbuilder.cpp @@ -96,6 +96,11 @@ DetermineAuthTypeJob::AuthType SetupWizardAccountBuilder::authType() return _authType; } +void SetupWizardAccountBuilder::setWebFingerUsername(const QString &username) +{ + _webFingerUsername = username; +} + AccountPtr SetupWizardAccountBuilder::build() { auto newAccountPtr = Account::create(); @@ -163,4 +168,19 @@ void SetupWizardAccountBuilder::setDefaultSyncTargetDir(const QString &syncTarge { _defaultSyncTargetDir = syncTargetDir; } + +QString SetupWizardAccountBuilder::webFingerUsername() const +{ + return _webFingerUsername; +} + +void SetupWizardAccountBuilder::setWebFingerServerUrl(const QUrl &webFingerServerUrl) +{ + _webFingerServerUrl = webFingerServerUrl; +} + +QUrl SetupWizardAccountBuilder::webFingerServerUrl() const +{ + return _webFingerServerUrl; +} } diff --git a/src/gui/newwizard/setupwizardaccountbuilder.h b/src/gui/newwizard/setupwizardaccountbuilder.h index 692b952563c..01dbd099897 100644 --- a/src/gui/newwizard/setupwizardaccountbuilder.h +++ b/src/gui/newwizard/setupwizardaccountbuilder.h @@ -97,12 +97,28 @@ class SetupWizardAccountBuilder SetupWizardAccountBuilder(); /** - * Set server URL. + * Set ownCloud server URL as well as the authentication type that needs to be used with this server. * @param serverUrl URL to server */ void setServerUrl(const QUrl &serverUrl, DetermineAuthTypeJob::AuthType workflowType); QUrl serverUrl() const; + /** + * Set URL of WebFinger server used to look up the user's server. + * Only used when WebFinger support is enabled by the theme. + * @param webFingerServerUrl URL to WebFinger server + */ + void setWebFingerServerUrl(const QUrl &webFingerServerUrl); + QUrl webFingerServerUrl() const; + + /** + * Set URL of WebFinger server used to look up the user's server. + * Only used when WebFinger support is enabled by the theme. + * @param username + */ + void setWebFingerUsername(const QString &username); + QString webFingerUsername() const; + // TODO: move this out of the class's state DetermineAuthTypeJob::AuthType authType(); @@ -142,12 +158,15 @@ class SetupWizardAccountBuilder private: QUrl _serverUrl; - QString _displayName; + QString _webFingerUsername; + QUrl _webFingerServerUrl; DetermineAuthTypeJob::AuthType _authType = DetermineAuthTypeJob::AuthType::Unknown; std::unique_ptr _authenticationStrategy; + QString _displayName; + QSet _customTrustedCaCertificates; QString _defaultSyncTargetDir; diff --git a/src/gui/newwizard/setupwizardcontroller.cpp b/src/gui/newwizard/setupwizardcontroller.cpp index 4d66b757083..de8350e4cbe 100644 --- a/src/gui/newwizard/setupwizardcontroller.cpp +++ b/src/gui/newwizard/setupwizardcontroller.cpp @@ -11,6 +11,8 @@ #include "states/basiccredentialssetupwizardstate.h" #include "states/oauthcredentialssetupwizardstate.h" #include "states/serverurlsetupwizardstate.h" +#include "states/webfingersetupwizardstate.h" +#include "theme.h" #include #include @@ -26,11 +28,20 @@ SetupWizardController::SetupWizardController(QWidget *parent) , _context(new SetupWizardContext(parent, this)) { // initialize pagination - _context->window()->setNavigationEntries({ - SetupWizardState::ServerUrlState, - SetupWizardState::CredentialsState, - SetupWizardState::AccountConfiguredState, - }); + if (Theme::instance()->wizardEnableWebfinger()) { + _context->window()->setNavigationEntries({ + SetupWizardState::ServerUrlState, + SetupWizardState::WebFingerState, + SetupWizardState::CredentialsState, + SetupWizardState::AccountConfiguredState, + }); + } else { + _context->window()->setNavigationEntries({ + SetupWizardState::ServerUrlState, + SetupWizardState::CredentialsState, + SetupWizardState::AccountConfiguredState, + }); + } // set up initial state changeStateTo(SetupWizardState::FirstState); @@ -59,7 +70,13 @@ SetupWizardController::SetupWizardController(QWidget *parent) qCDebug(lcSetupWizardController) << "back button clicked, current state" << _currentState; - const auto previousState = static_cast(currentStateIdx - 1); + auto previousState = static_cast(currentStateIdx - 1); + + // skip WebFinger page when WebFinger is not available + if (previousState == SetupWizardState::WebFingerState && !Theme::instance()->wizardEnableWebfinger()) { + previousState = SetupWizardState::ServerUrlState; + } + changeStateTo(previousState); }); } @@ -86,6 +103,10 @@ void SetupWizardController::changeStateTo(SetupWizardState nextState) _currentState = new ServerUrlSetupWizardState(_context); break; } + case SetupWizardState::WebFingerState: { + _currentState = new WebFingerSetupWizardState(_context); + break; + } case SetupWizardState::CredentialsState: { switch (_context->accountBuilder().authType()) { case DetermineAuthTypeJob::AuthType::Basic: @@ -118,6 +139,15 @@ void SetupWizardController::changeStateTo(SetupWizardState nextState) switch (_currentState->state()) { case SetupWizardState::ServerUrlState: { + if (Theme::instance()->wizardEnableWebfinger()) { + changeStateTo(SetupWizardState::WebFingerState); + } else { + changeStateTo(SetupWizardState::CredentialsState); + } + return; + Q_FALLTHROUGH(); + } + case SetupWizardState::WebFingerState: { changeStateTo(SetupWizardState::CredentialsState); return; } diff --git a/src/gui/newwizard/states/basiccredentialssetupwizardstate.cpp b/src/gui/newwizard/states/basiccredentialssetupwizardstate.cpp index 7b9f95ac1ad..171d44cfafa 100644 --- a/src/gui/newwizard/states/basiccredentialssetupwizardstate.cpp +++ b/src/gui/newwizard/states/basiccredentialssetupwizardstate.cpp @@ -21,7 +21,11 @@ namespace OCC::Wizard { BasicCredentialsSetupWizardState::BasicCredentialsSetupWizardState(SetupWizardContext *context) : AbstractSetupWizardState(context) { - _page = new BasicCredentialsSetupWizardPage(_context->accountBuilder().serverUrl()); + if (!context->accountBuilder().webFingerUsername().isEmpty()) { + _page = BasicCredentialsSetupWizardPage::createForWebFinger(_context->accountBuilder().serverUrl(), _context->accountBuilder().webFingerUsername()); + } else { + _page = new BasicCredentialsSetupWizardPage(_context->accountBuilder().serverUrl()); + } } void BasicCredentialsSetupWizardState::evaluatePage() diff --git a/src/gui/newwizard/states/oauthcredentialssetupwizardstate.cpp b/src/gui/newwizard/states/oauthcredentialssetupwizardstate.cpp index 9658c6172f1..83322ff85a5 100644 --- a/src/gui/newwizard/states/oauthcredentialssetupwizardstate.cpp +++ b/src/gui/newwizard/states/oauthcredentialssetupwizardstate.cpp @@ -17,6 +17,7 @@ #include "gui/application.h" #include "oauthcredentialssetupwizardstate.h" +#include "theme.h" namespace OCC::Wizard { @@ -26,8 +27,8 @@ OAuthCredentialsSetupWizardState::OAuthCredentialsSetupWizardState(SetupWizardCo auto oAuthCredentialsPage = new OAuthCredentialsSetupWizardPage(_context->accountBuilder().serverUrl()); _page = oAuthCredentialsPage; - // username might not be set yet, shouldn't matter, though - auto oAuth = new OAuth(_context->accountBuilder().serverUrl(), QString(), _context->accessManager(), {}, this); + // webFingerUsername will be empty when WebFinger is not in use + auto oAuth = new OAuth(_context->accountBuilder().serverUrl(), _context->accountBuilder().webFingerUsername(), _context->accessManager(), {}, this); connect(oAuth, &OAuth::result, this, [this, oAuthCredentialsPage](OAuth::Result result, const QString &userName, const QString &token, const QString &refreshToken, const QString &displayName) { // the button may not be clicked anymore, since the server has been shut down right before this signal was emitted by the OAuth instance diff --git a/src/gui/newwizard/states/serverurlsetupwizardstate.cpp b/src/gui/newwizard/states/serverurlsetupwizardstate.cpp index 6280e5a8e8c..519d00fa8dc 100644 --- a/src/gui/newwizard/states/serverurlsetupwizardstate.cpp +++ b/src/gui/newwizard/states/serverurlsetupwizardstate.cpp @@ -15,6 +15,7 @@ #include "serverurlsetupwizardstate.h" #include "determineauthtypejobfactory.h" #include "jobs/resolveurljobfactory.h" +#include "theme.h" #include #include @@ -33,7 +34,15 @@ Q_LOGGING_CATEGORY(lcSetupWizardServerUrlState, "setupwizard.states.serverurl"); ServerUrlSetupWizardState::ServerUrlSetupWizardState(SetupWizardContext *context) : AbstractSetupWizardState(context) { - _page = new ServerUrlSetupWizardPage(_context->accountBuilder().serverUrl()); + auto serverUrl = [this]() { + if (Theme::instance()->wizardEnableWebfinger()) { + return _context->accountBuilder().webFingerServerUrl(); + } else { + return _context->accountBuilder().serverUrl(); + } + }(); + + _page = new ServerUrlSetupWizardPage(serverUrl); } SetupWizardState ServerUrlSetupWizardState::state() const @@ -105,20 +114,25 @@ void ServerUrlSetupWizardState::evaluatePage() const auto resolvedUrl = qvariant_cast(resolveJob->result()); - // next, we need to find out which kind of authentication page we have to present to the user - auto authTypeJob = DetermineAuthTypeJobFactory(_context->accessManager()).startJob(resolvedUrl); + if (Theme::instance()->wizardEnableWebfinger()) { + _context->accountBuilder().setWebFingerServerUrl(resolvedUrl); + Q_EMIT evaluationSuccessful(); + } else { + // next, we need to find out which kind of authentication page we have to present to the user + auto authTypeJob = DetermineAuthTypeJobFactory(_context->accessManager(), this).startJob(resolvedUrl); - connect(authTypeJob, &CoreJob::finished, authTypeJob, [this, authTypeJob, resolvedUrl]() { - authTypeJob->deleteLater(); + connect(authTypeJob, &CoreJob::finished, authTypeJob, [this, authTypeJob, resolvedUrl]() { + authTypeJob->deleteLater(); - if (authTypeJob->result().isNull()) { - Q_EMIT evaluationFailed(authTypeJob->errorMessage()); - return; - } + if (authTypeJob->result().isNull()) { + Q_EMIT evaluationFailed(authTypeJob->errorMessage()); + return; + } - _context->accountBuilder().setServerUrl(resolvedUrl, qvariant_cast(authTypeJob->result())); - Q_EMIT evaluationSuccessful(); - }); + _context->accountBuilder().setServerUrl(resolvedUrl, qvariant_cast(authTypeJob->result())); + Q_EMIT evaluationSuccessful(); + }); + } }); connect( diff --git a/src/gui/newwizard/states/webfingersetupwizardstate.cpp b/src/gui/newwizard/states/webfingersetupwizardstate.cpp new file mode 100644 index 00000000000..48a7b0dac33 --- /dev/null +++ b/src/gui/newwizard/states/webfingersetupwizardstate.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "webfingersetupwizardstate.h" +#include "creds/webfinger.h" +#include "determineauthtypejobfactory.h" +#include "pages/webfingersetupwizardpage.h" + +namespace OCC::Wizard { + +WebFingerSetupWizardState::WebFingerSetupWizardState(SetupWizardContext *context) + : AbstractSetupWizardState(context) +{ + _page = new WebFingerSetupWizardPage(_context->accountBuilder().webFingerServerUrl()); +} + +void WebFingerSetupWizardState::evaluatePage() +{ + auto *webFingerSetupWizardPage = qobject_cast(_page); + OC_ASSERT(webFingerSetupWizardPage != nullptr); + + const QString resource = QStringLiteral("acct:%1").arg(webFingerSetupWizardPage->username()); + + auto webFinger = new WebFinger(_context->accessManager(), this); + + connect(webFinger, &WebFinger::finished, this, [this, webFingerSetupWizardPage, webFinger]() { + if (webFinger->error().error != QJsonParseError::NoError) { + Q_EMIT evaluationFailed(tr("Failed to parse WebFinger response: %1").arg(webFinger->error().errorString())); + return; + } + + if (webFinger->href().isEmpty()) { + Q_EMIT evaluationFailed(tr("WebFinger endpoint did not send href attribute")); + return; + } + + qDebug() << "WebFinger server sent href" << webFinger->href(); + + // next, we need to find out which kind of authentication page we have to present to the user + auto authTypeJob = DetermineAuthTypeJobFactory(_context->accessManager(), this).startJob(webFinger->href()); + + connect(authTypeJob, &CoreJob::finished, authTypeJob, [this, authTypeJob, webFingerSetupWizardPage, webFinger]() { + authTypeJob->deleteLater(); + + if (authTypeJob->result().isNull()) { + Q_EMIT evaluationFailed(authTypeJob->errorMessage()); + return; + } + + _context->accountBuilder().setWebFingerUsername(webFingerSetupWizardPage->username()); + _context->accountBuilder().setServerUrl(webFinger->href(), qvariant_cast(authTypeJob->result())); + Q_EMIT evaluationSuccessful(); + }); + }); + + webFinger->start(_context->accountBuilder().webFingerServerUrl(), resource); +} + +SetupWizardState WebFingerSetupWizardState::state() const +{ + return SetupWizardState::WebFingerState; +} + +} // OCC::Wizard diff --git a/src/gui/newwizard/states/webfingersetupwizardstate.h b/src/gui/newwizard/states/webfingersetupwizardstate.h new file mode 100644 index 00000000000..c09b4d41606 --- /dev/null +++ b/src/gui/newwizard/states/webfingersetupwizardstate.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "abstractsetupwizardstate.h" + +namespace OCC::Wizard { + +class WebFingerSetupWizardState : public AbstractSetupWizardState +{ + Q_OBJECT + +public: + WebFingerSetupWizardState(SetupWizardContext *context); + + [[nodiscard]] SetupWizardState state() const override; + + void evaluatePage() override; +}; + +} // OCC::Wizard diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index ee260420c8f..7d7e0d1facd 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -50,6 +50,7 @@ set(libsync_SRCS creds/abstractcredentials.cpp creds/credentialscommon.cpp creds/oauth.cpp + creds/webfinger.cpp networkjobs/fetchuserinfojobfactory.cpp networkjobs/checkserverjobfactory.cpp diff --git a/src/libsync/creds/webfinger.cpp b/src/libsync/creds/webfinger.cpp new file mode 100644 index 00000000000..7a75d3d61e9 --- /dev/null +++ b/src/libsync/creds/webfinger.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "webfinger.h" + +#include "common/asserts.h" + +#include "httpcredentials.h" + +#include +#include +#include +#include +#include + + +Q_LOGGING_CATEGORY(lcWebFinger, "sync.credentials.webfinger", QtInfoMsg) + +namespace { + +auto relId() +{ + return QStringLiteral("http://webfinger.owncloud/rel/server-instance"); +} +} + +using namespace OCC; + +WebFinger::WebFinger(QNetworkAccessManager *nam, QObject *parent) + : QObject(parent) + , _nam(nam) +{ +} + +void WebFinger::start(const QUrl &url, const QString &resourceId) +{ + // GET /.well-known/webfinger?rel=http://webfinger.owncloud/rel/server-instance&resource=acct:test@owncloud.com HTTP/1.1 + if (OC_ENSURE(url.scheme() == QLatin1String("https"))) { + QUrlQuery query; + query.setQueryItems({ { QStringLiteral("resource"), QString::fromUtf8(QUrl::toPercentEncoding(QStringLiteral("acct:") + resourceId)) }, + { QStringLiteral("rel"), relId() } }); + + QNetworkRequest req; + req.setUrl(Utility::concatUrlPath(url, QStringLiteral(".well-known/webfinger"), query)); + req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true); + req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); + + auto *reply = _nam->get(req); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + const auto status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status == 200) { + const auto data = reply->readAll(); + auto obj = QJsonDocument::fromJson(data, &_error).object(); + if (_error.error == QJsonParseError::NoError) { + const auto links = obj.value(QLatin1String("links")).toArray(); + if (!links.empty()) { + _href = QUrl::fromEncoded(links.first().toObject().value(QLatin1String("href")).toString().toUtf8()); + qCInfo(lcWebFinger) << "Webfinger provided" << _href << "as server"; + } else { + qCWarning(lcWebFinger) << reply->url() << "Did not reply a valid link"; + } + } else { + qCWarning(lcWebFinger) << "Failed with" << _error.errorString(); + } + } else { + qCWarning(lcWebFinger) << "Failed with status code" << status; + _error.error = QJsonParseError::MissingObject; + } + Q_EMIT finished(); + }); + } else { + Q_EMIT finished(); + } +} + +const QJsonParseError &WebFinger::error() const +{ + return _error; +} + +const QUrl &WebFinger::href() const +{ + return _href; +} diff --git a/src/libsync/creds/webfinger.h b/src/libsync/creds/webfinger.h new file mode 100644 index 00000000000..22037ba3942 --- /dev/null +++ b/src/libsync/creds/webfinger.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "owncloudlib.h" + +#include "account.h" + +#include + + +namespace OCC { + +class OWNCLOUDSYNC_EXPORT WebFinger : public QObject +{ + Q_OBJECT +public: + WebFinger(QNetworkAccessManager *nam, QObject *parent = nullptr); + + void start(const QUrl &url, const QString &resourceId); + + const QJsonParseError &error() const; + + const QUrl &href() const; + +Q_SIGNALS: + void finished(); + +private: + QNetworkAccessManager *_nam; + QJsonParseError _error; + QUrl _href; +}; +} diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index c9d2c6f904e..0f182357927 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -686,6 +686,11 @@ bool Theme::allowDuplicatedFolderSyncPair() const return true; } +bool Theme::wizardEnableWebfinger() const +{ + return false; +} + template <> OWNCLOUDSYNC_EXPORT QString Utility::enumToDisplayName(Theme::UserIDType userIdType) { diff --git a/src/libsync/theme.h b/src/libsync/theme.h index 252cf118e15..2cb3aad8f32 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -449,6 +449,11 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject */ virtual bool allowDuplicatedFolderSyncPair() const; + /** + * Enable Webfinger page in setup wizard. + */ + virtual bool wizardEnableWebfinger() const; + protected: #ifndef TOKEN_AUTH_ONLY QIcon themeUniversalIcon(const QString &name, IconType iconType = IconType::BrandedIcon) const; diff --git a/src/resources/wizard/style.qss b/src/resources/wizard/style.qss index ad705c51753..63bf06c3968 100644 --- a/src/resources/wizard/style.qss +++ b/src/resources/wizard/style.qss @@ -26,6 +26,7 @@ #contentWidget #serverUrlLabel, #contentWidget #youreAllSetLabel, #contentWidget #enterYourCredentialsLabel, +#contentWidget #enterYourUsernameLabel, #contentWidget #urlLabel, #contentWidget #usernameLabel, #contentWidget #passwordLabel,