Cleanup and replumb back to functional state

This commit is contained in:
Jonathan White 2018-12-19 17:52:12 -05:00 committed by Jonathan White
parent 9e2be34897
commit b96b86bfa7
14 changed files with 143 additions and 234 deletions

View File

@ -4,9 +4,9 @@ if(WITH_XC_CRYPTO_SSH)
set(crypto_ssh_SOURCES
bcrypt_pbkdf.cpp
blowfish.c
ASN1Key.cpp
BinaryStream.cpp
OpenSSHKey.cpp
ASN1Key.cpp
BinaryStream.cpp
OpenSSHKey.cpp
)
add_library(crypto_ssh STATIC ${crypto_ssh_SOURCES})

View File

@ -78,16 +78,17 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
connect(this, SIGNAL(apply()), SLOT(saveSettings()));
connect(this, SIGNAL(rejected()), SLOT(reject()));
connect(m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableAutoSaveOnExit(bool)));
// clang-format off
connect(m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)), SLOT(enableAutoSaveOnExit(bool)));
connect(m_generalUi->systrayShowCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableSystray(bool)));
connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)), m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
connect(m_secUi->lockDatabaseIdleCheckBox,
SIGNAL(toggled(bool)),
m_secUi->lockDatabaseIdleSpinBox,
SLOT(setEnabled(bool)));
connect(m_secUi->touchIDResetCheckBox, SIGNAL(toggled(bool)), m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)),
m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
connect(m_secUi->lockDatabaseIdleCheckBox, SIGNAL(toggled(bool)),
m_secUi->lockDatabaseIdleSpinBox, SLOT(setEnabled(bool)));
connect(m_secUi->touchIDResetCheckBox, SIGNAL(toggled(bool)),
m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
// clang-format on
#ifndef WITH_XC_NETWORKING
m_secUi->privacy->setVisible(false);

View File

@ -58,6 +58,7 @@
#include "gui/entry/EntryView.h"
#include "gui/group/EditGroupWidget.h"
#include "gui/group/GroupView.h"
#include "keeshare/KeeShare.h"
#include "touchid/TouchID.h"
#include "config-keepassx.h"
@ -372,6 +373,9 @@ void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
connectDatabaseSignals();
m_groupView->changeDatabase(m_db);
processAutoOpen();
#ifdef WITH_XC_KEESHARE
KeeShare::instance()->connectDatabase(m_db, oldDb);
#endif
}
void DatabaseWidget::cloneEntry()

View File

@ -161,12 +161,16 @@ MainWindow::MainWindow()
#ifdef WITH_XC_KEESHARE
KeeShare::init(this);
m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
connect(KeeShare::instance(), SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
#endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
// clang-format off
connect(m_ui->globalMessageWidget, &MessageWidget::linkActivated, &MessageWidget::openHttpUrl);
connect(m_ui->globalMessageWidget, SIGNAL(showAnimationStarted()), m_ui->globalMessageWidgetContainer, SLOT(show()));
connect(m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), m_ui->globalMessageWidgetContainer, SLOT(hide()));
// clang-format on
m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);

View File

@ -63,6 +63,9 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
addPage(tr("Group"), FilePath::instance()->icon("actions", "document-edit"), m_editGroupWidgetMain);
addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_editGroupWidgetIcons);
#ifdef WITH_XC_KEESHARE
addEditPage(new EditGroupPageKeeShare(this));
#endif
addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties);
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
@ -82,10 +85,6 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
// clang-format on
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
#ifdef WITH_XC_KEESHARE
addEditPage(new EditGroupPageKeeShare(this));
#endif
}
EditGroupWidget::~EditGroupWidget()

View File

@ -82,7 +82,6 @@ private:
QPointer<EditWidgetProperties> m_editWidgetProperties;
QScopedPointer<Group> m_temporaryGroup;
QPointer<Database> m_database;
QPointer<Group> m_group;
QSharedPointer<Database> m_db;

View File

