wizard/owncloudconnectionmethoddialog.ui
wizard/owncloudhttpcredspage.ui
wizard/owncloudoauthcredspage.ui
+ wizard/flow2authcredspage.ui
wizard/owncloudsetupnocredspage.ui
wizard/owncloudwizardresultpage.ui
wizard/webview.ui
creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp
creds/oauth.cpp
+ creds/flow2auth.cpp
creds/webflowcredentials.cpp
creds/webflowcredentialsdialog.cpp
wizard/postfixlineedit.cpp
wizard/owncloudconnectionmethoddialog.cpp
wizard/owncloudhttpcredspage.cpp
wizard/owncloudoauthcredspage.cpp
+ wizard/flow2authcredspage.cpp
wizard/owncloudsetuppage.cpp
wizard/owncloudwizardcommon.cpp
wizard/owncloudwizard.cpp
QObject::connect(configJob, &JsonApiJob::jsonReceived, _account.data(),
[=](const QJsonDocument &json) {
ocsConfigReceived(json, account);
+
+ /*int size = 0;
+ MessageBoxA(0, json.rawData(&size), "bla", MB_ICONINFORMATION);*/
});
configJob->start();
}
--- /dev/null
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
+ * Copyright (C) by Michael Schuster <michael@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 <QDesktopServices>
+//#include <QNetworkReply>
+#include <QTimer>
+#include <QBuffer>
+#include "account.h"
+#include "creds/flow2auth.h"
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include "theme.h"
+#include "networkjobs.h"
+#include "configfile.h"
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg)
+
+Flow2Auth::~Flow2Auth()
+{
+}
+
+void Flow2Auth::start()
+{
+ // Step 1: Initiate a login, do an anonymous POST request
+ QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
+
+ auto job = _account->sendRequest("POST", url);
+ job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
+ QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
+ auto jsonData = reply->readAll();
+ QJsonParseError jsonParseError;
+ QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
+ //MessageBoxA(0, jsonData.toStdString().c_str(), "Flow2Auth::start()", 0);
+
+ QString pollToken = json.value("poll").toObject().value("token").toString();
+ QString pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
+ QUrl loginUrl = json["login"].toString();
+ /*MessageBoxA(0, pollToken.toStdString().c_str(), "pollToken", 0);
+ MessageBoxA(0, pollEndpoint.toStdString().c_str(), "pollEndpoint", 0);
+ MessageBoxA(0, loginUrl.toString().toStdString().c_str(), "loginUrl", 0);*/
+
+ if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
+ || json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
+ QString errorReason;
+ QString errorFromJson = json["error"].toString();
+ if (!errorFromJson.isEmpty()) {
+ errorReason = tr("Error returned from the server: <em>%1</em>")
+ .arg(errorFromJson.toHtmlEscaped());
+ } else if (reply->error() != QNetworkReply::NoError) {
+ errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
+ .arg(reply->errorString().toHtmlEscaped());
+ } else if (jsonParseError.error != QJsonParseError::NoError) {
+ errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
+ .arg(jsonParseError.errorString());
+ } else {
+ errorReason = tr("The reply from the server did not contain all expected fields");
+ }
+ qCWarning(lcFlow2auth) << "Error when getting the loginUrl" << json << errorReason;
+ emit result(Error);
+ return;
+ }
+
+
+ _loginUrl = loginUrl;
+ _pollToken = pollToken;
+ _pollEndpoint = pollEndpoint;
+
+
+ ConfigFile cfg;
+ std::chrono::milliseconds polltime = cfg.remotePollInterval();
+ qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
+ _pollTimer.setInterval(polltime.count());
+ QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout);
+ _pollTimer.start();
+
+
+ if (!openBrowser())
+ return;
+
+
+ /*if (!_expectedUser.isNull() && user != _expectedUser) {
+ // Connected with the wrong user
+ QString message = tr("<h1>Wrong user</h1>"
+ "<p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>"
+ "Please log out of %3 in another tab, then <a href='%4'>click here</a> "
+ "and log in as user %2</p>")
+ .arg(user, _expectedUser, Theme::instance()->appNameGUI(),
+ authorisationLink().toString(QUrl::FullyEncoded));
+ httpReplyAndClose(socket, "200 OK", message.toUtf8().constData());
+ // We are still listening on the socket so we will get the new connection
+ return;
+ }
+ const char *loginSuccessfullHtml = "<h1>Login Successful</h1><p>You can close this window.</p>";
+ if (messageUrl.isValid()) {
+ httpReplyAndClose(socket, "303 See Other", loginSuccessfullHtml,
+ QByteArray("Location: " + messageUrl.toEncoded()).constData());
+ } else {
+ httpReplyAndClose(socket, "200 OK", loginSuccessfullHtml);
+ }
+ emit result(LoggedIn, user, accessToken, refreshToken);*/
+ });
+#if 0
+ return;
+
+
+ // Listen on the socket to get a port which will be used in the redirect_uri
+ if (!_server.listen(QHostAddress::LocalHost)) {
+ emit result(NotSupported, QString());
+ return;
+ }
+
+ if (!openBrowser())
+ return;
+
+ QObject::connect(&_server, &QTcpServer::newConnection, this, [this] {
+ while (QPointer<QTcpSocket> socket = _server.nextPendingConnection()) {
+ QObject::connect(socket.data(), &QTcpSocket::disconnected, socket.data(), &QTcpSocket::deleteLater);
+ QObject::connect(socket.data(), &QIODevice::readyRead, this, [this, socket] {
+ QByteArray peek = socket->peek(qMin(socket->bytesAvailable(), 4000LL)); //The code should always be within the first 4K
+ if (peek.indexOf('\n') < 0)
+ return; // wait until we find a \n
+ QRegExp rx("^GET /\\?code=([a-zA-Z0-9]+)[& ]"); // Match a /?code=... URL
+ if (rx.indexIn(peek) != 0) {
+ httpReplyAndClose(socket, "404 Not Found", "<html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</h1></center></body></html>");
+ return;
+ }
+
+ QString code = rx.cap(1); // The 'code' is the first capture of the regexp
+
+ QUrl requestToken = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/apps/oauth2/api/v1/token"));
+ QNetworkRequest req;
+ req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+
+ QString basicAuth = QString("%1:%2").arg(
+ Theme::instance()->oauthClientId(), Theme::instance()->oauthClientSecret());
+ req.setRawHeader("Authorization", "Basic " + basicAuth.toUtf8().toBase64());
+
+ auto requestBody = new QBuffer;
+ QUrlQuery arguments(QString(
+ "grant_type=authorization_code&code=%1&redirect_uri=http://localhost:%2")
+ .arg(code, QString::number(_server.serverPort())));
+ requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
+
+ auto job = _account->sendRequest("POST", requestToken, req, requestBody);
+ job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
+ QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, socket](QNetworkReply *reply) {
+ auto jsonData = reply->readAll();
+ QJsonParseError jsonParseError;
+ QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
+ QString accessToken = json["access_token"].toString();
+ QString refreshToken = json["refresh_token"].toString();
+ QString user = json["user_id"].toString();
+ QUrl messageUrl = json["message_url"].toString();
+
+ if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
+ || json.isEmpty() || refreshToken.isEmpty() || accessToken.isEmpty()
+ || json["token_type"].toString() != QLatin1String("Bearer")) {
+ QString errorReason;
+ QString errorFromJson = json["error"].toString();
+ if (!errorFromJson.isEmpty()) {
+ errorReason = tr("Error returned from the server: <em>%1</em>")
+ .arg(errorFromJson.toHtmlEscaped());
+ } else if (reply->error() != QNetworkReply::NoError) {
+ errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
+ .arg(reply->errorString().toHtmlEscaped());
+ } else if (jsonParseError.error != QJsonParseError::NoError) {
+ errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
+ .arg(jsonParseError.errorString());
+ } else {
+ errorReason = tr("The reply from the server did not contain all expected fields");
+ }
+ qCWarning(lcFlow2auth) << "Error when getting the accessToken" << json << errorReason;
+ httpReplyAndClose(socket, "500 Internal Server Error",
+ tr("<h1>Login Error</h1><p>%1</p>").arg(errorReason).toUtf8().constData());
+ emit result(Error);
+ return;
+ }
+ if (!_expectedUser.isNull() && user != _expectedUser) {
+ // Connected with the wrong user
+ QString message = tr("<h1>Wrong user</h1>"
+ "<p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>"
+ "Please log out of %3 in another tab, then <a href='%4'>click here</a> "
+ "and log in as user %2</p>")
+ .arg(user, _expectedUser, Theme::instance()->appNameGUI(),
+ authorisationLink().toString(QUrl::FullyEncoded));
+ httpReplyAndClose(socket, "200 OK", message.toUtf8().constData());
+ // We are still listening on the socket so we will get the new connection
+ return;
+ }
+ const char *loginSuccessfullHtml = "<h1>Login Successful</h1><p>You can close this window.</p>";
+ if (messageUrl.isValid()) {
+ httpReplyAndClose(socket, "303 See Other", loginSuccessfullHtml,
+ QByteArray("Location: " + messageUrl.toEncoded()).constData());
+ } else {
+ httpReplyAndClose(socket, "200 OK", loginSuccessfullHtml);
+ }
+ emit result(LoggedIn, user, accessToken, refreshToken);
+ });
+ });
+ }
+ });
+#endif
+}
+
+void Flow2Auth::slotPollTimerTimeout()
+{
+ _pollTimer.stop();
+
+// qCInfo(lcFlow2auth) << "reached poll timout";
+
+ // Step 2: Poll
+ QNetworkRequest req;
+ req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+
+ auto requestBody = new QBuffer;
+ QUrlQuery arguments(QString("token=%1").arg(_pollToken));
+ requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
+
+ 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;
+ QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
+
+ QUrl serverUrl = json["server"].toString();
+ QString loginName = json["loginName"].toString();
+ QString appPassword = json["appPassword"].toString();
+
+ if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
+ || json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
+ QString errorReason;
+ QString errorFromJson = json["error"].toString();
+ if (!errorFromJson.isEmpty()) {
+ errorReason = tr("Error returned from the server: <em>%1</em>")
+ .arg(errorFromJson.toHtmlEscaped());
+ } else if (reply->error() != QNetworkReply::NoError) {
+ errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
+ .arg(reply->errorString().toHtmlEscaped());
+ } else if (jsonParseError.error != QJsonParseError::NoError) {
+ errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
+ .arg(jsonParseError.errorString());
+ } else {
+ errorReason = tr("The reply from the server did not contain all expected fields");
+ }
+ qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
+// emit result(Error);
+ _pollTimer.start();
+ return;
+ }
+
+ qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << " on server: " << serverUrl;
+
+ _account->setUrl(serverUrl);
+
+ emit result(LoggedIn, loginName, appPassword);
+ });
+}
+
+QUrl Flow2Auth::authorisationLink() const
+{
+ return _loginUrl;
+ /*Q_ASSERT(_server.isListening());
+ QUrlQuery query;
+ query.setQueryItems({ { QLatin1String("response_type"), QLatin1String("code") },
+ { QLatin1String("client_id"), Theme::instance()->oauthClientId() },
+ { QLatin1String("redirect_uri"), QLatin1String("http://localhost:") + QString::number(_server.serverPort()) } });
+ if (!_expectedUser.isNull())
+ query.addQueryItem("user", _expectedUser);
+ QUrl url = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/authorize"), query);
+ return url;*/
+}
+
+bool Flow2Auth::openBrowser()
+{
+ if (!QDesktopServices::openUrl(authorisationLink())) {
+ // We cannot open the browser, then we claim we don't support OAuth.
+ emit result(NotSupported, QString());
+ return false;
+ }
+ return true;
+}
+
+} // namespace OCC
--- /dev/null
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
+ * Copyright (C) by Michael Schuster <michael@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.
+ */
+
+#pragma once
+#include <QPointer>
+#include <QUrl>
+#include <QTimer>
+#include "accountfwd.h"
+
+namespace OCC {
+
+/**
+ * Job that does the authorization grant and fetch the access token
+ *
+ * Normal workflow:
+ *
+ * --> start()
+ * |
+ * +----> openBrowser() open the browser to the login page, redirects to http://localhost:xxx
+ * |
+ * +----> _server starts listening on a TCP port waiting for an HTTP request with a 'code'
+ * |
+ * v
+ * request the access_token and the refresh_token via 'apps/oauth2/api/v1/token'
+ * |
+ * v
+ * emit result(...)
+ *
+ */
+class Flow2Auth : public QObject
+{
+ Q_OBJECT
+public:
+ Flow2Auth(Account *account, QObject *parent)
+ : QObject(parent)
+ , _account(account)
+ {
+ }
+ ~Flow2Auth();
+
+ enum Result { NotSupported,
+ LoggedIn,
+ Error };
+ Q_ENUM(Result);
+ void start();
+ bool openBrowser();
+ QUrl authorisationLink() const;
+
+signals:
+ /**
+ * The state has changed.
+ * when logged in, token has the value of the token.
+ */
+ void result(Flow2Auth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString());
+
+private slots:
+ void slotPollTimerTimeout();
+
+private:
+ Account *_account;
+ QUrl _loginUrl;
+ QString _pollToken;
+ QString _pollEndpoint;
+ QTimer _pollTimer;
+
+public:
+ QString _expectedUser;
+};
+
+
+} // namespace OCC
{
AccountPtr account = AccountManager::createAccount();
account->setCredentials(CredentialsFactory::create("dummy"));
- account->setUrl(Theme::instance()->overrideServerUrl());
+ account->setUrl(QUrl("https://127.0.0.1:8443/"));//Theme::instance()->overrideServerUrl());
_ocWizard->setAccount(account);
_ocWizard->setOCUrl(account->url().toString());
+// _ocWizard->setOCUrl("https://127.0.0.1:8443/"); //account->url().toString());
_remoteFolder = Theme::instance()->defaultServerFolder();
// remoteFolder may be empty, which means /
}
_ocWizard->show();
- if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds) {
+ if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds || _ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) {
_ocWizard->back();
}
_ocWizard->displayError(errorMsg, _ocWizard->currentId() == WizardCommon::Page_ServerSetup && checkDowngradeAdvised(reply));
--- /dev/null
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
+ * Copyright (C) by Michael Schuster <michael@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 <QVariant>
+#include <QMenu>
+#include <QClipboard>
+
+#include "wizard/flow2authcredspage.h"
+#include "theme.h"
+#include "account.h"
+#include "cookiejar.h"
+#include "wizard/owncloudwizardcommon.h"
+#include "wizard/owncloudwizard.h"
+#include "creds/httpcredentialsgui.h"
+#include "creds/credentialsfactory.h"
+
+namespace OCC {
+
+Flow2AuthCredsPage::Flow2AuthCredsPage()
+ : AbstractCredentialsWizardPage()
+{
+ _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);
+
+ setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI())));
+ setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser (Login Flow v2)")));
+
+ 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));
+ });
+}
+
+void Flow2AuthCredsPage::initializePage()
+{
+ OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
+ Q_ASSERT(ocWizard);
+ ocWizard->account()->setCredentials(CredentialsFactory::create("http"));
+ _asyncAuth.reset(new Flow2Auth(ocWizard->account().data(), this));
+ connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthCredsPage::asyncAuthResult, Qt::QueuedConnection);
+ _asyncAuth->start();
+ wizard()->hide();
+}
+
+void OCC::Flow2AuthCredsPage::cleanupPage()
+{
+ // The next or back button was activated, show the wizard again
+ wizard()->show();
+ _asyncAuth.reset();
+}
+
+void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &user,
+ const QString &token, const QString &refreshToken)
+{
+ switch (r) {
+ case Flow2Auth::NotSupported: {
+ /* Flow2Auth not supported (can't open browser), fallback to HTTP credentials */
+ OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
+ ocWizard->back();
+ ocWizard->setAuthType(DetermineAuthTypeJob::Basic);
+ break;
+ }
+ case Flow2Auth::Error:
+ /* Error while getting the access token. (Timeout, or the server did not accept our client credentials */
+ _ui.errorLabel->show();
+ wizard()->show();
+ break;
+ case Flow2Auth::LoggedIn: {
+ _token = token;
+ _user = user;
+ _refreshToken = refreshToken;
+ OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
+ Q_ASSERT(ocWizard);
+ emit connectToOCUrl(ocWizard->account()->url().toString());
+ break;
+ }
+ }
+}
+
+int Flow2AuthCredsPage::nextId() const
+{
+ return WizardCommon::Page_AdvancedSetup;
+}
+
+void Flow2AuthCredsPage::setConnected()
+{
+ wizard()->show();
+}
+
+AbstractCredentials *Flow2AuthCredsPage::getCredentials() const
+{
+ OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
+ Q_ASSERT(ocWizard);
+ return new HttpCredentialsGui(_user, _token, _refreshToken,
+ ocWizard->_clientSslCertificate, ocWizard->_clientSslKey);
+}
+
+bool Flow2AuthCredsPage::isComplete() const
+{
+ return false; /* We can never go forward manually */
+}
+
+} // namespace OCC
--- /dev/null
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
+ * Copyright (C) by Michael Schuster <michael@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.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QMap>
+#include <QNetworkCookie>
+#include <QUrl>
+#include <QPointer>
+
+#include "wizard/abstractcredswizardpage.h"
+#include "accountfwd.h"
+#include "creds/flow2auth.h"
+
+#include "ui_flow2authcredspage.h"
+
+
+namespace OCC {
+
+
+class Flow2AuthCredsPage : public AbstractCredentialsWizardPage
+{
+ Q_OBJECT
+public:
+ Flow2AuthCredsPage();
+
+ AbstractCredentials *getCredentials() const override;
+
+ void initializePage() override;
+ void cleanupPage() override;
+ int nextId() const override;
+ void setConnected();
+ bool isComplete() const override;
+
+public Q_SLOTS:
+ void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &token,
+ const QString &reniewToken);
+
+signals:
+ void connectToOCUrl(const QString &);
+
+public:
+ QString _user;
+ QString _token;
+ QString _refreshToken;
+ QScopedPointer<Flow2Auth> _asyncAuth;
+ Ui_Flow2AuthCredsPage _ui;
+};
+
+} // namespace OCC
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Flow2AuthCredsPage</class>
+ <widget class="QWidget" name="Flow2AuthCredsPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>424</width>
+ <height>373</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="topLabel">
+ <property name="text">
+ <string notr="true">TextLabel</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Please switch to your browser to proceed.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="errorLabel">
+ <property name="text">
+ <string>An error occurred while connecting. Please try again.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="openLinkButton">
+ <property name="text">
+ <string>Re-open Browser (or right-click to copy link)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>127</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="bottomLabel">
+ <property name="text">
+ <string notr="true">TextLabel</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
return WizardCommon::Page_HttpCreds;
case DetermineAuthTypeJob::OAuth:
return WizardCommon::Page_OAuthCreds;
+ case DetermineAuthTypeJob::LoginFlowV2:
+ return WizardCommon::Page_Flow2AuthCreds;
case DetermineAuthTypeJob::Shibboleth:
return WizardCommon::Page_ShibbolethCreds;
case DetermineAuthTypeJob::WebViewFlow:
#include "wizard/owncloudadvancedsetuppage.h"
#include "wizard/owncloudwizardresultpage.h"
#include "wizard/webviewpage.h"
+#include "wizard/flow2authcredspage.h"
#include "QProgressIndicator.h"
, _setupPage(new OwncloudSetupPage(this))
, _httpCredsPage(new OwncloudHttpCredsPage(this))
, _browserCredsPage(new OwncloudOAuthCredsPage)
+ , _flow2CredsPage(new Flow2AuthCredsPage)
#ifndef NO_SHIBBOLETH
, _shibbolethCredsPage(new OwncloudShibbolethCredsPage)
#endif
setPage(WizardCommon::Page_ServerSetup, _setupPage);
setPage(WizardCommon::Page_HttpCreds, _httpCredsPage);
setPage(WizardCommon::Page_OAuthCreds, _browserCredsPage);
+ setPage(WizardCommon::Page_Flow2AuthCreds, _flow2CredsPage);
#ifndef NO_SHIBBOLETH
setPage(WizardCommon::Page_ShibbolethCreds, _shibbolethCredsPage);
#endif
connect(_setupPage, &OwncloudSetupPage::determineAuthType, this, &OwncloudWizard::determineAuthType);
connect(_httpCredsPage, &OwncloudHttpCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
connect(_browserCredsPage, &OwncloudOAuthCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
+ connect(_flow2CredsPage, &Flow2AuthCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
#ifndef NO_SHIBBOLETH
connect(_shibbolethCredsPage, &OwncloudShibbolethCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
#endif
return url;
}
-bool OwncloudWizard::registration() {
+bool OwncloudWizard::registration()
+{
return _registration;
}
-void OwncloudWizard::setRegistration(bool registration) {
+void OwncloudWizard::setRegistration(bool registration)
+{
_registration = registration;
}
_browserCredsPage->setConnected();
break;
+ case WizardCommon::Page_Flow2AuthCreds:
+ _flow2CredsPage->setConnected();
+ break;
+
#ifndef NO_SHIBBOLETH
case WizardCommon::Page_ShibbolethCreds:
_shibbolethCredsPage->setConnected();
#endif
if (type == DetermineAuthTypeJob::OAuth) {
_credentialsPage = _browserCredsPage;
+ } else if (type == DetermineAuthTypeJob::LoginFlowV2) {
+ _credentialsPage = _flow2CredsPage;
} else if (type == DetermineAuthTypeJob::WebViewFlow) {
_credentialsPage = _webViewPage;
} else { // try Basic auth even for "Unknown"
}
setOption(QWizard::HaveCustomButton1, id == WizardCommon::Page_AdvancedSetup);
- if (id == WizardCommon::Page_AdvancedSetup && _credentialsPage == _browserCredsPage) {
+ if (id == WizardCommon::Page_AdvancedSetup && (_credentialsPage == _browserCredsPage || _credentialsPage == _flow2CredsPage)) {
// For OAuth, disable the back button in the Page_AdvancedSetup because we don't want
// to re-open the browser.
button(QWizard::BackButton)->setEnabled(false);
class AbstractCredentials;
class AbstractCredentialsWizardPage;
class WebViewPage;
+class Flow2AuthCredsPage;
/**
* @brief The OwncloudWizard class
OwncloudWizardResultPage *_resultPage;
AbstractCredentialsWizardPage *_credentialsPage;
WebViewPage *_webViewPage;
+ Flow2AuthCredsPage *_flow2CredsPage;
QStringList _setupLog;
Page_HttpCreds,
Page_ShibbolethCreds,
Page_OAuthCreds,
+ Page_Flow2AuthCreds,
Page_WebView,
Page_AdvancedSetup,
Page_Result
result = WebViewFlow;
}
+ // LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
+ if (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0)) {
+ result = LoginFlowV2;
+ }
+
qCInfo(lcDetermineAuthTypeJob) << "Auth type for" << _account->davUrl() << "is" << result;
emit authType(result);
deleteLater();
Basic, // also the catch-all fallback for backwards compatibility reasons
OAuth,
Shibboleth,
- WebViewFlow
+ WebViewFlow,
+ LoginFlowV2
};
explicit DetermineAuthTypeJob(AccountPtr account, QObject *parent = nullptr);