Merge pull request #2127 from nextcloud/fix/proxy-password-storage

ConfigFile security: Migrate Proxy password to keychain
This commit is contained in:
Michael Schuster 2020-07-06 22:36:21 +02:00 committed by GitHub
commit 2475fb73b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 807 additions and 472 deletions

View file

@ -112,7 +112,6 @@ set(client_SRCS
creds/httpcredentialsgui.cpp
creds/oauth.cpp
creds/flow2auth.cpp
creds/keychainchunk.cpp
creds/webflowcredentials.cpp
creds/webflowcredentialsdialog.cpp
wizard/postfixlineedit.cpp

View file

@ -1,221 +0,0 @@
/*
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "account.h"
#include "keychainchunk.h"
#include "theme.h"
#include "networkjobs.h"
#include "configfile.h"
#include "creds/abstractcredentials.h"
using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcKeychainChunk, "nextcloud.sync.credentials.keychainchunk", QtInfoMsg)
namespace KeychainChunk {
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
static void addSettingsToJob(Account *account, QKeychain::Job *job)
{
Q_UNUSED(account)
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
}
#endif
/*
* Job
*/
Job::Job(QObject *parent)
: QObject(parent)
{
_serviceName = Theme::instance()->appName();
}
/*
* WriteJob
*/
WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
// Windows workaround: Split the private key into chunks of 2048 bytes,
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
_chunkBuffer = data;
_chunkCount = 0;
}
void WriteJob::start()
{
slotWriteJobDone(nullptr);
}
void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
{
auto *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob);
// errors?
if (writeJob) {
_error = writeJob->error();
_errorString = writeJob->errorString();
if (writeJob->error() != NoError) {
qCWarning(lcKeychainChunk) << "Error while writing" << writeJob->key() << "chunk" << writeJob->errorString();
_chunkBuffer.clear();
}
}
// write a chunk if there is any in the buffer
if (!_chunkBuffer.isEmpty()) {
#if defined(Q_OS_WIN)
// Windows workaround: Split the data into chunks of 2048 bytes,
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
auto chunk = _chunkBuffer.left(KeychainChunk::ChunkSize);
_chunkBuffer = _chunkBuffer.right(_chunkBuffer.size() - chunk.size());
#else
// write full data in one chunk on non-Windows, as usual
auto chunk = _chunkBuffer;
_chunkBuffer.clear();
#endif
auto index = (_chunkCount++);
// keep the limit
if (_chunkCount > KeychainChunk::MaxChunks) {
qCWarning(lcKeychainChunk) << "Maximum chunk count exceeded while writing" << writeJob->key() << "chunk" << QString::number(index) << "cutting off after" << QString::number(KeychainChunk::MaxChunks) << "chunks";
writeJob->deleteLater();
_chunkBuffer.clear();
emit finished(this);
return;
}
const QString kck = AbstractCredentials::keychainKey(
_account->url().toString(),
_key + (index > 0 ? (QString(".") + QString::number(index)) : QString()),
_account->id());
auto *job = new QKeychain::WritePasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::WriteJob::slotWriteJobDone);
// only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows
job->setKey(kck);
job->setBinaryData(chunk);
job->start();
chunk.clear();
} else {
emit finished(this);
}
writeJob->deleteLater();
}
/*
* ReadJob
*/
ReadJob::ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
_keychainMigration = keychainMigration;
_chunkCount = 0;
_chunkBuffer.clear();
}
void ReadJob::start()
{
_chunkCount = 0;
_chunkBuffer.clear();
const QString kck = AbstractCredentials::keychainKey(
_account->url().toString(),
_key,
_keychainMigration ? QString() : _account->id());
auto *job = new QKeychain::ReadPasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
job->start();
}
void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
{
// Errors or next chunk?
auto *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob);
if (readJob) {
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
_chunkBuffer.append(readJob->binaryData());
_chunkCount++;
#if defined(Q_OS_WIN)
// try to fetch next chunk
if (_chunkCount < KeychainChunk::MaxChunks) {
const QString kck = AbstractCredentials::keychainKey(
_account->url().toString(),
_key + QString(".") + QString::number(_chunkCount),
_keychainMigration ? QString() : _account->id());
QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
job->start();
readJob->deleteLater();
return;
} else {
qCWarning(lcKeychainChunk) << "Maximum chunk count for" << readJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks;
}
#endif
} else {
if (readJob->error() != QKeychain::Error::EntryNotFound ||
((readJob->error() == QKeychain::Error::EntryNotFound) && _chunkCount == 0)) {
_error = readJob->error();
_errorString = readJob->errorString();
qCWarning(lcKeychainChunk) << "Unable to read" << readJob->key() << "chunk" << QString::number(_chunkCount) << readJob->errorString();
}
}
readJob->deleteLater();
}
emit finished(this);
}
} // namespace KeychainChunk
} // namespace OCC

View file

