mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-24 22:15:57 +03:00
Remove OAuth authentication method as it is now unused
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
This commit is contained in:
parent
9e74f5c508
commit
6cf04d660b
26 changed files with 30 additions and 1167 deletions
|
@ -48,7 +48,6 @@ set(client_UI_SRCS
|
|||
wizard/owncloudadvancedsetuppage.ui
|
||||
wizard/owncloudconnectionmethoddialog.ui
|
||||
wizard/owncloudhttpcredspage.ui
|
||||
wizard/owncloudoauthcredspage.ui
|
||||
wizard/owncloudsetupnocredspage.ui
|
||||
wizard/webview.ui
|
||||
wizard/welcomepage.ui
|
||||
|
@ -224,8 +223,6 @@ set(client_SRCS
|
|||
creds/credentialsfactory.cpp
|
||||
creds/httpcredentialsgui.h
|
||||
creds/httpcredentialsgui.cpp
|
||||
creds/oauth.h
|
||||
creds/oauth.cpp
|
||||
creds/flow2auth.h
|
||||
creds/flow2auth.cpp
|
||||
creds/webflowcredentials.h
|
||||
|
@ -242,8 +239,6 @@ set(client_SRCS
|
|||
wizard/owncloudconnectionmethoddialog.cpp
|
||||
wizard/owncloudhttpcredspage.h
|
||||
wizard/owncloudhttpcredspage.cpp
|
||||
wizard/owncloudoauthcredspage.h
|
||||
wizard/owncloudoauthcredspage.cpp
|
||||
wizard/flow2authcredspage.h
|
||||
wizard/flow2authcredspage.cpp
|
||||
wizard/flow2authwidget.h
|
||||
|
|
|
@ -1271,19 +1271,7 @@ void AccountSettings::slotAccountStateChanged()
|
|||
showConnectionLabel(tr("Signed out from %1.").arg(serverWithUser));
|
||||
break;
|
||||
case AccountState::AskingCredentials: {
|
||||
QUrl url;
|
||||
if (const auto cred = qobject_cast<HttpCredentialsGui *>(account->credentials())) {
|
||||
connect(cred, &HttpCredentialsGui::authorisationLinkChanged,
|
||||
this, &AccountSettings::slotAccountStateChanged, Qt::UniqueConnection);
|
||||
url = cred->authorisationLink();
|
||||
}
|
||||
if (url.isValid()) {
|
||||
showConnectionLabel(tr("Obtaining authorization from the browser. "
|
||||
"<a href='%1'>Click here</a> to re-open the browser.")
|
||||
.arg(url.toString(QUrl::FullyEncoded)));
|
||||
} else {
|
||||
showConnectionLabel(tr("Connecting to %1 …").arg(serverWithUser));
|
||||
}
|
||||
showConnectionLabel(tr("Connecting to %1 …").arg(serverWithUser));
|
||||
break;
|
||||
}
|
||||
case AccountState::NetworkError:
|
||||
|
|
|
@ -456,10 +456,6 @@ void AccountState::handleInvalidCredentials()
|
|||
if (account()->credentials()->ready()) {
|
||||
account()->credentials()->invalidateToken();
|
||||
}
|
||||
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
|
||||
if (creds->refreshAccessToken())
|
||||
return;
|
||||
}
|
||||
account()->credentials()->askFromUser();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,16 +46,7 @@ void HttpCredentialsGui::askFromUserAsync()
|
|||
// First, we will check what kind of auth we need.
|
||||
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||
QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
|
||||
if (type == DetermineAuthTypeJob::OAuth) {
|
||||
_asyncAuth.reset(new OAuth(_account, this));
|
||||
_asyncAuth->_expectedUser = _account->davUser();
|
||||
connect(_asyncAuth.data(), &OAuth::result,
|
||||
this, &HttpCredentialsGui::asyncAuthResult);
|
||||
connect(_asyncAuth.data(), &OAuth::destroyed,
|
||||
this, &HttpCredentialsGui::authorisationLinkChanged);
|
||||
_asyncAuth->start();
|
||||
emit authorisationLinkChanged();
|
||||
} else if (type == DetermineAuthTypeJob::Basic) {
|
||||
if (type == DetermineAuthTypeJob::Basic) {
|
||||
showDialog();
|
||||
} else {
|
||||
// Shibboleth?
|
||||
|
@ -66,32 +57,6 @@ void HttpCredentialsGui::askFromUserAsync()
|
|||
job->start();
|
||||
}
|
||||
|
||||
void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user,
|
||||
const QString &token, const QString &refreshToken)
|
||||
{
|
||||
switch (r) {
|
||||
case OAuth::NotSupported:
|
||||
showDialog();
|
||||
_asyncAuth.reset(nullptr);
|
||||
return;
|
||||
case OAuth::Error:
|
||||
_asyncAuth.reset(nullptr);
|
||||
emit asked();
|
||||
return;
|
||||
case OAuth::LoggedIn:
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT(_user == user); // ensured by _asyncAuth
|
||||
|
||||
_password = token;
|
||||
_refreshToken = refreshToken;
|
||||
_ready = true;
|
||||
persist();
|
||||
_asyncAuth.reset(nullptr);
|
||||
emit asked();
|
||||
}
|
||||
|
||||
void HttpCredentialsGui::showDialog()
|
||||
{
|
||||
QString msg = tr("Please enter %1 password:<br>"
|
||||
|
@ -128,7 +93,6 @@ void HttpCredentialsGui::showDialog()
|
|||
connect(dialog, &QDialog::finished, this, [this, dialog](int result) {
|
||||
if (result == QDialog::Accepted) {
|
||||
_password = dialog->textValue();
|
||||
_refreshToken.clear();
|
||||
_ready = true;
|
||||
persist();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
#pragma once
|
||||
#include "creds/httpcredentials.h"
|
||||
#include "creds/oauth.h"
|
||||
#include <QPointer>
|
||||
#include <QTcpServer>
|
||||
|
||||
|
@ -38,37 +37,13 @@ public:
|
|||
: HttpCredentials(user, password, clientCertBundle, clientCertPassword)
|
||||
{
|
||||
}
|
||||
HttpCredentialsGui(const QString &user, const QString &password, const QString &refreshToken,
|
||||
const QByteArray &clientCertBundle, const QByteArray &clientCertPassword)
|
||||
: HttpCredentials(user, password, clientCertBundle, clientCertPassword)
|
||||
{
|
||||
_refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will query the server and either uses OAuth via _asyncAuth->start()
|
||||
* or call showDialog to ask the password
|
||||
*/
|
||||
void askFromUser() override;
|
||||
/**
|
||||
* In case of oauth, return an URL to the link to open the browser.
|
||||
* An invalid URL otherwise
|
||||
*/
|
||||
[[nodiscard]] QUrl authorisationLink() const { return _asyncAuth ? _asyncAuth->authorisationLink() : QUrl(); }
|
||||
|
||||
|
||||
static QString requestAppPasswordText(const Account *account);
|
||||
private slots:
|
||||
void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken);
|
||||
void showDialog();
|
||||
void askFromUserAsync();
|
||||
|
||||
signals:
|
||||
void authorisationLinkChanged();
|
||||
|
||||
private:
|
||||
|
||||
QScopedPointer<OAuth, QScopedPointerObjectDeleteLater<OAuth>> _asyncAuth;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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/oauth.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include "theme.h"
|
||||
#include "networkjobs.h"
|
||||
#include "creds/httpcredentials.h"
|
||||
#include "guiutility.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcOauth, "nextcloud.sync.credentials.oauth", QtInfoMsg)
|
||||
|
||||
OAuth::~OAuth() = default;
|
||||
|
||||
static void httpReplyAndClose(QTcpSocket *socket, const char *code, const char *html,
|
||||
const char *moreHeaders = nullptr)
|
||||
{
|
||||
if (!socket)
|
||||
return; // socket can have been deleted if the browser was closed
|
||||
socket->write("HTTP/1.1 ");
|
||||
socket->write(code);
|
||||
socket->write("\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: ");
|
||||
socket->write(QByteArray::number(qstrlen(html)));
|
||||
if (moreHeaders) {
|
||||
socket->write("\r\n");
|
||||
socket->write(moreHeaders);
|
||||
}
|
||||
socket->write("\r\n\r\n");
|
||||
socket->write(html);
|
||||
socket->disconnectFromHost();
|
||||
// We don't want that deleting the server too early prevent queued data to be sent on this socket.
|
||||
// The socket will be deleted after disconnection because disconnected is connected to deleteLater
|
||||
socket->setParent(nullptr);
|
||||
}
|
||||
|
||||
void OAuth::start()
|
||||
{
|
||||
// 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
|
||||
static const QRegularExpression rx("^GET /\\?code=([a-zA-Z0-9]+)[& ]"); // Match a /?code=... URL
|
||||
const auto rxMatch = rx.match(peek);
|
||||
if (!rxMatch.hasMatch()) {
|
||||
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 = rxMatch.captured(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());
|
||||
// We just added the Authorization header, don't let HttpCredentialsAccessManager tamper with it
|
||||
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
||||
|
||||
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
|
||||
|| jsonData.isEmpty() || 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 (jsonData.isEmpty()) {
|
||||
// Can happen if a funky load balancer strips away POST data, e.g. BigIP APM my.policy
|
||||
errorReason = tr("Empty JSON from OAuth2 redirect");
|
||||
// We explicitly have this as error case since the json qcWarning output below is misleading,
|
||||
// it will show a fake json will null values that actually never was received like this as
|
||||
// soon as you access json["whatever"] the debug output json will claim to have "whatever":null
|
||||
} 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(lcOauth) << "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 account</h1>"
|
||||
"<p>You logged in with the account <em>%1</em>, but must log in with the account <em>%2</em>.<br>"
|
||||
"Please log out of %3 in another tab, then <a href='%4'>click here</a> "
|
||||
"and log in with %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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QUrl OAuth::authorisationLink() const
|
||||
{
|
||||
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 OAuth::openBrowser()
|
||||
{
|
||||
if (!Utility::openBrowser(authorisationLink())) {
|
||||
// We cannot open the browser, then we claim we don't support OAuth.
|
||||
emit result(NotSupported, QString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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 <QTcpServer>
|
||||
#include <QUrl>
|
||||
#include "accountfwd.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* Job that do 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 OAuth : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OAuth(Account *account, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _account(account)
|
||||
{
|
||||
}
|
||||
~OAuth() override;
|
||||
|
||||
enum Result { NotSupported,
|
||||
LoggedIn,
|
||||
Error };
|
||||
Q_ENUM(Result);
|
||||
void start();
|
||||
bool openBrowser();
|
||||
[[nodiscard]] QUrl authorisationLink() const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The state has changed.
|
||||
* when logged in, token has the value of the token.
|
||||
*/
|
||||
void result(OAuth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString());
|
||||
|
||||
private:
|
||||
Account *_account;
|
||||
QTcpServer _server;
|
||||
|
||||
public:
|
||||
QString _expectedUser;
|
||||
};
|
||||
|
||||
|
||||
} // namespace OCC
|
|
@ -146,7 +146,7 @@ void WebFlowCredentials::askFromUser() {
|
|||
// Do a DetermineAuthTypeJob to make sure that the server is still using Flow2
|
||||
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||
connect(job, &DetermineAuthTypeJob::authType, [this](DetermineAuthTypeJob::AuthType type) {
|
||||
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
|
||||
// LoginFlowV2 > WebViewFlow > Shib > Basic
|
||||
#ifdef WITH_WEBENGINE
|
||||
bool useFlow2 = (type != DetermineAuthTypeJob::WebViewFlow);
|
||||
#else // WITH_WEBENGINE
|
||||
|
|
|
@ -31,7 +31,6 @@ bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent)
|
|||
const QStringList allowedUrlSchemes = {
|
||||
"http",
|
||||
"https",
|
||||
"oauthtest"
|
||||
};
|
||||
|
||||
if (!allowedUrlSchemes.contains(url.scheme())) {
|
||||
|
|
|
@ -430,7 +430,7 @@ void OwncloudSetupWizard::slotAuthError()
|
|||
|
||||
// bring wizard to top
|
||||
_ocWizard->bringToTop();
|
||||
if (_ocWizard->currentId() == WizardCommon::Page_OAuthCreds || _ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) {
|
||||
if (_ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) {
|
||||
_ocWizard->back();
|
||||
}
|
||||
_ocWizard->displayError(errorMsg, _ocWizard->currentId() == WizardCommon::Page_ServerSetup && checkDowngradeAdvised(reply));
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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/owncloudoauthcredspage.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 {
|
||||
|
||||
OwncloudOAuthCredsPage::OwncloudOAuthCredsPage()
|
||||
: 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")));
|
||||
|
||||
connect(_ui.openLinkButton, &QCommandLinkButton::clicked, this, &OwncloudOAuthCredsPage::slotOpenBrowser);
|
||||
connect(_ui.copyLinkButton, &QCommandLinkButton::clicked, this, &OwncloudOAuthCredsPage::slotCopyLinkToClipboard);
|
||||
}
|
||||
|
||||
void OwncloudOAuthCredsPage::initializePage()
|
||||
{
|
||||
auto *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
Q_ASSERT(ocWizard);
|
||||
ocWizard->account()->setCredentials(CredentialsFactory::create("http"));
|
||||
_asyncAuth.reset(new OAuth(ocWizard->account().data(), this));
|
||||
connect(_asyncAuth.data(), &OAuth::result, this, &OwncloudOAuthCredsPage::asyncAuthResult, Qt::QueuedConnection);
|
||||
_asyncAuth->start();
|
||||
|
||||
// Don't hide the wizard (avoid user confusion)!
|
||||
//wizard()->hide();
|
||||
}
|
||||
|
||||
void OCC::OwncloudOAuthCredsPage::cleanupPage()
|
||||
{
|
||||
// The next or back button was activated, show the wizard again
|
||||
wizard()->show();
|
||||
_asyncAuth.reset();
|
||||
}
|
||||
|
||||
void OwncloudOAuthCredsPage::asyncAuthResult(OAuth::Result r, const QString &user,
|
||||
const QString &token, const QString &refreshToken)
|
||||
{
|
||||
switch (r) {
|
||||
case OAuth::NotSupported: {
|
||||
/* OAuth not supported (can't open browser), fallback to HTTP credentials */
|
||||
auto *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
ocWizard->back();
|
||||
ocWizard->setAuthType(DetermineAuthTypeJob::Basic);
|
||||
break;
|
||||
}
|
||||
case OAuth::Error:
|
||||
/* Error while getting the access token. (Timeout, or the server did not accept our client credentials */
|
||||
_ui.errorLabel->show();
|
||||
wizard()->show();
|
||||
break;
|
||||
case OAuth::LoggedIn: {
|
||||
_token = token;
|
||||
_user = user;
|
||||
_refreshToken = refreshToken;
|
||||
auto *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
Q_ASSERT(ocWizard);
|
||||
emit connectToOCUrl(ocWizard->account()->url().toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int OwncloudOAuthCredsPage::nextId() const
|
||||
{
|
||||
return WizardCommon::Page_AdvancedSetup;
|
||||
}
|
||||
|
||||
void OwncloudOAuthCredsPage::setConnected()
|
||||
{
|
||||
wizard()->show();
|
||||
}
|
||||
|
||||
AbstractCredentials *OwncloudOAuthCredsPage::getCredentials() const
|
||||
{
|
||||
auto *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
Q_ASSERT(ocWizard);
|
||||
return new HttpCredentialsGui(_user, _token, _refreshToken,
|
||||
ocWizard->_clientCertBundle, ocWizard->_clientCertPassword);
|
||||
}
|
||||
|
||||
bool OwncloudOAuthCredsPage::isComplete() const
|
||||
{
|
||||
return false; /* We can never go forward manually */
|
||||
}
|
||||
|
||||
void OwncloudOAuthCredsPage::slotOpenBrowser()
|
||||
{
|
||||
if (_ui.errorLabel)
|
||||
_ui.errorLabel->hide();
|
||||
|
||||
qobject_cast<OwncloudWizard *>(wizard())->account()->clearCookieJar(); // #6574
|
||||
|
||||
if (_asyncAuth)
|
||||
_asyncAuth->openBrowser();
|
||||
}
|
||||
|
||||
void OwncloudOAuthCredsPage::slotCopyLinkToClipboard()
|
||||
{
|
||||
if (_asyncAuth)
|
||||
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
|
||||
}
|
||||
|
||||
} // namespace OCC
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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/oauth.h"
|
||||
|
||||
#include "ui_owncloudoauthcredspage.h"
|
||||
|
||||
|
||||
namespace OCC {
|
||||
|
||||
|
||||
class OwncloudOAuthCredsPage : public AbstractCredentialsWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OwncloudOAuthCredsPage();
|
||||
|
||||
[[nodiscard]] AbstractCredentials *getCredentials() const override;
|
||||
|
||||
void initializePage() override;
|
||||
void cleanupPage() override;
|
||||
[[nodiscard]] int nextId() const override;
|
||||
void setConnected();
|
||||
[[nodiscard]] bool isComplete() const override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void asyncAuthResult(OAuth::Result, const QString &user, const QString &token,
|
||||
const QString &reniewToken);
|
||||
|
||||
signals:
|
||||
void connectToOCUrl(const QString &);
|
||||
|
||||
public:
|
||||
QString _user;
|
||||
QString _token;
|
||||
QString _refreshToken;
|
||||
QScopedPointer<OAuth> _asyncAuth;
|
||||
Ui_OwncloudOAuthCredsPage _ui{};
|
||||
|
||||
protected slots:
|
||||
void slotOpenBrowser();
|
||||
void slotCopyLinkToClipboard();
|
||||
};
|
||||
|
||||
} // namespace OCC
|
|
@ -1,100 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OwncloudOAuthCredsPage</class>
|
||||
<widget class="QWidget" name="OwncloudOAuthCredsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>424</width>
|
||||
<height>373</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">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</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCommandLinkButton" name="copyLinkButton">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>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>
|
|
@ -209,8 +209,6 @@ int OwncloudSetupPage::nextId() const
|
|||
switch (_authType) {
|
||||
case DetermineAuthTypeJob::Basic:
|
||||
return WizardCommon::Page_HttpCreds;
|
||||
case DetermineAuthTypeJob::OAuth:
|
||||
return WizardCommon::Page_OAuthCreds;
|
||||
case DetermineAuthTypeJob::LoginFlowV2:
|
||||
return WizardCommon::Page_Flow2AuthCreds;
|
||||
#ifdef WITH_WEBENGINE
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include "wizard/welcomepage.h"
|
||||
#include "wizard/owncloudsetuppage.h"
|
||||
#include "wizard/owncloudhttpcredspage.h"
|
||||
#include "wizard/owncloudoauthcredspage.h"
|
||||
#include "wizard/owncloudadvancedsetuppage.h"
|
||||
#include "wizard/webviewpage.h"
|
||||
#include "wizard/flow2authcredspage.h"
|
||||
|
@ -49,7 +48,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
|
|||
, _welcomePage(new WelcomePage(this))
|
||||
, _setupPage(new OwncloudSetupPage(this))
|
||||
, _httpCredsPage(new OwncloudHttpCredsPage(this))
|
||||
, _browserCredsPage(new OwncloudOAuthCredsPage)
|
||||
, _flow2CredsPage(new Flow2AuthCredsPage)
|
||||
, _advancedSetupPage(new OwncloudAdvancedSetupPage(this))
|
||||
#ifdef WITH_WEBENGINE
|
||||
|
@ -64,7 +62,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
|
|||
setPage(WizardCommon::Page_Welcome, _welcomePage);
|
||||
setPage(WizardCommon::Page_ServerSetup, _setupPage);
|
||||
setPage(WizardCommon::Page_HttpCreds, _httpCredsPage);
|
||||
setPage(WizardCommon::Page_OAuthCreds, _browserCredsPage);
|
||||
setPage(WizardCommon::Page_Flow2AuthCreds, _flow2CredsPage);
|
||||
setPage(WizardCommon::Page_AdvancedSetup, _advancedSetupPage);
|
||||
#ifdef WITH_WEBENGINE
|
||||
|
@ -79,7 +76,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
|
|||
connect(this, &QWizard::currentIdChanged, this, &OwncloudWizard::slotCurrentPageChanged);
|
||||
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);
|
||||
#ifdef WITH_WEBENGINE
|
||||
connect(_webViewPage, &WebViewPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
|
||||
|
@ -233,10 +229,6 @@ void OwncloudWizard::successfulStep()
|
|||
_httpCredsPage->setConnected();
|
||||
break;
|
||||
|
||||
case WizardCommon::Page_OAuthCreds:
|
||||
_browserCredsPage->setConnected();
|
||||
break;
|
||||
|
||||
case WizardCommon::Page_Flow2AuthCreds:
|
||||
_flow2CredsPage->setConnected();
|
||||
break;
|
||||
|
@ -280,9 +272,7 @@ void OwncloudWizard::setAuthType(DetermineAuthTypeJob::AuthType type)
|
|||
{
|
||||
_setupPage->setAuthType(type);
|
||||
|
||||
if (type == DetermineAuthTypeJob::OAuth) {
|
||||
_credentialsPage = _browserCredsPage;
|
||||
} else if (type == DetermineAuthTypeJob::LoginFlowV2) {
|
||||
if (type == DetermineAuthTypeJob::LoginFlowV2) {
|
||||
_credentialsPage = _flow2CredsPage;
|
||||
#ifdef WITH_WEBENGINE
|
||||
} else if (type == DetermineAuthTypeJob::WebViewFlow) {
|
||||
|
@ -329,8 +319,8 @@ void OwncloudWizard::slotCurrentPageChanged(int id)
|
|||
emit clearPendingRequests();
|
||||
}
|
||||
|
||||
if (id == WizardCommon::Page_AdvancedSetup && (_credentialsPage == _browserCredsPage || _credentialsPage == _flow2CredsPage)) {
|
||||
// For OAuth, disable the back button in the Page_AdvancedSetup because we don't want
|
||||
if (id == WizardCommon::Page_AdvancedSetup && _credentialsPage == _flow2CredsPage) {
|
||||
// Disable the back button in the Page_AdvancedSetup because we don't want
|
||||
// to re-open the browser.
|
||||
button(QWizard::BackButton)->setEnabled(false);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ Q_DECLARE_LOGGING_CATEGORY(lcWizard)
|
|||
class WelcomePage;
|
||||
class OwncloudSetupPage;
|
||||
class OwncloudHttpCredsPage;
|
||||
class OwncloudOAuthCredsPage;
|
||||
class OwncloudAdvancedSetupPage;
|
||||
class OwncloudWizardResultPage;
|
||||
class AbstractCredentials;
|
||||
|
@ -122,7 +121,6 @@ private:
|
|||
WelcomePage *_welcomePage;
|
||||
OwncloudSetupPage *_setupPage;
|
||||
OwncloudHttpCredsPage *_httpCredsPage;
|
||||
OwncloudOAuthCredsPage *_browserCredsPage;
|
||||
Flow2AuthCredsPage *_flow2CredsPage;
|
||||
OwncloudAdvancedSetupPage *_advancedSetupPage;
|
||||
OwncloudWizardResultPage *_resultPage = nullptr;
|
||||
|
|
|
@ -44,7 +44,6 @@ namespace WizardCommon {
|
|||
Page_Welcome,
|
||||
Page_ServerSetup,
|
||||
Page_HttpCreds,
|
||||
Page_OAuthCreds,
|
||||
Page_Flow2AuthCreds,
|
||||
#ifdef WITH_WEBENGINE
|
||||
Page_WebView,
|
||||
|
|
|
@ -39,7 +39,6 @@ Q_LOGGING_CATEGORY(lcHttpCredentials, "nextcloud.sync.credentials.http", QtInfoM
|
|||
|
||||
namespace {
|
||||
const char userC[] = "user";
|
||||
const char isOAuthC[] = "oauth";
|
||||
const char clientCertBundleC[] = "clientCertPkcs12";
|
||||
const char clientCertPasswordC[] = "_clientCertPassword";
|
||||
const char clientCertificatePEMC[] = "_clientCertificatePEM";
|
||||
|
@ -63,14 +62,10 @@ protected:
|
|||
QNetworkRequest req(request);
|
||||
if (!req.attribute(HttpCredentials::DontAddCredentialsAttribute).toBool()) {
|
||||
if (_cred && !_cred->password().isEmpty()) {
|
||||
if (_cred->isUsingOAuth()) {
|
||||
req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8());
|
||||
} else {
|
||||
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64();
|
||||
req.setRawHeader("Authorization", "Basic " + credHash);
|
||||
}
|
||||
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64();
|
||||
req.setRawHeader("Authorization", "Basic " + credHash);
|
||||
} else if (!request.url().password().isEmpty()) {
|
||||
// Typically the requests to get or refresh the OAuth access token. The client
|
||||
// Typically the requests to get or refresh the access token. The client
|
||||
// credentials are put in the URL from the code making the request.
|
||||
QByteArray credHash = request.url().userInfo().toUtf8().toBase64();
|
||||
req.setRawHeader("Authorization", "Basic " + credHash);
|
||||
|
@ -85,15 +80,7 @@ protected:
|
|||
req.setSslConfiguration(sslConfiguration);
|
||||
}
|
||||
|
||||
auto *reply = AccessManager::createRequest(op, req, outgoingData);
|
||||
|
||||
if (_cred->_isRenewingOAuthToken) {
|
||||
// We know this is going to fail, but we have no way to queue it there, so we will
|
||||
// simply restart the job after the failure.
|
||||
reply->setProperty(needRetryC, true);
|
||||
}
|
||||
|
||||
return reply;
|
||||
return AccessManager::createRequest(op, req, outgoingData);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -178,13 +165,6 @@ void HttpCredentials::fetchFromKeychain()
|
|||
// User must be fetched from config file
|
||||
fetchUser();
|
||||
|
||||
if (!_ready && !_refreshToken.isEmpty()) {
|
||||
// This happens if the credentials are still loaded from the keychain, but we are called
|
||||
// here because the auth is invalid, so this means we simply need to refresh the credentials
|
||||
refreshAccessToken();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ready) {
|
||||
Q_EMIT fetched();
|
||||
} else {
|
||||
|
@ -370,20 +350,13 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incoming)
|
|||
return;
|
||||
}
|
||||
|
||||
bool isOauth = _account->credentialSetting(QLatin1String(isOAuthC)).toBool();
|
||||
if (isOauth) {
|
||||
_refreshToken = job->textData();
|
||||
} else {
|
||||
_password = job->textData();
|
||||
}
|
||||
_password = job->textData();
|
||||
|
||||
if (_user.isEmpty()) {
|
||||
qCWarning(lcHttpCredentials) << "Strange: User is empty!";
|
||||
}
|
||||
|
||||
if (!_refreshToken.isEmpty() && error == QKeychain::NoError) {
|
||||
refreshAccessToken();
|
||||
} else if (!_password.isEmpty() && error == QKeychain::NoError) {
|
||||
if (!_password.isEmpty() && error == QKeychain::NoError) {
|
||||
// All cool, the keychain did not come back with error.
|
||||
// Still, the password can be empty which indicates a problem and
|
||||
// the password dialog has to be opened.
|
||||
|
@ -408,58 +381,6 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incoming)
|
|||
}
|
||||
}
|
||||
|
||||
bool HttpCredentials::refreshAccessToken()
|
||||
{
|
||||
if (_refreshToken.isEmpty())
|
||||
return false;
|
||||
|
||||
QUrl requestToken = Utility::concatUrlPath(_account->url(), 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());
|
||||
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
||||
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString("grant_type=refresh_token&refresh_token=%1").arg(_refreshToken));
|
||||
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](QNetworkReply *reply) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError{};
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
QString accessToken = json["access_token"].toString();
|
||||
if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) {
|
||||
// Invalid or empty JSON: Network error maybe?
|
||||
qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString();
|
||||
} else if (accessToken.isEmpty()) {
|
||||
// If the json was valid, but the reply did not contain an access token, the token
|
||||
// is considered expired. (Usually the HTTP reply code is 400)
|
||||
qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out";
|
||||
_refreshToken.clear();
|
||||
} else {
|
||||
_ready = true;
|
||||
_password = accessToken;
|
||||
_refreshToken = json["refresh_token"].toString();
|
||||
persist();
|
||||
}
|
||||
_isRenewingOAuthToken = false;
|
||||
for (const auto &job : qAsConst(_retryQueue)) {
|
||||
if (job)
|
||||
job->retry();
|
||||
}
|
||||
_retryQueue.clear();
|
||||
emit fetched();
|
||||
});
|
||||
_isRenewingOAuthToken = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void HttpCredentials::invalidateToken()
|
||||
{
|
||||
if (!_password.isEmpty()) {
|
||||
|
@ -480,12 +401,6 @@ void HttpCredentials::invalidateToken()
|
|||
// clear the session cookie.
|
||||
_account->clearCookieJar();
|
||||
|
||||
if (!_refreshToken.isEmpty()) {
|
||||
// Only invalidate the access_token (_password) but keep the _refreshToken in the keychain
|
||||
// (when coming from forgetSensitiveData, the _refreshToken is cleared)
|
||||
return;
|
||||
}
|
||||
|
||||
auto *job = new QKeychain::DeletePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(true);
|
||||
|
@ -502,9 +417,6 @@ void HttpCredentials::invalidateToken()
|
|||
|
||||
void HttpCredentials::forgetSensitiveData()
|
||||
{
|
||||
// need to be done before invalidateToken, so it actually deletes the refresh_token from the keychain
|
||||
_refreshToken.clear();
|
||||
|
||||
invalidateToken();
|
||||
_previousPassword.clear();
|
||||
}
|
||||
|
@ -517,7 +429,6 @@ void HttpCredentials::persist()
|
|||
}
|
||||
|
||||
_account->setCredentialSetting(QLatin1String(userC), _user);
|
||||
_account->setCredentialSetting(QLatin1String(isOAuthC), isUsingOAuth());
|
||||
if (!_clientCertBundle.isEmpty()) {
|
||||
// Note that the _clientCertBundle will often be cleared after usage,
|
||||
// it's just written if it gets passed into the constructor.
|
||||
|
@ -605,7 +516,7 @@ void HttpCredentials::slotWritePasswordToKeychain()
|
|||
job->setInsecureFallback(false);
|
||||
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotWriteJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
|
||||
job->setTextData(isUsingOAuth() ? _refreshToken : _password);
|
||||
job->setTextData(_password);
|
||||
job->start();
|
||||
}
|
||||
|
||||
|
@ -621,19 +532,12 @@ void HttpCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *a
|
|||
{
|
||||
if (!_ready)
|
||||
return;
|
||||
|
||||
Q_UNUSED(authenticator)
|
||||
// Because of issue #4326, we need to set the login and password manually at every requests
|
||||
// Thus, if we reach this signal, those credentials were invalid and we terminate.
|
||||
qCWarning(lcHttpCredentials) << "Stop request: Authentication failed for " << reply->url().toString();
|
||||
reply->setProperty(authenticationFailedC, true);
|
||||
|
||||
if (_isRenewingOAuthToken) {
|
||||
reply->setProperty(needRetryC, true);
|
||||
} else if (isUsingOAuth() && !reply->property(needRetryC).toBool()) {
|
||||
reply->setProperty(needRetryC, true);
|
||||
qCInfo(lcHttpCredentials) << "Refreshing token";
|
||||
refreshAccessToken();
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpCredentials::retryIfNeeded(AbstractNetworkJob *job)
|
||||
|
@ -641,11 +545,8 @@ bool HttpCredentials::retryIfNeeded(AbstractNetworkJob *job)
|
|||
auto *reply = job->reply();
|
||||
if (!reply || !reply->property(needRetryC).toBool())
|
||||
return false;
|
||||
if (_isRenewingOAuthToken) {
|
||||
_retryQueue.append(job);
|
||||
} else {
|
||||
job->retry();
|
||||
}
|
||||
|
||||
job->retry();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace OCC {
|
|||
|
||||
HttpCredentials is then split in HttpCredentials and HttpCredentialsGui.
|
||||
|
||||
This class handle both HTTP Basic Auth and OAuth. But anything that needs GUI to ask the user
|
||||
This class handles HTTP Basic Auth. But anything that needs GUI to ask the user
|
||||
is in HttpCredentialsGui.
|
||||
|
||||
The authentication mechanism looks like this.
|
||||
|
@ -52,13 +52,13 @@ namespace OCC {
|
|||
v }
|
||||
slotReadClientCertPEMJobDone } There are first 3 QtKeychain jobs to fetch
|
||||
| } the TLS client keys, if any, and the password
|
||||
v } (or refresh token
|
||||
v }
|
||||
slotReadClientKeyPEMJobDone }
|
||||
| }
|
||||
v
|
||||
slotReadJobDone
|
||||
| |
|
||||
| +-------> emit fetched() if OAuth is not used
|
||||
| +-------> emit fetched()
|
||||
|
|
||||
v
|
||||
refreshAccessToken()
|
||||
|
@ -97,17 +97,9 @@ public:
|
|||
QString fetchUser();
|
||||
virtual bool sslIsTrusted() { return false; }
|
||||
|
||||
/* If we still have a valid refresh token, try to refresh it asynchronously and emit fetched()
|
||||
* otherwise return false
|
||||
*/
|
||||
bool refreshAccessToken();
|
||||
|
||||
// To fetch the user name as early as possible
|
||||
void setAccount(Account *account) override;
|
||||
|
||||
// Whether we are using OAuth
|
||||
[[nodiscard]] bool isUsingOAuth() const { return !_refreshToken.isNull(); }
|
||||
|
||||
bool retryIfNeeded(AbstractNetworkJob *) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
|
@ -157,21 +149,18 @@ protected:
|
|||
bool unpackClientCertBundle();
|
||||
|
||||
QString _user;
|
||||
QString _password; // user's password, or access_token for OAuth
|
||||
QString _refreshToken; // OAuth _refreshToken, set if OAuth is used.
|
||||
QString _password; // user's password
|
||||
QString _previousPassword;
|
||||
|
||||
QString _fetchErrorString;
|
||||
bool _ready = false;
|
||||
bool _isRenewingOAuthToken = false;
|
||||
|
||||
QByteArray _clientCertBundle;
|
||||
QByteArray _clientCertPassword;
|
||||
QSslKey _clientSslKey;
|
||||
QSslCertificate _clientSslCertificate;
|
||||
bool _keychainMigration = false;
|
||||
bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
|
||||
|
||||
QVector<QPointer<AbstractNetworkJob>> _retryQueue; // Jobs we need to retry once the auth token is fetched
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1056,16 +1056,13 @@ void DetermineAuthTypeJob::start()
|
|||
});
|
||||
connect(propfind, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto authChallenge = reply->rawHeader("WWW-Authenticate").toLower();
|
||||
if (authChallenge.contains("bearer ")) {
|
||||
_resultPropfind = OAuth;
|
||||
|
||||
if (authChallenge.isEmpty()) {
|
||||
qCWarning(lcDetermineAuthTypeJob) << "Did not receive WWW-Authenticate reply to auth-test PROPFIND";
|
||||
} else {
|
||||
if (authChallenge.isEmpty()) {
|
||||
qCWarning(lcDetermineAuthTypeJob) << "Did not receive WWW-Authenticate reply to auth-test PROPFIND";
|
||||
} else {
|
||||
qCWarning(lcDetermineAuthTypeJob) << "Unknown WWW-Authenticate reply to auth-test PROPFIND:" << authChallenge;
|
||||
}
|
||||
_resultPropfind = Basic;
|
||||
qCWarning(lcDetermineAuthTypeJob) << "Unknown WWW-Authenticate reply to auth-test PROPFIND:" << authChallenge;
|
||||
}
|
||||
_resultPropfind = Basic;
|
||||
_propfindDone = true;
|
||||
checkAllDone();
|
||||
});
|
||||
|
@ -1111,13 +1108,13 @@ void DetermineAuthTypeJob::checkAllDone()
|
|||
auto result = _resultPropfind;
|
||||
|
||||
#ifdef WITH_WEBENGINE
|
||||
// WebViewFlow > OAuth > Basic
|
||||
// WebViewFlow > Basic
|
||||
if (_account->serverVersionInt() >= Account::makeServerVersion(12, 0, 0)) {
|
||||
result = WebViewFlow;
|
||||
}
|
||||
#endif // WITH_WEBENGINE
|
||||
|
||||
// LoginFlowV2 > WebViewFlow > OAuth > Basic
|
||||
// LoginFlowV2 > WebViewFlow > Basic
|
||||
if (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0)) {
|
||||
result = LoginFlowV2;
|
||||
}
|
||||
|
|
|
@ -523,7 +523,6 @@ public:
|
|||
WebViewFlow,
|
||||
#endif // WITH_WEBENGINE
|
||||
Basic, // also the catch-all fallback for backwards compatibility reasons
|
||||
OAuth,
|
||||
LoginFlowV2
|
||||
};
|
||||
Q_ENUM(AuthType)
|
||||
|
|
|
@ -163,7 +163,7 @@ void GETFileJob::slotMetaDataChanged()
|
|||
|
||||
if (httpStatus == 301 || httpStatus == 302 || httpStatus == 303 || httpStatus == 307
|
||||
|| httpStatus == 308 || httpStatus == 401) {
|
||||
// Redirects and auth failures (oauth token renew) are handled by AbstractNetworkJob and
|
||||
// Redirects and auth failures (token renew) are handled by AbstractNetworkJob and
|
||||
// will end up restarting the job. We do not want to process further data from the initial
|
||||
// request. newReplyHook() will reestablish signal connections for the follow-up request.
|
||||
bool ok = disconnect(reply(), &QNetworkReply::finished, this, &GETFileJob::slotReadyRead)
|
||||
|
|
|
@ -783,16 +783,6 @@ QString Theme::quotaBaseFolder() const
|
|||
return QLatin1String("/");
|
||||
}
|
||||
|
||||
QString Theme::oauthClientId() const
|
||||
{
|
||||
return "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69";
|
||||
}
|
||||
|
||||
QString Theme::oauthClientSecret() const
|
||||
{
|
||||
return "UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh";
|
||||
}
|
||||
|
||||
QString Theme::versionSwitchOutput() const
|
||||
{
|
||||
QString helpText;
|
||||
|
|
|
@ -466,13 +466,6 @@ public:
|
|||
*/
|
||||
[[nodiscard]] QString quotaBaseFolder() const;
|
||||
|
||||
/**
|
||||
* The OAuth client_id, secret pair.
|
||||
* Note that client that change these value cannot connect to un-branded owncloud servers.
|
||||
*/
|
||||
[[nodiscard]] QString oauthClientId() const;
|
||||
[[nodiscard]] QString oauthClientSecret() const;
|
||||
|
||||
/**
|
||||
* @brief What should be output for the --version command line switch.
|
||||
*
|
||||
|
|
|
@ -115,8 +115,6 @@ nextcloud_add_test(Account)
|
|||
nextcloud_add_test(FolderMan)
|
||||
nextcloud_add_test(RemoteWipe)
|
||||
|
||||
nextcloud_add_test(OAuth)
|
||||
|
||||
configure_file(test_journal.db "${PROJECT_BINARY_DIR}/bin/test_journal.db" COPYONLY)
|
||||
|
||||
find_package(CMocka)
|
||||
|
|
|
@ -1,337 +0,0 @@
|
|||
/*
|
||||
* This software is in the public domain, furnished "as is", without technical
|
||||
* support, and with no warranty, express or implied, as to its usefulness for
|
||||
* any purpose.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "gui/creds/oauth.h"
|
||||
#include "syncenginetestutils.h"
|
||||
#include "theme.h"
|
||||
#include "common/asserts.h"
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
class DesktopServiceHook : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void hooked(const QUrl &);
|
||||
public:
|
||||
DesktopServiceHook() { QDesktopServices::setUrlHandler("oauthtest", this, "hooked"); }
|
||||
};
|
||||
|
||||
static const QUrl sOAuthTestServer("oauthtest://someserver/owncloud");
|
||||
|
||||
|
||||
class FakePostReply : public QNetworkReply
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
std::unique_ptr<QIODevice> payload;
|
||||
bool aborted = false;
|
||||
bool redirectToPolicy = false;
|
||||
bool redirectToToken = false;
|
||||
|
||||
FakePostReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
|
||||
std::unique_ptr<QIODevice> payload_, QObject *parent)
|
||||
: QNetworkReply{parent}, payload{std::move(payload_)}
|
||||
{
|
||||
setRequest(request);
|
||||
setUrl(request.url());
|
||||
setOperation(op);
|
||||
open(QIODevice::ReadOnly);
|
||||
payload->open(QIODevice::ReadOnly);
|
||||
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
Q_INVOKABLE virtual void respond() {
|
||||
if (aborted) {
|
||||
setError(OperationCanceledError, "Operation Canceled");
|
||||
emit metaDataChanged();
|
||||
emit finished();
|
||||
return;
|
||||
} else if (redirectToPolicy) {
|
||||
setHeader(QNetworkRequest::LocationHeader, "/my.policy");
|
||||
setAttribute(QNetworkRequest::RedirectionTargetAttribute, "/my.policy");
|
||||
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 302); // 302 might or might not lose POST data in rfc
|
||||
setHeader(QNetworkRequest::ContentLengthHeader, 0);
|
||||
emit metaDataChanged();
|
||||
emit finished();
|
||||
return;
|
||||
} else if (redirectToToken) {
|
||||
// Redirect to self
|
||||
QVariant destination = QVariant(sOAuthTestServer.toString()+QLatin1String("/index.php/apps/oauth2/api/v1/token"));
|
||||
setHeader(QNetworkRequest::LocationHeader, destination);
|
||||
setAttribute(QNetworkRequest::RedirectionTargetAttribute, destination);
|
||||
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 307); // 307 explicitly in rfc says to not lose POST data
|
||||
setHeader(QNetworkRequest::ContentLengthHeader, 0);
|
||||
emit metaDataChanged();
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
setHeader(QNetworkRequest::ContentLengthHeader, payload->size());
|
||||
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
|
||||
emit metaDataChanged();
|
||||
if (bytesAvailable())
|
||||
emit readyRead();
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void abort() override {
|
||||
aborted = true;
|
||||
}
|
||||
[[nodiscard]] qint64 bytesAvailable() const override {
|
||||
if (aborted)
|
||||
return 0;
|
||||
return payload->bytesAvailable();
|
||||
}
|
||||
|
||||
qint64 readData(char *data, qint64 maxlen) override {
|
||||
return payload->read(data, maxlen);
|
||||
}
|
||||
};
|
||||
|
||||
// Reply with a small delay
|
||||
class SlowFakePostReply : public FakePostReply {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using FakePostReply::FakePostReply;
|
||||
void respond() override {
|
||||
// override of FakePostReply::respond, will call the real one with a delay.
|
||||
QTimer::singleShot(100, this, [this] { this->FakePostReply::respond(); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class OAuthTestCase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
DesktopServiceHook desktopServiceHook;
|
||||
public:
|
||||
enum State { StartState, BrowserOpened, TokenAsked, CustomState } state = StartState;
|
||||
Q_ENUM(State);
|
||||
bool replyToBrowserOk = false;
|
||||
bool gotAuthOk = false;
|
||||
[[nodiscard]] virtual bool done() const { return replyToBrowserOk && gotAuthOk; }
|
||||
|
||||
FakeQNAM *fakeQnam = nullptr;
|
||||
QNetworkAccessManager realQNAM;
|
||||
QPointer<QNetworkReply> browserReply = nullptr;
|
||||
QString code = generateEtag();
|
||||
OCC::AccountPtr account;
|
||||
|
||||
QScopedPointer<OAuth> oauth;
|
||||
|
||||
virtual void test() {
|
||||
fakeQnam = new FakeQNAM({});
|
||||
account = OCC::Account::create();
|
||||
account->setUrl(sOAuthTestServer);
|
||||
account->setCredentials(new FakeCredentials{fakeQnam});
|
||||
fakeQnam->setParent(this);
|
||||
fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
|
||||
ASSERT(device);
|
||||
ASSERT(device->bytesAvailable()>0); // OAuth2 always sends around POST data.
|
||||
return this->tokenReply(op, req);
|
||||
});
|
||||
|
||||
QObject::connect(&desktopServiceHook, &DesktopServiceHook::hooked,
|
||||
this, &OAuthTestCase::openBrowserHook);
|
||||
|
||||
oauth.reset(new OAuth(account.data(), nullptr));
|
||||
QObject::connect(oauth.data(), &OAuth::result, this, &OAuthTestCase::oauthResult);
|
||||
oauth->start();
|
||||
QTRY_VERIFY(done());
|
||||
}
|
||||
|
||||
virtual void openBrowserHook(const QUrl &url) {
|
||||
QCOMPARE(state, StartState);
|
||||
state = BrowserOpened;
|
||||
QCOMPARE(url.path(), QString(sOAuthTestServer.path() + "/index.php/apps/oauth2/authorize"));
|
||||
QVERIFY(url.toString().startsWith(sOAuthTestServer.toString()));
|
||||
QUrlQuery query(url);
|
||||
QCOMPARE(query.queryItemValue(QLatin1String("response_type")), QLatin1String("code"));
|
||||
QCOMPARE(query.queryItemValue(QLatin1String("client_id")), Theme::instance()->oauthClientId());
|
||||
QUrl redirectUri(query.queryItemValue(QLatin1String("redirect_uri")));
|
||||
QCOMPARE(redirectUri.host(), QLatin1String("localhost"));
|
||||
redirectUri.setQuery("code=" + code);
|
||||
createBrowserReply(QNetworkRequest(redirectUri));
|
||||
}
|
||||
|
||||
virtual QNetworkReply *createBrowserReply(const QNetworkRequest &request) {
|
||||
browserReply = realQNAM.get(request);
|
||||
QObject::connect(browserReply, &QNetworkReply::finished, this, &OAuthTestCase::browserReplyFinished);
|
||||
return browserReply;
|
||||
}
|
||||
|
||||
virtual void browserReplyFinished() {
|
||||
QCOMPARE(sender(), browserReply.data());
|
||||
QCOMPARE(state, TokenAsked);
|
||||
browserReply->deleteLater();
|
||||
QCOMPARE(browserReply->rawHeader("Location"), QByteArray("owncloud://success"));
|
||||
replyToBrowserOk = true;
|
||||
};
|
||||
|
||||
virtual QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req)
|
||||
{
|
||||
ASSERT(state == BrowserOpened);
|
||||
state = TokenAsked;
|
||||
ASSERT(op == QNetworkAccessManager::PostOperation);
|
||||
ASSERT(req.url().toString().startsWith(sOAuthTestServer.toString()));
|
||||
ASSERT(req.url().path() == sOAuthTestServer.path() + "/index.php/apps/oauth2/api/v1/token");
|
||||
std::unique_ptr<QBuffer> payload(new QBuffer());
|
||||
payload->setData(tokenReplyPayload());
|
||||
return new FakePostReply(op, req, std::move(payload), fakeQnam);
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual QByteArray tokenReplyPayload() const {
|
||||
QJsonDocument jsondata(QJsonObject{
|
||||
{ "access_token", "123" },
|
||||
{ "refresh_token" , "456" },
|
||||
{ "message_url", "owncloud://success"},
|
||||
{ "user_id", "789" },
|
||||
{ "token_type", "Bearer" }
|
||||
});
|
||||
return jsondata.toJson();
|
||||
}
|
||||
|
||||
virtual void oauthResult(OAuth::Result result, const QString &user, const QString &token , const QString &refreshToken) {
|
||||
QCOMPARE(state, TokenAsked);
|
||||
QCOMPARE(result, OAuth::LoggedIn);
|
||||
QCOMPARE(user, QString("789"));
|
||||
QCOMPARE(token, QString("123"));
|
||||
QCOMPARE(refreshToken, QString("456"));
|
||||
gotAuthOk = true;
|
||||
}
|
||||
};
|
||||
|
||||
class TestOAuth: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testBasic()
|
||||
{
|
||||
OAuthTestCase test;
|
||||
test.test();
|
||||
}
|
||||
|
||||
// Test for https://github.com/owncloud/client/pull/6057
|
||||
void testCloseBrowserDontCrash()
|
||||
{
|
||||
struct Test : OAuthTestCase {
|
||||
QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest & req) override
|
||||
{
|
||||
ASSERT(browserReply);
|
||||
// simulate the fact that the browser is closing the connection
|
||||
browserReply->abort();
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
ASSERT(state == BrowserOpened);
|
||||
state = TokenAsked;
|
||||
|
||||
std::unique_ptr<QBuffer> payload(new QBuffer);
|
||||
payload->setData(tokenReplyPayload());
|
||||
return new SlowFakePostReply(op, req, std::move(payload), fakeQnam);
|
||||
}
|
||||
|
||||
void browserReplyFinished() override
|
||||
{
|
||||
QCOMPARE(sender(), browserReply.data());
|
||||
QCOMPARE(browserReply->error(), QNetworkReply::OperationCanceledError);
|
||||
replyToBrowserOk = true;
|
||||
}
|
||||
} test;
|
||||
test.test();
|
||||
}
|
||||
|
||||
void testRandomConnections()
|
||||
{
|
||||
// Test that we can send random garbage to the litening socket and it does not prevent the connection
|
||||
struct Test : OAuthTestCase {
|
||||
QNetworkReply *createBrowserReply(const QNetworkRequest &request) override {
|
||||
QTimer::singleShot(0, this, [this, request] {
|
||||
auto port = request.url().port();
|
||||
state = CustomState;
|
||||
QVector<QByteArray> payloads = {
|
||||
"GET FOFOFO HTTP 1/1\n\n",
|
||||
"GET /?code=invalie HTTP 1/1\n\n",
|
||||
"GET /?code=xxxxx&bar=fff",
|
||||
QByteArray("\0\0\0", 3),
|
||||
QByteArray("GET \0\0\0 \n\n\n\n\n\0", 14),
|
||||
QByteArray("GET /?code=éléphant\xa5 HTTP\n"),
|
||||
QByteArray("\n\n\n\n"),
|
||||
};
|
||||
foreach (const auto &x, payloads) {
|
||||
auto socket = new QTcpSocket(this);
|
||||
socket->connectToHost("localhost", port);
|
||||
QVERIFY(socket->waitForConnected());
|
||||
socket->write(x);
|
||||
}
|
||||
|
||||
// Do the actual request a bit later
|
||||
QTimer::singleShot(100, this, [this, request] {
|
||||
QCOMPARE(state, CustomState);
|
||||
state = BrowserOpened;
|
||||
this->OAuthTestCase::createBrowserReply(request);
|
||||
});
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) override
|
||||
{
|
||||
if (state == CustomState)
|
||||
return new FakeErrorReply{op, req, this, 500};
|
||||
return OAuthTestCase::tokenReply(op, req);
|
||||
}
|
||||
|
||||
void oauthResult(OAuth::Result result, const QString &user, const QString &token ,
|
||||
const QString &refreshToken) override {
|
||||
if (state != CustomState)
|
||||
return OAuthTestCase::oauthResult(result, user, token, refreshToken);
|
||||
QCOMPARE(result, OAuth::Error);
|
||||
}
|
||||
} test;
|
||||
test.test();
|
||||
}
|
||||
|
||||
void testTokenUrlHasRedirect()
|
||||
{
|
||||
struct Test : OAuthTestCase {
|
||||
int redirectsDone = 0;
|
||||
QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest & request) override
|
||||
{
|
||||
ASSERT(browserReply);
|
||||
// Kind of reproduces what we had in https://github.com/owncloud/enterprise/issues/2951 (not 1:1)
|
||||
if (redirectsDone == 0) {
|
||||
std::unique_ptr<QBuffer> payload(new QBuffer());
|
||||
payload->setData("");
|
||||
auto *reply = new SlowFakePostReply(op, request, std::move(payload), this);
|
||||
reply->redirectToPolicy = true;
|
||||
redirectsDone++;
|
||||
return reply;
|
||||
} else if (redirectsDone == 1) {
|
||||
std::unique_ptr<QBuffer> payload(new QBuffer());
|
||||
payload->setData("");
|
||||
auto *reply = new SlowFakePostReply(op, request, std::move(payload), this);
|
||||
reply->redirectToToken = true;
|
||||
redirectsDone++;
|
||||
return reply;
|
||||
} else {
|
||||
// ^^ This is with a custom reply and not actually HTTP, so we're testing the HTTP redirect code
|
||||
// we have in AbstractNetworkJob::slotFinished()
|
||||
redirectsDone++;
|
||||
return OAuthTestCase::tokenReply(op, request);
|
||||
}
|
||||
}
|
||||
} test;
|
||||
test.test();
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestOAuth)
|
||||
#include "testoauth.moc"
|
Loading…
Reference in a new issue