2013-07-29 12:28:19 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Klaas Freitag <freitag@kde.org>
|
|
|
|
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.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.
|
|
|
|
*/
|
|
|
|
|
2017-05-09 12:24:11 +00:00
|
|
|
#include <QLoggingCategory>
|
2013-07-29 12:28:19 +00:00
|
|
|
#include <QMutex>
|
2013-10-21 19:42:52 +00:00
|
|
|
#include <QNetworkReply>
|
2013-11-04 15:36:23 +00:00
|
|
|
#include <QSettings>
|
2017-01-02 07:34:02 +00:00
|
|
|
#include <QSslKey>
|
2017-03-28 08:31:38 +00:00
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QJsonDocument>
|
2017-09-11 10:24:29 +00:00
|
|
|
#include <QBuffer>
|
2013-11-04 15:36:23 +00:00
|
|
|
|
2020-10-29 12:24:20 +00:00
|
|
|
#include <qt5keychain/keychain.h>
|
2013-07-29 12:28:19 +00:00
|
|
|
|
2014-07-10 22:31:24 +00:00
|
|
|
#include "account.h"
|
2014-11-09 22:25:57 +00:00
|
|
|
#include "accessmanager.h"
|
2017-08-16 06:36:52 +00:00
|
|
|
#include "configfile.h"
|
2014-07-10 22:31:24 +00:00
|
|
|
#include "theme.h"
|
2014-12-18 14:39:20 +00:00
|
|
|
#include "syncengine.h"
|
2013-08-06 09:50:08 +00:00
|
|
|
#include "creds/credentialscommon.h"
|
2013-10-21 19:42:52 +00:00
|
|
|
#include "creds/httpcredentials.h"
|
2017-09-20 15:14:33 +00:00
|
|
|
#include <QAuthenticator>
|
2013-07-29 12:28:19 +00:00
|
|
|
|
2014-11-09 21:34:07 +00:00
|
|
|
namespace OCC {
|
2013-07-29 12:28:19 +00:00
|
|
|
|
2017-12-28 19:33:10 +00:00
|
|
|
Q_LOGGING_CATEGORY(lcHttpCredentials, "nextcloud.sync.credentials.http", QtInfoMsg)
|
2017-05-09 12:24:11 +00:00
|
|
|
|
2014-09-16 21:52:28 +00:00
|
|
|
namespace {
|
2013-11-04 15:36:23 +00:00
|
|
|
const char userC[] = "user";
|
2017-03-28 08:31:38 +00:00
|
|
|
const char isOAuthC[] = "oauth";
|
2019-02-19 10:38:46 +00:00
|
|
|
const char clientCertBundleC[] = "clientCertPkcs12";
|
|
|
|
const char clientCertPasswordC[] = "_clientCertPassword";
|
2017-01-02 07:34:02 +00:00
|
|
|
const char clientCertificatePEMC[] = "_clientCertificatePEM";
|
|
|
|
const char clientKeyPEMC[] = "_clientKeyPEM";
|
2015-03-10 10:17:35 +00:00
|
|
|
const char authenticationFailedC[] = "owncloud-authentication-failed";
|
2018-10-17 12:49:00 +00:00
|
|
|
const char needRetryC[] = "owncloud-need-retry";
|
2013-07-29 12:28:19 +00:00
|
|
|
} // ns
|
|
|
|
|
2016-01-06 10:22:35 +00:00
|
|
|
class HttpCredentialsAccessManager : public AccessManager
|
|
|
|
{
|
|
|
|
public:
|
2018-11-12 17:46:39 +00:00
|
|
|
HttpCredentialsAccessManager(const HttpCredentials *cred, QObject *parent = nullptr)
|
2016-01-06 10:22:35 +00:00
|
|
|
: AccessManager(parent)
|
|
|
|
, _cred(cred)
|
|
|
|
{
|
|
|
|
}
|
2017-05-17 08:55:42 +00:00
|
|
|
|
2016-01-06 10:22:35 +00:00
|
|
|
protected:
|
2018-11-12 17:39:50 +00:00
|
|
|
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override
|
2016-01-06 10:22:35 +00:00
|
|
|
{
|
|
|
|
QNetworkRequest req(request);
|
2017-09-07 12:58:45 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
} else if (!request.url().password().isEmpty()) {
|
|
|
|
// Typically the requests to get or refresh the OAuth access token. The client
|
|
|
|
// credentials are put in the URL from the code making the request.
|
|
|
|
QByteArray credHash = request.url().userInfo().toUtf8().toBase64();
|
2017-03-28 08:31:38 +00:00
|
|
|
req.setRawHeader("Authorization", "Basic " + credHash);
|
|
|
|
}
|
|
|
|
}
|
2017-01-02 07:34:02 +00:00
|
|
|
|
2017-07-07 09:09:11 +00:00
|
|
|
if (_cred && !_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) {
|
2017-01-02 07:34:02 +00:00
|
|
|
// SSL configuration
|
|
|
|
QSslConfiguration sslConfiguration = req.sslConfiguration();
|
|
|
|
sslConfiguration.setLocalCertificate(_cred->_clientSslCertificate);
|
|
|
|
sslConfiguration.setPrivateKey(_cred->_clientSslKey);
|
|
|
|
req.setSslConfiguration(sslConfiguration);
|
|
|
|
}
|
|
|
|
|
2018-10-17 12:49:00 +00:00
|
|
|
auto *reply = AccessManager::createRequest(op, req, outgoingData);
|
2017-01-02 07:34:02 +00:00
|
|
|
|
2018-10-17 12:49:00 +00:00
|
|
|
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;
|
2016-01-06 10:22:35 +00:00
|
|
|
}
|
2017-05-17 08:55:42 +00:00
|
|
|
|
2016-01-06 10:22:35 +00:00
|
|
|
private:
|
2017-07-07 09:09:11 +00:00
|
|
|
// The credentials object dies along with the account, while the QNAM might
|
|
|
|
// outlive both.
|
|
|
|
QPointer<const HttpCredentials> _cred;
|
2016-01-06 10:22:35 +00:00
|
|
|
};
|
|
|
|
|
2017-01-02 07:34:02 +00:00
|
|
|
|
|
|
|
static void addSettingsToJob(Account *account, QKeychain::Job *job)
|
|
|
|
{
|
|
|
|
Q_UNUSED(account);
|
2017-08-16 06:36:52 +00:00
|
|
|
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
|
2017-01-02 07:34:02 +00:00
|
|
|
settings->setParent(job); // make the job parent to make setting deleted properly
|
|
|
|
job->setSettings(settings.release());
|
|
|
|
}
|
|
|
|
|
2020-08-12 15:19:49 +00:00
|
|
|
HttpCredentials::HttpCredentials() = default;
|
2013-07-29 12:28:19 +00:00
|
|
|
|
2017-01-02 07:34:02 +00:00
|
|
|
// From wizard
|
2019-02-19 10:38:46 +00:00
|
|
|
HttpCredentials::HttpCredentials(const QString &user, const QString &password, const QByteArray &clientCertBundle, const QByteArray &clientCertPassword)
|
2013-07-29 12:28:19 +00:00
|
|
|
: _user(user)
|
|
|
|
, _password(password)
|
2015-09-05 13:39:22 +00:00
|
|
|
, _ready(true)
|
2019-02-19 10:38:46 +00:00
|
|
|
, _clientCertBundle(clientCertBundle)
|
|
|
|
, _clientCertPassword(clientCertPassword)
|
2018-05-14 17:02:22 +00:00
|
|
|
, _retryOnKeyChainError(false)
|
2013-10-30 15:32:34 +00:00
|
|
|
{
|
2019-02-19 10:38:46 +00:00
|
|
|
if (!unpackClientCertBundle()) {
|
|
|
|
ASSERT(false, "pkcs12 client cert bundle passed to HttpCredentials must be valid");
|
|
|
|
}
|
2013-10-30 15:32:34 +00:00
|
|
|
}
|
2013-07-29 12:28:19 +00:00
|
|
|
|
|
|
|
QString HttpCredentials::authType() const
|
|
|
|
{
|
|
|
|
return QString::fromLatin1("http");
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HttpCredentials::user() const
|
|
|
|
{
|
|
|
|
return _user;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HttpCredentials::password() const
|
|
|
|
{
|
|
|
|
return _password;
|
|
|
|
}
|
|
|
|
|
2015-04-23 12:47:31 +00:00
|
|
|
void HttpCredentials::setAccount(Account *account)
|
|
|
|
{
|
|
|
|
AbstractCredentials::setAccount(account);
|
|
|
|
if (_user.isEmpty()) {
|
|
|
|
fetchUser();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-07 09:09:11 +00:00
|
|
|
QNetworkAccessManager *HttpCredentials::createQNAM() const
|
2013-07-29 12:28:19 +00:00
|
|
|
{
|
2016-01-06 10:22:35 +00:00
|
|
|
AccessManager *qnam = new HttpCredentialsAccessManager(this);
|
2013-07-29 12:28:19 +00:00
|
|
|
|
2017-09-20 08:14:48 +00:00
|
|
|
connect(qnam, &QNetworkAccessManager::authenticationRequired,
|
|
|
|
this, &HttpCredentials::slotAuthentication);
|
2013-07-29 12:28:19 +00:00
|
|
|
|
|
|
|
return qnam;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HttpCredentials::ready() const
|
|
|
|
{
|
|
|
|
return _ready;
|
|
|
|
}
|
|
|
|
|
2015-01-16 14:18:01 +00:00
|
|
|
QString HttpCredentials::fetchUser()
|
2013-07-29 12:28:19 +00:00
|
|
|
{
|
2015-01-16 14:18:01 +00:00
|
|
|
_user = _account->credentialSetting(QLatin1String(userC)).toString();
|
2014-01-28 12:18:21 +00:00
|
|
|
return _user;
|
|
|
|
}
|
|
|
|
|
2015-09-05 13:39:22 +00:00
|
|
|
void HttpCredentials::fetchFromKeychain()
|
2014-01-28 12:18:21 +00:00
|
|
|
{
|
2017-04-27 11:58:26 +00:00
|
|
|
_wasFetched = true;
|
|
|
|
|
2014-03-03 15:35:57 +00:00
|
|
|
// User must be fetched from config file
|
2015-01-16 14:18:01 +00:00
|
|
|
fetchUser();
|
2014-02-20 13:33:36 +00:00
|
|
|
|
2017-03-28 08:31:38 +00:00
|
|
|
if (!_ready && !_refreshToken.isEmpty()) {
|
2019-08-23 09:04:30 +00:00
|
|
|
// This happens if the credentials are still loaded from the keychain, but we are called
|
2017-03-28 08:31:38 +00:00
|
|
|
// here because the auth is invalid, so this means we simply need to refresh the credentials
|
|
|
|
refreshAccessToken();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-07-29 12:28:19 +00:00
|
|
|
if (_ready) {
|
|
|
|
Q_EMIT fetched();
|
|
|
|
} else {
|
2017-09-12 15:15:22 +00:00
|
|
|
_keychainMigration = false;
|
|
|
|
fetchFromKeychainHelper();
|
|
|
|
}
|
|
|
|
}
|
2017-01-02 07:34:02 +00:00
|
|
|
|
2017-09-12 15:15:22 +00:00
|
|
|
void HttpCredentials::fetchFromKeychainHelper()
|
|
|
|
{
|
2019-02-19 10:38:46 +00:00
|
|
|
_clientCertBundle = _account->credentialSetting(QLatin1String(clientCertBundleC)).toByteArray();
|
|
|
|
if (!_clientCertBundle.isEmpty()) {
|
|
|
|
// New case (>=2.6): We have a bundle in the settings and read the password from
|
|
|
|
// the keychain
|
2021-01-12 10:23:54 +00:00
|
|
|
auto job = new QKeychain::ReadPasswordJob(Theme::instance()->appName());
|
2019-02-19 10:38:46 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(keychainKey(_account->url().toString(), _user + clientCertPasswordC, _account->id()));
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotReadClientCertPasswordJobDone);
|
2019-02-19 10:38:46 +00:00
|
|
|
job->start();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Old case (pre 2.6): Read client cert and then key from keychain
|
2017-09-12 15:15:22 +00:00
|
|
|
const QString kck = keychainKey(
|
|
|
|
_account->url().toString(),
|
|
|
|
_user + clientCertificatePEMC,
|
|
|
|
_keychainMigration ? QString() : _account->id());
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::ReadPasswordJob(Theme::instance()->appName());
|
2017-09-12 15:15:22 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(kck);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotReadClientCertPEMJobDone);
|
2017-09-12 15:15:22 +00:00
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::deleteOldKeychainEntries()
|
|
|
|
{
|
|
|
|
auto startDeleteJob = [this](QString user) {
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::DeletePasswordJob(Theme::instance()->appName());
|
2017-09-12 15:15:22 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(true);
|
|
|
|
job->setKey(keychainKey(_account->url().toString(), user, QString()));
|
2013-11-04 15:36:23 +00:00
|
|
|
job->start();
|
2017-09-12 15:15:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
startDeleteJob(_user);
|
|
|
|
startDeleteJob(_user + clientKeyPEMC);
|
|
|
|
startDeleteJob(_user + clientCertificatePEMC);
|
2013-07-29 12:28:19 +00:00
|
|
|
}
|
2017-01-02 07:34:02 +00:00
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
bool HttpCredentials::keychainUnavailableRetryLater(QKeychain::ReadPasswordJob *incoming)
|
2017-01-02 07:34:02 +00:00
|
|
|
{
|
2018-05-14 17:02:22 +00:00
|
|
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
|
|
|
Q_ASSERT(!incoming->insecureFallback()); // If insecureFallback is set, the next test would be pointless
|
|
|
|
if (_retryOnKeyChainError && (incoming->error() == QKeychain::NoBackendAvailable
|
|
|
|
|| incoming->error() == QKeychain::OtherError)) {
|
|
|
|
// Could be that the backend was not yet available. Wait some extra seconds.
|
|
|
|
// (Issues #4274 and #6522)
|
|
|
|
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
|
|
|
|
qCInfo(lcHttpCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incoming->errorString();
|
|
|
|
QTimer::singleShot(10000, this, &HttpCredentials::fetchFromKeychainHelper);
|
|
|
|
_retryOnKeyChainError = false;
|
2019-02-19 10:38:46 +00:00
|
|
|
return true;
|
2018-05-14 17:02:22 +00:00
|
|
|
}
|
2021-01-12 10:23:54 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(incoming);
|
2018-05-14 17:02:22 +00:00
|
|
|
#endif
|
2019-02-19 10:38:46 +00:00
|
|
|
_retryOnKeyChainError = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotReadClientCertPasswordJobDone(QKeychain::Job *job)
|
|
|
|
{
|
2021-01-12 10:23:54 +00:00
|
|
|
auto readJob = qobject_cast<QKeychain::ReadPasswordJob*>(job);
|
|
|
|
if (keychainUnavailableRetryLater(readJob))
|
2019-02-19 10:38:46 +00:00
|
|
|
return;
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
if (readJob->error() == QKeychain::NoError) {
|
2019-02-19 10:38:46 +00:00
|
|
|
_clientCertPassword = readJob->binaryData();
|
|
|
|
} else {
|
2021-01-12 10:23:54 +00:00
|
|
|
qCWarning(lcHttpCredentials) << "Could not retrieve client cert password from keychain" << readJob->errorString();
|
2019-02-19 10:38:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!unpackClientCertBundle()) {
|
|
|
|
qCWarning(lcHttpCredentials) << "Could not unpack client cert bundle";
|
|
|
|
}
|
|
|
|
_clientCertBundle.clear();
|
|
|
|
_clientCertPassword.clear();
|
|
|
|
|
|
|
|
slotReadPasswordFromKeychain();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming)
|
|
|
|
{
|
2021-01-12 10:23:54 +00:00
|
|
|
auto readJob = qobject_cast<QKeychain::ReadPasswordJob*>(incoming);
|
|
|
|
if (keychainUnavailableRetryLater(readJob))
|
2019-02-19 10:38:46 +00:00
|
|
|
return;
|
2018-05-14 17:02:22 +00:00
|
|
|
|
2017-01-02 07:34:02 +00:00
|
|
|
// Store PEM in memory
|
2021-01-12 10:23:54 +00:00
|
|
|
if (readJob->error() == QKeychain::NoError && readJob->binaryData().length() > 0) {
|
2017-01-02 07:34:02 +00:00
|
|
|
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
|
|
|
if (sslCertificateList.length() >= 1) {
|
|
|
|
_clientSslCertificate = sslCertificateList.at(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load key too
|
2017-09-12 15:15:22 +00:00
|
|
|
const QString kck = keychainKey(
|
|
|
|
_account->url().toString(),
|
|
|
|
_user + clientKeyPEMC,
|
|
|
|
_keychainMigration ? QString() : _account->id());
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::ReadPasswordJob(Theme::instance()->appName());
|
2017-01-02 07:34:02 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(kck);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::ReadPasswordJob::finished, this, &HttpCredentials::slotReadClientKeyPEMJobDone);
|
2017-01-02 07:34:02 +00:00
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incoming)
|
|
|
|
{
|
2021-01-12 10:23:54 +00:00
|
|
|
auto readJob = qobject_cast<QKeychain::ReadPasswordJob*>(incoming);
|
2017-01-02 07:34:02 +00:00
|
|
|
// Store key in memory
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
if (readJob->error() == QKeychain::NoError && readJob->binaryData().length() > 0) {
|
2017-01-02 07:34:02 +00:00
|
|
|
QByteArray clientKeyPEM = readJob->binaryData();
|
|
|
|
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
|
|
|
|
// load whatever we have. So we try until it works.
|
|
|
|
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Rsa);
|
|
|
|
if (_clientSslKey.isNull()) {
|
|
|
|
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Dsa);
|
|
|
|
}
|
|
|
|
if (_clientSslKey.isNull()) {
|
|
|
|
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Ec);
|
|
|
|
}
|
|
|
|
if (_clientSslKey.isNull()) {
|
2017-03-30 11:46:20 +00:00
|
|
|
qCWarning(lcHttpCredentials) << "Could not load SSL key into Qt!";
|
2017-01-02 07:34:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-19 10:38:46 +00:00
|
|
|
slotReadPasswordFromKeychain();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotReadPasswordFromKeychain()
|
|
|
|
{
|
2017-09-12 15:15:22 +00:00
|
|
|
const QString kck = keychainKey(
|
|
|
|
_account->url().toString(),
|
|
|
|
_user,
|
|
|
|
_keychainMigration ? QString() : _account->id());
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::ReadPasswordJob(Theme::instance()->appName());
|
2017-01-02 07:34:02 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(kck);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::ReadPasswordJob::finished, this, &HttpCredentials::slotReadJobDone);
|
2017-01-02 07:34:02 +00:00
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-11-22 13:01:11 +00:00
|
|
|
bool HttpCredentials::stillValid(QNetworkReply *reply)
|
|
|
|
{
|
2016-01-06 10:22:35 +00:00
|
|
|
return ((reply->error() != QNetworkReply::AuthenticationRequiredError)
|
|
|
|
// returned if user or password is incorrect
|
|
|
|
&& (reply->error() != QNetworkReply::OperationCanceledError
|
|
|
|
|| !reply->property(authenticationFailedC).toBool()));
|
2013-11-22 13:01:11 +00:00
|
|
|
}
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
void HttpCredentials::slotReadJobDone(QKeychain::Job *incoming)
|
2013-07-29 12:28:19 +00:00
|
|
|
{
|
2022-09-30 19:36:58 +00:00
|
|
|
auto *job = dynamic_cast<QKeychain::ReadPasswordJob *>(incoming);
|
2017-09-12 15:15:22 +00:00
|
|
|
QKeychain::Error error = job->error();
|
|
|
|
|
|
|
|
// If we can't find the credentials at the keys that include the account id,
|
|
|
|
// try to read them from the legacy locations that don't have a account id.
|
|
|
|
if (!_keychainMigration && error == QKeychain::EntryNotFound) {
|
|
|
|
qCWarning(lcHttpCredentials)
|
|
|
|
<< "Could not find keychain entries, attempting to read from legacy locations";
|
|
|
|
_keychainMigration = true;
|
|
|
|
fetchFromKeychainHelper();
|
|
|
|
return;
|
|
|
|
}
|
2017-03-28 08:31:38 +00:00
|
|
|
|
|
|
|
bool isOauth = _account->credentialSetting(QLatin1String(isOAuthC)).toBool();
|
|
|
|
if (isOauth) {
|
|
|
|
_refreshToken = job->textData();
|
|
|
|
} else {
|
|
|
|
_password = job->textData();
|
|
|
|
}
|
2013-12-17 11:56:01 +00:00
|
|
|
|
2014-02-20 15:53:38 +00:00
|
|
|
if (_user.isEmpty()) {
|
2017-03-30 11:46:20 +00:00
|
|
|
qCWarning(lcHttpCredentials) << "Strange: User is empty!";
|
2014-02-20 15:53:38 +00:00
|
|
|
}
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
if (!_refreshToken.isEmpty() && error == QKeychain::NoError) {
|
2017-03-28 08:31:38 +00:00
|
|
|
refreshAccessToken();
|
2021-01-12 10:23:54 +00:00
|
|
|
} else if (!_password.isEmpty() && error == QKeychain::NoError) {
|
2014-02-20 15:53:38 +00:00
|
|
|
// 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.
|
2013-11-05 17:16:00 +00:00
|
|
|
_ready = true;
|
2014-02-20 15:53:38 +00:00
|
|
|
emit fetched();
|
|
|
|
} else {
|
2014-07-02 14:24:50 +00:00
|
|
|
// we come here if the password is empty or any other keychain
|
2023-06-02 11:55:08 +00:00
|
|
|
// error happened.
|
2015-08-30 10:08:40 +00:00
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
_fetchErrorString = job->error() != QKeychain::EntryNotFound ? job->errorString() : QString();
|
2015-09-05 13:39:22 +00:00
|
|
|
|
|
|
|
_password = QString();
|
|
|
|
_ready = false;
|
2015-08-30 10:08:40 +00:00
|
|
|
emit fetched();
|
2013-11-04 15:36:23 +00:00
|
|
|
}
|
2017-09-12 15:15:22 +00:00
|
|
|
|
|
|
|
// If keychain data was read from legacy location, wipe these entries and store new ones
|
|
|
|
if (_keychainMigration && _ready) {
|
|
|
|
persist();
|
|
|
|
deleteOldKeychainEntries();
|
|
|
|
qCWarning(lcHttpCredentials) << "Migrated old keychain entries";
|
|
|
|
}
|
2013-11-22 13:01:11 +00:00
|
|
|
}
|
2013-11-04 15:36:23 +00:00
|
|
|
|
2017-07-18 12:53:41 +00:00
|
|
|
bool HttpCredentials::refreshAccessToken()
|
2017-03-28 08:31:38 +00:00
|
|
|
{
|
2017-07-18 12:53:41 +00:00
|
|
|
if (_refreshToken.isEmpty())
|
|
|
|
return false;
|
|
|
|
|
2017-09-22 16:05:47 +00:00
|
|
|
QUrl requestToken = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/api/v1/token"));
|
2017-03-28 08:31:38 +00:00
|
|
|
QNetworkRequest req;
|
|
|
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
2017-09-11 10:24:29 +00:00
|
|
|
|
|
|
|
QString basicAuth = QString("%1:%2").arg(
|
|
|
|
Theme::instance()->oauthClientId(), Theme::instance()->oauthClientSecret());
|
|
|
|
req.setRawHeader("Authorization", "Basic " + basicAuth.toUtf8().toBase64());
|
2018-10-17 12:49:00 +00:00
|
|
|
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
2017-09-11 10:24:29 +00:00
|
|
|
|
|
|
|
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);
|
2017-09-15 07:11:52 +00:00
|
|
|
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
2017-09-08 14:43:59 +00:00
|
|
|
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
2017-03-28 08:31:38 +00:00
|
|
|
auto jsonData = reply->readAll();
|
2023-02-03 17:38:52 +00:00
|
|
|
QJsonParseError jsonParseError{};
|
2017-03-28 08:31:38 +00:00
|
|
|
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
|
|
|
QString accessToken = json["access_token"].toString();
|
2018-08-07 15:43:35 +00:00
|
|
|
if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) {
|
|
|
|
// Invalid or empty JSON: Network error maybe?
|
2017-06-13 08:24:26 +00:00
|
|
|
qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString();
|
2017-03-28 08:31:38 +00:00
|
|
|
} else if (accessToken.isEmpty()) {
|
2018-08-07 15:43:35 +00:00
|
|
|
// 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)
|
2017-06-13 08:24:26 +00:00
|
|
|
qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out";
|
2017-03-28 08:31:38 +00:00
|
|
|
_refreshToken.clear();
|
|
|
|
} else {
|
|
|
|
_ready = true;
|
|
|
|
_password = accessToken;
|
|
|
|
_refreshToken = json["refresh_token"].toString();
|
|
|
|
persist();
|
|
|
|
}
|
2018-10-17 12:49:00 +00:00
|
|
|
_isRenewingOAuthToken = false;
|
2022-10-24 15:53:40 +00:00
|
|
|
for (const auto &job : qAsConst(_retryQueue)) {
|
2018-10-17 12:49:00 +00:00
|
|
|
if (job)
|
|
|
|
job->retry();
|
|
|
|
}
|
|
|
|
_retryQueue.clear();
|
2017-03-28 08:31:38 +00:00
|
|
|
emit fetched();
|
|
|
|
});
|
2018-10-17 12:49:00 +00:00
|
|
|
_isRenewingOAuthToken = true;
|
2017-07-18 12:53:41 +00:00
|
|
|
return true;
|
2017-03-28 08:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-01-16 14:18:01 +00:00
|
|
|
void HttpCredentials::invalidateToken()
|
2013-11-22 23:14:02 +00:00
|
|
|
{
|
2015-03-25 12:27:40 +00:00
|
|
|
if (!_password.isEmpty()) {
|
|
|
|
_previousPassword = _password;
|
|
|
|
}
|
2013-11-22 23:14:02 +00:00
|
|
|
_password = QString();
|
2014-03-03 15:35:57 +00:00
|
|
|
_ready = false;
|
|
|
|
|
|
|
|
// User must be fetched from config file to generate a valid key
|
2015-01-16 14:18:01 +00:00
|
|
|
fetchUser();
|
2014-03-03 15:35:57 +00:00
|
|
|
|
2017-09-12 15:15:22 +00:00
|
|
|
const QString kck = keychainKey(_account->url().toString(), _user, _account->id());
|
2014-03-03 15:35:57 +00:00
|
|
|
if (kck.isEmpty()) {
|
2017-03-30 11:46:20 +00:00
|
|
|
qCWarning(lcHttpCredentials) << "InvalidateToken: User is empty, bailing out!";
|
2014-03-03 15:35:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-18 12:53:41 +00:00
|
|
|
// clear the session cookie.
|
|
|
|
_account->clearCookieJar();
|
|
|
|
|
2017-03-28 08:31:38 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::DeletePasswordJob(Theme::instance()->appName());
|
2017-01-02 07:34:02 +00:00
|
|
|
addSettingsToJob(_account, job);
|
2014-01-27 15:55:17 +00:00
|
|
|
job->setInsecureFallback(true);
|
2014-03-03 15:35:57 +00:00
|
|
|
job->setKey(kck);
|
2013-11-22 23:14:02 +00:00
|
|
|
job->start();
|
2014-03-03 15:35:57 +00:00
|
|
|
|
2015-10-05 03:20:09 +00:00
|
|
|
// let QNAM forget about the password
|
HTTP creds: Fix GUI blocking for 5 seconds when entering wrong password
The Qt HTTP thread calls authenticationRequired (indirectly) using a
BlockingQueuedConnection. So when we call invalidateToken from slot
connected to this signal and end up calling QNAM::clearAccessCache which
waits on the thread for 5 seconds
Backtraces:
Qt HTTP thread:
#0 0x00007ffff20c707f in pthread_cond_wait@@GLIBC_2.3.2 ()
#1 0x00007ffff43f0c0b in QWaitConditionPrivate::wait
#2 QWaitCondition::wait
#3 0x00007ffff43ea06b in QSemaphore::acquire
#4 0x00007ffff45dcf6f in QMetaObject::activate
[...]
#9 0x00007ffff45dd607 in QMetaObject::activate
#10 0x00007ffff4edbaf7 in QHttpNetworkReply::authenticationRequired
#11 0x00007ffff4e0b2b4 in QHttpNetworkConnectionPrivate::handleAuthenticateChallenge
#12 0x00007ffff4e10753 in QHttpNetworkConnectionChannel::handleStatus
#13 0x00007ffff4e11cc9 in QHttpNetworkConnectionChannel::allDone
#14 0x00007ffff4e14605 in QHttpProtocolHandler::_q_receiveReply
Main Thread:
#0 0x00007ffff20c7428 in pthread_cond_timedwait@@GLIBC_2.3.2 ()
#1 0x00007ffff43f0b56 in QWaitConditionPrivate::wait_relative (time=5000, this=0x136c580)
#2 QWaitConditionPrivate::wait (time=5000, this=0x136c580)
#3 QWaitCondition::wait (this=this@entry=0x136c788, mutex=mutex@entry=0x136c760, time=time@entry=5000)
#4 0x00007ffff43efa6e in QThread::wait (this=<optimized out>, time=time@entry=5000)
#5 0x00007ffff4e1edd3 in QNetworkAccessManagerPrivate::clearCache
#6 0x00007ffff7b6fb03 in OCC::HttpCredentials::invalidateToken()
#7 0x000000000057adb4 in OCC::AccountState::slotInvalidCredentials()
#8 0x000000000057ac76 in OCC::AccountState::slotConnectionValidatorResult(OCC::ConnectionValidator::Status, QStringList const&) ()
#9 0x00000000005ab45c in OCC::AccountState::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)
#10 0x00007ffff45dcd30 in QMetaObject::activate
#11 0x00007ffff7b78671 in OCC::ConnectionValidator::connectionResult(OCC::ConnectionValidator::Status, QStringList) ()
#12 0x00007ffff7ae2514 in OCC::ConnectionValidator::reportResult(OCC::ConnectionValidator::Status) ()
#13 0x00007ffff7ae39b7 in OCC::ConnectionValidator::slotAuthFailed(QNetworkReply*) ()
#14 0x00007ffff7b784a9 in OCC::ConnectionValidator::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
#15 0x00007ffff45dcd30 in QMetaObject::activate
#16 0x00007ffff7b766dc in OCC::AbstractNetworkJob::networkError(QNetworkReply*)
#17 0x00007ffff7af9f6e in OCC::AbstractNetworkJob::slotFinished()
#18 0x00007ffff7b7654d in OCC::AbstractNetworkJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
#20 0x00007ffff45dd607 in QMetaObject::activate
#21 0x00007ffff4edd143 in QNetworkReply::finished
#22 0x00007ffff4e3fec7 in QNetworkReplyHttpImplPrivate::finished
#23 0x00007ffff4e41818 in QNetworkReplyHttpImpl::close
#24 0x00007ffff7b7047b in OCC::HttpCredentials::slotAuthentication(QNetworkReply*, QAuthenticator*) ()
#25 0x00007ffff7b79092 in OCC::HttpCredentials::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
#27 0x00007ffff45dd607 in QMetaObject::activate
#28 0x00007ffff4e1d6fb in QNetworkAccessManager::authenticationRequired
#29 0x00007ffff4e1ea07 in QNetworkAccessManagerPrivate::authenticationRequired
#30 0x00007ffff4e3c784 in QNetworkReplyHttpImplPrivate::httpAuthenticationRequired
Another case of Main Thread:
#5 0x00007ffff4e1edd3 in QNetworkAccessManagerPrivate::clearCache
#6 0x00007ffff7b6fb03 in OCC::HttpCredentials::invalidateToken()
#7 0x000000000057b1e4 in OCC::AccountState::slotInvalidCredentials() ()
#8 0x00000000005abb8a in OCC::AccountState::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
#9 0x00007ffff45dcd30 in QMetaObject::activate
#10 0x00007ffff7b76ed5 in OCC::Account::invalidCredentials() ()
#11 0x00007ffff7ad55f5 in OCC::Account::handleInvalidCredentials()
#12 0x00007ffff7afa69a in OCC::AbstractNetworkJob::slotFinished()
#13 0x00007ffff7b7654d in OCC::AbstractNetworkJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
#15 0x00007ffff45dd607 in QMetaObject::activate
#16 0x00007ffff4edd143 in QNetworkReply::finished
#17 0x00007ffff4e3fec7 in QNetworkReplyHttpImplPrivate::finished
#18 0x00007ffff4e41818 in QNetworkReplyHttpImpl::close
#19 0x00007ffff7b7047b in OCC::HttpCredentials::slotAuthentication(QNetworkReply*, QAuthenticator*) ()
#20 0x00007ffff7b79092 in OCC::HttpCredentials::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
#22 0x00007ffff45dd607 in QMetaObject::activate
#23 0x00007ffff4e1d6fb in QNetworkAccessManager::authenticationRequired
2016-01-07 13:14:32 +00:00
|
|
|
// This needs to be done later in the event loop because we might be called (directly or
|
|
|
|
// indirectly) from QNetworkAccessManagerPrivate::authenticationRequired, which itself
|
|
|
|
// is a called from a BlockingQueuedConnection from the Qt HTTP thread. And clearing the
|
|
|
|
// cache needs to synchronize again with the HTTP thread.
|
2017-09-20 08:14:48 +00:00
|
|
|
QTimer::singleShot(0, _account, &Account::clearQNAMCache);
|
2013-11-22 23:14:02 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 10:06:28 +00:00
|
|
|
void HttpCredentials::forgetSensitiveData()
|
|
|
|
{
|
2017-03-28 08:31:38 +00:00
|
|
|
// need to be done before invalidateToken, so it actually deletes the refresh_token from the keychain
|
|
|
|
_refreshToken.clear();
|
|
|
|
|
2015-12-09 10:06:28 +00:00
|
|
|
invalidateToken();
|
|
|
|
_previousPassword.clear();
|
|
|
|
}
|
|
|
|
|
2015-01-16 14:18:01 +00:00
|
|
|
void HttpCredentials::persist()
|
2013-07-29 12:28:19 +00:00
|
|
|
{
|
2014-02-28 11:47:54 +00:00
|
|
|
if (_user.isEmpty()) {
|
|
|
|
// We never connected or fetched the user, there is nothing to save.
|
|
|
|
return;
|
|
|
|
}
|
2017-01-02 07:34:02 +00:00
|
|
|
|
2015-01-16 14:18:01 +00:00
|
|
|
_account->setCredentialSetting(QLatin1String(userC), _user);
|
2017-03-28 08:31:38 +00:00
|
|
|
_account->setCredentialSetting(QLatin1String(isOAuthC), isUsingOAuth());
|
2019-02-19 10:38:46 +00:00
|
|
|
if (!_clientCertBundle.isEmpty()) {
|
|
|
|
// Note that the _clientCertBundle will often be cleared after usage,
|
|
|
|
// it's just written if it gets passed into the constructor.
|
|
|
|
_account->setCredentialSetting(QLatin1String(clientCertBundleC), _clientCertBundle);
|
|
|
|
}
|
2022-12-19 14:11:18 +00:00
|
|
|
emit _account->wantsAccountSaved(_account);
|
2017-01-02 07:34:02 +00:00
|
|
|
|
2019-02-19 10:38:46 +00:00
|
|
|
// write secrets to the keychain
|
|
|
|
if (!_clientCertBundle.isEmpty()) {
|
|
|
|
// Option 1: If we have a pkcs12 bundle, that'll be written to the config file
|
2023-06-02 11:55:08 +00:00
|
|
|
// and we'll just store the bundle password in the keychain. That's preferred
|
2019-02-19 10:38:46 +00:00
|
|
|
// since the keychain on older Windows platforms can only store a limited number
|
|
|
|
// of bytes per entry and key/cert may exceed that.
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::WritePasswordJob(Theme::instance()->appName());
|
2019-02-19 10:38:46 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotWriteClientCertPasswordJobDone);
|
2019-02-19 10:38:46 +00:00
|
|
|
job->setKey(keychainKey(_account->url().toString(), _user + clientCertPasswordC, _account->id()));
|
|
|
|
job->setBinaryData(_clientCertPassword);
|
|
|
|
job->start();
|
|
|
|
_clientCertBundle.clear();
|
|
|
|
_clientCertPassword.clear();
|
|
|
|
} else if (_account->credentialSetting(QLatin1String(clientCertBundleC)).isNull() && !_clientSslCertificate.isNull()) {
|
|
|
|
// Option 2, pre 2.6 configs: We used to store the raw cert/key in the keychain and
|
|
|
|
// still do so if no bundle is available. We can't currently migrate to Option 1
|
|
|
|
// because we have no functions for creating an encrypted pkcs12 bundle.
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::WritePasswordJob(Theme::instance()->appName());
|
2017-10-25 08:43:42 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotWriteClientCertPEMJobDone);
|
2017-10-25 08:43:42 +00:00
|
|
|
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
|
|
|
|
job->setBinaryData(_clientSslCertificate.toPem());
|
|
|
|
job->start();
|
|
|
|
} else {
|
2019-02-19 10:38:46 +00:00
|
|
|
// Option 3: no client certificate at all (or doesn't need to be written)
|
|
|
|
slotWritePasswordToKeychain();
|
2017-10-25 08:43:42 +00:00
|
|
|
}
|
2017-01-02 07:34:02 +00:00
|
|
|
}
|
2014-03-03 15:35:57 +00:00
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
void HttpCredentials::slotWriteClientCertPasswordJobDone(QKeychain::Job *finishedJob)
|
2019-02-19 10:38:46 +00:00
|
|
|
{
|
|
|
|
if (finishedJob && finishedJob->error() != QKeychain::NoError) {
|
|
|
|
qCWarning(lcHttpCredentials) << "Could not write client cert password to credentials"
|
|
|
|
<< finishedJob->error() << finishedJob->errorString();
|
|
|
|
}
|
|
|
|
|
|
|
|
slotWritePasswordToKeychain();
|
|
|
|
}
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
void HttpCredentials::slotWriteClientCertPEMJobDone(QKeychain::Job *finishedJob)
|
2017-01-02 07:34:02 +00:00
|
|
|
{
|
2019-01-22 09:04:08 +00:00
|
|
|
if (finishedJob && finishedJob->error() != QKeychain::NoError) {
|
|
|
|
qCWarning(lcHttpCredentials) << "Could not write client cert to credentials"
|
|
|
|
<< finishedJob->error() << finishedJob->errorString();
|
|
|
|
}
|
|
|
|
|
2017-10-25 08:43:42 +00:00
|
|
|
// write ssl key if there is one
|
|
|
|
if (!_clientSslKey.isNull()) {
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::WritePasswordJob(Theme::instance()->appName());
|
2017-10-25 08:43:42 +00:00
|
|
|
addSettingsToJob(_account, job);
|
|
|
|
job->setInsecureFallback(false);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotWriteClientKeyPEMJobDone);
|
2017-10-25 08:43:42 +00:00
|
|
|
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
|
|
|
|
job->setBinaryData(_clientSslKey.toPem());
|
|
|
|
job->start();
|
|
|
|
} else {
|
2019-01-22 09:04:08 +00:00
|
|
|
slotWriteClientKeyPEMJobDone(nullptr);
|
2017-10-25 08:43:42 +00:00
|
|
|
}
|
2017-01-02 07:34:02 +00:00
|
|
|
}
|
|
|
|
|
2021-01-12 10:23:54 +00:00
|
|
|
void HttpCredentials::slotWriteClientKeyPEMJobDone(QKeychain::Job *finishedJob)
|
2017-01-02 07:34:02 +00:00
|
|
|
{
|
2019-01-22 09:04:08 +00:00
|
|
|
if (finishedJob && finishedJob->error() != QKeychain::NoError) {
|
|
|
|
qCWarning(lcHttpCredentials) << "Could not write client key to credentials"
|
|
|
|
<< finishedJob->error() << finishedJob->errorString();
|
|
|
|
}
|
|
|
|
|
2019-02-19 10:38:46 +00:00
|
|
|
slotWritePasswordToKeychain();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotWritePasswordToKeychain()
|
|
|
|
{
|
2021-01-12 10:23:54 +00:00
|
|
|
auto *job = new QKeychain::WritePasswordJob(Theme::instance()->appName());
|
2017-01-02 07:34:02 +00:00
|
|
|
addSettingsToJob(_account, job);
|
2014-02-20 13:33:36 +00:00
|
|
|
job->setInsecureFallback(false);
|
2021-01-12 10:23:54 +00:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &HttpCredentials::slotWriteJobDone);
|
2017-09-12 15:15:22 +00:00
|
|
|
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
|
2017-03-28 08:31:38 +00:00
|
|
|
job->setTextData(isUsingOAuth() ? _refreshToken : _password);
|
2013-11-04 15:36:23 +00:00
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotWriteJobDone(QKeychain::Job *job)
|
|
|
|
{
|
2019-01-22 09:04:08 +00:00
|
|
|
if (job && job->error() != QKeychain::NoError) {
|
|
|
|
qCWarning(lcHttpCredentials) << "Error while writing password"
|
|
|
|
<< job->error() << job->errorString();
|
2013-07-29 12:28:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator)
|
|
|
|
{
|
2017-03-28 08:31:38 +00:00
|
|
|
if (!_ready)
|
|
|
|
return;
|
2016-01-06 10:22:35 +00:00
|
|
|
Q_UNUSED(authenticator)
|
2016-01-07 13:58:02 +00:00
|
|
|
// 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.
|
2017-03-30 11:46:20 +00:00
|
|
|
qCWarning(lcHttpCredentials) << "Stop request: Authentication failed for " << reply->url().toString();
|
2015-03-10 10:17:35 +00:00
|
|
|
reply->setProperty(authenticationFailedC, true);
|
2018-10-17 12:49:00 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
auto *reply = job->reply();
|
|
|
|
if (!reply || !reply->property(needRetryC).toBool())
|
|
|
|
return false;
|
|
|
|
if (_isRenewingOAuthToken) {
|
|
|
|
_retryQueue.append(job);
|
|
|
|
} else {
|
|
|
|
job->retry();
|
|
|
|
}
|
|
|
|
return true;
|
2013-07-29 12:28:19 +00:00
|
|
|
}
|
|
|
|
|
2019-02-19 10:38:46 +00:00
|
|
|
bool HttpCredentials::unpackClientCertBundle()
|
|
|
|
{
|
|
|
|
if (_clientCertBundle.isEmpty())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
QBuffer certBuffer(&_clientCertBundle);
|
|
|
|
certBuffer.open(QIODevice::ReadOnly);
|
|
|
|
QList<QSslCertificate> clientCaCertificates;
|
|
|
|
return QSslCertificate::importPkcs12(
|
|
|
|
&certBuffer, &_clientSslKey, &_clientSslCertificate, &clientCaCertificates, _clientCertPassword);
|
|
|
|
}
|
|
|
|
|
2014-11-09 21:34:07 +00:00
|
|
|
} // namespace OCC
|