@ -1,120 +0,0 @@
/*
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#pragma once
#ifndef KEYCHAINCHUNK_H
#define KEYCHAINCHUNK_H
#include <QObject>
#include <keychain.h>
#include "accountfwd.h"
// We don't support insecure fallback
// #define KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK
namespace OCC {
namespace KeychainChunk {
/*
* Workaround for Windows:
*
* Split the keychain entry's data into chunks of 2048 bytes,
* to allow 4k (4096 bit) keys / large certs to be saved (see limits in webflowcredentials.h)
*/
static constexpr int ChunkSize = 2048;
static constexpr int MaxChunks = 10;
/*
* @brief: Abstract base class for KeychainChunk jobs.
*/
class Job : public QObject {
Q_OBJECT
public:
Job(QObject *parent = nullptr);
const QKeychain::Error error() const {
return _error;
}
const QString errorString() const {
return _errorString;
}
QByteArray binaryData() const {
return _chunkBuffer;
}
const bool insecureFallback() const {
return _insecureFallback;
}
// If we use it but don't support insecure fallback, give us nice compilation errors ;p
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
void setInsecureFallback(const bool &insecureFallback)
{
_insecureFallback = insecureFallback;
}
#endif
protected:
QString _serviceName;
Account *_account;
QString _key;
bool _insecureFallback = false;
bool _keychainMigration = false;
QKeychain::Error _error = QKeychain::NoError;
QString _errorString;
int _chunkCount = 0;
QByteArray _chunkBuffer;
}; // class Job
/*
* @brief: Simple wrapper class for QKeychain::WritePasswordJob, splits too large keychain entry's data into chunks on Windows
*/
class WriteJob : public KeychainChunk::Job {
Q_OBJECT
public:
WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr);
void start();
signals:
void finished(KeychainChunk::WriteJob *incomingJob);
private slots:
void slotWriteJobDone(QKeychain::Job *incomingJob);
}; // class WriteJob
/*
* @brief: Simple wrapper class for QKeychain::ReadPasswordJob, splits too large keychain entry's data into chunks on Windows
*/
class ReadJob : public KeychainChunk::Job {
Q_OBJECT
public:
ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent = nullptr);
void start();
signals:
void finished(KeychainChunk::ReadJob *incomingJob);
private slots:
void slotReadJobDone(QKeychain::Job *incomingJob);
}; // class ReadJob
} // namespace KeychainChunk
} // namespace OCC
#endif // KEYCHAINCHUNK_H

View file

