mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-28 11:48:56 +03:00
Login Flow V2: 1st test-implementation
Signed-off-by: Michael Schuster <michael@schuster.ms>
This commit is contained in:
parent
9941d49579
commit
2742411abd
14 changed files with 703 additions and 6 deletions
|
@ -38,6 +38,7 @@ set(client_UI_SRCS
|
|||
wizard/owncloudconnectionmethoddialog.ui
|
||||
wizard/owncloudhttpcredspage.ui
|
||||
wizard/owncloudoauthcredspage.ui
|
||||
wizard/flow2authcredspage.ui
|
||||
wizard/owncloudsetupnocredspage.ui
|
||||
wizard/owncloudwizardresultpage.ui
|
||||
wizard/webview.ui
|
||||
|
@ -103,6 +104,7 @@ set(client_SRCS
|
|||
creds/credentialsfactory.cpp
|
||||
creds/httpcredentialsgui.cpp
|
||||
creds/oauth.cpp
|
||||
creds/flow2auth.cpp
|
||||
creds/webflowcredentials.cpp
|
||||
creds/webflowcredentialsdialog.cpp
|
||||
wizard/postfixlineedit.cpp
|
||||
|
@ -111,6 +113,7 @@ set(client_SRCS
|
|||
wizard/owncloudconnectionmethoddialog.cpp
|
||||
wizard/owncloudhttpcredspage.cpp
|
||||
wizard/owncloudoauthcredspage.cpp
|
||||
wizard/flow2authcredspage.cpp
|
||||
wizard/owncloudsetuppage.cpp
|
||||
wizard/owncloudwizardcommon.cpp
|
||||
wizard/owncloudwizard.cpp
|
||||
|
|
|
@ -233,6 +233,9 @@ void ConnectionValidator::checkServerCapabilities()
|
|||
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();
|
||||
}
|
||||
|
|
299
src/gui/creds/flow2auth.cpp
Normal file
299
src/gui/creds/flow2auth.cpp
Normal file
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* 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
|
83
src/gui/creds/flow2auth.h
Normal file
83
src/gui/creds/flow2auth.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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
|
|
@ -105,9 +105,10 @@ void OwncloudSetupWizard::startWizard()
|
|||
{
|
||||
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 /
|
||||
|
@ -408,7 +409,7 @@ void OwncloudSetupWizard::slotAuthError()
|
|||
}
|
||||
|
||||
_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));
|
||||
|
|
135
src/gui/wizard/flow2authcredspage.cpp
Normal file
135
src/gui/wizard/flow2authcredspage.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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
|
63
src/gui/wizard/flow2authcredspage.h
Normal file
63
src/gui/wizard/flow2authcredspage.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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
|
87
src/gui/wizard/flow2authcredspage.ui
Normal file
87
src/gui/wizard/flow2authcredspage.ui
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?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>
|
|
@ -258,6 +258,8 @@ int OwncloudSetupPage::nextId() const
|
|||
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:
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "wizard/owncloudadvancedsetuppage.h"
|
||||
#include "wizard/owncloudwizardresultpage.h"
|
||||
#include "wizard/webviewpage.h"
|
||||
#include "wizard/flow2authcredspage.h"
|
||||
|
||||
#include "QProgressIndicator.h"
|
||||
|
||||
|
@ -45,6 +46,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
|
|||
, _setupPage(new OwncloudSetupPage(this))
|
||||
, _httpCredsPage(new OwncloudHttpCredsPage(this))
|
||||
, _browserCredsPage(new OwncloudOAuthCredsPage)
|
||||
, _flow2CredsPage(new Flow2AuthCredsPage)
|
||||
#ifndef NO_SHIBBOLETH
|
||||
, _shibbolethCredsPage(new OwncloudShibbolethCredsPage)
|
||||
#endif
|
||||
|
@ -59,6 +61,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
|
|||
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
|
||||
|
@ -76,6 +79,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
|
|||
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
|
||||
|
@ -129,11 +133,13 @@ QString OwncloudWizard::ocUrl() const
|
|||
return url;
|
||||
}
|
||||
|
||||
bool OwncloudWizard::registration() {
|
||||
bool OwncloudWizard::registration()
|
||||
{
|
||||
return _registration;
|
||||
}
|
||||
|
||||
void OwncloudWizard::setRegistration(bool registration) {
|
||||
void OwncloudWizard::setRegistration(bool registration)
|
||||
{
|
||||
_registration = registration;
|
||||
}
|
||||
|
||||
|
@ -162,6 +168,10 @@ void OwncloudWizard::successfulStep()
|
|||
_browserCredsPage->setConnected();
|
||||
break;
|
||||
|
||||
case WizardCommon::Page_Flow2AuthCreds:
|
||||
_flow2CredsPage->setConnected();
|
||||
break;
|
||||
|
||||
#ifndef NO_SHIBBOLETH
|
||||
case WizardCommon::Page_ShibbolethCreds:
|
||||
_shibbolethCredsPage->setConnected();
|
||||
|
@ -195,6 +205,8 @@ void OwncloudWizard::setAuthType(DetermineAuthTypeJob::AuthType type)
|
|||
#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"
|
||||
|
@ -221,7 +233,7 @@ void OwncloudWizard::slotCurrentPageChanged(int id)
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -40,6 +40,7 @@ class OwncloudWizardResultPage;
|
|||
class AbstractCredentials;
|
||||
class AbstractCredentialsWizardPage;
|
||||
class WebViewPage;
|
||||
class Flow2AuthCredsPage;
|
||||
|
||||
/**
|
||||
* @brief The OwncloudWizard class
|
||||
|
@ -107,6 +108,7 @@ private:
|
|||
OwncloudWizardResultPage *_resultPage;
|
||||
AbstractCredentialsWizardPage *_credentialsPage;
|
||||
WebViewPage *_webViewPage;
|
||||
Flow2AuthCredsPage *_flow2CredsPage;
|
||||
|
||||
QStringList _setupLog;
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace WizardCommon {
|
|||
Page_HttpCreds,
|
||||
Page_ShibbolethCreds,
|
||||
Page_OAuthCreds,
|
||||
Page_Flow2AuthCreds,
|
||||
Page_WebView,
|
||||
Page_AdvancedSetup,
|
||||
Page_Result
|
||||
|
|
|
@ -925,6 +925,11 @@ void DetermineAuthTypeJob::checkBothDone()
|
|||
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();
|
||||
|
|
|
@ -412,7 +412,8 @@ public:
|
|||
Basic, // also the catch-all fallback for backwards compatibility reasons
|
||||
OAuth,
|
||||
Shibboleth,
|
||||
WebViewFlow
|
||||
WebViewFlow,
|
||||
LoginFlowV2
|
||||
};
|
||||
|
||||
explicit DetermineAuthTypeJob(AccountPtr account, QObject *parent = nullptr);
|
||||
|
|
Loading…
Reference in a new issue