KeychainChunk: Add synchronous method startAwait()

Awaits completion with no need to connect some slot to the finished() signal first,
inspired by the ProxyAuthHandler class.

Also add:
- Job dtor to safely erase passwords
- textData() method
- New ctor overloads to work with arbitrary keys (without Account ptrs)

Signed-off-by: Michael Schuster <michael@schuster.ms>
This commit is contained in:
Michael Schuster 2020-06-25 16:52:40 +02:00
parent 2a3ef044be
commit 18cbbc5052
No known key found for this signature in database
GPG key ID: 00819E3BF4177B28
2 changed files with 138 additions and 12 deletions

View file

@ -19,6 +19,8 @@
#include "configfile.h" #include "configfile.h"
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
#include <QApplication>
using namespace QKeychain; using namespace QKeychain;
namespace OCC { namespace OCC {
@ -46,6 +48,12 @@ Job::Job(QObject *parent)
_serviceName = Theme::instance()->appName(); _serviceName = Theme::instance()->appName();
} }
Job::~Job()
{
_chunkCount = 0;
_chunkBuffer.clear();
}
/* /*
* WriteJob * WriteJob
*/ */
@ -61,11 +69,45 @@ WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data,
_chunkCount = 0; _chunkCount = 0;
} }
WriteJob::WriteJob(const QString &key, const QByteArray &data, QObject *parent)
: WriteJob(nullptr, key, data, parent)
{
#ifdef Q_OS_WIN
// 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
}
void WriteJob::start() void WriteJob::start()
{ {
_isJobRunning = true;
slotWriteJobDone(nullptr); slotWriteJobDone(nullptr);
} }
bool WriteJob::startAwait()
{
start();
while (_isJobRunning) {
QApplication::processEvents(QEventLoop::AllEvents, 200);
}
if (error() != NoError) {
qCWarning(lcKeychainChunk) << "WritePasswordJob failed with" << errorString();
return false;
}
return true;
}
void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob) void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
{ {
auto *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob); auto *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob);
@ -105,14 +147,17 @@ void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
_chunkBuffer.clear(); _chunkBuffer.clear();
_isJobRunning = false;
emit finished(this); emit finished(this);
return; return;
} }
const QString kck = AbstractCredentials::keychainKey( const QString keyWithIndex = _key + (index > 0 ? (QString(".") + QString::number(index)) : QString());
_account->url().toString(), const QString kck = _account ? AbstractCredentials::keychainKey(
_key + (index > 0 ? (QString(".") + QString::number(index)) : QString()), _account->url().toString(),
_account->id()); keyWithIndex,
_account->id()
) : keyWithIndex;
auto *job = new QKeychain::WritePasswordJob(_serviceName); auto *job = new QKeychain::WritePasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) #if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
@ -127,6 +172,7 @@ void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
chunk.clear(); chunk.clear();
} else { } else {
_isJobRunning = false;
emit finished(this); emit finished(this);
} }
@ -148,15 +194,33 @@ ReadJob::ReadJob(Account *account, const QString &key, const bool &keychainMigra
_chunkBuffer.clear(); _chunkBuffer.clear();
} }
ReadJob::ReadJob(const QString &key, QObject *parent)
: ReadJob(nullptr, key, false, parent)
{
#ifdef Q_OS_WIN
// 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
}
void ReadJob::start() void ReadJob::start()
{ {
_chunkCount = 0; _chunkCount = 0;
_chunkBuffer.clear(); _chunkBuffer.clear();
const QString kck = AbstractCredentials::keychainKey( const QString kck = _account ? AbstractCredentials::keychainKey(
_account->url().toString(), _account->url().toString(),
_key, _key,
_keychainMigration ? QString() : _account->id()); _keychainMigration ? QString() : _account->id()
) : _key;
auto *job = new QKeychain::ReadPasswordJob(_serviceName); auto *job = new QKeychain::ReadPasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) #if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
@ -165,9 +229,30 @@ void ReadJob::start()
job->setInsecureFallback(_insecureFallback); job->setInsecureFallback(_insecureFallback);
job->setKey(kck); job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone); connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
_isJobRunning = true;
job->start(); job->start();
} }
bool ReadJob::startAwait()
{
start();
while (_isJobRunning) {
QApplication::processEvents(QEventLoop::AllEvents, 200);
}
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) void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
{ {
// Errors or next chunk? // Errors or next chunk?
@ -181,10 +266,12 @@ void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
// try to fetch next chunk // try to fetch next chunk
if (_chunkCount < KeychainChunk::MaxChunks) { if (_chunkCount < KeychainChunk::MaxChunks) {
const QString kck = AbstractCredentials::keychainKey( const QString keyWithIndex = _key + QString(".") + QString::number(_chunkCount);
_account->url().toString(), const QString kck = _account ? AbstractCredentials::keychainKey(
_key + QString(".") + QString::number(_chunkCount), _account->url().toString(),
_keychainMigration ? QString() : _account->id()); keyWithIndex,
_keychainMigration ? QString() : _account->id()
) : keyWithIndex;
QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName); QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) #if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
@ -230,6 +317,7 @@ void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
readJob->deleteLater(); readJob->deleteLater();
} }
_isJobRunning = false;
emit finished(this); emit finished(this);
} }

View file

@ -45,6 +45,8 @@ class Job : public QObject
public: public:
Job(QObject *parent = nullptr); Job(QObject *parent = nullptr);
virtual ~Job();
const QKeychain::Error error() const { const QKeychain::Error error() const {
return _error; return _error;
} }
@ -55,6 +57,9 @@ public:
QByteArray binaryData() const { QByteArray binaryData() const {
return _chunkBuffer; return _chunkBuffer;
} }
QString textData() const {
return _chunkBuffer;
}
const bool insecureFallback() const { const bool insecureFallback() const {
return _insecureFallback; return _insecureFallback;
@ -74,6 +79,7 @@ protected:
QString _key; QString _key;
bool _insecureFallback = false; bool _insecureFallback = false;
bool _keychainMigration = false; bool _keychainMigration = false;
bool _isJobRunning = false;
QKeychain::Error _error = QKeychain::NoError; QKeychain::Error _error = QKeychain::NoError;
QString _errorString; QString _errorString;
@ -90,8 +96,24 @@ class OWNCLOUDSYNC_EXPORT WriteJob : public KeychainChunk::Job
Q_OBJECT Q_OBJECT
public: public:
WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr); 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(); 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 startAwait();
signals: signals:
void finished(KeychainChunk::WriteJob *incomingJob); void finished(KeychainChunk::WriteJob *incomingJob);
@ -107,8 +129,24 @@ class OWNCLOUDSYNC_EXPORT ReadJob : public KeychainChunk::Job
Q_OBJECT Q_OBJECT
public: public:
ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent = nullptr); ReadJob(Account *account, const QString &key, const 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(); 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 startAwait();
signals: signals:
void finished(KeychainChunk::ReadJob *incomingJob); void finished(KeychainChunk::ReadJob *incomingJob);