@ -1,13 +1,13 @@
#include "webflowcredentials.h"
#include "creds/httpcredentials.h"
#include "creds/keychainchunk.h"
#include <QAuthenticator>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPointer>
#include <QTimer>
#include <keychain.h>
#include <QDialog>
#include <QVBoxLayout>
#include <QLabel>
@ -18,7 +18,6 @@
#include "theme.h"
#include "wizard/webview.h"
#include "webflowcredentialsdialog.h"
#include "keychainchunk.h"
using namespace QKeychain;
@ -146,7 +145,7 @@ void WebFlowCredentials::fetchFromKeychain() {
void WebFlowCredentials::askFromUser() {
// Determine if the old flow has to be used (GS for now)
// Do a DetermineAuthTypeJob to make sure that the server is still using Flow2
auto *job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
connect(job, &DetermineAuthTypeJob::authType, [this](DetermineAuthTypeJob::AuthType type) {
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
bool useFlow2 = (type != DetermineAuthTypeJob::WebViewFlow);
@ -239,9 +238,10 @@ void WebFlowCredentials::persist() {
// write cert if there is one
if (!_clientSslCertificate.isNull()) {
auto *job = new KeychainChunk::WriteJob(_account,
auto job = new KeychainChunk::WriteJob(_account,
_user + clientCertificatePEMC,
_clientSslCertificate.toPem());
_clientSslCertificate.toPem(),
this);
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
job->start();
} else {
@ -252,14 +252,12 @@ void WebFlowCredentials::persist() {
void WebFlowCredentials::slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
if(writeJob)
writeJob->deleteLater();
// write ssl key if there is one
if (!_clientSslKey.isNull()) {
auto *job = new KeychainChunk::WriteJob(_account,
auto job = new KeychainChunk::WriteJob(_account,
_user + clientKeyPEMC,
_clientSslKey.toPem());
_clientSslKey.toPem(),
this);
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
job->start();
} else {
@ -287,9 +285,10 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
return;
}
auto *job = new KeychainChunk::WriteJob(_account,
auto job = new KeychainChunk::WriteJob(_account,
_user + clientCaCertificatePEMC + QString::number(index),
cert.toPem());
cert.toPem(),
this);
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
job->start();
} else {
@ -299,9 +298,6 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
void WebFlowCredentials::slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
if(writeJob)
writeJob->deleteLater();
_clientSslCaCertificatesWriteQueue.clear();
// write ca certs if there are any
@ -324,8 +320,6 @@ void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJo
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
}
writeJob->deleteLater();
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
// next ca cert
writeSingleClientCaCertPEM();
@ -334,7 +328,7 @@ void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJo
}
// done storing ca certs, time for the password
auto *job = new WritePasswordJob(Theme::instance()->appName());
auto job = new WritePasswordJob(Theme::instance()->appName(), this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
@ -354,8 +348,6 @@ void WebFlowCredentials::slotWriteJobDone(QKeychain::Job *job)
default:
qCWarning(lcWebFlowCredentials) << "Error while writing password" << job->errorString();
}
auto *wjob = qobject_cast<WritePasswordJob *>(job);
wjob->deleteLater();
}
void WebFlowCredentials::invalidateToken() {
@ -384,13 +376,9 @@ void WebFlowCredentials::forgetSensitiveData() {
return;
}
auto *job = new DeletePasswordJob(Theme::instance()->appName());
auto job = new DeletePasswordJob(Theme::instance()->appName(), this);
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, [](QKeychain::Job *job) {
auto *djob = qobject_cast<DeletePasswordJob *>(job);
djob->deleteLater();
});
job->start();
invalidateToken();
@ -441,30 +429,16 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
void WebFlowCredentials::fetchFromKeychainHelper() {
// Read client cert from keychain
auto *job = new KeychainChunk::ReadJob(_account,
auto job = new KeychainChunk::ReadJob(_account,
_user + clientCertificatePEMC,
_keychainMigration);
_keychainMigration,
this);
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
job->start();
}
void WebFlowCredentials::slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
Q_ASSERT(!readJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
if (_retryOnKeyChainError && (readJob->error() == QKeychain::NoBackendAvailable
|| readJob->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(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
_retryOnKeyChainError = false;
return;
}
_retryOnKeyChainError = false;
#endif
// Store PEM in memory
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
@ -473,12 +447,11 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *re
}
}
readJob->deleteLater();
// Load key too
auto *job = new KeychainChunk::ReadJob(_account,
auto job = new KeychainChunk::ReadJob(_account,
_user + clientKeyPEMC,
_keychainMigration);
_keychainMigration,
this);
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
job->start();
}
@ -505,8 +478,6 @@ void WebFlowCredentials::slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *rea
qCWarning(lcWebFlowCredentials) << "Unable to read client key" << readJob->errorString();
}
readJob->deleteLater();
// Start fetching client CA certs
_clientSslCaCertificates.clear();
@ -517,9 +488,10 @@ void WebFlowCredentials::readSingleClientCaCertPEM()
{
// try to fetch a client ca cert
if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) {
auto *job = new KeychainChunk::ReadJob(_account,
auto job = new KeychainChunk::ReadJob(_account,
_user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
_keychainMigration);
_keychainMigration,
this);
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
job->start();
} else {
@ -538,8 +510,6 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob
_clientSslCaCertificates.append(sslCertificateList.at(0));
}
readJob->deleteLater();
// try next cert
readSingleClientCaCertPEM();
return;
@ -549,8 +519,6 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot" << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
}
}
readJob->deleteLater();
}
// Now fetch the actual server password
@ -559,7 +527,7 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob
_user,
_keychainMigration ? QString() : _account->id());
auto *job = new ReadPasswordJob(Theme::instance()->appName());
auto job = new ReadPasswordJob(Theme::instance()->appName(), this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
@ -570,7 +538,7 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob
}
void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
auto *job = qobject_cast<ReadPasswordJob *>(incomingJob);
auto job = qobject_cast<ReadPasswordJob *>(incomingJob);
QKeychain::Error error = job->error();
// If we could not find the entry try the old entries
@ -593,8 +561,6 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
}
emit fetched();
job->deleteLater();
// If keychain data was read from legacy location, wipe these entries and store new ones
if (_keychainMigration && _ready) {
_keychainMigration = false;
@ -606,19 +572,7 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
auto startDeleteJob = [this, oldKeychainEntries](QString key) {
auto *job = new DeletePasswordJob(Theme::instance()->appName());
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(false);
job->setKey(keychainKey(_account->url().toString(),
key,
oldKeychainEntries ? QString() : _account->id()));
connect(job, &Job::finished, this, [](QKeychain::Job *job) {
auto *djob = qobject_cast<DeletePasswordJob *>(job);
djob->deleteLater();
});
auto job = new KeychainChunk::DeleteJob(_account, key, oldKeychainEntries, this);
job->start();
};
@ -635,27 +589,17 @@ void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
*/
if(_account->isRemoteWipeRequested_HACK()) {
// <-- FIXME MS@2019-12-07
// Also delete key / cert sub-chunks (KeychainChunk takes care of the Windows workaround)
// The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows
startDeleteJob(_user + clientKeyPEMC);
startDeleteJob(_user + clientCertificatePEMC);
// CA cert slots
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
}
#if defined(Q_OS_WIN)
// Also delete key / cert sub-chunks (Windows workaround)
// The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows
for (auto chunk = 1; chunk < KeychainChunk::MaxChunks; chunk++) {
const QString strChunkSuffix = QString(".") + QString::number(chunk);
startDeleteJob(_user + clientKeyPEMC + strChunkSuffix);
startDeleteJob(_user + clientCertificatePEMC + strChunkSuffix);
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
}
}
#endif
// FIXME MS@2019-12-07 -->
}
// <-- FIXME MS@2019-12-07

