177 lines
6.9 KiB
C++
177 lines
6.9 KiB
C++
/*
|
|
* 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 <QTimer>
|
|
#include <QBuffer>
|
|
#include "account.h"
|
|
#include "creds/flow2auth.h"
|
|
#include <QJsonObject>
|
|
#include <QJsonDocument>
|
|
#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()
|
|
{
|
|
// Note: All startup code is in openBrowser() to allow reinitiate a new request with
|
|
// fresh tokens. Opening the same pollEndpoint link twice triggers an expiration
|
|
// message by the server (security, intended design).
|
|
openBrowser();
|
|
}
|
|
|
|
QUrl Flow2Auth::authorisationLink() const
|
|
{
|
|
return _loginUrl;
|
|
}
|
|
|
|
void Flow2Auth::openBrowser()
|
|
{
|
|
_pollTimer.stop();
|
|
|
|
// 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();
|
|
|
|
QString pollToken = json.value("poll").toObject().value("token").toString();
|
|
QString pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
|
|
QUrl loginUrl = json["login"].toString();
|
|
|
|
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;
|
|
|
|
|
|
// Start polling
|
|
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();
|
|
|
|
|
|
// Try to open Browser
|
|
if (!QDesktopServices::openUrl(authorisationLink())) {
|
|
// We cannot open the browser, then we claim we don't support Flow2Auth.
|
|
// Our UI callee should ask the user to copy and open the link.
|
|
emit result(NotSupported, QString());
|
|
}
|
|
});
|
|
}
|
|
|
|
void Flow2Auth::slotPollTimerTimeout()
|
|
{
|
|
_pollTimer.stop();
|
|
|
|
// 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;
|
|
|
|
// Forget sensitive data
|
|
appPassword.clear();
|
|
loginName.clear();
|
|
|
|
// Failed: poll again
|
|
_pollTimer.start();
|
|
return;
|
|
}
|
|
|
|
// Success
|
|
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
|
|
|
|
_account->setUrl(serverUrl);
|
|
|
|
emit result(LoggedIn, loginName, appPassword);
|
|
|
|
// Forget sensitive data
|
|
appPassword.clear();
|
|
loginName.clear();
|
|
});
|
|
}
|
|
|
|
} // namespace OCC
|