Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/FileWatcher.cpp127
-rw-r--r--src/core/FileWatcher.h19
-rw-r--r--src/keeshare/ShareObserver.cpp1210
-rw-r--r--src/keeshare/ShareObserver.h2
4 files changed, 719 insertions, 639 deletions
diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp
index 4873adbf4..d57d7de52 100644
--- a/src/core/FileWatcher.cpp
+++ b/src/core/FileWatcher.cpp
@@ -37,8 +37,7 @@ DelayingFileWatcher::DelayingFileWatcher(QObject* parent)
{
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
connect(&m_fileUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
- connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged()));
-
+ connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), this, SIGNAL(fileChanged()));
m_fileChangeDelayTimer.setSingleShot(true);
m_fileUnblockTimer.setSingleShot(true);
}
@@ -122,6 +121,7 @@ BulkFileWatcher::BulkFileWatcher(QObject* parent)
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileChanged(QString)));
connect(&m_fileWatcher, SIGNAL(directoryChanged(QString)), SLOT(handleDirectoryChanged(QString)));
connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
+ connect(&m_pendingSignalsTimer, SIGNAL(timeout()), this, SLOT(emitSignals()));
m_fileWatchUnblockTimer.setSingleShot(true);
}
@@ -132,7 +132,7 @@ void BulkFileWatcher::clear()
m_fileWatcher.removePath(info.absoluteFilePath());
m_fileWatcher.removePath(info.absolutePath());
}
- m_filePaths.clear();
+ m_watchedPaths.clear();
m_watchedFilesInDirectory.clear();
m_ignoreFilesChangess.clear();
}
@@ -140,70 +140,127 @@ void BulkFileWatcher::clear()
void BulkFileWatcher::removePath(const QString& path)
{
const QFileInfo info(path);
- m_fileWatcher.removePath(info.absoluteFilePath());
- m_fileWatcher.removePath(info.absolutePath());
- m_filePaths.remove(info.absoluteFilePath());
- m_filePaths.remove(info.absolutePath());
- m_watchedFilesInDirectory[info.absolutePath()].remove(info.absoluteFilePath());
+ const QString filePath = info.absoluteFilePath();
+ const QString directoryPath = info.absolutePath();
+ m_watchedFilesInDirectory[directoryPath].remove(filePath);
+ m_fileWatcher.removePath(filePath);
+ m_watchedPaths.remove(filePath);
+ if (m_watchedFilesInDirectory[directoryPath].isEmpty()) {
+ m_fileWatcher.removePath(directoryPath);
+ m_watchedPaths.remove(directoryPath);
+ m_watchedFilesInDirectory.remove(directoryPath);
+ }
}
void BulkFileWatcher::addPath(const QString& path)
{
const QFileInfo info(path);
- m_fileWatcher.addPath(info.absoluteFilePath());
- m_fileWatcher.addPath(info.absolutePath());
- m_filePaths.insert(info.absoluteFilePath());
- m_filePaths.insert(info.absolutePath());
- m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists();
-}
-
-void BulkFileWatcher::restart(const QString& path)
-{
- const QFileInfo info(path);
- Q_ASSERT(m_filePaths.contains(info.absoluteFilePath()));
- Q_ASSERT(m_filePaths.contains(info.absolutePath()));
- m_fileWatcher.addPath(info.absoluteFilePath());
- m_fileWatcher.addPath(info.absolutePath());
+ const QString filePath = info.absoluteFilePath();
+ const QString directoryPath = info.absolutePath();
+ if (!m_watchedPaths.value(filePath)) {
+ const bool fileSuccess = m_fileWatcher.addPath(filePath);
+ m_watchedPaths[filePath] = fileSuccess;
+ }
+ if (!m_watchedPaths.value(directoryPath)) {
+ const bool directorySuccess = m_fileWatcher.addPath(directoryPath);
+ m_watchedPaths[directoryPath] = directorySuccess;
+ }
+ m_watchedFilesInDirectory[directoryPath][filePath] = info.exists();
}
void BulkFileWatcher::handleFileChanged(const QString& path)
{
+ const QFileInfo info(path);
+ const QString filePath = info.absoluteFilePath();
+ const QString directoryPath = info.absolutePath();
+ const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryPath];
+ const bool created = !watchedFiles[filePath] && info.exists();
+ const bool deleted = watchedFiles[filePath] && !info.exists();
+ const bool changed = !created && !deleted;
addPath(path);
- const QFileInfo info(path);
if (m_ignoreFilesChangess[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) {
// changes are blocked
return;
}
-
- emit fileChanged(path);
+ if (created) {
+ qDebug("File created %s", qPrintable(path));
+ scheduleSignal(Created, filePath);
+ }
+ if (changed) {
+ qDebug("File changed %s", qPrintable(path));
+ scheduleSignal(Updated, filePath);
+ }
+ if (deleted) {
+ qDebug("File removed %s", qPrintable(path));
+ scheduleSignal(Removed, filePath);
+ }
}
void BulkFileWatcher::handleDirectoryChanged(const QString& path)
{
qDebug("Directory changed %s", qPrintable(path));
const QFileInfo directoryInfo(path);
- const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryInfo.absoluteFilePath()];
+ const QString directoryPath = directoryInfo.absoluteFilePath();
+ QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryPath];
for (const QString& filename : watchedFiles.keys()) {
const QFileInfo fileInfo(filename);
- const bool existed = watchedFiles[fileInfo.absoluteFilePath()];
+ const QString filePath = fileInfo.absoluteFilePath();
+ const bool existed = watchedFiles[filePath];
if (!fileInfo.exists() && existed) {
- qDebug("Remove watch file %s", qPrintable(fileInfo.absoluteFilePath()));
- m_fileWatcher.removePath(fileInfo.absolutePath());
- emit fileRemoved(fileInfo.absoluteFilePath());
+ qDebug("Remove watch file %s", qPrintable(filePath));
+ m_fileWatcher.removePath(filePath);
+ m_watchedPaths.remove(filePath);
+ watchedFiles.remove(filePath);
+ scheduleSignal(Removed, filePath);
}
if (!existed && fileInfo.exists()) {
- qDebug("Add watch file %s", qPrintable(fileInfo.absoluteFilePath()));
- m_fileWatcher.addPath(fileInfo.absolutePath());
- emit fileCreated(fileInfo.absoluteFilePath());
+ qDebug("Add watch file %s", qPrintable(filePath));
+ if (!m_watchedPaths.value(filePath)) {
+ const bool success = m_fileWatcher.addPath(filePath);
+ m_watchedPaths[filePath] = success;
+ watchedFiles[filePath] = fileInfo.exists();
+ }
+ scheduleSignal(Created, filePath);
}
if (existed && fileInfo.exists()) {
+ // this case is handled using
qDebug("Refresh watch file %s", qPrintable(fileInfo.absoluteFilePath()));
m_fileWatcher.removePath(fileInfo.absolutePath());
m_fileWatcher.addPath(fileInfo.absolutePath());
- emit fileChanged(fileInfo.absoluteFilePath());
+ scheduleSignal(Updated, filePath);
+ }
+ m_watchedFilesInDirectory[directoryPath][filePath] = fileInfo.exists();
+ }
+}
+
+void BulkFileWatcher::emitSignals()
+{
+ QMap<QString, QList<Signal>> queued;
+ m_pendingSignals.swap(queued);
+ for (const auto& path : queued.keys()){
+ const auto &signal = queued[path];
+ if (signal.last() == Removed) {
+ emit fileRemoved(path);
+ continue;
+ }
+ if (signal.first() == Created){
+ emit fileCreated(path);
+ continue;
}
- m_watchedFilesInDirectory[fileInfo.absolutePath()][fileInfo.absoluteFilePath()] = fileInfo.exists();
+ emit fileChanged(path);
+ }
+}
+
+void BulkFileWatcher::scheduleSignal(Signal signal, const QString &path)
+{
+ // we need to collect signals since the file watcher API may send multiple signals for a "single" change
+ // therefore we wait until the event loop finished before starting to import any changes
+ const QString filePath = QFileInfo(path).absoluteFilePath();
+ m_pendingSignals[filePath] << signal;
+
+ if (!m_pendingSignalsTimer.isActive()) {
+ m_pendingSignalsTimer.start();
}
}
diff --git a/src/core/FileWatcher.h b/src/core/FileWatcher.h
index de7dbb1c2..1ef46785c 100644
--- a/src/core/FileWatcher.h
+++ b/src/core/FileWatcher.h
@@ -57,6 +57,12 @@ private:
class BulkFileWatcher : public QObject
{
Q_OBJECT
+
+ enum Signal {
+ Created,
+ Updated,
+ Removed
+ };
public:
explicit BulkFileWatcher(QObject* parent = nullptr);
@@ -65,7 +71,6 @@ public:
void removePath(const QString& path);
void addPath(const QString& path);
- void restart(const QString& path);
void ignoreFileChanges(const QString& path);
@@ -80,13 +85,21 @@ public slots:
private slots:
void handleFileChanged(const QString& path);
void handleDirectoryChanged(const QString& path);
+ void emitSignals();
+
+private:
+ void scheduleSignal(Signal event, const QString &path);
private:
- QSet<QString> m_filePaths;
+ QMap<QString, bool> m_watchedPaths;
QMap<QString, QDateTime> m_ignoreFilesChangess;
QFileSystemWatcher m_fileWatcher;
QMap<QString, QMap<QString, bool>> m_watchedFilesInDirectory;
- QTimer m_fileWatchUnblockTimer; // needed for Import/Export-References
+ // needed for Import/Export-References to prevent update after self-write
+ QTimer m_fileWatchUnblockTimer;
+ // needed to tolerate multiple signals for same event
+ QTimer m_pendingSignalsTimer;
+ QMap<QString, QList<Signal>> m_pendingSignals;
};
#endif // KEEPASSXC_FILEWATCHER_H
diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp
index bb87c1c75..036413e6b 100644
--- a/src/keeshare/ShareObserver.cpp
+++ b/src/keeshare/ShareObserver.cpp
@@ -56,116 +56,111 @@ static const QString KeeShare_Container("container.share.kdbx");
enum Trust
{
- Invalid,
- Own,
- UntrustedForever,
- UntrustedOnce,
- TrustedOnce,
- TrustedForever,
+ Invalid,
+ Own,
+ UntrustedForever,
+ UntrustedOnce,
+ TrustedOnce,
+ TrustedForever,
};
bool isOfExportType(const QFileInfo &fileInfo, const QString type)
{
- return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive);
+ return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive);
}
QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data,
- const KeeShareSettings::Reference& reference,
- const KeeShareSettings::Certificate& ownCertificate,
- const QList<KeeShareSettings::ScopedCertificate>& knownCertificates,
- const KeeShareSettings::Sign& sign)
+ const KeeShareSettings::Reference& reference,
+ const KeeShareSettings::Certificate& ownCertificate,
+ const QList<KeeShareSettings::ScopedCertificate>& knownCertificates,
+ const KeeShareSettings::Sign& sign)
{
- KeeShareSettings::Certificate certificate;
- if (!sign.signature.isEmpty()) {
- certificate = sign.certificate;
- auto key = sign.certificate.sshKey();
- key.openKey(QString());
- const auto signer = Signature();
- if (!signer.verify(data, sign.signature, key)) {
- qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path));
- return {Invalid, KeeShareSettings::Certificate()};
- }
-
- if (ownCertificate.key == sign.certificate.key) {
- return {Own, ownCertificate };
- }
- }
- enum Scope { Invalid, Global, Local };
- Scope scope = Invalid;
- KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask;
- for (const auto& scopedCertificate : knownCertificates) {
- if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) {
- // Global scope is overwritten by local scope
- scope = Global;
- trusted = scopedCertificate.trust;
- }
- if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) {
- scope = Local;
- trusted = scopedCertificate.trust;
- break;
- }
- }
- if (scope != Invalid && trusted != KeeShareSettings::Trust::Ask){
- // we introduce now scopes if there is a global
- return {trusted == KeeShareSettings::Trust::Trusted ? TrustedForever : UntrustedForever, certificate};
- }
-
- QMessageBox warning;
- if (sign.signature.isEmpty()){
- warning.setIcon(QMessageBox::Warning);
- warning.setWindowTitle(ShareObserver::tr("Import from container without signature"));
- warning.setText(ShareObserver::tr("We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?")
- .arg(reference.path));
- }
- else {
- warning.setIcon(QMessageBox::Question);
- warning.setWindowTitle(ShareObserver::tr("Import from container with certificate"));
- warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2 from %3")
- .arg(certificate.signer, certificate.fingerprint(), reference.path));
- }
- auto untrustedOnce = warning.addButton(ShareObserver::tr("Not this time"), QMessageBox::ButtonRole::NoRole);
- auto untrustedForever = warning.addButton(ShareObserver::tr("Never"), QMessageBox::ButtonRole::NoRole);
- auto trustedForever = warning.addButton(ShareObserver::tr("Always"), QMessageBox::ButtonRole::YesRole);
- auto trustedOnce = warning.addButton(ShareObserver::tr("Just this time"), QMessageBox::ButtonRole::YesRole);
- warning.setDefaultButton(untrustedOnce);
- warning.exec();
- if (warning.clickedButton() == trustedForever){
- return {TrustedForever, certificate };
- }
- if (warning.clickedButton() == trustedOnce){
- return {TrustedOnce, certificate};
- }
- if (warning.clickedButton() == untrustedOnce){
- return {UntrustedOnce, certificate };
- }
- if (warning.clickedButton() == untrustedForever){
- return {UntrustedForever, certificate };
- }
- return {UntrustedOnce, certificate };
+ KeeShareSettings::Certificate certificate;
+ if (!sign.signature.isEmpty()) {
+ certificate = sign.certificate;
+ auto key = sign.certificate.sshKey();
+ key.openKey(QString());
+ const auto signer = Signature();
+ if (!signer.verify(data, sign.signature, key)) {
+ qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path));
+ return {Invalid, KeeShareSettings::Certificate()};
+ }
+
+ if (ownCertificate.key == sign.certificate.key) {
+ return {Own, ownCertificate };
+ }
+ }
+ enum Scope { Invalid, Global, Local };
+ Scope scope = Invalid;
+ KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask;
+ for (const auto& scopedCertificate : knownCertificates) {
+ if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) {
+ // Global scope is overwritten by local scope
+ scope = Global;
+ trusted = scopedCertificate.trust;
+ }
+ if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) {
+ scope = Local;
+ trusted = scopedCertificate.trust;
+ break;
+ }
+ }
+ if (scope != Invalid && trusted != KeeShareSettings::Trust::Ask){
+ // we introduce now scopes if there is a global
+ return {trusted == KeeShareSettings::Trust::Trusted ? TrustedForever : UntrustedForever, certificate};
+ }
+
+ QMessageBox warning;
+ if (sign.signature.isEmpty()){
+ warning.setIcon(QMessageBox::Warning);
+ warning.setWindowTitle(ShareObserver::tr("Import from container without signature"));
+ warning.setText(ShareObserver::tr("We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?")
+ .arg(reference.path));
+ }
+ else {
+ warning.setIcon(QMessageBox::Question);
+ warning.setWindowTitle(ShareObserver::tr("Import from container with certificate"));
+ warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2 from %3")
+ .arg(certificate.signer, certificate.fingerprint(), reference.path));
+ }
+ auto untrustedOnce = warning.addButton(ShareObserver::tr("Not this time"), QMessageBox::ButtonRole::NoRole);
+ auto untrustedForever = warning.addButton(ShareObserver::tr("Never"), QMessageBox::ButtonRole::NoRole);
+ auto trustedForever = warning.addButton(ShareObserver::tr("Always"), QMessageBox::ButtonRole::YesRole);
+ auto trustedOnce = warning.addButton(ShareObserver::tr("Just this time"), QMessageBox::ButtonRole::YesRole);
+ warning.setDefaultButton(untrustedOnce);
+ warning.exec();
+ if (warning.clickedButton() == trustedForever){
+ return {TrustedForever, certificate };
+ }
+ if (warning.clickedButton() == trustedOnce){
+ return {TrustedOnce, certificate};
+ }
+ if (warning.clickedButton() == untrustedOnce){
+ return {UntrustedOnce, certificate };
+ }
+ if (warning.clickedButton() == untrustedForever){
+ return {UntrustedForever, certificate };
+ }
+ return {UntrustedOnce, 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()), SLOT(handleDatabaseChanged()));
+ connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(handleDatabaseChanged()));
- connect(m_db.data(), SIGNAL(databaseModified()), SLOT(handleDatabaseChanged()));
- connect(m_db.data(), SIGNAL(databaseSaved()), SLOT(handleDatabaseSaved()));
+ connect(m_db.data(), SIGNAL(databaseModified()), SLOT(handleDatabaseChanged()));
+ connect(m_db.data(), SIGNAL(databaseSaved()), SLOT(handleDatabaseSaved()));
- 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)));
+ connect(m_fileWatcher, SIGNAL(fileCreated(QString)), SLOT(handleFileCreated(QString)));
+ connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileUpdated(QString)));
+ connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), SLOT(handleFileDeleted(QString)));
- const auto active = KeeShare::active();
- if (!active.in && !active.out) {
- deinitialize();
- } else {
- reinitialize();
- }
+ handleDatabaseChanged();
}
ShareObserver::~ShareObserver()
@@ -174,604 +169,617 @@ ShareObserver::~ShareObserver()
void ShareObserver::deinitialize()
{
- m_fileWatcher->clear();
- m_groupToReference.clear();
- m_referenceToGroup.clear();
+ m_fileWatcher->clear();
+ m_groupToReference.clear();
+ m_referenceToGroup.clear();
}
void ShareObserver::reinitialize()
{
- struct Update
- {
- Group* group;
- KeeShareSettings::Reference oldReference;
- KeeShareSettings::Reference newReference;
- };
-
- const auto active = KeeShare::active();
- QList<Update> updated;
- QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
- for (Group* group : groups) {
- Update couple{group, m_groupToReference.value(group), KeeShare::referenceOf(group)};
- if (couple.oldReference == couple.newReference) {
- continue;
- }
-
- m_groupToReference.remove(couple.group);
- m_referenceToGroup.remove(couple.oldReference);
- m_shareToGroup.remove(couple.oldReference.path);
- if (couple.newReference.isValid() && ((active.in && couple.newReference.isImporting())
- || (active.out && couple.newReference.isExporting()))) {
- m_groupToReference[couple.group] = couple.newReference;
- m_referenceToGroup[couple.newReference] = couple.group;
- m_shareToGroup[couple.newReference.path] = couple.group;
- }
- updated << couple;
- }
-
- QStringList success;
- QStringList warning;
- QStringList error;
- for (const auto& update : updated) {
- 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);
- }
-
- if (update.newReference.isImporting()) {
- const auto result = this->importFromReferenceContainer(update.newReference.path);
- if (!result.isValid()) {
- // tolerable result - blocked import or missing source
- continue;
- }
-
- if (result.isError()) {
- error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
- } else if (result.isWarning()) {
- warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
- } else if (result.isInfo()) {
- success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message);
- } else {
- success << tr("Imported from %1").arg(result.path);
- }
- }
- }
-
- notifyAbout(success, warning, error);
+ struct Update
+ {
+ Group* group;
+ KeeShareSettings::Reference oldReference;
+ KeeShareSettings::Reference newReference;
+ };
+
+ const auto active = KeeShare::active();
+ QList<Update> updated;
+ QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
+ for (Group* group : groups) {
+ Update couple{group, m_groupToReference.value(group), KeeShare::referenceOf(group)};
+ if (couple.oldReference == couple.newReference) {
+ continue;
+ }
+
+ m_groupToReference.remove(couple.group);
+ m_referenceToGroup.remove(couple.oldReference);
+ m_shareToGroup.remove(couple.oldReference.path);
+ if (couple.newReference.isValid() && ((active.in && couple.newReference.isImporting())
+ || (active.out && couple.newReference.isExporting()))) {
+ m_groupToReference[couple.group] = couple.newReference;
+ m_referenceToGroup[couple.newReference] = couple.group;
+ m_shareToGroup[couple.newReference.path] = couple.group;
+ }
+ updated << couple;
+ }
+
+ QStringList success;
+ QStringList warning;
+ QStringList error;
+ for (const auto& update : updated) {
+ 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);
+ }
+
+ if (update.newReference.isImporting()) {
+ const auto result = this->importFromReferenceContainer(update.newReference.path);
+ if (!result.isValid()) {
+ // tolerable result - blocked import or missing source
+ continue;
+ }
+
+ if (result.isError()) {
+ error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
+ } else if (result.isWarning()) {
+ warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
+ } else if (result.isInfo()) {
+ success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message);
+ } else {
+ success << tr("Imported from %1").arg(result.path);
+ }
+ }
+ }
+
+ notifyAbout(success, warning, error);
}
void ShareObserver::notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error)
{
- if (error.isEmpty() && warning.isEmpty() && success.isEmpty()) {
- return;
- }
-
- MessageWidget::MessageType type = MessageWidget::Positive;
- if (!warning.isEmpty()) {
- type = MessageWidget::Warning;
- }
- if (!error.isEmpty()) {
- type = MessageWidget::Error;
- }
- emit sharingMessage((success + warning + error).join("\n"), type);
+ if (error.isEmpty() && warning.isEmpty() && success.isEmpty()) {
+ return;
+ }
+
+ MessageWidget::MessageType type = MessageWidget::Positive;
+ if (!warning.isEmpty()) {
+ type = MessageWidget::Warning;
+ }
+ if (!error.isEmpty()) {
+ type = MessageWidget::Error;
+ }
+ emit sharingMessage((success + warning + error).join("\n"), type);
}
void ShareObserver::handleDatabaseChanged()
{
- if (!m_db) {
- Q_ASSERT(m_db);
- return;
- }
- const auto active = KeeShare::active();
- if (!active.out && !active.in) {
- deinitialize();
- } else {
- reinitialize();
- }
+ if (!m_db) {
+ Q_ASSERT(m_db);
+ return;
+ }
+ const auto active = KeeShare::active();
+ if (!active.out && !active.in) {
+ deinitialize();
+ } else {
+ reinitialize();
+ }
+}
+
+void ShareObserver::handleFileCreated(const QString& path)
+{
+ // there is currently no difference in handling an added share or updating from one
+ this->handleFileUpdated(path);
+}
+
+void ShareObserver::handleFileDeleted(const QString& path)
+{
+ Q_UNUSED(path);
+ // There is nothing we can or should do for now, ignore deletion
}
void ShareObserver::handleFileUpdated(const QString& path)
{
- const Result result = this->importFromReferenceContainer(path);
- if (!result.isValid()) {
- return;
- }
- QStringList success;
- QStringList warning;
- QStringList error;
- if (result.isError()) {
- error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
- } else if (result.isWarning()) {
- warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
- } else if (result.isInfo()) {
- success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message);
- } else {
- success << tr("Imported from %1").arg(result.path);
- }
- notifyAbout(success, warning, error);
+ const Result result = this->importFromReferenceContainer(path);
+ if (!result.isValid()) {
+ return;
+ }
+ QStringList success;
+ QStringList warning;
+ QStringList error;
+ if (result.isError()) {
+ error << tr("Import from %1 failed (%2)").arg(result.path, result.message);
+ } else if (result.isWarning()) {
+ warning << tr("Import from %1 failed (%2)").arg(result.path, result.message);
+ } else if (result.isInfo()) {
+ success << tr("Import from %1 successful (%2)").arg(result.path, result.message);
+ } else {
+ success << tr("Imported from %1").arg(result.path);
+ }
+ notifyAbout(success, warning, error);
}
ShareObserver::Result ShareObserver::importSingedContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup)
{
#if !defined(WITH_XC_KEESHARE_SECURE)
- Q_UNUSED(targetGroup);
- return { reference.path, Result::Warning, tr("Signed share container are not supported - import prevented") };
+ Q_UNUSED(targetGroup);
+ return { reference.path, Result::Warning, tr("Signed share container are not supported - import prevented") };
#else
- QuaZip zip(reference.path);
- if (!zip.open(QuaZip::mdUnzip)) {
- qCritical("Unable to open file %s.", qPrintable(reference.path));
- return {reference.path, Result::Error, tr("File is not readable")};
- }
- const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container;
- const auto files = zip.getFileInfoList();
- QSet<QString> actual;
- for (const auto& file : files) {
- actual << file.name;
- }
- if (expected != actual) {
- qCritical("Invalid sharing container %s.", qPrintable(reference.path));
- return {reference.path, Result::Error, tr("Invalid sharing container")};
- }
-
- zip.setCurrentFile(KeeShare_Signature);
- QuaZipFile signatureFile(&zip);
- signatureFile.open(QuaZipFile::ReadOnly);
- QTextStream stream(&signatureFile);
-
- const auto sign = KeeShareSettings::Sign::deserialize(stream.readAll());
- signatureFile.close();
-
- zip.setCurrentFile(KeeShare_Container);
- QuaZipFile databaseFile(&zip);
- databaseFile.open(QuaZipFile::ReadOnly);
- auto payload = databaseFile.readAll();
- databaseFile.close();
- QBuffer buffer(&payload);
- buffer.open(QIODevice::ReadOnly);
-
- KeePass2Reader reader;
- auto key = QSharedPointer<CompositeKey>::create();
- key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
- auto sourceDb = QSharedPointer<Database>::create();
- if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
- qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
- return {reference.path, Result::Error, reader.errorString()};
- }
-
- auto foreign = KeeShare::foreign();
- auto own = KeeShare::own();
- auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
- switch (trust.first) {
- case Invalid:
- qWarning("Prevent untrusted import");
- return {reference.path, Result::Error, tr("Untrusted import prevented")};
-
- case UntrustedForever:
- case TrustedForever: {
- bool found = false;
- const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
- for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
- if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) {
- scopedCertificate.certificate.signer = trust.second.signer;
- scopedCertificate.path = reference.path;
- scopedCertificate.trust = trusted;
- found = true;
- }
- }
- if (!found) {
- foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted};
- // we need to update with the new signer
- KeeShare::setForeign(foreign);
- }
- if (trust.first == TrustedForever) {
- qDebug("Synchronize %s %s with %s",
- qPrintable(reference.path),
- qPrintable(targetGroup->name()),
- qPrintable(sourceDb->rootGroup()->name()));
- Merger merger(sourceDb->rootGroup(), targetGroup);
- merger.setForcedMergeMode(Group::Synchronize);
- const bool changed = merger.merge();
- if (changed) {
- return {reference.path, Result::Success, tr("Successful signed import")};
- }
- }
- // Silent ignore of untrusted import or unchanging import
- return {};
- }
- case TrustedOnce:
- case Own: {
- qDebug("Synchronize %s %s with %s",
- qPrintable(reference.path),
- qPrintable(targetGroup->name()),
- qPrintable(sourceDb->rootGroup()->name()));
- Merger merger(sourceDb->rootGroup(), targetGroup);
- merger.setForcedMergeMode(Group::Synchronize);
- const bool changed = merger.merge();
- if (changed) {
- return {reference.path, Result::Success, tr("Successful signed import")};
- }
- return {};
- }
- default:
- Q_ASSERT(false);
- return {reference.path, Result::Error, tr("Unexpected error")};
- }
+ QuaZip zip(reference.path);
+ if (!zip.open(QuaZip::mdUnzip)) {
+ qCritical("Unable to open file %s.", qPrintable(reference.path));
+ return {reference.path, Result::Error, tr("File is not readable")};
+ }
+ const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container;
+ const auto files = zip.getFileInfoList();
+ QSet<QString> actual;
+ for (const auto& file : files) {
+ actual << file.name;
+ }
+ if (expected != actual) {
+ qCritical("Invalid sharing container %s.", qPrintable(reference.path));
+ return {reference.path, Result::Error, tr("Invalid sharing container")};
+ }
+
+ zip.setCurrentFile(KeeShare_Signature);
+ QuaZipFile signatureFile(&zip);
+ signatureFile.open(QuaZipFile::ReadOnly);
+ QTextStream stream(&signatureFile);
+
+ const auto sign = KeeShareSettings::Sign::deserialize(stream.readAll());
+ signatureFile.close();
+
+ zip.setCurrentFile(KeeShare_Container);
+ QuaZipFile databaseFile(&zip);
+ databaseFile.open(QuaZipFile::ReadOnly);
+ auto payload = databaseFile.readAll();
+ databaseFile.close();
+ QBuffer buffer(&payload);
+ buffer.open(QIODevice::ReadOnly);
+
+ KeePass2Reader reader;
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
+ auto sourceDb = QSharedPointer<Database>::create();
+ if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
+ qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
+ return {reference.path, Result::Error, reader.errorString()};
+ }
+
+ auto foreign = KeeShare::foreign();
+ auto own = KeeShare::own();
+ auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
+ switch (trust.first) {
+ case Invalid:
+ qWarning("Prevent untrusted import");
+ return {reference.path, Result::Error, tr("Untrusted import prevented")};
+
+ case UntrustedForever:
+ case TrustedForever: {
+ bool found = false;
+ const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
+ for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
+ if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) {
+ scopedCertificate.certificate.signer = trust.second.signer;
+ scopedCertificate.path = reference.path;
+ scopedCertificate.trust = trusted;
+ found = true;
+ }
+ }
+ if (!found) {
+ foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted};
+ // we need to update with the new signer
+ KeeShare::setForeign(foreign);
+ }
+ if (trust.first == TrustedForever) {
+ qDebug("Synchronize %s %s with %s",
+ qPrintable(reference.path),
+ qPrintable(targetGroup->name()),
+ qPrintable(sourceDb->rootGroup()->name()));
+ Merger merger(sourceDb->rootGroup(), targetGroup);
+ merger.setForcedMergeMode(Group::Synchronize);
+ const bool changed = merger.merge();
+ if (changed) {
+ return {reference.path, Result::Success, tr("Successful signed import")};
+ }
+ }
+ // Silent ignore of untrusted import or unchanging import
+ return {};
+ }
+ case TrustedOnce:
+ case Own: {
+ qDebug("Synchronize %s %s with %s",
+ qPrintable(reference.path),
+ qPrintable(targetGroup->name()),
+ qPrintable(sourceDb->rootGroup()->name()));
+ Merger merger(sourceDb->rootGroup(), targetGroup);
+ merger.setForcedMergeMode(Group::Synchronize);
+ const bool changed = merger.merge();
+ if (changed) {
+ return {reference.path, Result::Success, tr("Successful signed import")};
+ }
+ return {};
+ }
+ default:
+ Q_ASSERT(false);
+ return {reference.path, Result::Error, tr("Unexpected error")};
+ }
#endif
}
ShareObserver::Result ShareObserver::importUnsignedContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup)
{
#if !defined(WITH_XC_KEESHARE_INSECURE)
- Q_UNUSED(targetGroup);
- return {reference.path, Result::Warning, tr("Unsigned share container are not supported - import prevented")};
+ Q_UNUSED(targetGroup);
+ return {reference.path, Result::Warning, tr("Unsigned share container are not supported - import prevented")};
#else
- QFile file(reference.path);
- if (!file.open(QIODevice::ReadOnly)){
- qCritical("Unable to open file %s.", qPrintable(reference.path));
- return {reference.path, Result::Error, tr("File is not readable")};
- }
- auto payload = file.readAll();
- file.close();
- QBuffer buffer(&payload);
- buffer.open(QIODevice::ReadOnly);
-
- KeePass2Reader reader;
- auto key = QSharedPointer<CompositeKey>::create();
- key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
- auto sourceDb = QSharedPointer<Database>::create();
- if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
- qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
- return {reference.path, Result::Error, reader.errorString()};
- }
-
- auto foreign = KeeShare::foreign();
- const auto own = KeeShare::own();
- const auto sign = KeeShareSettings::Sign(); // invalid sign
- auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
- switch(trust.first) {
- case UntrustedForever:
- case TrustedForever: {
- bool found = false;
- const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
- for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
- if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) {
- scopedCertificate.certificate.signer = trust.second.signer;
- scopedCertificate.path = reference.path;
- scopedCertificate.trust = trusted;
- found = true;
- }
- }
- if (!found) {
- foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted};
- // we need to update with the new signer
- KeeShare::setForeign(foreign);
- }
- if (trust.first == TrustedForever) {
- qDebug("Synchronize %s %s with %s",
- qPrintable(reference.path),
- qPrintable(targetGroup->name()),
- qPrintable(sourceDb->rootGroup()->name()));
- Merger merger(sourceDb->rootGroup(), targetGroup);
- merger.setForcedMergeMode(Group::Synchronize);
- const bool changed = merger.merge();
- if (changed) {
- return {reference.path, Result::Success, tr("Successful signed import")};
- }
- }
- return {};
- }
-
- case TrustedOnce: {
- qDebug("Synchronize %s %s with %s",
- qPrintable(reference.path),
- qPrintable(targetGroup->name()),
- qPrintable(sourceDb->rootGroup()->name()));
- Merger merger(sourceDb->rootGroup(), targetGroup);
- merger.setForcedMergeMode(Group::Synchronize);
- const bool changed = merger.merge();
- if (changed) {
- return {reference.path, Result::Success, tr("Successful unsigned import")};
- }
- return {};
- }
- default:
- qWarning("Prevent untrusted import");
- return {reference.path, Result::Warning, tr("Untrusted import prevented")};
- }
+ QFile file(reference.path);
+ if (!file.open(QIODevice::ReadOnly)){
+ qCritical("Unable to open file %s.", qPrintable(reference.path));
+ return {reference.path, Result::Error, tr("File is not readable")};
+ }
+ auto payload = file.readAll();
+ file.close();
+ QBuffer buffer(&payload);
+ buffer.open(QIODevice::ReadOnly);
+
+ KeePass2Reader reader;
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
+ auto sourceDb = QSharedPointer<Database>::create();
+ if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
+ qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
+ return {reference.path, Result::Error, reader.errorString()};
+ }
+
+ auto foreign = KeeShare::foreign();
+ const auto own = KeeShare::own();
+ const auto sign = KeeShareSettings::Sign(); // invalid sign
+ auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
+ switch(trust.first) {
+ case UntrustedForever:
+ case TrustedForever: {
+ bool found = false;
+ const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
+ for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
+ if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) {
+ scopedCertificate.certificate.signer = trust.second.signer;
+ scopedCertificate.path = reference.path;
+ scopedCertificate.trust = trusted;
+ found = true;
+ }
+ }
+ if (!found) {
+ foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted};
+ // we need to update with the new signer
+ KeeShare::setForeign(foreign);
+ }
+ if (trust.first == TrustedForever) {
+ qDebug("Synchronize %s %s with %s",
+ qPrintable(reference.path),
+ qPrintable(targetGroup->name()),
+ qPrintable(sourceDb->rootGroup()->name()));
+ Merger merger(sourceDb->rootGroup(), targetGroup);
+ merger.setForcedMergeMode(Group::Synchronize);
+ const bool changed = merger.merge();
+ if (changed) {
+ return {reference.path, Result::Success, tr("Successful signed import")};
+ }
+ }
+ return {};
+ }
+
+ case TrustedOnce: {
+ qDebug("Synchronize %s %s with %s",
+ qPrintable(reference.path),
+ qPrintable(targetGroup->name()),
+ qPrintable(sourceDb->rootGroup()->name()));
+ Merger merger(sourceDb->rootGroup(), targetGroup);
+ merger.setForcedMergeMode(Group::Synchronize);
+ const bool changed = merger.merge();
+ if (changed) {
+ return {reference.path, Result::Success, tr("Successful unsigned import")};
+ }
+ return {};
+ }
+ default:
+ qWarning("Prevent untrusted import");
+ return {reference.path, Result::Warning, tr("Untrusted import prevented")};
+ }
#endif
}
ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup)
{
- const QFileInfo info(reference.path);
- if (!info.exists()) {
- qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath()));
- return {reference.path, Result::Warning, tr("File does not exist")};
- }
-
- if (isOfExportType(info, KeeShare::signedContainerFileType())) {
- return importSingedContainerInto(reference, targetGroup);
- }
- if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
- return importUnsignedContainerInto(reference, targetGroup);
- }
- return {reference.path, Result::Error, tr("Unknown share container type")};
+ const QFileInfo info(reference.path);
+ if (!info.exists()) {
+ qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath()));
+ return {reference.path, Result::Warning, tr("File does not exist")};
+ }
+
+ if (isOfExportType(info, KeeShare::signedContainerFileType())) {
+ return importSingedContainerInto(reference, targetGroup);
+ }
+ if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
+ return importUnsignedContainerInto(reference, targetGroup);
+ }
+ return {reference.path, Result::Error, tr("Unknown share container type")};
}
ShareObserver::Result ShareObserver::importFromReferenceContainer(const QString& path)
{
- if (!KeeShare::active().in) {
- return {};
- }
- auto shareGroup = m_shareToGroup.value(path);
- if (!shareGroup) {
- qWarning("Source for %s does not exist", qPrintable(path));
- Q_ASSERT(shareGroup);
- return {};
- }
- const auto reference = KeeShare::referenceOf(shareGroup);
- if (reference.type == KeeShareSettings::Inactive) {
- qDebug("Ignore change of inactive reference %s", qPrintable(reference.path));
- return {};
- }
- if (reference.type == KeeShareSettings::ExportTo) {
- qDebug("Ignore change of export reference %s", qPrintable(reference.path));
- return {};
- }
- Q_ASSERT(shareGroup->database() == m_db);
- Q_ASSERT(shareGroup == m_db->rootGroup()->findGroupByUuid(shareGroup->uuid()));
- return importContainerInto(reference, shareGroup);
+ if (!KeeShare::active().in) {
+ return {};
+ }
+ auto shareGroup = m_shareToGroup.value(path);
+ if (!shareGroup) {
+ qWarning("Source for %s does not exist", qPrintable(path));
+ Q_ASSERT(shareGroup);
+ return {};
+ }
+ const auto reference = KeeShare::referenceOf(shareGroup);
+ if (reference.type == KeeShareSettings::Inactive) {
+ // changes of inactive references are ignored
+ return {};
+ }
+ if (reference.type == KeeShareSettings::ExportTo) {
+ // changes of export only references are ignored
+ return {};
+ }
+
+ Q_ASSERT(shareGroup->database() == m_db);
+ Q_ASSERT(shareGroup == m_db->rootGroup()->findGroupByUuid(shareGroup->uuid()));
+ return importContainerInto(reference, shareGroup);
}
void ShareObserver::resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb)
{
- for (const auto& attribute : EntryAttributes::DefaultAttributes) {
- const auto standardValue = targetEntry->attributes()->value(attribute);
- const auto type = targetEntry->placeholderType(standardValue);
- if (type != Entry::PlaceholderType::Reference) {
- // No reference to resolve
- continue;
- }
- const auto* referencedTargetEntry = targetEntry->resolveReference(standardValue);
- if (referencedTargetEntry) {
- // References is within scope, no resolving needed
- continue;
- }
- // We could do more sophisticated **** trying to point the reference to the next in-scope reference
- // but those cases with high propability constructed examples and very rare in real usage
- const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid());
- const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue);
- targetEntry->setUpdateTimeinfo(false);
- targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute));
- targetEntry->setUpdateTimeinfo(true);
- }
+ for (const auto& attribute : EntryAttributes::DefaultAttributes) {
+ const auto standardValue = targetEntry->attributes()->value(attribute);
+ const auto type = targetEntry->placeholderType(standardValue);
+ if (type != Entry::PlaceholderType::Reference) {
+ // No reference to resolve
+ continue;
+ }
+ const auto* referencedTargetEntry = targetEntry->resolveReference(standardValue);
+ if (referencedTargetEntry) {
+ // References is within scope, no resolving needed
+ continue;
+ }
+ // We could do more sophisticated **** trying to point the reference to the next in-scope reference
+ // but those cases with high propability constructed examples and very rare in real usage
+ const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid());
+ const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue);
+ targetEntry->setUpdateTimeinfo(false);
+ targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute));
+ targetEntry->setUpdateTimeinfo(true);
+ }
}
Database* ShareObserver::exportIntoContainer(const KeeShareSettings::Reference& reference, const Group* sourceRoot)
{
- const auto* sourceDb = sourceRoot->database();
- auto* targetDb = new Database();
- targetDb->metadata()->setRecycleBinEnabled(false);
- auto key = QSharedPointer<CompositeKey>::create();
- key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
-
- // Copy the source root as the root of the export database, memory manage the old root node
- auto* targetRoot = sourceRoot->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
- const bool updateTimeinfo = targetRoot->canUpdateTimeinfo();
- targetRoot->setUpdateTimeinfo(false);
- KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference());
- targetRoot->setUpdateTimeinfo(updateTimeinfo);
- const auto sourceEntries = sourceRoot->entriesRecursive(false);
- for (const Entry* sourceEntry : sourceEntries) {
- auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
- const bool updateTimeinfo = targetEntry->canUpdateTimeinfo();
- targetEntry->setUpdateTimeinfo(false);
- targetEntry->setGroup(targetRoot);
- targetEntry->setUpdateTimeinfo(updateTimeinfo);
- const auto iconUuid = targetEntry->iconUuid();
- if (!iconUuid.isNull()) {
- targetDb->metadata()->addCustomIcon(iconUuid, sourceEntry->icon());
- }
- }
-
- targetDb->setKey(key);
- auto* obsoleteRoot = targetDb->rootGroup();
- targetDb->setRootGroup(targetRoot);
- delete obsoleteRoot;
-
- targetDb->metadata()->setName(sourceRoot->name());
-
- // Push all deletions of the source database to the target
- // simple moving out of a share group will not trigger a deletion in the
- // target - a more elaborate mechanism may need the use of another custom
- // attribute to share unshared entries from the target db
- for (const auto& object : sourceDb->deletedObjects()) {
- targetDb->addDeletedObject(object);
- }
- for (auto* targetEntry : targetRoot->entriesRecursive(false)) {
- if (targetEntry->hasReferences()) {
- resolveReferenceAttributes(targetEntry, sourceDb);
- }
- }
- return targetDb;
+ const auto* sourceDb = sourceRoot->database();
+ auto* targetDb = new Database();
+ targetDb->metadata()->setRecycleBinEnabled(false);
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
+
+ // Copy the source root as the root of the export database, memory manage the old root node
+ auto* targetRoot = sourceRoot->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
+ const bool updateTimeinfo = targetRoot->canUpdateTimeinfo();
+ targetRoot->setUpdateTimeinfo(false);
+ KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference());
+ targetRoot->setUpdateTimeinfo(updateTimeinfo);
+ const auto sourceEntries = sourceRoot->entriesRecursive(false);
+ for (const Entry* sourceEntry : sourceEntries) {
+ auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
+ const bool updateTimeinfo = targetEntry->canUpdateTimeinfo();
+ targetEntry->setUpdateTimeinfo(false);
+ targetEntry->setGroup(targetRoot);
+ targetEntry->setUpdateTimeinfo(updateTimeinfo);
+ const auto iconUuid = targetEntry->iconUuid();
+ if (!iconUuid.isNull()) {
+ targetDb->metadata()->addCustomIcon(iconUuid, sourceEntry->icon());
+ }
+ }
+
+ targetDb->setKey(key);
+ auto* obsoleteRoot = targetDb->rootGroup();
+ targetDb->setRootGroup(targetRoot);
+ delete obsoleteRoot;
+
+ targetDb->metadata()->setName(sourceRoot->name());
+
+ // Push all deletions of the source database to the target
+ // simple moving out of a share group will not trigger a deletion in the
+ // target - a more elaborate mechanism may need the use of another custom
+ // attribute to share unshared entries from the target db
+ for (const auto& object : sourceDb->deletedObjects()) {
+ targetDb->addDeletedObject(object);
+ }
+ for (auto* targetEntry : targetRoot->entriesRecursive(false)) {
+ if (targetEntry->hasReferences()) {
+ resolveReferenceAttributes(targetEntry, sourceDb);
+ }
+ }
+ return targetDb;
}
QSharedPointer<Database> ShareObserver::database()
{
- return m_db;
+ return m_db;
}
ShareObserver::Result ShareObserver::exportIntoReferenceSignedContainer(const KeeShareSettings::Reference &reference, Database *targetDb)
{
#if !defined(WITH_XC_KEESHARE_SECURE)
- Q_UNUSED(targetDb);
- return {reference.path, Result::Warning, tr("Overwriting signed share container is not supported - export prevented")};
+ Q_UNUSED(targetDb);
+ return {reference.path, Result::Warning, tr("Overwriting signed share container is not supported - export prevented")};
#else
- QByteArray bytes;
- {
- QBuffer buffer(&bytes);
- buffer.open(QIODevice::WriteOnly);
- KeePass2Writer writer;
- writer.writeDatabase(&buffer, targetDb);
- if (writer.hasError()) {
- qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data());
- return {reference.path, Result::Error, writer.errorString()};
- }
- }
- const auto own = KeeShare::own();
- QuaZip zip(reference.path);
- zip.setFileNameCodec("UTF-8");
- const bool zipOpened = zip.open(QuaZip::mdCreate);
- if (!zipOpened) {
- ::qWarning("Opening export file failed: %d", zip.getZipError());
- return {reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())};
- }
- {
- QuaZipFile file(&zip);
- const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature));
- if (!signatureOpened) {
- ::qWarning("Embedding signature failed: %d", zip.getZipError());
- return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())};
- }
- QTextStream stream(&file);
- KeeShareSettings::Sign sign;
- auto sshKey = own.key.sshKey();
- sshKey.openKey(QString());
- const Signature signer;
- sign.signature = signer.create(bytes, sshKey);
- sign.certificate = own.certificate;
- stream << KeeShareSettings::Sign::serialize(sign);
- stream.flush();
- if (file.getZipError() != ZIP_OK) {
- ::qWarning("Embedding signature failed: %d", zip.getZipError());
- return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())};
- }
- file.close();
- }
- {
- QuaZipFile file(&zip);
- const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container));
- if (!dbOpened) {
- ::qWarning("Embedding database failed: %d", zip.getZipError());
- return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())};
- }
- if (file.getZipError() != ZIP_OK) {
- ::qWarning("Embedding database failed: %d", zip.getZipError());
- return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())};
- }
- file.write(bytes);
- file.close();
- }
- zip.close();
- return {reference.path};
+ QByteArray bytes;
+ {
+ QBuffer buffer(&bytes);
+ buffer.open(QIODevice::WriteOnly);
+ KeePass2Writer writer;
+ writer.writeDatabase(&buffer, targetDb);
+ if (writer.hasError()) {
+ qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data());
+ return {reference.path, Result::Error, writer.errorString()};
+ }
+ }
+ const auto own = KeeShare::own();
+ QuaZip zip(reference.path);
+ zip.setFileNameCodec("UTF-8");
+ const bool zipOpened = zip.open(QuaZip::mdCreate);
+ if (!zipOpened) {
+ ::qWarning("Opening export file failed: %d", zip.getZipError());
+ return {reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())};
+ }
+ {
+ QuaZipFile file(&zip);
+ const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature));
+ if (!signatureOpened) {
+ ::qWarning("Embedding signature failed: %d", zip.getZipError());
+ return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())};
+ }
+ QTextStream stream(&file);
+ KeeShareSettings::Sign sign;
+ auto sshKey = own.key.sshKey();
+ sshKey.openKey(QString());
+ const Signature signer;
+ sign.signature = signer.create(bytes, sshKey);
+ sign.certificate = own.certificate;
+ stream << KeeShareSettings::Sign::serialize(sign);
+ stream.flush();
+ if (file.getZipError() != ZIP_OK) {
+ ::qWarning("Embedding signature failed: %d", zip.getZipError());
+ return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())};
+ }
+ file.close();
+ }
+ {
+ QuaZipFile file(&zip);
+ const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container));
+ if (!dbOpened) {
+ ::qWarning("Embedding database failed: %d", zip.getZipError());
+ return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())};
+ }
+ if (file.getZipError() != ZIP_OK) {
+ ::qWarning("Embedding database failed: %d", zip.getZipError());
+ return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())};
+ }
+ file.write(bytes);
+ file.close();
+ }
+ zip.close();
+ return {reference.path};
#endif
}
ShareObserver::Result ShareObserver::exportIntoReferenceUnsignedContainer(const KeeShareSettings::Reference &reference, Database *targetDb)
{
#if !defined(WITH_XC_KEESHARE_INSECURE)
- Q_UNUSED(targetDb);
- return {reference.path, Result::Warning, tr("Overwriting unsigned share container is not supported - export prevented")};
+ Q_UNUSED(targetDb);
+ return {reference.path, Result::Warning, tr("Overwriting unsigned share container is not supported - export prevented")};
#else
- QFile file(reference.path);
- const bool fileOpened = file.open(QIODevice::WriteOnly);
- if (!fileOpened) {
- ::qWarning("Opening export file failed");
- return {reference.path, Result::Error, tr("Could not write export container")};
- }
- KeePass2Writer writer;
- writer.writeDatabase(&file, targetDb);
- if (writer.hasError()) {
- qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data());
- return {reference.path, Result::Error, writer.errorString()};
- }
- file.close();
+ QFile file(reference.path);
+ const bool fileOpened = file.open(QIODevice::WriteOnly);
+ if (!fileOpened) {
+ ::qWarning("Opening export file failed");
+ return {reference.path, Result::Error, tr("Could not write export container")};
+ }
+ KeePass2Writer writer;
+ writer.writeDatabase(&file, targetDb);
+ if (writer.hasError()) {
+ qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data());
+ return {reference.path, Result::Error, writer.errorString()};
+ }
+ file.close();
#endif
- return {reference.path};
+ return {reference.path};
}
QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
{
- QList<Result> results;
- const auto groups = m_db->rootGroup()->groupsRecursive(true);
- for (const auto* group : groups) {
- const auto reference = KeeShare::referenceOf(group);
- if (!reference.isExporting()) {
- continue;
- }
-
- m_fileWatcher->ignoreFileChanges(reference.path);
- QScopedPointer<Database> targetDb(exportIntoContainer(reference, group));
- QFileInfo info(reference.path);
- if (isOfExportType(info, KeeShare::signedContainerFileType())) {
- results << exportIntoReferenceSignedContainer(reference, targetDb.data());
- m_fileWatcher->observeFileChanges(true);
- continue;
- }
- if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
- results << exportIntoReferenceUnsignedContainer(reference, targetDb.data());
- m_fileWatcher->observeFileChanges(true);
- continue;
- }
- Q_ASSERT(false);
- results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")};
- }
- return results;
+ QList<Result> results;
+ const auto groups = m_db->rootGroup()->groupsRecursive(true);
+ for (const auto* group : groups) {
+ const auto reference = KeeShare::referenceOf(group);
+ if (!reference.isExporting()) {
+ continue;
+ }
+
+ m_fileWatcher->ignoreFileChanges(reference.path);
+ QScopedPointer<Database> targetDb(exportIntoContainer(reference, group));
+ QFileInfo info(reference.path);
+ if (isOfExportType(info, KeeShare::signedContainerFileType())) {
+ results << exportIntoReferenceSignedContainer(reference, targetDb.data());
+ m_fileWatcher->observeFileChanges(true);
+ continue;
+ }
+ if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
+ results << exportIntoReferenceUnsignedContainer(reference, targetDb.data());
+ m_fileWatcher->observeFileChanges(true);
+ continue;
+ }
+ Q_ASSERT(false);
+ results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")};
+ }
+ return results;
}
void ShareObserver::handleDatabaseSaved()
{
- if (!KeeShare::active().out) {
- return;
- }
- QStringList error;
- QStringList warning;
- QStringList success;
- const auto results = exportIntoReferenceContainers();
- for (const Result& result : results) {
- if (!result.isValid()) {
- Q_ASSERT(result.isValid());
- continue;
- }
- if (result.isError()) {
- error << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
- } else if (result.isWarning()) {
- warning << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
- } else if (result.isInfo()) {
- success << tr("Export to %1 successful (%2)").arg(result.path).arg(result.message);
- } else {
- success << tr("Export to %1").arg(result.path);
- }
- }
- notifyAbout(success, warning, error);
+ if (!KeeShare::active().out) {
+ return;
+ }
+ QStringList error;
+ QStringList warning;
+ QStringList success;
+ const auto results = exportIntoReferenceContainers();
+ for (const Result& result : results) {
+ if (!result.isValid()) {
+ Q_ASSERT(result.isValid());
+ continue;
+ }
+ if (result.isError()) {
+ error << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
+ } else if (result.isWarning()) {
+ warning << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
+ } else if (result.isInfo()) {
+ success << tr("Export to %1 successful (%2)").arg(result.path).arg(result.message);
+ } else {
+ success << tr("Export to %1").arg(result.path);
+ }
+ }
+ notifyAbout(success, warning, error);
}
ShareObserver::Result::Result(const QString& path, ShareObserver::Result::Type type, const QString& message)
- : path(path)
- , type(type)
- , message(message)
+ : path(path)
+ , type(type)
+ , message(message)
{
}
bool ShareObserver::Result::isValid() const
{
- return !path.isEmpty() || !message.isEmpty() || !message.isEmpty() || !message.isEmpty();
+ return !path.isEmpty() || !message.isEmpty() || !message.isEmpty() || !message.isEmpty();
}
bool ShareObserver::Result::isError() const
{
- return !message.isEmpty() && type == Error;
+ return !message.isEmpty() && type == Error;
}
bool ShareObserver::Result::isInfo() const
{
- return !message.isEmpty() && type == Info;
+ return !message.isEmpty() && type == Info;
}
bool ShareObserver::Result::isWarning() const
{
- return !message.isEmpty() && type == Warning;
+ return !message.isEmpty() && type == Warning;
}
diff --git a/src/keeshare/ShareObserver.h b/src/keeshare/ShareObserver.h
index 26f010813..d67a1f7ef 100644
--- a/src/keeshare/ShareObserver.h
+++ b/src/keeshare/ShareObserver.h
@@ -49,7 +49,9 @@ signals:
private slots:
void handleDatabaseChanged();
void handleDatabaseSaved();
+ void handleFileCreated(const QString& path);
void handleFileUpdated(const QString& path);
+ void handleFileDeleted(const QString& path);
private:
struct Result