View file

@ -122,7 +122,6 @@ protected:
bool _ready = false;
bool _credentialsValid = false;
bool _keychainMigration = false;
bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
WebFlowCredentialsDialog *_askDialog = nullptr;
};

View file

@ -24,6 +24,7 @@
#include <keychain.h>
using namespace OCC;
using namespace QKeychain;
Q_LOGGING_CATEGORY(lcProxy, "nextcloud.gui.credentials.proxy", QtInfoMsg)
@ -81,7 +82,7 @@ void ProxyAuthHandler::handleProxyAuthenticationRequired(
// Find the responsible QNAM if possible.
QNetworkAccessManager *sending_qnam = nullptr;
QWeakPointer<QNetworkAccessManager> qnam_alive;
if (auto *account = qobject_cast<Account *>(sender())) {
if (auto account = qobject_cast<Account *>(sender())) {
// Since we go into an event loop, it's possible for the account's qnam
// to be destroyed before we get back. We can use this to check for its
// liveness.
@ -132,11 +133,6 @@ void ProxyAuthHandler::handleProxyAuthenticationRequired(
}
}
void ProxyAuthHandler::slotKeychainJobDone()
{
_keychainJobRunning = false;
}
void ProxyAuthHandler::slotSenderDestroyed(QObject *obj)
{
_gaveCredentialsTo.remove(obj);
@ -154,11 +150,12 @@ bool ProxyAuthHandler::getCredsFromDialog()
// This function can be reentered while the dialog is open.
// If that's the case, continue processing the dialog until
// it's done.
++_waitingForDialog;
while (_dialog && _dialog->isVisible()) {
QApplication::processEvents(QEventLoop::ExcludeSocketNotifiers, 200);
if(_dialog) {
execAwait(_dialog.data(),
&QDialog::finished,
_waitingForDialog,
QEventLoop::ExcludeSocketNotifiers);
}
--_waitingForDialog;
if (_dialog && _dialog->result() == QDialog::Accepted) {
qCInfo(lcProxy) << "got creds for" << _proxy << "from dialog";
@ -169,10 +166,26 @@ bool ProxyAuthHandler::getCredsFromDialog()
return false;
}
template<class T, typename PointerToMemberFunction>
void ProxyAuthHandler::execAwait(const T *sender,
PointerToMemberFunction signal,
int &counter,
const QEventLoop::ProcessEventsFlags flags)
{
if (!sender) {
return;
}
QEventLoop waitLoop;
connect(sender, signal, &waitLoop, &QEventLoop::quit);
++counter;
waitLoop.exec(flags);
--counter;
}
bool ProxyAuthHandler::getCredsFromKeychain()
{
using namespace QKeychain;
if (_waitingForDialog) {
return false;
}
@ -190,9 +203,6 @@ bool ProxyAuthHandler::getCredsFromKeychain()
_readPasswordJob->setInsecureFallback(false);
_readPasswordJob->setKey(keychainPasswordKey());
_readPasswordJob->setAutoDelete(false);
connect(_readPasswordJob.data(), &QKeychain::Job::finished,
this, &ProxyAuthHandler::slotKeychainJobDone);
_keychainJobRunning = true;
_readPasswordJob->start();
}
@ -200,12 +210,9 @@ bool ProxyAuthHandler::getCredsFromKeychain()
// This really needs the counter and the flag here, because otherwise we get
// bad behavior when we reenter this code after the flag has been switched
// but before the while loop has finished.
++_waitingForKeychain;
_keychainJobRunning = true;
while (_keychainJobRunning) {
QApplication::processEvents(QEventLoop::AllEvents, 200);
}
--_waitingForKeychain;
execAwait(_readPasswordJob.data(),
&QKeychain::Job::finished,
_waitingForKeychain);
if (_readPasswordJob->error() == NoError) {
qCInfo(lcProxy) << "got creds for" << _proxy << "from keychain";
@ -222,8 +229,6 @@ bool ProxyAuthHandler::getCredsFromKeychain()
void ProxyAuthHandler::storeCredsInKeychain()
{
using namespace QKeychain;
if (_waitingForKeychain) {
return;
}
@ -232,22 +237,17 @@ void ProxyAuthHandler::storeCredsInKeychain()
_settings->setValue(keychainUsernameKey(), _username);
auto *job = new WritePasswordJob(Theme::instance()->appName(), this);
auto job = new WritePasswordJob(Theme::instance()->appName(), this);
job->setSettings(_settings.data());
job->setInsecureFallback(false);
job->setKey(keychainPasswordKey());
job->setTextData(_password);
job->setAutoDelete(false);
connect(job, &QKeychain::Job::finished, this, &ProxyAuthHandler::slotKeychainJobDone);
_keychainJobRunning = true;
job->start();
++_waitingForKeychain;
_keychainJobRunning = true;
while (_keychainJobRunning) {
QApplication::processEvents(QEventLoop::AllEvents, 200);
}
--_waitingForKeychain;
execAwait(job,
&QKeychain::Job::finished,
_waitingForKeychain);
job->deleteLater();
if (job->error() != NoError) {

View file

@ -57,7 +57,6 @@ public slots:
QAuthenticator *authenticator);
private slots:
void slotKeychainJobDone();
void slotSenderDestroyed(QObject *);
private:
@ -72,6 +71,12 @@ private:
/// Stores the current credentials in the keychain.
void storeCredsInKeychain();
template<class T, typename PointerToMemberFunction>
void execAwait(const T *sender,
PointerToMemberFunction signal,
int &counter,
const QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents);
QString keychainUsernameKey() const;
QString keychainPasswordKey() const;
@ -91,7 +96,6 @@ private:
/// waiting for.
int _waitingForDialog = 0;
int _waitingForKeychain = 0;
bool _keychainJobRunning = false;
QPointer<ProxyAuthDialog> _dialog;

View file

@ -59,6 +59,7 @@ set(libsync_SRCS
creds/dummycredentials.cpp
creds/abstractcredentials.cpp
creds/credentialscommon.cpp
creds/keychainchunk.cpp
)
if(TOKEN_AUTH_ONLY)

View file

@ -20,6 +20,7 @@
#include "common/asserts.h"
#include "creds/abstractcredentials.h"
#include "creds/keychainchunk.h"
#include "csync_exclude.h"
@ -651,7 +652,22 @@ void ConfigFile::setProxyType(int proxyType,
settings.setValue(QLatin1String(proxyPortC), port);
settings.setValue(QLatin1String(proxyNeedsAuthC), needsAuth);
settings.setValue(QLatin1String(proxyUserC), user);
settings.setValue(QLatin1String(proxyPassC), pass.toUtf8().toBase64());
if (pass.isEmpty()) {
// Security: Don't keep password in config file
settings.remove(QLatin1String(proxyPassC));
// Delete password from keychain
auto job = new KeychainChunk::DeleteJob(keychainProxyPasswordKey());
job->exec();
} else {
// Write password to keychain
auto job = new KeychainChunk::WriteJob(keychainProxyPasswordKey(), pass.toUtf8());
if (job->exec()) {
// Security: Don't keep password in config file
settings.remove(QLatin1String(proxyPassC));
}
}
}
settings.sync();
}
@ -726,8 +742,34 @@ QString ConfigFile::proxyUser() const
QString ConfigFile::proxyPassword() const
{
QByteArray pass = getValue(proxyPassC).toByteArray();
return QString::fromUtf8(QByteArray::fromBase64(pass));
QByteArray passEncoded = getValue(proxyPassC).toByteArray();
auto pass = QString::fromUtf8(QByteArray::fromBase64(passEncoded));
passEncoded.clear();
const auto key = keychainProxyPasswordKey();
if (!pass.isEmpty()) {
// Security: Migrate password from config file to keychain
auto job = new KeychainChunk::WriteJob(key, pass.toUtf8());
if (job->exec()) {
QSettings settings(configFile(), QSettings::IniFormat);
settings.remove(QLatin1String(proxyPassC));
qCInfo(lcConfigFile()) << "Migrated proxy password to keychain";
}
} else {
// Read password from keychain
auto job = new KeychainChunk::ReadJob(key);
if (job->exec()) {
pass = job->textData();
}
}
return pass;
}
QString ConfigFile::keychainProxyPasswordKey() const
{
return QString::fromLatin1("proxy-password");
}
int ConfigFile::useUploadLimit() const

View file

@ -198,6 +198,8 @@ private:
const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
QString keychainProxyPasswordKey() const;
private:
typedef QSharedPointer<AbstractCredentials> SharedCreds;

View file

@ -0,0 +1,487 @@
/*
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "account.h"
#include "keychainchunk.h"
#include "theme.h"
#include "networkjobs.h"
#include "configfile.h"
#include "creds/abstractcredentials.h"
#include <QApplication>
using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcKeychainChunk, "nextcloud.sync.credentials.keychainchunk", QtInfoMsg)
namespace KeychainChunk {
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
static void addSettingsToJob(Account *account, QKeychain::Job *job)
{
Q_UNUSED(account)
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
}
#endif
#ifdef Q_OS_WIN
void jobKeyPrependAppName(QString &key)
{
// NOTE: The following is normally done in AbstractCredentials::keychainKey
// when an _account is specified by our other ctr overload (see 'kck' in this file).
// On Windows the credential keys aren't namespaced properly
// by qtkeychain. To work around that we manually add namespacing
// to the generated keys. See #6125.
// It's safe to do that since the key format is changing for 2.4
// anyway to include the account ids. That means old keys can be
// migrated to new namespaced keys on windows for 2.4.
key.prepend(QCoreApplication::applicationName() + "_");
}
#endif
/*
* Job
*/
Job::Job(QObject *parent)
: QObject(parent)
{
_serviceName = Theme::instance()->appName();
}
Job::~Job()
{
_chunkCount = 0;
_chunkBuffer.clear();
}
QKeychain::Error Job::error() const
{
return _error;
}
QString Job::errorString() const
{
return _errorString;
}
QByteArray Job::binaryData() const
{
return _chunkBuffer;
}
QString Job::textData() const
{
return _chunkBuffer;
}
bool Job::insecureFallback() const
{
return _insecureFallback;
}
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
void Job::setInsecureFallback(bool insecureFallback)
{
_insecureFallback = insecureFallback;
}
#endif
bool Job::autoDelete() const
{
return _autoDelete;
}
void Job::setAutoDelete(bool autoDelete)
{
_autoDelete = autoDelete;
}
/*
* WriteJob
*/
WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
// Windows workaround: Split the private key into chunks of 2048 bytes,
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
_chunkBuffer = data;
_chunkCount = 0;
}
WriteJob::WriteJob(const QString &key, const QByteArray &data, QObject *parent)
: WriteJob(nullptr, key, data, parent)
{
#ifdef Q_OS_WIN
jobKeyPrependAppName(_key);
#endif
}
void WriteJob::start()
{
_error = QKeychain::NoError;
slotWriteJobDone(nullptr);
}
bool WriteJob::exec()
{
start();
QEventLoop waitLoop;
connect(this, &WriteJob::finished, &waitLoop, &QEventLoop::quit);
waitLoop.exec();
if (error() != NoError) {
qCWarning(lcKeychainChunk) << "WritePasswordJob failed with" << errorString();
return false;
}
return true;
}
void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
{
auto writeJob = qobject_cast<QKeychain::WritePasswordJob *>(incomingJob);
// Errors? (writeJob can be nullptr here, see: WriteJob::start)
if (writeJob) {
_error = writeJob->error();
_errorString = writeJob->errorString();
if (writeJob->error() != NoError) {
qCWarning(lcKeychainChunk) << "Error while writing" << writeJob->key() << "chunk" << writeJob->errorString();
_chunkBuffer.clear();
}
}
// write a chunk if there is any in the buffer
if (!_chunkBuffer.isEmpty()) {
#if defined(Q_OS_WIN)
// Windows workaround: Split the data into chunks of 2048 bytes,
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
auto chunk = _chunkBuffer.left(KeychainChunk::ChunkSize);
_chunkBuffer = _chunkBuffer.right(_chunkBuffer.size() - chunk.size());
#else
// write full data in one chunk on non-Windows, as usual
auto chunk = _chunkBuffer;
_chunkBuffer.clear();
#endif
auto index = (_chunkCount++);
// keep the limit
if (_chunkCount > KeychainChunk::MaxChunks) {
qCWarning(lcKeychainChunk) << "Maximum chunk count exceeded while writing" << writeJob->key() << "chunk" << QString::number(index) << "cutting off after" << QString::number(KeychainChunk::MaxChunks) << "chunks";
writeJob->deleteLater();
_chunkBuffer.clear();
emit finished(this);
if (_autoDelete) {
deleteLater();
}
return;
}
const QString keyWithIndex = _key + (index > 0 ? (QString(".") + QString::number(index)) : QString());
const QString kck = _account ? AbstractCredentials::keychainKey(
_account->url().toString(),
keyWithIndex,
_account->id()
) : keyWithIndex;
auto job = new QKeychain::WritePasswordJob(_serviceName, this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::WriteJob::slotWriteJobDone);
// only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows
job->setKey(kck);
job->setBinaryData(chunk);
job->start();
chunk.clear();
} else {
emit finished(this);
if (_autoDelete) {
deleteLater();
}
}
writeJob->deleteLater();
}
/*
* ReadJob
*/
ReadJob::ReadJob(Account *account, const QString &key, bool keychainMigration, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
_keychainMigration = keychainMigration;
_chunkCount = 0;
_chunkBuffer.clear();
}
ReadJob::ReadJob(const QString &key, QObject *parent)
: ReadJob(nullptr, key, false, parent)
{
#ifdef Q_OS_WIN
jobKeyPrependAppName(_key);
#endif
}
void ReadJob::start()
{
_chunkCount = 0;
_chunkBuffer.clear();
_error = QKeychain::NoError;
const QString kck = _account ? AbstractCredentials::keychainKey(
_account->url().toString(),
_key,
_keychainMigration ? QString() : _account->id()
) : _key;
auto job = new QKeychain::ReadPasswordJob(_serviceName, this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
job->start();
}
bool ReadJob::exec()
{
start();
QEventLoop waitLoop;
connect(this, &ReadJob::finished, &waitLoop, &QEventLoop::quit);
waitLoop.exec();
if (error() == NoError) {
return true;
}
_chunkCount = 0;
_chunkBuffer.clear();
if (error() != EntryNotFound) {
qCWarning(lcKeychainChunk) << "ReadPasswordJob failed with" << errorString();
}
return false;
}
void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
{
// Errors or next chunk?
auto readJob = qobject_cast<QKeychain::ReadPasswordJob *>(incomingJob);
Q_ASSERT(readJob);
if (readJob->error() == NoError && !readJob->binaryData().isEmpty()) {
_chunkBuffer.append(readJob->binaryData());
_chunkCount++;
#if defined(Q_OS_WIN)
// try to fetch next chunk
if (_chunkCount < KeychainChunk::MaxChunks) {
const QString keyWithIndex = _key + QString(".") + QString::number(_chunkCount);
const QString kck = _account ? AbstractCredentials::keychainKey(
_account->url().toString(),
keyWithIndex,
_keychainMigration ? QString() : _account->id()
) : keyWithIndex;
auto job = new QKeychain::ReadPasswordJob(_serviceName, this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
job->start();
readJob->deleteLater();
return;
} else {
qCWarning(lcKeychainChunk) << "Maximum chunk count for" << readJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks;
}
#endif
} else {
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
if (!readJob->insecureFallback()) { // If insecureFallback is set, the next test would be pointless
if (_retryOnKeyChainError && (readJob->error() == QKeychain::NoBackendAvailable
|| readJob->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(lcKeychainChunk) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
QTimer::singleShot(10000, this, &ReadJob::start);
_retryOnKeyChainError = false;
readJob->deleteLater();
return;
}
_retryOnKeyChainError = false;
}
#endif
if (readJob->error() != QKeychain::EntryNotFound ||
((readJob->error() == QKeychain::EntryNotFound) && _chunkCount == 0)) {
_error = readJob->error();
_errorString = readJob->errorString();
qCWarning(lcKeychainChunk) << "Unable to read" << readJob->key() << "chunk" << QString::number(_chunkCount) << readJob->errorString();
}
}
readJob->deleteLater();
emit finished(this);
if (_autoDelete) {
deleteLater();
}
}
/*
* DeleteJob
*/
DeleteJob::DeleteJob(Account *account, const QString &key, bool keychainMigration, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
_keychainMigration = keychainMigration;
}
DeleteJob::DeleteJob(const QString &key, QObject *parent)
: DeleteJob(nullptr, key, false, parent)
{
#ifdef Q_OS_WIN
jobKeyPrependAppName(_key);
#endif
}
void DeleteJob::start()
{
_chunkCount = 0;
_error = QKeychain::NoError;
const QString kck = _account ? AbstractCredentials::keychainKey(
_account->url().toString(),
_key,
_keychainMigration ? QString() : _account->id()
) : _key;
auto job = new QKeychain::DeletePasswordJob(_serviceName, this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::DeleteJob::slotDeleteJobDone);
job->start();
}
bool DeleteJob::exec()
{
start();
QEventLoop waitLoop;
connect(this, &DeleteJob::finished, &waitLoop, &QEventLoop::quit);
waitLoop.exec();
if (error() == NoError) {
return true;
}
_chunkCount = 0;
if (error() != EntryNotFound) {
qCWarning(lcKeychainChunk) << "DeletePasswordJob failed with" << errorString();
}
return false;
}
void DeleteJob::slotDeleteJobDone(QKeychain::Job *incomingJob)
{
// Errors or next chunk?
auto deleteJob = qobject_cast<QKeychain::DeletePasswordJob *>(incomingJob);
Q_ASSERT(deleteJob);
if (deleteJob->error() == NoError) {
_chunkCount++;
#if defined(Q_OS_WIN)
// try to delete next chunk
if (_chunkCount < KeychainChunk::MaxChunks) {
const QString keyWithIndex = _key + QString(".") + QString::number(_chunkCount);
const QString kck = _account ? AbstractCredentials::keychainKey(
_account->url().toString(),
keyWithIndex,
_keychainMigration ? QString() : _account->id()
) : keyWithIndex;
auto job = new QKeychain::DeletePasswordJob(_serviceName, this);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::DeleteJob::slotDeleteJobDone);
job->start();
deleteJob->deleteLater();
return;
} else {
qCWarning(lcKeychainChunk) << "Maximum chunk count for" << deleteJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks;
}
#endif
} else {
if (deleteJob->error() != QKeychain::EntryNotFound ||
((deleteJob->error() == QKeychain::EntryNotFound) && _chunkCount == 0)) {
_error = deleteJob->error();
_errorString = deleteJob->errorString();
qCWarning(lcKeychainChunk) << "Unable to delete" << deleteJob->key() << "chunk" << QString::number(_chunkCount) << deleteJob->errorString();
}
}
deleteJob->deleteLater();
emit finished(this);
if (_autoDelete) {
deleteLater();
}
}
} // namespace KeychainChunk
} // namespace OCC

View file

@ -0,0 +1,198 @@
/*
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#pragma once
#ifndef KEYCHAINCHUNK_H
#define KEYCHAINCHUNK_H
#include <QObject>
#include <keychain.h>
#include "accountfwd.h"
// We don't support insecure fallback
// #define KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK
namespace OCC {
namespace KeychainChunk {
/*
* Workaround for Windows:
*
* Split the keychain entry's data into chunks of 2048 bytes,
* to allow 4k (4096 bit) keys / large certs to be saved (see limits in webflowcredentials.h)
*/
static constexpr int ChunkSize = 2048;
static constexpr int MaxChunks = 10;
/*
* @brief: Abstract base class for KeychainChunk jobs.
*/
class OWNCLOUDSYNC_EXPORT Job : public QObject
{
Q_OBJECT
public:
Job(QObject *parent = nullptr);
virtual ~Job();
QKeychain::Error error() const;
QString errorString() const;
QByteArray binaryData() const;
QString textData() const;
bool insecureFallback() const;
// If we use it but don't support insecure fallback, give us nice compilation errors ;p
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
void setInsecureFallback(bool insecureFallback);
#endif
/**
* @return Whether this job autodeletes itself once finished() has been emitted. Default is true.
* @see setAutoDelete()
*/
bool autoDelete() const;
/**
* Set whether this job should autodelete itself once finished() has been emitted.
* @see autoDelete()
*/
void setAutoDelete(bool autoDelete);
protected:
QString _serviceName;
Account *_account;
QString _key;
bool _insecureFallback = false;
bool _autoDelete = true;
bool _keychainMigration = false;
QKeychain::Error _error = QKeychain::NoError;
QString _errorString;
int _chunkCount = 0;
QByteArray _chunkBuffer;
}; // class Job
/*
* @brief: Simple wrapper class for QKeychain::WritePasswordJob, splits too large keychain entry's data into chunks on Windows
*/
class OWNCLOUDSYNC_EXPORT WriteJob : public KeychainChunk::Job
{
Q_OBJECT
public:
WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr);
WriteJob(const QString &key, const QByteArray &data, QObject *parent = nullptr);
/**
* Call this method to start the job (async).
* You should connect some slot to the finished() signal first.
*
* @see QKeychain::Job::start()
*/
void start();
/**
* Call this method to start the job synchronously.
* Awaits completion with no need to connect some slot to the finished() signal first.
*
* @return Returns true on succeess (QKeychain::NoError).
*/
bool exec();
signals:
void finished(KeychainChunk::WriteJob *incomingJob);
private slots:
void slotWriteJobDone(QKeychain::Job *incomingJob);
}; // class WriteJob
/*
* @brief: Simple wrapper class for QKeychain::ReadPasswordJob, splits too large keychain entry's data into chunks on Windows
*/
class OWNCLOUDSYNC_EXPORT ReadJob : public KeychainChunk::Job
{
Q_OBJECT
public:
ReadJob(Account *account, const QString &key, bool keychainMigration, QObject *parent = nullptr);
ReadJob(const QString &key, QObject *parent = nullptr);
/**
* Call this method to start the job (async).
* You should connect some slot to the finished() signal first.
*
* @see QKeychain::Job::start()
*/
void start();
/**
* Call this method to start the job synchronously.
* Awaits completion with no need to connect some slot to the finished() signal first.
*
* @return Returns true on succeess (QKeychain::NoError).
*/
bool exec();
signals:
void finished(KeychainChunk::ReadJob *incomingJob);
private slots:
void slotReadJobDone(QKeychain::Job *incomingJob);
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
private:
bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
#endif
}; // class ReadJob
/*
* @brief: Simple wrapper class for QKeychain::DeletePasswordJob
*/
class OWNCLOUDSYNC_EXPORT DeleteJob : public KeychainChunk::Job
{
Q_OBJECT
public:
DeleteJob(Account *account, const QString &key, bool keychainMigration, QObject *parent = nullptr);
DeleteJob(const QString &key, QObject *parent = nullptr);
/**
* Call this method to start the job (async).
* You should connect some slot to the finished() signal first.
*
* @see QKeychain::Job::start()
*/
void start();
/**
* Call this method to start the job synchronously.
* Awaits completion with no need to connect some slot to the finished() signal first.
*
* @return Returns true on succeess (QKeychain::NoError).
*/
bool exec();
signals:
void finished(KeychainChunk::DeleteJob *incomingJob);
private slots:
void slotDeleteJobDone(QKeychain::Job *incomingJob);
}; // class DeleteJob
} // namespace KeychainChunk
} // namespace OCC
#endif // KEYCHAINCHUNK_H