@ -32,10 +32,10 @@
namespace
{
static const QString KeeShare_Reference("KeeShare/Reference");
static const QString KeeShare_Own("KeeShare/Settings.own");
static const QString KeeShare_Foreign("KeeShare/Settings.foreign");
static const QString KeeShare_Active("KeeShare/Settings.active");
static const QString KeeShare_Reference("KeeShare/Reference");
static const QString KeeShare_Own("KeeShare/Settings.own");
static const QString KeeShare_Foreign("KeeShare/Settings.foreign");
static const QString KeeShare_Active("KeeShare/Settings.active");
}
KeeShare* KeeShare::m_instance = nullptr;
@ -49,6 +49,12 @@ KeeShare* KeeShare::instance()
return m_instance;
}
KeeShare::KeeShare(QObject* parent)
: QObject(parent)
{
connect(config(), SIGNAL(changed(QString)), SLOT(handleSettingsChanged(QString)));
}
void KeeShare::init(QObject* parent)
{
Q_ASSERT(!m_instance);
@ -162,61 +168,19 @@ QString KeeShare::indicatorSuffix(const Group* group, const QString& text)
void KeeShare::connectDatabase(QSharedPointer<Database> newDb, QSharedPointer<Database> oldDb)
{
if (oldDb && m_observersByDatabase.contains(oldDb.data())) {
QPointer<ShareObserver> observer = m_observersByDatabase.take(oldDb.data());
if (oldDb && m_observersByDatabase.contains(oldDb->uuid())) {
QPointer<ShareObserver> observer = m_observersByDatabase.take(oldDb->uuid());
if (observer) {
delete observer;
}
}
if (newDb && !m_observersByDatabase.contains(newDb.data())) {
if (newDb && !m_observersByDatabase.contains(newDb->uuid())) {
QPointer<ShareObserver> observer(new ShareObserver(newDb, this));
m_observersByDatabase[newDb.data()] = observer;
m_observersByDatabase[newDb->uuid()] = observer;
connect(observer.data(),
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
this,
SLOT(emitSharingMessage(QString, MessageWidget::MessageType)));
}
}
void KeeShare::handleDatabaseOpened(QSharedPointer<Database> db)
{
QPointer<ShareObserver> observer = m_observersByDatabase.value(db.data());
if (observer) {
observer->handleDatabaseOpened();
}
}
void KeeShare::handleDatabaseSaved(QSharedPointer<Database> db)
{
QPointer<ShareObserver> observer = m_observersByDatabase.value(db.data());
if (observer) {
observer->handleDatabaseSaved();
}
}
void KeeShare::emitSharingMessage(const QString& message, KMessageWidget::MessageType type)
{
QObject* observer = sender();
auto db = m_databasesByObserver.value(observer);
if (db) {
emit sharingMessage(db, message, type);
}
}
void KeeShare::handleDatabaseDeleted(QObject* db)
{
auto observer = m_observersByDatabase.take(db);
if (observer) {
m_databasesByObserver.remove(observer);
}
}
void KeeShare::handleObserverDeleted(QObject* observer)
{
auto database = m_databasesByObserver.take(observer);
if (database) {
m_observersByDatabase.remove(database.data());
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)));
}
}
@ -226,9 +190,3 @@ void KeeShare::handleSettingsChanged(const QString& key)
emit activeChanged();
}
}
KeeShare::KeeShare(QObject* parent)
: QObject(parent)
{
connect(config(), SIGNAL(changed(QString)), this, SLOT(handleSettingsChanged(QString)));
}

View File

@ -19,7 +19,7 @@
#define KEEPASSXC_KEESHARE_H
#include <QMap>
#include <QObject>
#include <QUuid>
#include "gui/MessageWidget.h"
#include "keeshare/KeeShareSettings.h"
@ -32,7 +32,7 @@ class QXmlStreamReader;
class KeeShare : public QObject
{
Q_OBJECT
Q_OBJECT
public:
static KeeShare* instance();
static void init(QObject* parent);
@ -54,17 +54,12 @@ public:
static QString referenceTypeLabel(const KeeShareSettings::Reference& reference);
void connectDatabase(QSharedPointer<Database> newDb, QSharedPointer<Database> oldDb);
void handleDatabaseOpened(QSharedPointer<Database> db);
void handleDatabaseSaved(QSharedPointer<Database> db);
signals:
void activeChanged();
void sharingMessage(QSharedPointer<Database>, QString, MessageWidget::MessageType);
void sharingMessage(QString, MessageWidget::MessageType);
private slots:
void emitSharingMessage(const QString&, MessageWidget::MessageType);
void handleDatabaseDeleted(QObject*);
void handleObserverDeleted(QObject*);
void handleSettingsChanged(const QString&);
private:
@ -72,8 +67,7 @@ private:
explicit KeeShare(QObject* parent);
QMap<QObject*, QPointer<ShareObserver>> m_observersByDatabase;
QMap<QObject*, QSharedPointer<Database>> m_databasesByObserver;
QMap<QUuid, QPointer<ShareObserver>> m_observersByDatabase;
};
#endif // KEEPASSXC_KEESHARE_H

