Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 88 additions & 82 deletions src/gui/creds/flow2auth.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
* Copyright (C) by Michael Schuster <michael@schuster.ms>
Expand All @@ -13,7 +13,7 @@
* for more details.
*/

#include <QDesktopServices>

Check failure on line 16 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:16:10 [clang-diagnostic-error]

'QDesktopServices' file not found
#include <QApplication>
#include <QClipboard>
#include <QTimer>
Expand Down Expand Up @@ -67,73 +67,48 @@

void Flow2Auth::fetchNewToken(const TokenAction action)
{
if(_isBusy)
if (_isBusy) {
return;
}

_isBusy = true;
_hasToken = false;

emit statusChanged(PollStatus::statusFetchToken, 0);

// Step 1: Initiate a login, do an anonymous POST request
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
_enforceHttps = url.scheme() == QStringLiteral("https");
const auto loginV2url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
_enforceHttps = loginV2url.scheme() == QStringLiteral("https");

// add 'Content-Length: 0' header (see https://github.com/nextcloud/desktop/issues/1473)
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentLengthHeader, "0");
req.setHeader(QNetworkRequest::UserAgentHeader, Utility::friendlyUserAgentString());
QNetworkRequest request;

Check warning on line 84 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:84:21 [cppcoreguidelines-init-variables]

variable 'request' is not initialized
request.setHeader(QNetworkRequest::ContentLengthHeader, "0");
request.setHeader(QNetworkRequest::UserAgentHeader, Utility::friendlyUserAgentString());

auto job = _account->sendRequest("POST", url, req);
auto job = _account->sendRequest("POST", loginV2url, request);
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));

QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, action](QNetworkReply *reply) {
auto jsonData = reply->readAll();
QJsonParseError jsonParseError{};
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
const auto json = handleResponse(reply);
QString pollToken, pollEndpoint, loginUrl;

if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
&& !json.isEmpty()) {
if (!json.isEmpty()) {
pollToken = json.value("poll").toObject().value("token").toString();
pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
if (_enforceHttps && QUrl(pollEndpoint).scheme() != QStringLiteral("https")) {
qCWarning(lcFlow2auth) << "Can not poll endpoint because the returned url" << pollEndpoint << "does not start with https";
emit result(Error, tr("The polling URL does not start with HTTPS despite the login URL started with HTTPS. Login will not be possible because this might be a security issue. Please contact your administrator."));
return;
}
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, errorReason);
if (json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
_pollTimer.stop();
_isBusy = false;
return;
}


_loginUrl = loginUrl;

if (_account->isUsernamePrefillSupported()) {
const auto userName = Utility::getCurrentUserName();
if (!userName.isEmpty()) {
if (const auto userName = Utility::getCurrentUserName();
!userName.isEmpty()) {
auto query = QUrlQuery(_loginUrl);
query.addQueryItem(QStringLiteral("user"), userName);
_loginUrl.setQuery(query);
Expand All @@ -143,22 +118,18 @@
_pollToken = pollToken;
_pollEndpoint = pollEndpoint;


// Start polling
ConfigFile cfg;
std::chrono::milliseconds polltime = cfg.remotePollInterval();
std::chrono::milliseconds polltime = ConfigFile().remotePollInterval();
qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
_secondsInterval = (polltime.count() / 1000);
_secondsLeft = _secondsInterval;
emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft);

if(!_pollTimer.isActive()) {
if (!_pollTimer.isActive()) {
_pollTimer.start();
}


switch(action)
{
switch (action) {
case actionOpenBrowser:
// Try to open Browser
if (!Utility::openBrowser(authorisationLink())) {
Expand All @@ -180,8 +151,9 @@

void Flow2Auth::slotPollTimerTimeout()
{
if(_isBusy || !_hasToken)
if (_isBusy || !_hasToken) {
return;
}

_isBusy = true;

Expand All @@ -194,57 +166,28 @@
emit statusChanged(PollStatus::statusPollNow, 0);

// Step 2: Poll
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkRequest request;

Check warning on line 169 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:169:21 [cppcoreguidelines-init-variables]

variable 'request' is not initialized
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");

auto requestBody = new QBuffer;
QUrlQuery arguments(QStringLiteral("token=%1").arg(_pollToken));
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());

auto job = _account->sendRequest("POST", _pollEndpoint, req, requestBody);
auto job = _account->sendRequest("POST", _pollEndpoint, request, 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();
const QJsonObject json = handleResponse(reply);
QUrl serverUrl;
QString loginName, appPassword;

if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
&& !json.isEmpty()) {
if (!json.isEmpty()) {
serverUrl = json["server"].toString();
if (_enforceHttps && serverUrl.scheme() != QStringLiteral("https")) {
qCWarning(lcFlow2auth) << "Returned server url" << serverUrl << "does not start with https";
emit result(Error, tr("The returned server URL does not start with HTTPS despite the login URL started with HTTPS. Login will not be possible because this might be a security issue. Please contact your administrator."));
return;
}
loginName = json["loginName"].toString();
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;

// We get a 404 until authentication is done, so don't show this error in the GUI.
if(reply->error() != QNetworkReply::ContentNotFoundError)
emit result(Error, errorReason);

if (json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
// Forget sensitive data
appPassword.clear();
loginName.clear();
Expand Down Expand Up @@ -277,11 +220,74 @@
});
}

QJsonObject Flow2Auth::handleResponse(QNetworkReply *reply)

Check warning on line 223 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:223:24 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
const auto jsonData = reply->readAll();
QJsonParseError jsonParseError{};

Check warning on line 226 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:226:21 [cppcoreguidelines-init-variables]

variable 'jsonParseError' is not initialized
const auto json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
&& !json.isEmpty()) {
const auto isHttps = [&]() {
const auto endpoint = json["server"].toString().isEmpty()
? json.value("poll").toObject().value("endpoint").toString() //from login/v2 endpoint
: json["server"].toString(); //from login/v2/poll endpoint

if (endpoint.isEmpty()) {
return false;
}

qCDebug(lcFlow2auth) << "Server url returned is" << endpoint;
if (QUrl(endpoint).scheme() != QStringLiteral("https")) {
return false;
}

return true;
};

if (_enforceHttps && !isHttps()) {
qCWarning(lcFlow2auth) << "Returned server url | poll endpoint does not start with https";
emit result(Error, tr("The returned server URL does not start with HTTPS despite the login URL started with HTTPS. "

Check warning on line 249 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:249:18 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
"Login will not be possible because this might be a security issue. Please contact your administrator."));
return {};
}
}

if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError) {
QString errorReason;

Check warning on line 256 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:256:17 [cppcoreguidelines-init-variables]

variable 'errorReason' is not initialized
if (const QString errorFromJson = json["error"].toString();

Check warning on line 257 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:257:27 [cppcoreguidelines-init-variables]

variable 'errorFromJson' is not initialized
!errorFromJson.isEmpty()) {

Check warning on line 258 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:258:39 [bugprone-branch-clone]

repeated branch in conditional chain
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 if (json.isEmpty()) {
errorReason = tr("The reply from the server did not contain all expected fields: <br><em>%1</em>")
.arg(jsonParseError.errorString());
}

qCDebug(lcFlow2auth) << "Error when requesting:" << reply->url() << "- json returned:" << json << "- error:" << errorReason;

// We get a 404 until authentication is done, so don't show this error in the GUI.
if (reply->error() != QNetworkReply::ContentNotFoundError) {
emit result(Error, errorReason);

Check warning on line 276 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:276:18 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
}

return {};
}

return json;
}

void Flow2Auth::slotPollNow()

Check warning on line 285 in src/gui/creds/flow2auth.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.cpp:285:17 [readability-make-member-function-const]

method 'slotPollNow' can be made const
{
// poll now if we're not already doing so
if(_isBusy || !_hasToken)
if (_isBusy || !_hasToken) {
return;
}

_secondsLeft = 1;
slotPollTimerTimeout();
Expand Down
3 changes: 3 additions & 0 deletions src/gui/creds/flow2auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
*/

#pragma once
#include <QPointer>

Check failure on line 17 in src/gui/creds/flow2auth.h

View workflow job for this annotation

GitHub Actions / build

src/gui/creds/flow2auth.h:17:10 [clang-diagnostic-error]

'QPointer' file not found
#include <QUrl>
#include <QTimer>
#include "accountfwd.h"

class QNetworkReply;

namespace OCC {

/**
Expand Down Expand Up @@ -71,6 +73,7 @@

private:
void fetchNewToken(const TokenAction action);
[[nodiscard]] QJsonObject handleResponse(QNetworkReply *reply);

Account *_account;
QUrl _loginUrl;
Expand Down
Loading