View File

@ -95,10 +95,6 @@ void SettingsWidgetKeeShare::saveSettings()
// TODO HNH: This depends on the order of saving new data - a better model would be to
// store changes to the settings in a temporary object and check on the final values
// of this object (similar scheme to Entry) - this way we could validate the settings before save
if (active.in) {
emit settingsMessage(tr("Make sure to have a history size greater than 2 to prevent data loss when importing!"), MessageWidget::Warning);
}
KeeShare::setOwn(m_own);
KeeShare::setForeign(m_foreign);
KeeShare::setActive(active);

View File

@ -48,84 +48,96 @@
namespace
{
static const QString KeeShare_Signature("container.share.signature");
static const QString KeeShare_Container("container.share.kdbx");
static const QString KeeShare_Signature("container.share.signature");
static const QString KeeShare_Container("container.share.kdbx");
enum Trust
{
None,
Invalid,
Single,
Lasting,
Known,
Own
};
QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data,
const KeeShareSettings::Reference& reference,
const KeeShareSettings::Certificate& ownCertificate,
const QList<KeeShareSettings::Certificate>& knownCertificates,
const KeeShareSettings::Sign& sign)
{
if (sign.signature.isEmpty()) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(ShareObserver::tr("Untrustworthy container without signature"));
warning.setText(ShareObserver::tr("Do you want to import from unsigned container %1").arg(reference.path));
auto yes = warning.addButton(ShareObserver::tr("Import once"), QMessageBox::ButtonRole::YesRole);
auto no = warning.addButton(ShareObserver::tr("No"), QMessageBox::ButtonRole::NoRole);
warning.setDefaultButton(no);
warning.exec();
const auto trust = warning.clickedButton() == yes ? Single : None;
return qMakePair(trust, KeeShareSettings::Certificate());
}
auto key = sign.certificate.sshKey();
key.openKey(QString());
const Signature signer;
if (!signer.verify(data, sign.signature, key)) {
const QFileInfo info(reference.path);
qCritical("Invalid signature for sharing container %s.", qPrintable(info.absoluteFilePath()));
return qMakePair(Invalid, KeeShareSettings::Certificate());
}
if (ownCertificate.key == sign.certificate.key) {
return qMakePair(Own, ownCertificate);
}
for (const auto& certificate : knownCertificates) {
if (certificate.key == certificate.key && certificate.trusted) {
return qMakePair(Known, certificate);
}
}
enum Trust
{
None,
Invalid,
Single,
Lasting,
Known,
Own
};
QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data,
const KeeShareSettings::Reference& reference,
const KeeShareSettings::Certificate& ownCertificate,
const QList<KeeShareSettings::Certificate>& knownCertificates,
const KeeShareSettings::Sign& sign)
{
if (sign.signature.isEmpty()) {
QMessageBox warning;
warning.setIcon(QMessageBox::Question);
warning.setWindowTitle(ShareObserver::tr("Import from untrustworthy certificate for sharing container"));
warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2")
.arg(sign.certificate.signer)
.arg(sign.certificate.fingerprint()));
auto yes = warning.addButton(ShareObserver::tr("Import and trust"), QMessageBox::ButtonRole::YesRole);
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(ShareObserver::tr("Untrustworthy container without signature"));
warning.setText(ShareObserver::tr("Do you want to import from unsigned container %1").arg(reference.path));
auto yes = warning.addButton(ShareObserver::tr("Import once"), QMessageBox::ButtonRole::YesRole);
auto no = warning.addButton(ShareObserver::tr("No"), QMessageBox::ButtonRole::NoRole);
warning.setDefaultButton(no);
warning.exec();
if (warning.clickedButton() != yes) {
qWarning("Prevented import due to untrusted certificate of %s", qPrintable(sign.certificate.signer));
return qMakePair(None, sign.certificate);
}
return qMakePair(Lasting, sign.certificate);
const auto trust = warning.clickedButton() == yes ? Single : None;
return qMakePair(trust, KeeShareSettings::Certificate());
}
auto key = sign.certificate.sshKey();
key.openKey(QString());
const Signature signer;
if (!signer.verify(data, sign.signature, key)) {
const QFileInfo info(reference.path);
qCritical("Invalid signature for sharing container %s.", qPrintable(info.absoluteFilePath()));
return qMakePair(Invalid, KeeShareSettings::Certificate());
}
if (ownCertificate.key == sign.certificate.key) {
return qMakePair(Own, ownCertificate);
}
for (const auto& certificate : knownCertificates) {
if (certificate.key == certificate.key && certificate.trusted) {
return qMakePair(Known, certificate);
}
}
QMessageBox warning;
warning.setIcon(QMessageBox::Question);
warning.setWindowTitle(ShareObserver::tr("Import from untrustworthy certificate for sharing container"));
warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2")
.arg(sign.certificate.signer)
.arg(sign.certificate.fingerprint()));
auto yes = warning.addButton(ShareObserver::tr("Import and trust"), QMessageBox::ButtonRole::YesRole);
auto no = warning.addButton(ShareObserver::tr("No"), QMessageBox::ButtonRole::NoRole);
warning.setDefaultButton(no);
warning.exec();
if (warning.clickedButton() != yes) {
qWarning("Prevented import due to untrusted certificate of %s", qPrintable(sign.certificate.signer));
return qMakePair(None, sign.certificate);
}
return qMakePair(Lasting, sign.certificate);
}
} // End Namespace
ShareObserver::ShareObserver(QSharedPointer<Database> db, QObject* parent)
: QObject(parent)
, m_db(std::move(db))
, m_fileWatcher(new BulkFileWatcher(this))
: QObject(parent)
, m_db(std::move(db))
, m_fileWatcher(new BulkFileWatcher(this))
{
connect(KeeShare::instance(), SIGNAL(activeChanged()), this, SLOT(handleDatabaseChanged()));
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(handleDatabaseChanged()));
connect(m_db.data(), SIGNAL(modified()), this, SLOT(handleDatabaseChanged()));
connect(m_db.data(), SIGNAL(modified()), SLOT(handleDatabaseChanged()));
connect(m_db.data(), SIGNAL(databaseSaved()), SLOT(handleDatabaseSaved()));
connect(m_fileWatcher, SIGNAL(fileCreated(QString)), this, SLOT(handleFileCreated(QString)));
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(handleFileChanged(QString)));
connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), this, SLOT(handleFileRemoved(QString)));
connect(m_fileWatcher, SIGNAL(fileCreated(QString)), SLOT(handleFileUpdated(QString)));
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileUpdated(QString)));
connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), SLOT(handleFileUpdated(QString)));
const auto active = KeeShare::active();
if (!active.in && !active.out) {
deinitialize();
} else {
reinitialize();
}
}
ShareObserver::~ShareObserver()
@ -147,6 +159,7 @@ void ShareObserver::reinitialize()
KeeShareSettings::Reference oldReference;
KeeShareSettings::Reference newReference;
};
const auto active = KeeShare::active();
QList<Update> updated;
QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
@ -155,6 +168,7 @@ void ShareObserver::reinitialize()
if (couple.oldReference == couple.newReference) {
continue;
}
m_groupToReference.remove(couple.group);
m_referenceToGroup.remove(couple.oldReference);
m_shareToGroup.remove(couple.oldReference.path);
@ -174,6 +188,7 @@ void ShareObserver::reinitialize()
if (!update.oldReference.path.isEmpty()) {
m_fileWatcher->removePath(update.oldReference.path);
}
if (!update.newReference.path.isEmpty() && update.newReference.type != KeeShareSettings::Inactive) {
m_fileWatcher->addPath(update.newReference.path);
}
@ -196,6 +211,7 @@ void ShareObserver::reinitialize()
}
}
}
notifyAbout(success, warning, error);
}
@ -229,20 +245,8 @@ void ShareObserver::handleDatabaseChanged()
}
}
void ShareObserver::handleFileUpdated(const QString& path, Change change)
void ShareObserver::handleFileUpdated(const QString& path)
{
switch (change) {
case Creation:
qDebug("File created %s", qPrintable(path));
break;
case Update:
qDebug("File changed %s", qPrintable(path));
break;
case Deletion:
qDebug("File deleted %s", qPrintable(path));
break;
}
const Result result = this->importFromReferenceContainer(path);
if (!result.isValid()) {
return;
@ -262,21 +266,6 @@ void ShareObserver::handleFileUpdated(const QString& path, Change change)
notifyAbout(success, warning, error);
}
void ShareObserver::handleFileCreated(const QString& path)
{
handleFileUpdated(path, Creation);
}
void ShareObserver::handleFileChanged(const QString& path)
{
handleFileUpdated(path, Update);
}
void ShareObserver::handleFileRemoved(const QString& path)
{
handleFileUpdated(path, Deletion);
}
ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup)
{
const QFileInfo info(reference.path);
@ -471,30 +460,11 @@ Database* ShareObserver::exportIntoContainer(const KeeShareSettings::Reference&
return targetDb;
}
const QSharedPointer<Database> ShareObserver::database() const
{
return m_db;
}
QSharedPointer<Database> ShareObserver::database()
{
return m_db;
}
void ShareObserver::handleDatabaseOpened()
{
if (!m_db) {
Q_ASSERT(m_db);
return;
}
const auto active = KeeShare::active();
if (!active.in && !active.out) {
deinitialize();
} else {
reinitialize();
}
}
QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
{
QList<Result> results;

View File

@ -35,37 +35,23 @@ class Database;
class ShareObserver : public QObject
{
Q_OBJECT
Q_OBJECT
public:
explicit ShareObserver(QSharedPointer<Database> db, QObject* parent = nullptr);
~ShareObserver();
void handleDatabaseSaved();
void handleDatabaseOpened();
const QSharedPointer<Database> database() const;
QSharedPointer<Database> database();
signals:
void sharingMessage(QString, MessageWidget::MessageType);
public slots:
void handleDatabaseChanged();
private slots:
void handleFileCreated(const QString& path);
void handleFileChanged(const QString& path);
void handleFileRemoved(const QString& path);
void handleDatabaseChanged();
void handleDatabaseSaved();
void handleFileUpdated(const QString& path);
private:
enum Change
{
Creation,
Update,
Deletion
};
struct Result
{
enum Type
@ -97,7 +83,6 @@ private:
QList<ShareObserver::Result> exportIntoReferenceContainers();
void deinitialize();
void reinitialize();
void handleFileUpdated(const QString& path, Change change);
void notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error);
private:

View File

@ -55,10 +55,10 @@ EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(showSharingState()));
const auto types = QList<KeeShareSettings::Type>() << KeeShareSettings::Inactive
<< KeeShareSettings::ImportFrom
<< KeeShareSettings::ExportTo
<< KeeShareSettings::SynchronizeWith;
const auto types = QList<KeeShareSettings::Type>() << KeeShareSettings::Inactive
<< KeeShareSettings::ImportFrom
<< KeeShareSettings::ExportTo
<< KeeShareSettings::SynchronizeWith;
for (const auto& type : types) {
QString name;
switch (type) {
@ -121,7 +121,6 @@ void EditGroupWidgetKeeShare::update()
m_ui->pathEdit->clear();
m_ui->passwordGenerator->hide();
m_ui->togglePasswordGeneratorButton->setChecked(false);
} else {
const auto reference = KeeShare::referenceOf(m_temporaryGroup);
@ -179,14 +178,9 @@ void EditGroupWidgetKeeShare::selectPath()
}
switch (reference.type) {
case KeeShareSettings::ImportFrom:
filename = fileDialog()->getFileName(this,
tr("Select import source"),
defaultDirPath,
filters,
nullptr,
QFileDialog::DontConfirmOverwrite,
filetype,
filename);
filename = fileDialog()->getFileName(
this, tr("Select import source"), defaultDirPath, filters, nullptr, QFileDialog::DontConfirmOverwrite,
filetype, filename);
break;
case KeeShareSettings::ExportTo:
filename = fileDialog()->getFileName(
@ -203,7 +197,7 @@ void EditGroupWidgetKeeShare::selectPath()
return;
}
setPath(filename);
m_ui->pathEdit->setText(filename);
config()->set("KeeShare/LastShareDir", QFileInfo(filename).absolutePath());
}

View File

@ -9,5 +9,5 @@ if(WITH_XC_SSHAGENT)
)
add_library(sshagent STATIC ${sshagent_SOURCES})
target_link_libraries(sshagent Qt5::Core Qt5::Widgets Qt5::Network ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
target_link_libraries(sshagent Qt5::Core Qt5::Widgets Qt5::Network ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
endif()

View File

@ -99,7 +99,12 @@ set(TEST_LIBRARIES
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES})
set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp mock/MockClock.cpp util/TemporaryFile.cpp stub/TestRandom.cpp)
set(testsupport_SOURCES
modeltest.cpp
FailDevice.cpp
mock/MockClock.cpp
util/TemporaryFile.cpp
stub/TestRandom.cpp)
add_library(testsupport STATIC ${testsupport_SOURCES})
target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test)