diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67aada93f..61582d7f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -359,7 +359,7 @@ configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h) # Core Library Definition add_library(keepassxc_core STATIC ${core_SOURCES}) set_target_properties(keepassxc_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE) -target_link_libraries(keepassxc_core +target_link_libraries(keepassxc_core ${qrcode_LIB} Qt5::Core Qt5::Concurrent diff --git a/src/fdosecrets/CMakeLists.txt b/src/fdosecrets/CMakeLists.txt index 7489debef..efaed2d03 100644 --- a/src/fdosecrets/CMakeLists.txt +++ b/src/fdosecrets/CMakeLists.txt @@ -2,8 +2,11 @@ if(WITH_XC_FDOSECRETS) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) add_library(fdosecrets STATIC - # app settings page FdoSecretsPlugin.cpp + FdoSecretsPluginGUI.cpp + + # app settings page + FdoSecretsSettingsPage.cpp widgets/SettingsModels.cpp widgets/SettingsWidgetFdoSecrets.cpp widgets/RowButtonHelper.cpp diff --git a/src/fdosecrets/FdoSecretsPlugin.cpp b/src/fdosecrets/FdoSecretsPlugin.cpp index b86ae805b..2ad5bf36f 100644 --- a/src/fdosecrets/FdoSecretsPlugin.cpp +++ b/src/fdosecrets/FdoSecretsPlugin.cpp @@ -18,54 +18,28 @@ #include "FdoSecretsPlugin.h" #include "fdosecrets/FdoSecretsSettings.h" +#include "fdosecrets/dbus/DBusClient.h" #include "fdosecrets/objects/Service.h" -#include "fdosecrets/widgets/SettingsWidgetFdoSecrets.h" - -#include "gui/DatabaseTabWidget.h" using FdoSecrets::DBusMgr; using FdoSecrets::Service; -// TODO: Only used for testing. Need to split service functions away from settings page. -QPointer g_fdoSecretsPlugin; - -FdoSecretsPlugin::FdoSecretsPlugin(DatabaseTabWidget* tabWidget) - : m_dbTabs(tabWidget) +FdoSecretsPlugin::FdoSecretsPlugin(QObject* parent) + : QObject{parent} , m_dbus(new DBusMgr()) + , m_secretService{} { registerDBusTypes(m_dbus); m_dbus->populateMethodCache(); connect(m_dbus.data(), &DBusMgr::error, this, &FdoSecretsPlugin::emitError); - g_fdoSecretsPlugin = this; -} - -FdoSecretsPlugin* FdoSecretsPlugin::getPlugin() -{ - return g_fdoSecretsPlugin; -} - -QWidget* FdoSecretsPlugin::createWidget() -{ - return new SettingsWidgetFdoSecrets(this); -} - -void FdoSecretsPlugin::loadSettings(QWidget* widget) -{ - qobject_cast(widget)->loadSettings(); -} - -void FdoSecretsPlugin::saveSettings(QWidget* widget) -{ - qobject_cast(widget)->saveSettings(); - updateServiceState(); } void FdoSecretsPlugin::updateServiceState() { if (FdoSecrets::settings()->isEnabled()) { - if (!m_secretService && m_dbTabs) { - m_secretService = Service::Create(this, m_dbTabs, m_dbus); + if (!m_secretService) { + m_secretService = Service::Create(this, m_dbus); if (!m_secretService) { FdoSecrets::settings()->setEnabled(false); return; @@ -85,21 +59,11 @@ Service* FdoSecretsPlugin::serviceInstance() const return m_secretService.data(); } -DatabaseTabWidget* FdoSecretsPlugin::dbTabs() const -{ - return m_dbTabs; -} - const QSharedPointer& FdoSecretsPlugin::dbus() const { return m_dbus; } -void FdoSecretsPlugin::emitRequestSwitchToDatabases() -{ - emit requestSwitchToDatabases(); -} - void FdoSecretsPlugin::emitRequestShowNotification(const QString& msg, const QString& title) { if (!FdoSecrets::settings()->showNotification()) { @@ -108,6 +72,38 @@ void FdoSecretsPlugin::emitRequestShowNotification(const QString& msg, const QSt emit requestShowNotification(msg, title, 10000); } +void FdoSecretsPlugin::registerDatabase(const QString& name) +{ + if (name.isEmpty()) { + return; + } + + serviceInstance()->registerDatabase(name); +} + +void FdoSecretsPlugin::unregisterDatabase(const QString& name) +{ + serviceInstance()->unregisterDatabase(name); +} + +void FdoSecretsPlugin::databaseLocked(const QString& name) +{ + if (name.isEmpty()) { + return; + } + + serviceInstance()->databaseLocked(name); +} + +void FdoSecretsPlugin::databaseUnlocked(const QString& name, QSharedPointer db) +{ + if (name.isEmpty()) { + return; + } + + serviceInstance()->databaseUnlocked(name, db); +} + void FdoSecretsPlugin::emitError(const QString& msg) { emit error(tr("Fdo Secret Service: %1").arg(msg)); diff --git a/src/fdosecrets/FdoSecretsPlugin.h b/src/fdosecrets/FdoSecretsPlugin.h index ad4e7003a..5884cc62e 100644 --- a/src/fdosecrets/FdoSecretsPlugin.h +++ b/src/fdosecrets/FdoSecretsPlugin.h @@ -18,40 +18,36 @@ #ifndef KEEPASSXC_FDOSECRETSPLUGIN_H #define KEEPASSXC_FDOSECRETSPLUGIN_H -#include "gui/ApplicationSettingsWidget.h" -#include "gui/Icons.h" - +#include "core/Global.h" #include -class DatabaseTabWidget; +class Database; +class Entry; +class FdoSecretsPluginRequestHandler; namespace FdoSecrets { - class Service; + class Collection; + class DBusClient; class DBusMgr; + class Service; + class UnlockPrompt; + class PromptBase; } // namespace FdoSecrets -class FdoSecretsPlugin : public QObject, public ISettingsPage +class FdoSecretsPlugin : public QObject { Q_OBJECT + + friend class FdoSecrets::Collection; + friend class FdoSecrets::Service; + friend class FdoSecrets::UnlockPrompt; + friend class FdoSecrets::PromptBase; + public: - explicit FdoSecretsPlugin(DatabaseTabWidget* tabWidget); + FdoSecretsPlugin(QObject* parent); ~FdoSecretsPlugin() override = default; - QString name() override - { - return QObject::tr("Secret Service Integration"); - } - - QIcon icon() override - { - return icons()->icon("freedesktop"); - } - - QWidget* createWidget() override; - void loadSettings(QWidget* widget) override; - void saveSettings(QWidget* widget) override; - void updateServiceState(); /** @@ -59,39 +55,54 @@ public: */ FdoSecrets::Service* serviceInstance() const; - /** - * @return The db tabs widget, containing opened databases. Can be nullptr. - */ - DatabaseTabWidget* dbTabs() const; - /** * @brief The dbus manager instance * @return */ const QSharedPointer& dbus() const; - // TODO: Only used for testing. Need to split service functions away from settings page. - static FdoSecretsPlugin* getPlugin(); - public slots: - void emitRequestSwitchToDatabases(); void emitRequestShowNotification(const QString& msg, const QString& title = {}); + void registerDatabase(const QString& name); + void unregisterDatabase(const QString& name); + + void databaseLocked(const QString& name); + void databaseUnlocked(const QString& name, QSharedPointer db); + /** - * @brief Show error in the GUI + * @brief Show error in the UI * @param msg */ void emitError(const QString& msg); signals: void error(const QString& msg); - void requestSwitchToDatabases(); void requestShowNotification(const QString& msg, const QString& title, int msTimeoutHint); void secretServiceStarted(); void secretServiceStopped(); private: - QPointer m_dbTabs; + virtual size_t requestEntriesRemove(const QSharedPointer& client, + const QString& name, + const QList& entries, + bool permanent) const = 0; + + virtual bool requestEntriesUnlock(const QSharedPointer& client, + const QString& windowId, + const QList& entries, + QHash& decisions, + AuthDecision& forFutureEntries) const = 0; + + virtual bool doLockDatabase(const QSharedPointer& client, const QString& name) = 0; + virtual bool doUnlockDatabase(const QSharedPointer& client, const QString& name) = 0; + + virtual bool requestUnlockAnyDatabase(const QSharedPointer& client) const = 0; + virtual QString requestNewDatabase(const QSharedPointer& client) = 0; + + virtual QString overrideMessageBoxParent(const QString& windowId) const = 0; + +private: QSharedPointer m_dbus; QSharedPointer m_secretService; }; diff --git a/src/fdosecrets/FdoSecretsPluginGUI.cpp b/src/fdosecrets/FdoSecretsPluginGUI.cpp new file mode 100644 index 000000000..4ae580fee --- /dev/null +++ b/src/fdosecrets/FdoSecretsPluginGUI.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2022 KeePassXC Team + * + * 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 or (at your option) + * version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "FdoSecretsPluginGUI.h" + +#include + +#include "core/Entry.h" +#include "core/Group.h" +#include "fdosecrets/FdoSecretsSettings.h" +#include "fdosecrets/dbus/DBusClient.h" +#include "fdosecrets/objects/Collection.h" +#include "fdosecrets/objects/Service.h" +#include "fdosecrets/widgets/AccessControlDialog.h" +#include "gui/DatabaseTabWidget.h" +#include "gui/DatabaseWidget.h" +#include "gui/GuiTools.h" +#include "gui/MessageBox.h" + +FdoSecretsPluginGUI::FdoSecretsPluginGUI(DatabaseTabWidget* databases) + : FdoSecretsPlugin{databases} + , m_databases{databases} + , m_lockingDb{} + , m_unlockingDb{} +{ + // Don't connect signals until service is enabled + connect(this, &FdoSecretsPlugin::secretServiceStarted, this, [this]() { + // Connect to new database signal + connect(m_databases, &DatabaseTabWidget::databaseOpened, this, &FdoSecretsPluginGUI::databaseTabOpened); + + // Populate/Clear the database when lock state changes + connect(m_databases, &DatabaseTabWidget::databaseUnlocked, this, [this](DatabaseWidget* dbWidget) { + auto db = dbWidget->database(); + auto name = getDatabaseName(db); + if (FdoSecrets::settings()->exposedGroup(db.data()).isNull()) { + return; + } + + Q_ASSERT(m_nameToWidget.value(name) == dbWidget); + databaseUnlocked(name, db); + }); + + connect(m_databases, &DatabaseTabWidget::databaseLocked, this, [this](DatabaseWidget* dbWidget) { + databaseLocked(getDatabaseName(dbWidget->database())); + }); + + // Make default alias track current activated database + connect(m_databases, &DatabaseTabWidget::activeDatabaseChanged, this, [this](DatabaseWidget* dbWidget) { + if (dbWidget) { + auto db = dbWidget->database(); + if (FdoSecrets::settings()->exposedGroup(db.data()).isNull()) { + return; + } + + serviceInstance()->setAlias("default", getDatabaseName(db)); + } + }); + + // Clear the local state when collection deletes + connect(serviceInstance(), &FdoSecrets::Service::collectionDeleted, this, [this](FdoSecrets::Collection* coll) { + if (!FdoSecrets::settings()->exposedGroup(coll->backend()).isNull()) { + m_databases->closeDatabaseTab(m_nameToWidget.value(coll->name())); + } + + m_nameToWidget.remove(coll->name()); + }); + + // Add existing database tabs + for (int idx = 0; idx < m_databases->count(); ++idx) { + auto dbWidget = m_databases->databaseWidgetFromIndex(idx); + databaseTabOpened(dbWidget); + } + }); + + // Clear local state when service stops + connect(this, &FdoSecretsPlugin::secretServiceStopped, this, [this]() { + m_databases->disconnect(this); + for (int idx = 0; idx < m_databases->count(); ++idx) { + m_databases->databaseWidgetFromIndex(idx)->disconnect(this); + } + + m_nameToWidget.clear(); + }); +} + +void FdoSecretsPluginGUI::registerDatabase(const QString& name, DatabaseWidget* dbWidget) +{ + Q_ASSERT(!name.isEmpty()); + Q_ASSERT(m_nameToWidget.value(name) == nullptr); + m_nameToWidget[name] = dbWidget; + FdoSecretsPlugin::registerDatabase(name); + + // If the db is already unlocked we won't get additional signal + if (!dbWidget->isLocked()) { + databaseUnlocked(name, dbWidget->database()); + } +} + +void FdoSecretsPluginGUI::unregisterDatabase(const QString& name) +{ + Q_ASSERT(!name.isEmpty()); + Q_ASSERT(m_nameToWidget.value(name)); + FdoSecretsPlugin::unregisterDatabase(name); + m_nameToWidget.remove(name); +} + +void FdoSecretsPluginGUI::databaseTabOpened(DatabaseWidget* dbWidget) +{ + // The Collection will monitor the database's exposed group. + // When the Collection finds that no exposed group, it will delete itself. + // Thus, the plugin also needs to monitor it and recreate the collection if the user changes + // from no exposed to exposed something. + connect(dbWidget, &DatabaseWidget::databaseReplaced, this, [this, dbWidget]() { + monitorDatabaseExposedGroup(dbWidget); + }); + + if (!dbWidget->isLocked()) { + monitorDatabaseExposedGroup(dbWidget); + } + + // No exposed group, no point in registering as it will remove itself immediately anyway + if (!dbWidget->isLocked() && FdoSecrets::settings()->exposedGroup(dbWidget->database().data()).isNull()) { + return; + } + + registerDatabase(getDatabaseName(dbWidget->database()), dbWidget); + + // Reload Collection when database is replaced + connect(dbWidget, + &DatabaseWidget::databaseReplaced, + this, + [this, dbWidget](QSharedPointer oldDb, QSharedPointer newDb) { + Q_ASSERT(oldDb != newDb); + auto oldName = getDatabaseName(oldDb); + auto newName = getDatabaseName(newDb); + if (oldName != newName) { + unregisterDatabase(oldName); + registerDatabase(newName, dbWidget); + } else { + // If the db is already unlocked we won't get additional signal + if (!dbWidget->isLocked()) { + databaseUnlocked(newName, dbWidget->database()); + } + } + }); + + // Unregister on close + connect(dbWidget, &DatabaseWidget::closeRequest, this, [this, dbWidget]() { + unregisterDatabase(getDatabaseName(dbWidget->database())); + }); + + // Reload Collection on file path changed + connect(dbWidget, + &DatabaseWidget::databaseFilePathChanged, + this, + [this, dbWidget](const QString& oldPath, const QString&) { + unregisterDatabase(QFileInfo(oldPath).completeBaseName()); + registerDatabase(getDatabaseName(dbWidget->database()), dbWidget); + }); +} + +size_t FdoSecretsPluginGUI::requestEntriesRemove(const QSharedPointer&, + const QString& name, + const QList& entries, + bool permanent) const +{ + DatabaseWidget* dbWidget = m_nameToWidget.value(name); + if (!dbWidget) { + return 0; + } + + if (FdoSecrets::settings()->confirmDeleteItem() && !GuiTools::confirmDeleteEntries(dbWidget, entries, permanent)) { + return 0; + } + + return GuiTools::deleteEntriesResolveReferences(dbWidget, entries, permanent); +} + +bool FdoSecretsPluginGUI::requestEntriesUnlock(const QSharedPointer& client, + const QString& windowId, + const QList& entries, + QHash& decisions, + AuthDecision& forFutureEntries) const +{ + QString app = QObject::tr("%1 (PID: %2)").arg(client->name()).arg(client->pid()); + auto ac = new AccessControlDialog(findWindow(windowId), entries, app, client->processInfo(), AuthOption::Remember); + QObject::connect(ac, + &AccessControlDialog::finished, + [&](const QHash& dialogDecisions, AuthDecision dialogForFutureEntries) { + decisions = dialogDecisions; + forFutureEntries = dialogForFutureEntries; + }); + ac->exec(); + ac->deleteLater(); + return true; +} + +bool FdoSecretsPluginGUI::doLockDatabase(const QSharedPointer&, const QString& name) +{ + DatabaseWidget* dbWidget = m_nameToWidget.value(name); + if (!dbWidget) { + return false; + } + + // return immediately if the db is already locked + if (dbWidget->isLocked()) { + return true; + } + + // Prevent multiple dialogs on the same database + if (m_lockingDb.contains(dbWidget)) { + return true; + } + + m_lockingDb.insert(dbWidget); + bool ret = dbWidget->lock(); + m_lockingDb.remove(dbWidget); + return ret; +} + +bool FdoSecretsPluginGUI::doUnlockDatabase(const QSharedPointer&, const QString& name) +{ + DatabaseWidget* dbWidget = m_nameToWidget.value(name); + if (!dbWidget) { + return false; + } + + // return immediately if the db is already unlocked + if (!dbWidget->isLocked()) { + return true; + } + + // Prevent multiple dialogs on the same database + if (!m_unlockingDb.contains(dbWidget)) { + m_unlockingDb.insert(dbWidget); + m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None); + } + + QEventLoop loop; + bool wasAccepted = false; + QObject::connect(m_databases, + &DatabaseTabWidget::databaseUnlockDialogFinished, + &loop, + [&](bool accepted, DatabaseWidget* unlocked) { + if (unlocked == dbWidget) { + wasAccepted = accepted; + m_unlockingDb.remove(dbWidget); + loop.quit(); + } + }); + + loop.exec(); + return wasAccepted; +} + +bool FdoSecretsPluginGUI::requestUnlockAnyDatabase(const QSharedPointer&) const +{ + m_databases->unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::None); + QEventLoop loop; + bool wasAccepted = false; + QObject::connect(m_databases, &DatabaseTabWidget::databaseUnlockDialogFinished, &loop, [&](bool accepted) { + wasAccepted = accepted; + loop.quit(); + }); + + loop.exec(); + return wasAccepted; +} + +QString FdoSecretsPluginGUI::requestNewDatabase(const QSharedPointer&) +{ + auto dbWidget = m_databases->newDatabase(); + if (!dbWidget) { + return {}; + } + + // database created through D-Bus will be exposed to D-Bus by default + auto db = dbWidget->database(); + FdoSecrets::settings()->setExposedGroup(db.data(), db->rootGroup()->uuid()); + return getDatabaseName(db); +} + +QString FdoSecretsPluginGUI::overrideMessageBoxParent(const QString& windowId) const +{ + QString oldParentWindowId = {}; + + if (MessageBox::m_overrideParent && MessageBox::m_overrideParent->winId()) { + oldParentWindowId = QString::number(MessageBox::m_overrideParent->winId()); + } + + MessageBox::m_overrideParent = findWindow(windowId); + return oldParentWindowId; +} + +QWindow* FdoSecretsPluginGUI::findWindow(const QString& windowId) const +{ + // find parent window, or nullptr if not found + bool ok = false; + WId wid = windowId.toULongLong(&ok, 0); + QWindow* parent = nullptr; + if (ok) { + parent = QWindow::fromWinId(wid); + } + if (parent) { + // parent is not the child of any object, so make sure it gets deleted at some point + QObject::connect(this, &QObject::destroyed, parent, &QObject::deleteLater); + } + return parent; +} + +QString FdoSecretsPluginGUI::getDatabaseName(const QSharedPointer& db) const +{ + if (db->canonicalFilePath().isEmpty()) { + // This is a newly created db without saving to file, + // but we have to give a name, which is used to register D-Bus path. + // We use database name for this purpose. For simplicity, we don't monitor the name change. + // So the D-Bus object path is not updated if the db name changes. + // This should not be a problem because once the database gets saved, + // the D-Bus path will be updated to use filename and everything back to normal. + return db->metadata()->name(); + } + + return db->canonicalFilePath(); +} + +void FdoSecretsPluginGUI::monitorDatabaseExposedGroup(DatabaseWidget* dbWidget) +{ + Q_ASSERT(dbWidget->database()); + connect(dbWidget->database()->metadata()->customData(), &CustomData::modified, this, [this, dbWidget]() { + if (!FdoSecrets::settings()->exposedGroup(dbWidget->database().data()).isNull() + && !m_nameToWidget.value(getDatabaseName(dbWidget->database()))) { + databaseTabOpened(dbWidget); + } + }); +} diff --git a/src/fdosecrets/FdoSecretsPluginGUI.h b/src/fdosecrets/FdoSecretsPluginGUI.h new file mode 100644 index 000000000..4b006b57d --- /dev/null +++ b/src/fdosecrets/FdoSecretsPluginGUI.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 KeePassXC Team + * + * 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 or (at your option) + * version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_FDOSECRETSPLUGINGUI_H +#define KEEPASSXC_FDOSECRETSPLUGINGUI_H + +#include "fdosecrets/FdoSecretsPlugin.h" + +#include + +class DatabaseTabWidget; +class DatabaseWidget; +class Database; +class QWindow; + +class FdoSecretsPluginGUI : public FdoSecretsPlugin +{ + +public: + FdoSecretsPluginGUI(DatabaseTabWidget* databases); + ~FdoSecretsPluginGUI() override = default; + +private slots: + void registerDatabase(const QString& name, DatabaseWidget* dbWidget); + void unregisterDatabase(const QString& name); + + void databaseTabOpened(DatabaseWidget* dbWidget); + +private: + size_t requestEntriesRemove(const QSharedPointer& client, + const QString& name, + const QList& entries, + bool permanent) const override; + + bool requestEntriesUnlock(const QSharedPointer& client, + const QString& windowId, + const QList& entries, + QHash& decisions, + AuthDecision& forFutureEntries) const override; + + bool doLockDatabase(const QSharedPointer& client, const QString& name) override; + bool doUnlockDatabase(const QSharedPointer& client, const QString& name) override; + bool requestUnlockAnyDatabase(const QSharedPointer& client) const override; + QString requestNewDatabase(const QSharedPointer& client) override; + QString overrideMessageBoxParent(const QString& windowId) const override; + QWindow* findWindow(const QString& windowId) const; + + QString getDatabaseName(const QSharedPointer& db) const; + void monitorDatabaseExposedGroup(DatabaseWidget* dbWidget); + +private: + QHash m_nameToWidget; + DatabaseTabWidget* m_databases; + QSet m_lockingDb; // list of dbs being locked + QSet m_unlockingDb; // list of dbs being unlocked +}; + +#endif // KEEPASSXC_FDOSECRETSPLUGINGUI_H diff --git a/src/fdosecrets/FdoSecretsSettings.cpp b/src/fdosecrets/FdoSecretsSettings.cpp index d24bc69a1..903c00f1e 100644 --- a/src/fdosecrets/FdoSecretsSettings.cpp +++ b/src/fdosecrets/FdoSecretsSettings.cpp @@ -84,16 +84,6 @@ namespace FdoSecrets config()->set(Config::FdoSecrets_UnlockBeforeSearch, unlockBeforeSearch); } - QUuid FdoSecretsSettings::exposedGroup(const QSharedPointer& db) const - { - return exposedGroup(db.data()); - } - - void FdoSecretsSettings::setExposedGroup(const QSharedPointer& db, const QUuid& group) - { - setExposedGroup(db.data(), group); - } - QUuid FdoSecretsSettings::exposedGroup(Database* db) const { return {db->metadata()->customData()->value(CustomData::FdoSecretsExposedGroup)}; diff --git a/src/fdosecrets/FdoSecretsSettings.h b/src/fdosecrets/FdoSecretsSettings.h index 31ab005f6..a86e5c929 100644 --- a/src/fdosecrets/FdoSecretsSettings.h +++ b/src/fdosecrets/FdoSecretsSettings.h @@ -48,9 +48,6 @@ namespace FdoSecrets void setUnlockBeforeSearch(bool unlockBeforeSearch); // Per db settings - - QUuid exposedGroup(const QSharedPointer& db) const; - void setExposedGroup(const QSharedPointer& db, const QUuid& group); QUuid exposedGroup(Database* db) const; void setExposedGroup(Database* db, const QUuid& group); diff --git a/src/fdosecrets/FdoSecretsSettingsPage.cpp b/src/fdosecrets/FdoSecretsSettingsPage.cpp new file mode 100644 index 000000000..8ad4ed116 --- /dev/null +++ b/src/fdosecrets/FdoSecretsSettingsPage.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 KeePassXC Team + * + * 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 or (at your option) + * version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "FdoSecretsSettingsPage.h" + +#include "FdoSecretsPlugin.h" +#include "fdosecrets/widgets/SettingsWidgetFdoSecrets.h" + +#include "gui/DatabaseTabWidget.h" +#include "gui/DatabaseWidget.h" + +FdoSecretsSettingsPage::FdoSecretsSettingsPage(FdoSecretsPlugin* plugin, DatabaseTabWidget* dbTabs) + : QObject{plugin} + , m_plugin{plugin} + , m_dbTabs{dbTabs} +{ +} + +QWidget* FdoSecretsSettingsPage::createWidget() +{ + return new SettingsWidgetFdoSecrets(m_plugin, this); +} + +void FdoSecretsSettingsPage::loadSettings(QWidget* widget) +{ + qobject_cast(widget)->loadSettings(); +} + +void FdoSecretsSettingsPage::saveSettings(QWidget* widget) +{ + qobject_cast(widget)->saveSettings(); +} + +void FdoSecretsSettingsPage::unlockDatabaseInDialog(DatabaseWidget* dbWidget) +{ + Q_ASSERT(dbWidget); + // return immediately if the db is already unlocked + if (dbWidget && !dbWidget->isLocked()) { + return; + } + + // actually show the dialog + m_dbTabs->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None); +} + +void FdoSecretsSettingsPage::switchToDatabaseSettings(DatabaseWidget* dbWidget) +{ + if (dbWidget->isLocked()) { + return; + } + // switch selected to current + m_dbTabs->setCurrentWidget(dbWidget); + m_dbTabs->showDatabaseSettings(); + + // open settings (switch from app settings to m_dbTabs) + emit requestSwitchToDatabases(); +} diff --git a/src/fdosecrets/FdoSecretsSettingsPage.h b/src/fdosecrets/FdoSecretsSettingsPage.h new file mode 100644 index 000000000..343781c4f --- /dev/null +++ b/src/fdosecrets/FdoSecretsSettingsPage.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 KeePassXC Team + * + * 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 or (at your option) + * version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_FDOSECRETSSETTINGSPAGE_H +#define KEEPASSXC_FDOSECRETSSETTINGSPAGE_H + +#include "gui/ApplicationSettingsWidget.h" +#include "gui/Icons.h" + +class DatabaseWidget; +class DatabaseTabWidget; +class FdoSecretsPlugin; + +class FdoSecretsSettingsPage : public QObject, public ISettingsPage +{ + Q_OBJECT + +public: + FdoSecretsSettingsPage(FdoSecretsPlugin* plugin, DatabaseTabWidget* dbTabs); + ~FdoSecretsSettingsPage() override = default; + + QString name() override + { + return QObject::tr("Secret Service Integration"); + } + + QIcon icon() override + { + return icons()->icon("freedesktop"); + } + + DatabaseTabWidget* dbTabs() const + { + return m_dbTabs; + } + + QWidget* createWidget() override; + void loadSettings(QWidget* widget) override; + void saveSettings(QWidget* widget) override; + + void unlockDatabaseInDialog(DatabaseWidget* dbWidget); + void switchToDatabaseSettings(DatabaseWidget* dbWidget); + +signals: + void requestSwitchToDatabases(); + +private: + FdoSecretsPlugin* m_plugin; + DatabaseTabWidget* m_dbTabs; +}; + +#endif // KEEPASSXC_FDOSECRETSSETTINGSPAGE_H diff --git a/src/fdosecrets/dbus/DBusMgr.cpp b/src/fdosecrets/dbus/DBusMgr.cpp index 3765e6f09..065417f0c 100644 --- a/src/fdosecrets/dbus/DBusMgr.cpp +++ b/src/fdosecrets/dbus/DBusMgr.cpp @@ -445,7 +445,7 @@ namespace FdoSecrets bool DBusMgr::registerObject(Collection* coll) { - auto name = encodePath(coll->name()); + auto name = encodePath(coll->dbusName()); auto path = DBUS_PATH_TEMPLATE_COLLECTION.arg(DBUS_PATH_SECRETS, name); if (!registerObject(path, coll)) { // try again with a suffix @@ -460,7 +460,6 @@ namespace FdoSecrets } connect(coll, &Collection::itemCreated, this, &DBusMgr::emitItemCreated); - connect(coll, &Collection::itemChanged, this, &DBusMgr::emitItemChanged); connect(coll, &Collection::itemDeleted, this, &DBusMgr::emitItemDeleted); return true; @@ -483,6 +482,9 @@ namespace FdoSecrets emit error(tr("Failed to register item on DBus at path '%1'").arg(path)); return false; } + + connect(item, &Item::itemChanged, this, [this, item]() { emitItemChanged(item); }); + return true; } @@ -560,7 +562,7 @@ namespace FdoSecrets sendDBusSignal( coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemCreated"), args); // also send on all alias path - for (const auto& alias : coll->aliases()) { + for (const auto& alias : coll->service()->collectionAliases(coll)) { auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias); sendDBusSignal(path, DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemCreated"), args); } @@ -575,7 +577,7 @@ namespace FdoSecrets sendDBusSignal( coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemChanged"), args); // also send on all alias path - for (const auto& alias : coll->aliases()) { + for (const auto& alias : coll->service()->collectionAliases(coll)) { auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias); sendDBusSignal(path, DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemChanged"), args); } @@ -590,7 +592,7 @@ namespace FdoSecrets sendDBusSignal( coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemDeleted"), args); // also send on all alias path - for (const auto& alias : coll->aliases()) { + for (const auto& alias : coll->service()->collectionAliases(coll)) { auto path = DBUS_PATH_TEMPLATE_ALIAS.arg(DBUS_PATH_SECRETS, alias); sendDBusSignal(path, DBUS_INTERFACE_SECRET_COLLECTION, QStringLiteral("ItemDeleted"), args); } diff --git a/src/fdosecrets/objects/Collection.cpp b/src/fdosecrets/objects/Collection.cpp index 44b0f0361..d608b1d20 100644 --- a/src/fdosecrets/objects/Collection.cpp +++ b/src/fdosecrets/objects/Collection.cpp @@ -17,48 +17,45 @@ #include "Collection.h" +#include "fdosecrets/FdoSecretsPlugin.h" #include "fdosecrets/FdoSecretsSettings.h" #include "fdosecrets/objects/Item.h" #include "fdosecrets/objects/Prompt.h" #include "fdosecrets/objects/Service.h" +#include "core/Database.h" +#include "core/Group.h" +#include "core/Metadata.h" #include "core/Tools.h" -#include "gui/DatabaseWidget.h" -#include "gui/GuiTools.h" #include #include namespace FdoSecrets { - Collection* Collection::Create(Service* parent, DatabaseWidget* backend) + Collection* Collection::Create(Service* parent, const QString& name) { - return new Collection(parent, backend); + QScopedPointer res{new Collection(parent, name)}; + if (!res->dbus()->registerObject(res.data())) { + return nullptr; + } + + return res.take(); } - Collection::Collection(Service* parent, DatabaseWidget* backend) + Collection::Collection(Service* parent, const QString& name) : DBusObject(parent) - , m_backend(backend) - , m_exposedGroup(nullptr) + , m_backend() + , m_name{name} + , m_exposedGroup() + , m_items{} + , m_entryToItem{} { - // whenever the file path or the database object itself change, we do a full reload. - connect(backend, &DatabaseWidget::databaseFilePathChanged, this, &Collection::reloadBackendOrDelete); - connect(backend, &DatabaseWidget::databaseReplaced, this, &Collection::reloadBackendOrDelete); - - // also remember to clear/populate the database when lock state changes. - connect(backend, &DatabaseWidget::databaseUnlocked, this, &Collection::onDatabaseLockChanged); - connect(backend, &DatabaseWidget::databaseLocked, this, &Collection::onDatabaseLockChanged); - - // get notified whenever unlock db dialog finishes - connect(parent, &Service::doneUnlockDatabaseInDialog, this, [this](bool accepted, DatabaseWidget* dbWidget) { - if (!dbWidget || dbWidget != m_backend) { - return; - } - emit doneUnlockCollection(accepted); - }); } - bool Collection::reloadBackend() + Collection::~Collection() = default; + + void Collection::reloadBackend() { Q_ASSERT(m_backend); @@ -69,50 +66,23 @@ namespace FdoSecrets m_items.first()->removeFromDBus(); } cleanupConnections(); - dbus()->unregisterObject(this); - - // make sure we have updated copy of the filepath, which is used to identify the database. - m_backendPath = m_backend->database()->canonicalFilePath(); - - // register the object, handling potentially duplicated name - if (!dbus()->registerObject(this)) { - return false; - } - - // populate contents after expose on dbus, because items rely on parent's dbus object path - if (!backendLocked()) { - populateContents(); - } else { - cleanupConnections(); - } + populateContents(); emit collectionChanged(); - return true; - } - - void Collection::reloadBackendOrDelete() - { - if (!reloadBackend()) { - removeFromDBus(); - } } DBusResult Collection::ensureBackend() const { if (!m_backend) { - return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT); - } - return {}; - } - - DBusResult Collection::ensureUnlocked() const - { - if (backendLocked()) { return DBusResult(DBUS_ERROR_SECRET_IS_LOCKED); } return {}; } + /** + * D-Bus Properties + */ + DBusResult Collection::items(QList& items) const { auto ret = ensureBackend(); @@ -130,11 +100,12 @@ namespace FdoSecrets return ret; } - if (backendLocked()) { + if (!m_backend) { label = name(); } else { - label = m_backend->database()->metadata()->name(); + label = m_backend->metadata()->name(); } + return {}; } @@ -144,22 +115,14 @@ namespace FdoSecrets if (ret.err()) { return ret; } - ret = ensureUnlocked(); - if (ret.err()) { - return ret; - } - m_backend->database()->metadata()->setName(label); + m_backend->metadata()->setName(label); return {}; } DBusResult Collection::locked(bool& locked) const { - auto ret = ensureBackend(); - if (ret.err()) { - return ret; - } - locked = backendLocked(); + locked = !m_backend; return {}; } @@ -169,12 +132,7 @@ namespace FdoSecrets if (ret.err()) { return ret; } - ret = ensureUnlocked(); - if (ret.err()) { - return ret; - } - created = static_cast( - m_backend->database()->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000); + created = static_cast(m_backend->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000); return {}; } @@ -185,17 +143,17 @@ namespace FdoSecrets if (ret.err()) { return ret; } - ret = ensureUnlocked(); - if (ret.err()) { - return ret; - } // FIXME: there seems not to have a global modified time. // Use a more accurate time, considering all metadata, group, entry. - modified = static_cast( - m_backend->database()->rootGroup()->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000); + modified = static_cast(m_backend->rootGroup()->timeInfo().lastModificationTime().toMSecsSinceEpoch() + / 1000); return {}; } + /** + * D-Bus Methods + */ + DBusResult Collection::remove(PromptBase*& prompt) { auto ret = ensureBackend(); @@ -219,13 +177,6 @@ namespace FdoSecrets return ret; } - if (backendLocked()) { - // searchItems should work, whether `this` is locked or not. - // however, we can't search items the same way as in gnome-keying, - // because there's no database at all when locked. - return {}; - } - // shortcut logic for Uuid/Path attributes, as they can uniquely identify an item. if (attributes.contains(ItemAttributes::UuidKey)) { auto uuid = QUuid::fromRfc4122(QByteArray::fromHex(attributes.value(ItemAttributes::UuidKey).toLatin1())); @@ -294,11 +245,6 @@ namespace FdoSecrets Item*& item, PromptBase*& prompt) { - auto ret = ensureBackend(); - if (ret.err()) { - return ret; - } - item = nullptr; prompt = PromptBase::Create(service(), this, properties, secret, replace); @@ -320,84 +266,6 @@ namespace FdoSecrets return {}; } - const QSet Collection::aliases() const - { - return m_aliases; - } - - DBusResult Collection::addAlias(QString alias) - { - auto ret = ensureBackend(); - if (ret.err()) { - return ret; - } - - alias = encodePath(alias); - - if (m_aliases.contains(alias)) { - return {}; - } - - emit aliasAboutToAdd(alias); - - if (dbus()->registerAlias(this, alias)) { - m_aliases.insert(alias); - emit aliasAdded(alias); - } else { - return QDBusError::InvalidObjectPath; - } - - return {}; - } - - DBusResult Collection::removeAlias(QString alias) - { - auto ret = ensureBackend(); - if (ret.err()) { - return ret; - } - - alias = encodePath(alias); - - if (!m_aliases.contains(alias)) { - return {}; - } - - dbus()->unregisterAlias(alias); - m_aliases.remove(alias); - emit aliasRemoved(alias); - - return {}; - } - - QString Collection::name() const - { - if (m_backendPath.isEmpty()) { - // This is a newly created db without saving to file, - // but we have to give a name, which is used to register dbus path. - // We use database name for this purpose. For simplicity, we don't monitor the name change. - // So the dbus object path is not updated if the db name changes. - // This should not be a problem because once the database gets saved, - // the dbus path will be updated to use filename and everything back to normal. - return m_backend->database()->metadata()->name(); - } - return QFileInfo(m_backendPath).completeBaseName(); - } - - DatabaseWidget* Collection::backend() const - { - return m_backend; - } - - void Collection::onDatabaseLockChanged() - { - if (!reloadBackend()) { - removeFromDBus(); - return; - } - emit collectionLockChanged(backendLocked()); - } - void Collection::populateContents() { if (!m_backend) { @@ -406,8 +274,8 @@ namespace FdoSecrets // we have an unlocked db - auto newUuid = FdoSecrets::settings()->exposedGroup(m_backend->database()); - auto newGroup = m_backend->database()->rootGroup()->findGroupByUuid(newUuid); + auto newUuid = FdoSecrets::settings()->exposedGroup(m_backend.data()); + auto newGroup = m_backend->rootGroup()->findGroupByUuid(newUuid); if (!newGroup || newGroup->isRecycled()) { // no exposed group, delete self removeFromDBus(); @@ -425,90 +293,75 @@ namespace FdoSecrets // signal should be first emitted, and we will clean up connection in reloadDatabase, // so this handler won't be triggered. connect(m_exposedGroup.data(), &Group::groupAboutToRemove, this, [this](Group* toBeRemoved) { - if (backendLocked()) { + if (!m_backend) { return; } - auto db = m_backend->database(); - if (toBeRemoved->database() != db) { + + Q_ASSERT(toBeRemoved->database() == m_backend); + if (toBeRemoved->database() != m_backend) { // should not happen, but anyway. // somehow our current database has been changed, and the old group is being deleted // possibly logic changes in replaceDatabase. return; } - auto uuid = FdoSecrets::settings()->exposedGroup(db); - auto exposedGroup = db->rootGroup()->findGroupByUuid(uuid); + auto uuid = FdoSecrets::settings()->exposedGroup(m_backend.data()); + auto exposedGroup = m_backend->rootGroup()->findGroupByUuid(uuid); if (toBeRemoved == exposedGroup) { // reset the exposed group to none - FdoSecrets::settings()->setExposedGroup(db, {}); + FdoSecrets::settings()->setExposedGroup(m_backend.data(), {}); } }); // Another possibility is the group being moved to recycle bin. connect(m_exposedGroup.data(), &Group::modified, this, [this]() { if (m_exposedGroup->isRecycled()) { // reset the exposed group to none - FdoSecrets::settings()->setExposedGroup(m_backend->database().data(), {}); + FdoSecrets::settings()->setExposedGroup(m_backend.data(), {}); } }); // Monitor exposed group settings - connect(m_backend->database()->metadata()->customData(), &CustomData::modified, this, [this]() { - if (!m_exposedGroup || backendLocked()) { + connect(m_backend->metadata()->customData(), &CustomData::modified, this, [this]() { + if (!m_exposedGroup || !m_backend) { return; } - if (m_exposedGroup->uuid() == FdoSecrets::settings()->exposedGroup(m_backend->database())) { + if (m_exposedGroup->uuid() == FdoSecrets::settings()->exposedGroup(m_backend.data())) { // no change return; } - onDatabaseExposedGroupChanged(); + reloadBackend(); }); // Add items for existing entry const auto entries = m_exposedGroup->entriesRecursive(false); for (const auto& entry : entries) { - onEntryAdded(entry, false); + onEntryAdded(entry); } // Do not connect to Database::modified signal because we only want signals for the subset under m_exposedGroup - connect(m_backend->database()->metadata(), &Metadata::modified, this, &Collection::collectionChanged); + connect(m_backend->metadata(), &Metadata::modified, this, &Collection::collectionChanged); connectGroupSignalRecursive(m_exposedGroup); } - void Collection::onDatabaseExposedGroupChanged() - { - // delete all items - // this has to be done because the backend is actually still there - // just we don't expose them - for (const auto& item : asConst(m_items)) { - item->removeFromDBus(); - } - - // repopulate - if (!backendLocked()) { - populateContents(); - } - } - - void Collection::onEntryAdded(Entry* entry, bool emitSignal) + Item* Collection::onEntryAdded(Entry* entry) { if (entry->isRecycled()) { - return; + return nullptr; } - auto item = Item::Create(this, entry); + auto item = m_entryToItem.value(entry); + // Entry already has corresponding Item + if (item) { + return item; + } + + item = Item::Create(this, entry); if (!item) { - return; + return nullptr; } m_items << item; m_entryToItem[entry] = item; - // forward delete signals - connect(entry->group(), &Group::entryAboutToRemove, item, [item](Entry* toBeRemoved) { - if (item->backend() == toBeRemoved) { - item->removeFromDBus(); - } - }); - // relay signals connect(item, &Item::itemChanged, this, [this, item]() { emit itemChanged(item); }); connect(item, &Item::itemAboutToDelete, this, [this, item]() { @@ -517,9 +370,7 @@ namespace FdoSecrets emit itemDeleted(item); }); - if (emitSignal) { - emit itemCreated(item); - } + return item; } void Collection::connectGroupSignalRecursive(Group* group) @@ -529,7 +380,11 @@ namespace FdoSecrets } connect(group, &Group::modified, this, &Collection::collectionChanged); - connect(group, &Group::entryAdded, this, [this](Entry* entry) { onEntryAdded(entry, true); }); + connect(group, &Group::entryAdded, this, [this](Entry* entry) { + if (Item* item = onEntryAdded(entry)) { + emit itemCreated(item); + } + }); const auto children = group->children(); for (const auto& cg : children) { @@ -537,37 +392,34 @@ namespace FdoSecrets } } - Service* Collection::service() const + FdoSecretsPlugin* Collection::plugin() const { - return qobject_cast(parent()); + return service()->plugin(); } - bool Collection::doLock() + bool Collection::doLock(const DBusClientPtr& client) const { - Q_ASSERT(m_backend); + // Already locked + if (!m_backend) { + return true; + } - // do not call m_backend->lock() directly - // let the service handle locking which handles concurrent calls - return service()->doLockDatabase(m_backend); + return plugin()->doLockDatabase(client, m_name); } - void Collection::doUnlock() + bool Collection::doUnlock(const DBusClientPtr& client) const { - Q_ASSERT(m_backend); + // Already unlocked + if (m_backend) { + return true; + } - return service()->doUnlockDatabaseInDialog(m_backend); - } - - bool Collection::doDelete() - { - Q_ASSERT(m_backend); - - return service()->doCloseDatabase(m_backend); + return plugin()->doUnlockDatabase(client, m_name); } void Collection::removeFromDBus() { - if (!m_backend) { + if (!m_backend && !m_exposedGroup) { // I'm already deleted return; } @@ -577,41 +429,38 @@ namespace FdoSecrets // remove from dbus early dbus()->unregisterObject(this); - // remove alias manually to trigger signal - for (const auto& a : aliases()) { - removeAlias(a).okOrDie(); - } - // cleanup connection on Database cleanupConnections(); - // cleanup connection on Backend itself - m_backend->disconnect(this); + + // items will be removed automatically as they are children objects + m_items.clear(); + parent()->disconnect(this); m_exposedGroup = nullptr; - // reset backend and delete self + // reset database and delete self m_backend = nullptr; - deleteLater(); - // items will be removed automatically as they are children objects + deleteLater(); } void Collection::cleanupConnections() { - m_backend->database()->metadata()->customData()->disconnect(this); + if (m_backend && m_backend->metadata() && m_backend->metadata()->customData()) { + m_backend->metadata()->customData()->disconnect(this); + } + if (m_exposedGroup) { for (const auto group : m_exposedGroup->groupsRecursive(true)) { group->disconnect(this); } } - - m_items.clear(); } - QString Collection::backendFilePath() const + QString Collection::dbusName() const { - return m_backendPath; + return QFileInfo(m_name).completeBaseName(); } Group* Collection::exposedRootGroup() const @@ -619,23 +468,13 @@ namespace FdoSecrets return m_exposedGroup; } - bool Collection::backendLocked() const + bool Collection::doDeleteItem(const DBusClientPtr& client, const Item* item) const { - return !m_backend || !m_backend->database()->isInitialized() || m_backend->isLocked(); - } + Q_ASSERT(m_backend); - bool Collection::doDeleteEntry(Entry* entry) - { - // Confirm entry removal before moving forward - bool permanent = entry->isRecycled() || !m_backend->database()->metadata()->recycleBinEnabled(); - if (FdoSecrets::settings()->confirmDeleteItem() - && !GuiTools::confirmDeleteEntries(m_backend, {entry}, permanent)) { - return false; - } - - auto num = GuiTools::deleteEntriesResolveReferences(m_backend, {entry}, permanent); - - return num != 0; + auto entry = item->backend(); + bool permanent = entry->isRecycled() || !m_backend->metadata()->recycleBinEnabled(); + return plugin()->requestEntriesRemove(client, m_name, {entry}, permanent); } Group* Collection::findCreateGroupByPath(const QString& groupPath) @@ -682,7 +521,7 @@ namespace FdoSecrets auto* entry = new Entry(); entry->setUuid(QUuid::createUuid()); entry->setTitle(itemName); - entry->setUsername(m_backend->database()->metadata()->defaultUserName()); + entry->setUsername(m_backend->metadata()->defaultUserName()); group->applyGroupIconOnCreateTo(entry); entry->setGroup(group); @@ -691,9 +530,27 @@ namespace FdoSecrets client->setItemAuthorized(entry->uuid(), AuthDecision::Allowed); // when creation finishes in backend, we will already have item - auto created = m_entryToItem.value(entry, nullptr); - + auto created = m_entryToItem.value(entry); return created; } + void Collection::databaseLocked() + { + if (!m_backend) { + return; + } + + m_backend = nullptr; + emit collectionChanged(); + emit collectionLockChanged(false); + } + + void Collection::databaseUnlocked(QSharedPointer db) + { + Q_ASSERT(!m_backend); + m_backend = db; + reloadBackend(); + emit collectionLockChanged(true); + } + } // namespace FdoSecrets diff --git a/src/fdosecrets/objects/Collection.h b/src/fdosecrets/objects/Collection.h index c8a49ef35..f8b3c7125 100644 --- a/src/fdosecrets/objects/Collection.h +++ b/src/fdosecrets/objects/Collection.h @@ -24,8 +24,8 @@ #include "core/EntrySearcher.h" class Database; -class DatabaseWidget; class Entry; +class FdoSecretsPlugin; class Group; namespace FdoSecrets @@ -38,19 +38,23 @@ namespace FdoSecrets Q_OBJECT Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_COLLECTION_LITERAL) - explicit Collection(Service* parent, DatabaseWidget* backend); + explicit Collection(Service* parent, const QString& name); public: /** * @brief Create a new instance of `Collection` * @param parent the owning Service - * @param backend the widget containing the database + * @param name the collection name * @return pointer to created instance, or nullptr when error happens. * This may be caused by * - DBus path registration error - * - database has no exposed group */ - static Collection* Create(Service* parent, DatabaseWidget* backend); + static Collection* Create(Service* parent, const QString& name); + ~Collection() override; + + /** + * D-Bus Properties + */ Q_INVOKABLE DBUS_PROPERTY DBusResult items(QList& items) const; @@ -63,6 +67,9 @@ namespace FdoSecrets Q_INVOKABLE DBUS_PROPERTY DBusResult modified(qulonglong& modified) const; + /** + * D-Bus Methods + */ Q_INVOKABLE DBusResult remove(PromptBase*& prompt); Q_INVOKABLE DBusResult searchItems(const DBusClientPtr& client, const StringStringMap& attributes, @@ -71,6 +78,9 @@ namespace FdoSecrets createItem(const QVariantMap& properties, const Secret& secret, bool replace, Item*& item, PromptBase*& prompt); signals: + /** + * D-Bus Signals + */ void itemCreated(Item* item); void itemDeleted(Item* item); void itemChanged(Item* item); @@ -79,85 +89,69 @@ namespace FdoSecrets void collectionAboutToDelete(); void collectionLockChanged(bool newLocked); - void aliasAboutToAdd(const QString& alias); - void aliasAdded(const QString& alias); - void aliasRemoved(const QString& alias); - - void doneUnlockCollection(bool accepted); - public: + // For setting multiple properties at once DBusResult setProperties(const QVariantMap& properties); - bool isValid() const - { - return backend(); - } - - DBusResult removeAlias(QString alias); - DBusResult addAlias(QString alias); - const QSet aliases() const; - /** * A human readable name of the collection, available even if the db is locked * @return */ - QString name() const; + QString name() const + { + return m_name; + } + QString dbusName() const; Group* exposedRootGroup() const; - DatabaseWidget* backend() const; - QString backendFilePath() const; - Service* service() const; + Database* backend() const + { + return m_backend.data(); + } + + // Access to ancestors + Service* service() const + { + return qobject_cast(parent()); + } + FdoSecretsPlugin* plugin() const; static EntrySearcher::SearchTerm attributeToTerm(const QString& key, const QString& value); public slots: // expose some methods for Prompt to use + bool doLock(const DBusClientPtr& client) const; + bool doUnlock(const DBusClientPtr& client) const; - bool doLock(); - // actually only closes database tab in KPXC - bool doDelete(); - // Async, finish signal doneUnlockCollection - void doUnlock(); + bool doDeleteItem(const DBusClientPtr& client, const Item* item) const; - bool doDeleteEntry(Entry* entry); Item* doNewItem(const DBusClientPtr& client, QString itemPath); // Only delete from dbus, will remove self. Do not affect database in KPXC void removeFromDBus(); - // force reload info from backend, potentially delete self - bool reloadBackend(); + void reloadBackend(); - private slots: - void onDatabaseLockChanged(); - void onDatabaseExposedGroupChanged(); - - // calls reloadBackend, delete self when error - void reloadBackendOrDelete(); + void databaseLocked(); + void databaseUnlocked(QSharedPointer db); private: + friend class LockCollectionsPrompt; + friend class UnlockPrompt; friend class DeleteCollectionPrompt; - friend class CreateCollectionPrompt; + friend class CreateItemPrompt; - void onEntryAdded(Entry* entry, bool emitSignal); + Item* onEntryAdded(Entry* entry); void populateContents(); void connectGroupSignalRecursive(Group* group); void cleanupConnections(); - bool backendLocked() const; - /** * Check if the backend is a valid object, send error reply if not. * @return true if the backend is valid. */ DBusResult ensureBackend() const; - /** - * Ensure the database is unlocked, send error reply if locked. - * @return true if the database is locked - */ - DBusResult ensureUnlocked() const; - /** * Like mkdir -p, find or create the group by path, under m_exposedGroup * @param groupPath @@ -166,11 +160,12 @@ namespace FdoSecrets Group* findCreateGroupByPath(const QString& groupPath); private: - QPointer m_backend; - QString m_backendPath; + QSharedPointer m_backend; + + QString m_name; + QPointer m_exposedGroup; - QSet m_aliases; QList m_items; QMap m_entryToItem; }; diff --git a/src/fdosecrets/objects/Item.cpp b/src/fdosecrets/objects/Item.cpp index 2c6d13435..37e4c4a71 100644 --- a/src/fdosecrets/objects/Item.cpp +++ b/src/fdosecrets/objects/Item.cpp @@ -74,8 +74,20 @@ namespace FdoSecrets , m_backend(backend) { connect(m_backend, &Entry::modified, this, &Item::itemChanged); + // Remove from dbus when deleted + connect(m_backend->group(), &Group::entryAboutToRemove, this, [this](Entry* toBeRemoved) { + if (m_backend == toBeRemoved) { + removeFromDBus(); + } + }); } + Item::~Item() = default; + + /** + * D-Bus Properties + */ + DBusResult Item::locked(const DBusClientPtr& client, bool& locked) const { auto ret = ensureBackend(); @@ -232,6 +244,10 @@ namespace FdoSecrets return {}; } + /** + * D-Bus Methods + */ + DBusResult Item::remove(PromptBase*& prompt) { auto ret = ensureBackend(); @@ -249,9 +265,8 @@ namespace FdoSecrets { auto ret = getSecretNoNotification(client, session, secret); if (ret.ok()) { - service()->plugin()->emitRequestShowNotification( - tr(R"(Entry "%1" from database "%2" was used by %3)") - .arg(m_backend->title(), collection()->name(), client->name())); + plugin()->emitRequestShowNotification(tr(R"(Entry "%1" from database "%2" was used by %3)") + .arg(m_backend->title(), collection()->name(), client->name())); } return ret; } @@ -331,11 +346,6 @@ namespace FdoSecrets return {}; } - Collection* Item::collection() const - { - return qobject_cast(parent()); - } - DBusResult Item::ensureBackend() const { if (!m_backend) { @@ -362,11 +372,11 @@ namespace FdoSecrets return m_backend; } - bool Item::doDelete() + bool Item::doDelete(const DBusClientPtr& client) const { Q_ASSERT(m_backend); - return collection()->doDeleteEntry(m_backend); + return collection()->doDeleteItem(client, this); } void Item::removeFromDBus() @@ -378,6 +388,8 @@ namespace FdoSecrets // before the current Item is deleted in the event loop. dbus()->unregisterObject(this); + m_backend->group()->disconnect(this); + m_backend = nullptr; deleteLater(); } @@ -387,6 +399,11 @@ namespace FdoSecrets return collection()->service(); } + FdoSecretsPlugin* Item::plugin() const + { + return service()->plugin(); + } + QString Item::path() const { QStringList pathComponents{m_backend->title()}; @@ -405,6 +422,10 @@ namespace FdoSecrets return pathComponents.join('/'); } + /** + * Helper functions + */ + DBusResult setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType) { auto mimeName = contentType.split(';').takeFirst().trimmed(); diff --git a/src/fdosecrets/objects/Item.h b/src/fdosecrets/objects/Item.h index a8d4f4ba3..a3e7a4339 100644 --- a/src/fdosecrets/objects/Item.h +++ b/src/fdosecrets/objects/Item.h @@ -22,6 +22,7 @@ #include "fdosecrets/dbus/DBusObject.h" class Entry; +class FdoSecretsPlugin; namespace FdoSecrets { @@ -33,9 +34,10 @@ namespace FdoSecrets constexpr const auto TotpKey = "TOTP"; } // namespace ItemAttributes - class Session; class Collection; class PromptBase; + class Service; + class Session; class Item : public DBusObject { @@ -54,6 +56,11 @@ namespace FdoSecrets * - DBus path registration error */ static Item* Create(Collection* parent, Entry* backend); + ~Item() override; + + /** + * D-Bus Properties + */ Q_INVOKABLE DBUS_PROPERTY DBusResult locked(const DBusClientPtr& client, bool& locked) const; @@ -67,6 +74,10 @@ namespace FdoSecrets Q_INVOKABLE DBUS_PROPERTY DBusResult modified(qulonglong& modified) const; + /** + * D-Bus Methods + */ + Q_INVOKABLE DBusResult remove(PromptBase*& prompt); Q_INVOKABLE DBusResult getSecret(const DBusClientPtr& client, Session* session, Secret& secret); Q_INVOKABLE DBusResult setSecret(const DBusClientPtr& client, const Secret& secret); @@ -78,12 +89,20 @@ namespace FdoSecrets public: static const QSet ReadOnlyAttributes; + // Helper for Service::getSecrets DBusResult getSecretNoNotification(const DBusClientPtr& client, Session* session, Secret& secret) const; + // For setting multiple properties at once DBusResult setProperties(const QVariantMap& properties); Entry* backend() const; - Collection* collection() const; + + // Access to ancestors + Collection* collection() const + { + return qobject_cast(parent()); + } Service* service() const; + FdoSecretsPlugin* plugin() const; /** * Compute the entry path relative to the exposed group @@ -93,7 +112,7 @@ namespace FdoSecrets public slots: // will actually delete the entry in KPXC - bool doDelete(); + bool doDelete(const DBusClientPtr& client) const; // Only delete from dbus, will remove self. Do not affect database in KPXC void removeFromDBus(); @@ -101,7 +120,7 @@ namespace FdoSecrets private slots: /** * Check if the backend is a valid object, send error reply if not. - * @return No error if the backend is valid. + * @return No error if the entry is valid. */ DBusResult ensureBackend() const; @@ -111,6 +130,9 @@ namespace FdoSecrets */ DBusResult ensureUnlocked() const; + private: + friend class DeleteItemPrompt; + private: QPointer m_backend; }; diff --git a/src/fdosecrets/objects/Prompt.cpp b/src/fdosecrets/objects/Prompt.cpp index e89cd499b..54bee9535 100644 --- a/src/fdosecrets/objects/Prompt.cpp +++ b/src/fdosecrets/objects/Prompt.cpp @@ -17,51 +17,42 @@ #include "Prompt.h" +#include "fdosecrets/FdoSecretsPlugin.h" #include "fdosecrets/objects/Collection.h" #include "fdosecrets/objects/Item.h" #include "fdosecrets/objects/Service.h" #include "fdosecrets/objects/Session.h" -#include "fdosecrets/widgets/AccessControlDialog.h" #include "FdoSecretsSettings.h" #include "core/Entry.h" -#include "gui/MessageBox.h" #include #include -#include namespace FdoSecrets { - const PromptResult PromptResult::Pending{PromptResult::AsyncPending}; - PromptBase::PromptBase(Service* parent) : DBusObject(parent) { connect(this, &PromptBase::completed, this, &PromptBase::deleteLater); } - QWindow* PromptBase::findWindow(const QString& windowId) - { - // find parent window, or nullptr if not found - bool ok = false; - WId wid = windowId.toULongLong(&ok, 0); - QWindow* parent = nullptr; - if (ok) { - parent = QWindow::fromWinId(wid); - } - if (parent) { - // parent is not the child of any object, so make sure it gets deleted at some point - QObject::connect(this, &QObject::destroyed, parent, &QObject::deleteLater); - } - return parent; - } - Service* PromptBase::service() const { return qobject_cast(parent()); } + PromptBase::OverrideParentWindow::OverrideParentWindow(PromptBase* prompt, const QString& newParentWindowId) + : m_prompt(prompt) + , m_oldParentWindowId(prompt->service()->plugin()->overrideMessageBoxParent(newParentWindowId)) + { + } + + PromptBase::OverrideParentWindow::~OverrideParentWindow() + { + m_prompt->service()->plugin()->overrideMessageBoxParent(m_oldParentWindowId); + } + DBusResult PromptBase::prompt(const DBusClientPtr& client, const QString& windowId) { if (thread() != QThread::currentThread()) { @@ -78,20 +69,25 @@ namespace FdoSecrets if (!c) { return; } + if (m_signalSent) { return; } + auto res = promptSync(c, windowId); - if (!res.isPending()) { - finishPrompt(res.isDismiss()); + if (m_signalSent) { + return; } + m_signalSent = true; + emit completed(!res.ok(), currentResult()); }); return {}; } DBusResult PromptBase::dismiss() { - finishPrompt(true); + m_signalSent = true; + emit completed(true, currentResult()); return {}; } @@ -100,31 +96,22 @@ namespace FdoSecrets return ""; } - void PromptBase::finishPrompt(bool dismissed) - { - if (m_signalSent) { - return; - } - m_signalSent = true; - emit completed(dismissed, currentResult()); - } - DeleteCollectionPrompt::DeleteCollectionPrompt(Service* parent, Collection* coll) : PromptBase(parent) , m_collection(coll) { } - PromptResult DeleteCollectionPrompt::promptSync(const DBusClientPtr&, const QString& windowId) + DBusResult DeleteCollectionPrompt::promptSync(const DBusClientPtr&, const QString& windowId) { - MessageBox::OverrideParent override(findWindow(windowId)); + OverrideParentWindow override(this, windowId); - // if m_collection is already gone then treat as deletion accepted - auto accepted = true; if (m_collection) { - accepted = m_collection->doDelete(); + // Collection removal is just disconnecting it from dbus. + // GUI then reacts with potentially closing the database. + m_collection->removeFromDBus(); } - return PromptResult::accepted(accepted); + return {}; } CreateCollectionPrompt::CreateCollectionPrompt(Service* parent, QVariantMap properties, QString alias) @@ -139,9 +126,9 @@ namespace FdoSecrets return QVariant::fromValue(DBusMgr::objectPathSafe(m_coll)); } - PromptResult CreateCollectionPrompt::promptSync(const DBusClientPtr&, const QString& windowId) + DBusResult CreateCollectionPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) { - MessageBox::OverrideParent override(findWindow(windowId)); + OverrideParentWindow override(this, windowId); bool created = false; // collection with the alias may be created since the prompt was created @@ -149,13 +136,15 @@ namespace FdoSecrets if (ret.err()) { return ret; } + if (!m_coll) { created = true; - m_coll = service()->doNewDatabase(); + m_coll = service()->doNewDatabase(client); if (!m_coll) { - return PromptResult::accepted(false); + return {QDBusError::AccessDenied}; } } + ret = m_coll->setProperties(m_properties); if (ret.err()) { if (created) { @@ -164,7 +153,7 @@ namespace FdoSecrets return ret; } if (!m_alias.isEmpty()) { - ret = m_coll->addAlias(m_alias); + ret = service()->setAlias(m_alias, m_coll); if (ret.err()) { if (created) { m_coll->removeFromDBus(); @@ -190,19 +179,19 @@ namespace FdoSecrets return QVariant::fromValue(m_locked); } - PromptResult LockCollectionsPrompt::promptSync(const DBusClientPtr&, const QString& windowId) + DBusResult LockCollectionsPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) { - MessageBox::OverrideParent override(findWindow(windowId)); + OverrideParentWindow override(this, windowId); for (const auto& c : asConst(m_collections)) { if (c) { - auto accepted = c->doLock(); + auto accepted = c->doLock(client); if (accepted) { m_locked << c->objectPath(); } } } - return PromptResult::accepted(m_locked.size() == m_collections.size()); + return {m_locked.size() == m_collections.size() ? QDBusError::NoError : QDBusError::AccessDenied}; } UnlockPrompt::UnlockPrompt(Service* parent, const QSet& colls, const QSet& items) @@ -222,73 +211,24 @@ namespace FdoSecrets return QVariant::fromValue(m_unlocked); } - PromptResult UnlockPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) + DBusResult UnlockPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) { - MessageBox::OverrideParent override(findWindow(windowId)); - - // for use in unlockItems - m_windowId = windowId; - m_client = client; + OverrideParentWindow override(this, windowId); // first unlock any collections - bool waitingForCollections = false; for (const auto& c : asConst(m_collections)) { if (c) { - connect(c, &Collection::doneUnlockCollection, this, &UnlockPrompt::collectionUnlockFinished); - // doUnlock is nonblocking, execution will continue in collectionUnlockFinished - // it is ok to call doUnlock multiple times before it's actually unlocked by the user - c->doUnlock(); - waitingForCollections = true; + bool accepted = c->doUnlock(client); + if (accepted) { + m_unlocked << c->objectPath(); + } else { + m_numRejected += 1; + // no longer need to unlock the item if its containing collection didn't unlock. + m_items.remove(c); + } } } - // unlock items directly if no collection unlocking pending - // o.w. doing it in collectionUnlockFinished - if (!waitingForCollections) { - unlockItems(); - } - - return PromptResult::Pending; - } - - void UnlockPrompt::collectionUnlockFinished(bool accepted) - { - auto coll = qobject_cast(sender()); - if (!coll) { - return; - } - - // one shot - coll->disconnect(this); - - if (!m_collections.contains(coll)) { - // should not happen - return; - } - - if (accepted) { - m_unlocked << coll->objectPath(); - } else { - m_numRejected += 1; - // no longer need to unlock the item if its containing collection didn't unlock. - m_items.remove(coll); - } - - // if we got response for all collections - if (m_numRejected + m_unlocked.size() == m_collections.size()) { - // next step is to unlock items - unlockItems(); - } - } - - void UnlockPrompt::unlockItems() - { - auto client = m_client.lock(); - if (!client) { - // client already gone - return; - } - // flatten to list of entries QList entries; for (const auto& itemsPerColl : asConst(m_items)) { @@ -310,50 +250,40 @@ namespace FdoSecrets entries << entry; } } + if (!entries.isEmpty()) { - QString app = tr("%1 (PID: %2)").arg(client->name()).arg(client->pid()); - auto ac = new AccessControlDialog( - findWindow(m_windowId), entries, app, client->processInfo(), AuthOption::Remember); - connect(ac, &AccessControlDialog::finished, this, &UnlockPrompt::itemUnlockFinished); - connect(ac, &AccessControlDialog::finished, ac, &AccessControlDialog::deleteLater); - ac->open(); - } else { - itemUnlockFinished({}, AuthDecision::Undecided); - } - } - - void UnlockPrompt::itemUnlockFinished(const QHash& decisions, AuthDecision forFutureEntries) - { - auto client = m_client.lock(); - if (!client) { - // client already gone - qDebug() << "DBus client gone before item unlocking finish"; - return; - } - for (auto it = decisions.constBegin(); it != decisions.constEnd(); ++it) { - auto entry = it.key(); - auto uuid = entry->uuid(); - // get back the corresponding item - auto item = m_entryToItems.value(uuid); - if (!item) { - continue; + QHash decisions{}; + AuthDecision forFutureEntries{}; + if (!service()->plugin()->requestEntriesUnlock(client, windowId, entries, decisions, forFutureEntries)) { + return {QDBusError::InternalError}; } - // set auth - client->setItemAuthorized(uuid, it.value()); + for (auto it = decisions.constBegin(); it != decisions.constEnd(); ++it) { + auto entry = it.key(); + auto uuid = entry->uuid(); + // get back the corresponding item + auto item = m_entryToItems.value(uuid); + if (!item) { + continue; + } - if (client->itemAuthorized(uuid)) { - m_unlocked += item->objectPath(); - } else { - m_numRejected += 1; + // set auth + client->setItemAuthorized(uuid, it.value()); + + if (client->itemAuthorized(uuid)) { + m_unlocked += item->objectPath(); + } else { + m_numRejected += 1; + } + } + if (forFutureEntries != AuthDecision::Undecided) { + client->setAllAuthorized(forFutureEntries); } } - if (forFutureEntries != AuthDecision::Undecided) { - client->setAllAuthorized(forFutureEntries); - } + // if anything is not unlocked, treat the whole prompt as dismissed // so the client has a chance to handle the error - finishPrompt(m_numRejected > 0); + return {!m_numRejected ? QDBusError::NoError : QDBusError::AccessDenied}; } DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item) @@ -362,16 +292,16 @@ namespace FdoSecrets { } - PromptResult DeleteItemPrompt::promptSync(const DBusClientPtr&, const QString& windowId) + DBusResult DeleteItemPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) { - MessageBox::OverrideParent override(findWindow(windowId)); + OverrideParentWindow override(this, windowId); // if m_item is gone, assume it's already deleted bool deleted = true; if (m_item) { - deleted = m_item->doDelete(); + deleted = m_item->doDelete(client); } - return PromptResult::accepted(deleted); + return {deleted ? QDBusError::NoError : QDBusError::AccessDenied}; } CreateItemPrompt::CreateItemPrompt(Service* parent, @@ -384,9 +314,7 @@ namespace FdoSecrets , m_properties(std::move(properties)) , m_secret(std::move(secret)) , m_replace(replace) - , m_item(nullptr) - // session aliveness also need to be tracked, for potential use later in updateItem - , m_sess(m_secret.session) + , m_item{nullptr} { } @@ -395,53 +323,34 @@ namespace FdoSecrets return QVariant::fromValue(DBusMgr::objectPathSafe(m_item)); } - PromptResult CreateItemPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) + DBusResult CreateItemPrompt::promptSync(const DBusClientPtr& client, const QString& windowId) { if (!m_coll) { - return PromptResult::accepted(false); + return DBusResult{DBUS_ERROR_SECRET_NO_SUCH_OBJECT}; } - // save a weak reference to the client which may be used asynchronously later - m_client = client; - // give the user a chance to unlock the collection // UnlockPrompt will handle the case of collection already unlocked auto prompt = PromptBase::Create(service(), QSet{m_coll.data()}, QSet{}); if (!prompt) { - return DBusResult{QDBusError::InternalError}; + return {QDBusError::InternalError}; } - // postpone anything after the prompt - connect(prompt, &PromptBase::completed, this, [this, windowId](bool dismissed) { - if (dismissed) { - finishPrompt(dismissed); - } else { - auto res = createItem(windowId); - if (res.err()) { - qWarning() << "FdoSecrets:" << res; - finishPrompt(true); - } - } - }); - auto ret = prompt->prompt(client, windowId); + auto ret = prompt->promptSync(client, windowId); if (ret.err()) { return ret; } - return PromptResult::Pending; + + auto res = createItem(client, windowId); + if (res.err()) { + qWarning() << "FdoSecrets:" << res; + } + + return {res.ok() ? QDBusError::NoError : QDBusError::InternalError}; } - DBusResult CreateItemPrompt::createItem(const QString& windowId) + DBusResult CreateItemPrompt::createItem(const DBusClientPtr& client, const QString& windowId) { - auto client = m_client.lock(); - if (!client) { - // client already gone - return {}; - } - - if (!m_coll) { - return DBusResult{DBUS_ERROR_SECRET_NO_SUCH_OBJECT}; - } - // get itemPath to create item and // try to find an existing item using attributes QString itemPath{}; @@ -481,51 +390,19 @@ namespace FdoSecrets if (!prompt) { return DBusResult{QDBusError::InternalError}; } - // postpone anything after the confirmation - connect(prompt, &PromptBase::completed, this, [this](bool dismissed) { - if (dismissed) { - finishPrompt(dismissed); - } else { - auto res = updateItem(); - if (res.err()) { - qWarning() << "FdoSecrets:" << res; - finishPrompt(true); - } - } - }); - auto ret = prompt->prompt(client, windowId); + auto ret = prompt->promptSync(client, windowId); if (ret.err()) { return ret; } - return {}; - } - DBusResult CreateItemPrompt::updateItem() - { - auto client = m_client.lock(); - if (!client) { - // client already gone - return {}; - } - - if (!m_sess || m_sess != m_secret.session) { - return DBusResult(DBUS_ERROR_SECRET_NO_SESSION); - } - if (!m_item) { - return {}; - } - auto ret = m_item->setProperties(m_properties); + ret = m_item->setProperties(m_properties); if (ret.err()) { return ret; } + ret = m_item->setSecret(client, m_secret); - if (ret.err()) { - return ret; - } - - // finally can finish the prompt without dismissing it - finishPrompt(false); - return {}; + return ret; } + } // namespace FdoSecrets diff --git a/src/fdosecrets/objects/Prompt.h b/src/fdosecrets/objects/Prompt.h index 078e4ce86..72e206494 100644 --- a/src/fdosecrets/objects/Prompt.h +++ b/src/fdosecrets/objects/Prompt.h @@ -22,8 +22,6 @@ #include "fdosecrets/dbus/DBusClient.h" #include "fdosecrets/dbus/DBusObject.h" -class QWindow; - class DatabaseWidget; class Entry; @@ -32,58 +30,15 @@ namespace FdoSecrets class Service; - // a simple helper class to auto convert - // true/false, DBusResult and Pending values - class PromptResult - { - enum Value - { - Accepted, - Dismissed, - AsyncPending, - }; - const Value value; - - explicit PromptResult(Value v) noexcept - : value(v) - { - } - explicit PromptResult(bool accepted) - : value(accepted ? Accepted : Dismissed) - { - } - - public: - PromptResult() - : PromptResult(true) - { - } - PromptResult(const DBusResult& res) // NOLINT(google-explicit-constructor) - : PromptResult(res.ok()) - { - } - - static const PromptResult Pending; - static PromptResult accepted(bool accepted) - { - return PromptResult{accepted}; - } - - bool isDismiss() const - { - return value == Dismissed; - } - bool isPending() const - { - return value == AsyncPending; - } - }; - class PromptBase : public DBusObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_PROMPT_LITERAL) public: + /** + * D-Bus Methods + */ + Q_INVOKABLE DBusResult prompt(const DBusClientPtr& client, const QString& windowId); Q_INVOKABLE DBusResult dismiss(); @@ -97,18 +52,29 @@ namespace FdoSecrets return res.take(); } + virtual DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) = 0; + signals: void completed(bool dismissed, const QVariant& result); protected: explicit PromptBase(Service* parent); - virtual PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) = 0; virtual QVariant currentResult() const; - QWindow* findWindow(const QString& windowId); Service* service() const; - void finishPrompt(bool dismissed); + + // For overriding MessageBox parent window in GUI + class OverrideParentWindow + { + public: + explicit OverrideParentWindow(PromptBase* prompt, const QString& newParentWindowId); + ~OverrideParentWindow(); + + private: + PromptBase const* m_prompt; + const QString m_oldParentWindowId; + }; private: bool m_signalSent = false; @@ -123,7 +89,7 @@ namespace FdoSecrets explicit DeleteCollectionPrompt(Service* parent, Collection* coll); - PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override; + DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override; QPointer m_collection; }; @@ -135,12 +101,12 @@ namespace FdoSecrets explicit CreateCollectionPrompt(Service* parent, QVariantMap properties, QString alias); - PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override; + DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override; QVariant currentResult() const override; QVariantMap m_properties; QString m_alias; - Collection* m_coll{}; + Collection* m_coll; }; class LockCollectionsPrompt : public PromptBase @@ -150,7 +116,7 @@ namespace FdoSecrets explicit LockCollectionsPrompt(Service* parent, const QList& colls); - PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override; + DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override; QVariant currentResult() const override; QList> m_collections; @@ -165,23 +131,15 @@ namespace FdoSecrets explicit UnlockPrompt(Service* parent, const QSet& colls, const QSet& items); - PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override; + DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override; QVariant currentResult() const override; - void collectionUnlockFinished(bool accepted); - void itemUnlockFinished(const QHash& results, AuthDecision forFutureEntries); - void unlockItems(); - QList> m_collections; QHash>> m_items; QHash m_entryToItems; QList m_unlocked; int m_numRejected = 0; - - // info about calling client - QWeakPointer m_client; - QString m_windowId; }; class Item; @@ -192,7 +150,7 @@ namespace FdoSecrets explicit DeleteItemPrompt(Service* parent, Item* item); - PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override; + DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override; QPointer m_item; }; @@ -208,11 +166,10 @@ namespace FdoSecrets Secret secret, bool replace); - PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override; + DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override; QVariant currentResult() const override; - DBusResult createItem(const QString& windowId); - DBusResult updateItem(); + DBusResult createItem(const DBusClientPtr& client, const QString& windowId); QPointer m_coll; QVariantMap m_properties; @@ -220,9 +177,6 @@ namespace FdoSecrets bool m_replace; QPointer m_item; - - QPointer m_sess; - QWeakPointer m_client; }; } // namespace FdoSecrets diff --git a/src/fdosecrets/objects/Service.cpp b/src/fdosecrets/objects/Service.cpp index ae1e9d4b6..ca1c68831 100644 --- a/src/fdosecrets/objects/Service.cpp +++ b/src/fdosecrets/objects/Service.cpp @@ -24,9 +24,6 @@ #include "fdosecrets/objects/Prompt.h" #include "fdosecrets/objects/Session.h" -#include "gui/DatabaseTabWidget.h" -#include "gui/DatabaseWidget.h" - namespace { constexpr auto DEFAULT_ALIAS = "default"; @@ -34,150 +31,37 @@ namespace namespace FdoSecrets { - QSharedPointer - Service::Create(FdoSecretsPlugin* plugin, QPointer dbTabs, QSharedPointer dbus) + QSharedPointer Service::Create(FdoSecretsPlugin* plugin, QSharedPointer dbus) { - QSharedPointer res{new Service(plugin, std::move(dbTabs), std::move(dbus))}; - if (!res->initialize()) { - return {}; + QSharedPointer res{new Service(plugin, std::move(dbus))}; + if (!res->dbus()->registerObject(res.data())) { + return nullptr; } + return res; } Service::Service(FdoSecretsPlugin* plugin, - QPointer dbTabs, QSharedPointer dbus) // clazy: exclude=ctor-missing-parent-argument : DBusObject(std::move(dbus)) , m_plugin(plugin) - , m_databases(std::move(dbTabs)) - , m_insideEnsureDefaultAlias(false) { - connect(m_databases, - &DatabaseTabWidget::databaseUnlockDialogFinished, - this, - &Service::onDatabaseUnlockDialogFinished); } Service::~Service() = default; - bool Service::initialize() - { - if (!dbus()->registerObject(this)) { - return false; - } - - // Add existing database tabs - for (int idx = 0; idx != m_databases->count(); ++idx) { - auto dbWidget = m_databases->databaseWidgetFromIndex(idx); - onDatabaseTabOpened(dbWidget, false); - } - - // Connect to new database signal - // No need to connect to close signal, as collection will remove itself when backend delete/close database tab. - connect(m_databases.data(), &DatabaseTabWidget::databaseOpened, this, [this](DatabaseWidget* dbWidget) { - onDatabaseTabOpened(dbWidget, true); - }); - - // make default alias track current activated database - connect(m_databases.data(), &DatabaseTabWidget::activeDatabaseChanged, this, &Service::ensureDefaultAlias); - - return true; - } - - void Service::onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal) - { - // The Collection will monitor the database's exposed group. - // When the Collection finds that no exposed group, it will delete itself. - // Thus, the service also needs to monitor it and recreate the collection if the user changes - // from no exposed to exposed something. - connect(dbWidget, &DatabaseWidget::databaseReplaced, this, [this, dbWidget]() { - monitorDatabaseExposedGroup(dbWidget); - }); - if (!dbWidget->isLocked()) { - monitorDatabaseExposedGroup(dbWidget); - } - - auto coll = Collection::Create(this, dbWidget); - if (!coll) { - return; - } - - m_collections << coll; - m_dbToCollection[dbWidget] = coll; - - // keep record of the collection existence - connect(coll, &Collection::collectionAboutToDelete, this, [this, coll]() { - m_collections.removeAll(coll); - m_dbToCollection.remove(coll->backend()); - }); - - // keep record of alias - connect(coll, &Collection::aliasAboutToAdd, this, &Service::onCollectionAliasAboutToAdd); - connect(coll, &Collection::aliasAdded, this, &Service::onCollectionAliasAdded); - connect(coll, &Collection::aliasRemoved, this, &Service::onCollectionAliasRemoved); - - // Forward delete signal, we have to rely on filepath to identify the database being closed, - // but we can not access m_backend safely because during the databaseClosed signal, - // m_backend may already be reset to nullptr - // We want to remove the collection object from dbus as early as possible, to avoid - // race conditions when deleteLater was called on the m_backend, but not delivered yet, - // and new method calls from dbus occurred. Therefore, we can't rely on the destroyed - // signal on m_backend. - // bind to coll lifespan - connect(m_databases.data(), &DatabaseTabWidget::databaseClosed, coll, [coll](const QString& filePath) { - if (filePath == coll->backendFilePath()) { - coll->removeFromDBus(); - } - }); - - // actual load, must after updates to m_collections, because reloading may trigger - // another onDatabaseTabOpen, and m_collections will be used to prevent recursion. - if (!coll->reloadBackend()) { - // error in dbus - return; - } - if (!coll->backend()) { - // no exposed group on this db - return; - } - - ensureDefaultAlias(); - - // only start relay signals when the collection is fully setup - connect(coll, &Collection::collectionChanged, this, [this, coll]() { emit collectionChanged(coll); }); - connect(coll, &Collection::collectionAboutToDelete, this, [this, coll]() { emit collectionDeleted(coll); }); - if (emitSignal) { - emit collectionCreated(coll); - } - } - - void Service::monitorDatabaseExposedGroup(DatabaseWidget* dbWidget) - { - Q_ASSERT(dbWidget); - connect(dbWidget->database()->metadata()->customData(), &CustomData::modified, this, [this, dbWidget]() { - if (!FdoSecrets::settings()->exposedGroup(dbWidget->database()).isNull() && !findCollection(dbWidget)) { - onDatabaseTabOpened(dbWidget, true); - } - }); - } - void Service::ensureDefaultAlias() { - if (m_insideEnsureDefaultAlias) { - return; + auto coll = m_aliasToCollection.value(DEFAULT_ALIAS); + if (!m_collections.isEmpty() && !coll) { + m_aliasToCollection[DEFAULT_ALIAS] = m_collections.first(); } - - m_insideEnsureDefaultAlias = true; - - auto coll = findCollection(m_databases->currentDatabaseWidget()); - if (coll) { - // adding alias will automatically remove the association with previous collection. - coll->addAlias(DEFAULT_ALIAS).okOrDie(); - } - - m_insideEnsureDefaultAlias = false; } + /** + * D-Bus Properties + */ + DBusResult Service::collections(QList& collections) const { collections = m_collections; @@ -208,6 +92,10 @@ namespace FdoSecrets return {}; } + /** + * D-Bus Methods + */ + DBusResult Service::openSession(const DBusClientPtr& client, const QString& algorithm, const QVariant& input, @@ -253,7 +141,7 @@ namespace FdoSecrets prompt = nullptr; // return existing collection if alias is non-empty and exists. - collection = findCollection(alias); + collection = m_aliasToCollection.value(alias); if (!collection) { prompt = PromptBase::Create(this, properties, alias); if (!prompt) { @@ -277,17 +165,7 @@ namespace FdoSecrets while (unlockedColls.isEmpty() && settings()->unlockBeforeSearch()) { // enable compatibility mode by making sure at least one database is unlocked - QEventLoop loop; - bool wasAccepted = false; - connect(this, &Service::doneUnlockDatabaseInDialog, &loop, [&](bool accepted) { - wasAccepted = accepted; - loop.quit(); - }); - - doUnlockAnyDatabaseInDialog(); - - // blocking wait - loop.exec(); + bool wasAccepted = plugin()->requestUnlockAnyDatabase(client); if (!wasAccepted) { // user cancelled, do not proceed @@ -446,66 +324,22 @@ namespace FdoSecrets DBusResult Service::readAlias(const QString& name, Collection*& collection) const { - collection = findCollection(name); + collection = m_aliasToCollection.value(name); return {}; } DBusResult Service::setAlias(const QString& name, Collection* collection) { if (!collection) { - // remove alias name from its collection - collection = findCollection(name); - if (!collection) { + if (!m_aliasToCollection.remove(name)) { return DBusResult(DBUS_ERROR_SECRET_NO_SUCH_OBJECT); } - return collection->removeAlias(name); - } - return collection->addAlias(name); - } - - Collection* Service::findCollection(const QString& alias) const - { - if (alias.isEmpty()) { - return nullptr; + ensureDefaultAlias(); + return {}; } - auto it = m_aliases.find(alias); - if (it != m_aliases.end()) { - return it.value(); - } - return nullptr; - } - - void Service::onCollectionAliasAboutToAdd(const QString& alias) - { - auto coll = qobject_cast(sender()); - - auto it = m_aliases.constFind(alias); - if (it != m_aliases.constEnd() && it.value() != coll) { - // another collection holds the alias - // remove it first - it.value()->removeAlias(alias).okOrDie(); - - // onCollectionAliasRemoved called through signal - // `it` becomes invalidated now - } - } - - void Service::onCollectionAliasAdded(const QString& alias) - { - auto coll = qobject_cast(sender()); - m_aliases[alias] = coll; - } - - void Service::onCollectionAliasRemoved(const QString& alias) - { - m_aliases.remove(alias); - ensureDefaultAlias(); - } - - Collection* Service::findCollection(const DatabaseWidget* db) const - { - return m_dbToCollection.value(db, nullptr); + m_aliasToCollection[name] = collection; + return {}; } QList Service::sessions() const @@ -513,111 +347,100 @@ namespace FdoSecrets return m_sessions; } - bool Service::doCloseDatabase(DatabaseWidget* dbWidget) + Collection* Service::doNewDatabase(const DBusClientPtr& client) { - return m_databases->closeDatabaseTab(dbWidget); + auto name = plugin()->requestNewDatabase(client); + return m_nameToCollection.value(name); } - Collection* Service::doNewDatabase() + bool Service::setAlias(const QString& alias, const QString& name) { - auto dbWidget = m_databases->newDatabase(); - if (!dbWidget) { + Q_ASSERT(!name.isEmpty()); + return !setAlias(alias, m_nameToCollection.value(name)).err(); + } + + QStringList Service::collectionAliases(const Collection* collection) const + { + QStringList result; + for (auto it = m_aliasToCollection.constBegin(); it != m_aliasToCollection.constEnd(); ++it) { + if (it.value() == collection) { + result << it.key(); + } + } + + return result; + } + + Collection* Service::createCollection(const QString& name) + { + Q_ASSERT(!name.isEmpty()); + auto coll = m_nameToCollection.value(name); + if (coll) { + return coll; + } + + coll = Collection::Create(this, name); + if (!coll) { return nullptr; } - // database created through dbus will be exposed to dbus by default - auto db = dbWidget->database(); - FdoSecrets::settings()->setExposedGroup(db, db->rootGroup()->uuid()); + m_collections << coll; + m_nameToCollection[name] = coll; - auto collection = findCollection(dbWidget); + // keep record of the collection existence + connect(coll, &Collection::collectionChanged, this, [this, coll]() { emit collectionChanged(coll); }); + connect(coll, &Collection::collectionAboutToDelete, this, [this, coll]() { + m_collections.removeAll(coll); + m_nameToCollection.remove(coll->name()); + emit collectionDeleted(coll); - Q_ASSERT(collection); + for (auto it = m_aliasToCollection.begin(); it != m_aliasToCollection.end();) { + if (*it == coll) { + it = m_aliasToCollection.erase(it); + } else { + ++it; + } + } + }); - return collection; + ensureDefaultAlias(); + emit collectionCreated(coll); + return coll; } - void Service::doSwitchToDatabaseSettings(DatabaseWidget* dbWidget) + void Service::registerDatabase(const QString& name) { - if (dbWidget->isLocked()) { - return; + Q_ASSERT(!name.isEmpty()); + if (!m_nameToCollection.value(name)) { + createCollection(name); } - // switch selected to current - m_databases->setCurrentWidget(dbWidget); - m_databases->showDatabaseSettings(); - - // open settings (switch from app settings to m_dbTabs) - m_plugin->emitRequestSwitchToDatabases(); } - bool Service::doLockDatabase(DatabaseWidget* dbWidget) + void Service::unregisterDatabase(const QString& name) { - // return immediately if the db is already unlocked - if (dbWidget && dbWidget->isLocked()) { - return true; + auto coll = m_nameToCollection.value(name); + if (coll) { + coll->removeFromDBus(); } - - // mark the db as being unlocked to prevent multiple dialogs for the same db - if (m_lockingDb.contains(dbWidget)) { - return true; - } - m_lockingDb.insert(dbWidget); - auto ret = dbWidget->lock(); - m_lockingDb.remove(dbWidget); - - return ret; } - void Service::doUnlockDatabaseInDialog(DatabaseWidget* dbWidget) + void Service::databaseLocked(const QString& name) { - // return immediately if the db is already unlocked - if (dbWidget && !dbWidget->isLocked()) { - emit doneUnlockDatabaseInDialog(true, dbWidget); - return; + auto coll = m_nameToCollection.value(name); + if (coll) { + coll->databaseLocked(); } - - // check if the db is already being unlocked to prevent multiple dialogs for the same db - if (m_unlockingAnyDatabase || m_unlockingDb.contains(dbWidget)) { - return; - } - - // insert a dummy one here, just to prevent multiple dialogs - // the real one will be inserted in onDatabaseUnlockDialogFinished - m_unlockingDb[dbWidget] = {}; - - // actually show the dialog - m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None); } - void Service::doUnlockAnyDatabaseInDialog() + void Service::databaseUnlocked(const QString& name, QSharedPointer db) { - if (m_unlockingAnyDatabase || !m_unlockingDb.isEmpty()) { - return; + Q_ASSERT(!name.isEmpty()); + auto coll = m_nameToCollection.value(name); + if (!coll) { + coll = createCollection(name); } - m_unlockingAnyDatabase = true; - m_databases->unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::None); + coll->databaseUnlocked(db); } - void Service::onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget) - { - if (!m_unlockingAnyDatabase && !m_unlockingDb.contains(dbWidget)) { - // not our concern - return; - } - - if (!accepted) { - emit doneUnlockDatabaseInDialog(false, dbWidget); - m_unlockingAnyDatabase = false; - m_unlockingDb.remove(dbWidget); - } else { - // delay the done signal to when the database is actually done with unlocking - // this is a oneshot connection to prevent superfluous signals - auto conn = connect(dbWidget, &DatabaseWidget::databaseUnlocked, this, [dbWidget, this]() { - emit doneUnlockDatabaseInDialog(true, dbWidget); - m_unlockingAnyDatabase = false; - disconnect(m_unlockingDb.take(dbWidget)); - }); - m_unlockingDb[dbWidget] = conn; - } - } } // namespace FdoSecrets diff --git a/src/fdosecrets/objects/Service.h b/src/fdosecrets/objects/Service.h index 5ec7499b1..de9a435cd 100644 --- a/src/fdosecrets/objects/Service.h +++ b/src/fdosecrets/objects/Service.h @@ -21,8 +21,8 @@ #include "fdosecrets/dbus/DBusClient.h" #include "fdosecrets/dbus/DBusObject.h" -class DatabaseTabWidget; -class DatabaseWidget; +class Database; +class Entry; class Group; class FdoSecretsPlugin; @@ -40,7 +40,7 @@ namespace FdoSecrets Q_OBJECT Q_CLASSINFO("D-Bus Interface", DBUS_INTERFACE_SECRET_SERVICE_LITERAL) - explicit Service(FdoSecretsPlugin* plugin, QPointer dbTabs, QSharedPointer dbus); + explicit Service(FdoSecretsPlugin* plugin, QSharedPointer dbus); public: /** @@ -49,10 +49,13 @@ namespace FdoSecrets * This may be caused by * - failed initialization */ - static QSharedPointer - Create(FdoSecretsPlugin* plugin, QPointer dbTabs, QSharedPointer dbus); + static QSharedPointer Create(FdoSecretsPlugin* plugin, QSharedPointer dbus); ~Service() override; + /** + * D-Bus Methods + */ + Q_INVOKABLE DBusResult openSession(const DBusClientPtr& client, const QString& algorithm, const QVariant& input, @@ -83,6 +86,10 @@ namespace FdoSecrets Q_INVOKABLE DBusResult setAlias(const QString& name, Collection* collection); + /** + * D-Bus Properties + */ + /** * List of collections * @return @@ -90,17 +97,14 @@ namespace FdoSecrets Q_INVOKABLE DBUS_PROPERTY DBusResult collections(QList& collections) const; signals: + /** + * D-Bus Signals + */ + void collectionCreated(Collection* collection); void collectionDeleted(Collection* collection); void collectionChanged(Collection* collection); - /** - * Finish signal for async action doUnlockDatabaseInDialog - * @param accepted If false, the action is cancelled by the user - * @param dbWidget The dbWidget the action is on - */ - void doneUnlockDatabaseInDialog(bool accepted, DatabaseWidget* dbWidget); - public: /** * List of sessions @@ -108,75 +112,41 @@ namespace FdoSecrets */ QList sessions() const; + bool setAlias(const QString& alias, const QString& name); + QStringList collectionAliases(const Collection* collection) const; + + // Access to ancestors FdoSecretsPlugin* plugin() const { return m_plugin; } public slots: - bool doLockDatabase(DatabaseWidget* dbWidget); - bool doCloseDatabase(DatabaseWidget* dbWidget); - Collection* doNewDatabase(); - void doSwitchToDatabaseSettings(DatabaseWidget* dbWidget); + Collection* doNewDatabase(const DBusClientPtr& client); - /** - * Async, connect to signal doneUnlockDatabaseInDialog for finish notification - * @param dbWidget - */ - void doUnlockDatabaseInDialog(DatabaseWidget* dbWidget); - - /** - * Async, connect to signal doneUnlockDatabaseInDialog for finish notification - * @param dbWidget - */ - void doUnlockAnyDatabaseInDialog(); + void registerDatabase(const QString& name); + void unregisterDatabase(const QString& name); + void databaseLocked(const QString& name); + void databaseUnlocked(const QString& name, QSharedPointer db); private slots: void ensureDefaultAlias(); - void onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget); - void onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal); - void monitorDatabaseExposedGroup(DatabaseWidget* dbWidget); - - void onCollectionAliasAboutToAdd(const QString& alias); - void onCollectionAliasAdded(const QString& alias); - - void onCollectionAliasRemoved(const QString& alias); - private: - bool initialize(); - - /** - * Find collection by alias name - * @param alias - * @return the collection under alias - */ - Collection* findCollection(const QString& alias) const; - - /** - * Find collection by dbWidget - * @param db - * @return the collection corresponding to the db - */ - Collection* findCollection(const DatabaseWidget* db) const; + friend class CreateCollectionPrompt; DBusResult unlockedCollections(QList& unlocked) const; + Collection* createCollection(const QString& name); + private: FdoSecretsPlugin* m_plugin{nullptr}; - QPointer m_databases{}; - QHash m_aliases{}; + QHash m_aliasToCollection{}; + QHash m_nameToCollection{}; QList m_collections{}; - QHash m_dbToCollection{}; QList m_sessions{}; - - bool m_insideEnsureDefaultAlias{false}; - bool m_unlockingAnyDatabase{false}; - // list of db currently has unlock dialog shown - QHash m_unlockingDb{}; - QSet m_lockingDb{}; // list of db being locking }; } // namespace FdoSecrets diff --git a/src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.cpp b/src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.cpp index 855906098..84bafc147 100644 --- a/src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.cpp +++ b/src/fdosecrets/widgets/DatabaseSettingsWidgetFdoSecrets.cpp @@ -122,7 +122,7 @@ void DatabaseSettingsWidgetFdoSecrets::loadSettings(QSharedPointer db) recycleBin = m_db->metadata()->recycleBin(); } - auto group = m_db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(m_db)); + auto group = m_db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(m_db.data())); if (!group || group->isRecycled() || (recycleBin && group->uuid() == recycleBin->uuid())) { m_ui->radioDonotExpose->setChecked(true); } else { @@ -157,7 +157,7 @@ void DatabaseSettingsWidgetFdoSecrets::saveSettings() } } - FdoSecrets::settings()->setExposedGroup(m_db, exposedGroup); + FdoSecrets::settings()->setExposedGroup(m_db.data(), exposedGroup); } void DatabaseSettingsWidgetFdoSecrets::settingsWarning() diff --git a/src/fdosecrets/widgets/SettingsModels.cpp b/src/fdosecrets/widgets/SettingsModels.cpp index d68ee4b0c..e5783e2bb 100644 --- a/src/fdosecrets/widgets/SettingsModels.cpp +++ b/src/fdosecrets/widgets/SettingsModels.cpp @@ -141,7 +141,7 @@ namespace FdoSecrets } } auto db = dbWidget->database(); - auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db)); + auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db.data())); if (group) { switch (role) { case Qt::DisplayRole: diff --git a/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp b/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp index 692a7561b..62a65fd12 100644 --- a/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp +++ b/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp @@ -20,11 +20,13 @@ #include "fdosecrets/FdoSecretsPlugin.h" #include "fdosecrets/FdoSecretsSettings.h" +#include "fdosecrets/FdoSecretsSettingsPage.h" #include "fdosecrets/widgets/RowButtonHelper.h" #include "fdosecrets/widgets/SettingsModels.h" #include "objects/Service.h" #include "gui/DatabaseWidget.h" +#include "gui/Icons.h" #include #include @@ -42,32 +44,31 @@ namespace Q_PROPERTY(DatabaseWidget* dbWidget READ dbWidget WRITE setDbWidget USER true) public: - explicit ManageDatabase(FdoSecretsPlugin* plugin, QWidget* parent = nullptr) + explicit ManageDatabase(FdoSecretsSettingsPage* page, QWidget* parent = nullptr) : QWidget(parent) - , m_plugin(plugin) { // db settings m_dbSettingsAct = new QAction(tr("Database settings"), this); m_dbSettingsAct->setIcon(icons()->icon(QStringLiteral("document-edit"))); m_dbSettingsAct->setToolTip(tr("Edit database settings")); m_dbSettingsAct->setEnabled(false); - connect(m_dbSettingsAct, &QAction::triggered, this, [this]() { + connect(m_dbSettingsAct, &QAction::triggered, this, [this, page]() { if (!m_dbWidget) { return; } - m_plugin->serviceInstance()->doSwitchToDatabaseSettings(m_dbWidget); + page->switchToDatabaseSettings(m_dbWidget); }); // unlock/lock m_lockAct = new QAction(tr("Unlock database"), this); m_lockAct->setIcon(icons()->icon(QStringLiteral("object-unlocked"))); m_lockAct->setToolTip(tr("Unlock database to show more information")); - connect(m_lockAct, &QAction::triggered, this, [this]() { + connect(m_lockAct, &QAction::triggered, this, [this, page]() { if (!m_dbWidget) { return; } if (m_dbWidget->isLocked()) { - m_plugin->serviceInstance()->doUnlockDatabaseInDialog(m_dbWidget); + page->unlockDatabaseInDialog(m_dbWidget); } else { m_dbWidget->lock(); } @@ -147,7 +148,6 @@ namespace } private: - FdoSecretsPlugin* m_plugin = nullptr; QPointer m_dbWidget = nullptr; QAction* m_dbSettingsAct = nullptr; QAction* m_lockAct = nullptr; @@ -215,16 +215,19 @@ namespace }; } // namespace -SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent) +SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, + FdoSecretsSettingsPage* page, + QWidget* parent) : QWidget(parent) , m_ui(new Ui::SettingsWidgetFdoSecrets()) , m_plugin(plugin) + , m_page{page} { m_ui->setupUi(this); m_ui->warningMsg->setHidden(true); m_ui->warningMsg->setCloseButtonVisible(false); - auto clientModel = new SettingsClientModel(*plugin->dbus(), this); + auto clientModel = new SettingsClientModel(*m_plugin->dbus(), this); m_ui->tableClients->setModel(clientModel); installWidgetItemDelegate(m_ui->tableClients, SettingsClientModel::ColumnManage, @@ -237,11 +240,11 @@ SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWi clientViewHeader->setSectionResizeMode(QHeaderView::ResizeToContents); clientViewHeader->setSectionResizeMode(SettingsClientModel::ColumnApplication, QHeaderView::Stretch); - auto dbModel = new SettingsDatabaseModel(plugin->dbTabs(), this); + auto dbModel = new SettingsDatabaseModel(m_page->dbTabs(), this); m_ui->tableDatabases->setModel(dbModel); installWidgetItemDelegate( - m_ui->tableDatabases, SettingsDatabaseModel::ColumnManage, [plugin](QWidget* p, const QModelIndex&) { - return new ManageDatabase(plugin, p); + m_ui->tableDatabases, SettingsDatabaseModel::ColumnManage, [this](QWidget* p, const QModelIndex&) { + return new ManageDatabase(m_page, p); }); // config header after setting model, otherwise the header doesn't have enough sections diff --git a/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.h b/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.h index df7bcf7ff..fd5630abd 100644 --- a/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.h +++ b/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.h @@ -25,6 +25,8 @@ class QAbstractItemView; class FdoSecretsPlugin; +class FdoSecretsSettingsPage; +class DatabaseWidget; namespace Ui { @@ -34,7 +36,9 @@ class SettingsWidgetFdoSecrets : public QWidget { Q_OBJECT public: - explicit SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent = nullptr); + explicit SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, + FdoSecretsSettingsPage* page, + QWidget* parent = nullptr); ~SettingsWidgetFdoSecrets() override; public slots: @@ -52,6 +56,7 @@ protected: private: QScopedPointer m_ui; FdoSecretsPlugin* m_plugin; + FdoSecretsSettingsPage* m_page; QTimer m_checkTimer; }; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 9e38dcda9..7c0a0d5cb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -63,7 +63,8 @@ #endif #ifdef WITH_XC_FDOSECRETS -#include "fdosecrets/FdoSecretsPlugin.h" +#include "fdosecrets/FdoSecretsPluginGUI.h" +#include "fdosecrets/FdoSecretsSettingsPage.h" #endif #ifdef WITH_XC_YUBIKEY @@ -218,12 +219,14 @@ MainWindow::MainWindow() #endif #ifdef WITH_XC_FDOSECRETS - auto fdoSS = new FdoSecretsPlugin(m_ui->tabWidget); + auto fdoSS = new FdoSecretsPluginGUI(m_ui->tabWidget); connect(fdoSS, &FdoSecretsPlugin::error, this, &MainWindow::showErrorMessage); - connect(fdoSS, &FdoSecretsPlugin::requestSwitchToDatabases, this, &MainWindow::switchToDatabases); connect(fdoSS, &FdoSecretsPlugin::requestShowNotification, this, &MainWindow::displayDesktopNotification); fdoSS->updateServiceState(); - m_ui->settingsWidget->addSettingsPage(fdoSS); + + auto fdoSSP = new FdoSecretsSettingsPage(fdoSS, m_ui->tabWidget); + connect(fdoSSP, &FdoSecretsSettingsPage::requestSwitchToDatabases, this, &MainWindow::switchToDatabases); + m_ui->settingsWidget->addSettingsPage(fdoSSP); #endif #ifdef WITH_XC_YUBIKEY diff --git a/src/gui/MessageBox.cpp b/src/gui/MessageBox.cpp index a2c1498cd..017299f0c 100644 --- a/src/gui/MessageBox.cpp +++ b/src/gui/MessageBox.cpp @@ -189,14 +189,3 @@ void MessageBox::setNextAnswer(MessageBox::Button button) { m_nextAnswer = button; } - -MessageBox::OverrideParent::OverrideParent(QWindow* newParent) - : m_oldParent(MessageBox::m_overrideParent) -{ - MessageBox::m_overrideParent = newParent; -} - -MessageBox::OverrideParent::~OverrideParent() -{ - MessageBox::m_overrideParent = m_oldParent; -} diff --git a/src/gui/MessageBox.h b/src/gui/MessageBox.h index 1dbcc2076..c345ca29a 100644 --- a/src/gui/MessageBox.h +++ b/src/gui/MessageBox.h @@ -24,6 +24,8 @@ class MessageBox { + friend class FdoSecretsPluginGUI; + public: enum Button : uint64_t { @@ -105,16 +107,6 @@ public: Action action = MessageBox::None, QCheckBox* checkbox = nullptr); - class OverrideParent - { - public: - explicit OverrideParent(QWindow* newParent); - ~OverrideParent(); - - private: - QWindow* m_oldParent; - }; - private: static QWindow* m_overrideParent; static Button m_nextAnswer; diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index 18b926dc2..4f654e773 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -41,13 +41,13 @@ void GroupModel::changeDatabase(Database* newDb) m_db = newDb; // clang-format off - connect(m_db, SIGNAL(groupDataChanged(Group*)), SLOT(groupDataChanged(Group*))); - connect(m_db, SIGNAL(groupAboutToAdd(Group*,int)), SLOT(groupAboutToAdd(Group*,int))); - connect(m_db, SIGNAL(groupAdded()), SLOT(groupAdded())); - connect(m_db, SIGNAL(groupAboutToRemove(Group*)), SLOT(groupAboutToRemove(Group*))); - connect(m_db, SIGNAL(groupRemoved()), SLOT(groupRemoved())); - connect(m_db, SIGNAL(groupAboutToMove(Group*,Group*,int)), SLOT(groupAboutToMove(Group*,Group*,int))); - connect(m_db, SIGNAL(groupMoved()), SLOT(groupMoved())); + connect(m_db, SIGNAL(groupDataChanged(Group*)), this, SLOT(groupDataChanged(Group*))); + connect(m_db, SIGNAL(groupAboutToAdd(Group*,int)), this, SLOT(groupAboutToAdd(Group*,int))); + connect(m_db, SIGNAL(groupAdded()), this, SLOT(groupAdded())); + connect(m_db, SIGNAL(groupAboutToRemove(Group*)), this, SLOT(groupAboutToRemove(Group*))); + connect(m_db, SIGNAL(groupRemoved()), this, SLOT(groupRemoved())); + connect(m_db, SIGNAL(groupAboutToMove(Group*,Group*,int)), this, SLOT(groupAboutToMove(Group*,Group*,int))); + connect(m_db, SIGNAL(groupMoved()), this, SLOT(groupMoved())); // clang-format on endResetModel(); diff --git a/tests/TestFdoSecrets.cpp b/tests/TestFdoSecrets.cpp index 38a50af99..1011004d1 100644 --- a/tests/TestFdoSecrets.cpp +++ b/tests/TestFdoSecrets.cpp @@ -21,6 +21,7 @@ #include "core/Group.h" #include "crypto/Random.h" #include "fdosecrets/objects/Collection.h" +#include "fdosecrets/objects/Service.h" #include "fdosecrets/objects/SessionCipher.h" #include diff --git a/tests/gui/TestGuiFdoSecrets.cpp b/tests/gui/TestGuiFdoSecrets.cpp index d9fe1f8ea..c895fdc0f 100644 --- a/tests/gui/TestGuiFdoSecrets.cpp +++ b/tests/gui/TestGuiFdoSecrets.cpp @@ -17,10 +17,11 @@ #include "TestGuiFdoSecrets.h" -#include "fdosecrets/FdoSecretsPlugin.h" +#include "fdosecrets/FdoSecretsPluginGUI.h" #include "fdosecrets/FdoSecretsSettings.h" #include "fdosecrets/objects/Collection.h" #include "fdosecrets/objects/Item.h" +#include "fdosecrets/objects/Service.h" #include "fdosecrets/objects/SessionCipher.h" #include "fdosecrets/widgets/AccessControlDialog.h" @@ -153,7 +154,7 @@ void TestGuiFdoSecrets::initTestCase() m_mainWindow.reset(new MainWindow()); m_tabWidget = m_mainWindow->findChild("tabWidget"); VERIFY(m_tabWidget); - m_plugin = FdoSecretsPlugin::getPlugin(); + m_plugin = m_mainWindow->findChild(); VERIFY(m_plugin); m_mainWindow->show(); @@ -197,7 +198,7 @@ void TestGuiFdoSecrets::init() m_db = m_dbWidget->database(); // by default expose the root group - FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid()); + FdoSecrets::settings()->setExposedGroup(m_db.data(), m_db->rootGroup()->uuid()); VERIFY(m_dbWidget->save()); // enforce consistent default settings at the beginning @@ -249,10 +250,10 @@ void TestGuiFdoSecrets::cleanupTestCase() void TestGuiFdoSecrets::testServiceEnable() { - QSignalSpy sigError(m_plugin, SIGNAL(error(QString))); + QSignalSpy sigError(m_plugin.data(), SIGNAL(error(QString))); VERIFY(sigError.isValid()); - QSignalSpy sigStarted(m_plugin, SIGNAL(secretServiceStarted())); + QSignalSpy sigStarted(m_plugin.data(), SIGNAL(secretServiceStarted())); VERIFY(sigStarted.isValid()); // make sure no one else is holding the service @@ -287,7 +288,7 @@ void TestGuiFdoSecrets::testServiceEnable() void TestGuiFdoSecrets::testServiceEnableNoExposedDatabase() { // reset the exposed group and then enable the service - FdoSecrets::settings()->setExposedGroup(m_db, {}); + FdoSecrets::settings()->setExposedGroup(m_db.data(), {}); auto service = enableService(); VERIFY(service); @@ -384,7 +385,8 @@ void TestGuiFdoSecrets::testServiceSearchBlockingUnlock() // using a local QEventLoop. // so we do a little trick here to get the return value back bool unlockDialogWorks = false; - QTimer::singleShot(50, [&]() { unlockDialogWorks = driveUnlockDialog(); }); + QObject guard; + QTimer::singleShot(50, &guard, [&]() { unlockDialogWorks = driveUnlockDialog(); }); DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", title}})); VERIFY(unlockDialogWorks); @@ -426,9 +428,10 @@ void TestGuiFdoSecrets::testServiceSearchBlockingUnlockMultiple() lockDatabaseInBackend(); { bool unlockDialogWorks = false; - QTimer::singleShot(50, [&]() { + QObject guard; + QTimer::singleShot(50, &guard, [&]() { unlockDialogWorks = driveUnlockDialog(anotherWidget); - QTimer::singleShot(50, [&]() { unlockDialogWorks &= driveUnlockDialog(); }); + QTimer::singleShot(50, &guard, [&]() { unlockDialogWorks &= driveUnlockDialog(); }); }); DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", "Sample Entry"}})); @@ -441,7 +444,8 @@ void TestGuiFdoSecrets::testServiceSearchBlockingUnlockMultiple() lockDatabaseInBackend(); { bool unlockDialogWorks = false; - QTimer::singleShot(50, [&]() { unlockDialogWorks = driveUnlockDialog(m_dbWidget); }); + QObject guard; + QTimer::singleShot(50, &guard, [&]() { unlockDialogWorks = driveUnlockDialog(m_dbWidget); }); DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", "Sample Entry"}})); VERIFY(unlockDialogWorks); @@ -505,16 +509,18 @@ void TestGuiFdoSecrets::testServiceUnlock() VERIFY(waitForSignal(spyPromptCompleted, 0)); DBUS_COMPARE(coll->locked(), true); + bool unlockDialogWorks = false; + QObject guard; + QTimer::singleShot(50, &guard, [&]() { unlockDialogWorks = driveUnlockDialog(); }); + // show the prompt DBUS_VERIFY(prompt->Prompt("")); // still not unlocked before user action - VERIFY(waitForSignal(spyPromptCompleted, 0)); DBUS_COMPARE(coll->locked(), true); - VERIFY(driveUnlockDialog()); - VERIFY(waitForSignal(spyPromptCompleted, 1)); + VERIFY(unlockDialogWorks); { auto args = spyPromptCompleted.takeFirst(); COMPARE(args.size(), 2); @@ -548,6 +554,12 @@ void TestGuiFdoSecrets::testServiceUnlockDatabaseConcurrent() VERIFY(prompt); QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant))); VERIFY(spyPromptCompleted.isValid()); + + // there should be only one unlock dialog + bool unlockDialogWorks = false; + QObject guard; + QTimer::singleShot(50, &guard, [&]() { unlockDialogWorks = driveUnlockDialog(); }); + DBUS_VERIFY(prompt->Prompt("")); // while the first prompt is running, another request come in @@ -558,11 +570,9 @@ void TestGuiFdoSecrets::testServiceUnlockDatabaseConcurrent() VERIFY(spyPromptCompleted2.isValid()); DBUS_VERIFY(prompt2->Prompt("")); - // there should be only one unlock dialog - VERIFY(driveUnlockDialog()); - // both prompts should complete VERIFY(waitForSignal(spyPromptCompleted, 1)); + VERIFY(unlockDialogWorks); { auto args = spyPromptCompleted.takeFirst(); COMPARE(args.size(), 2); @@ -611,11 +621,15 @@ void TestGuiFdoSecrets::testServiceUnlockItems() DBUS_COMPARE(item->locked(), true); // drive the prompt + bool accessControlDialogWorks = false; + QObject guard; + QTimer::singleShot(50, &guard, [&]() { accessControlDialogWorks = driveAccessControlDialog(false); }); + DBUS_VERIFY(prompt->Prompt("")); // only allow once - VERIFY(driveAccessControlDialog(false)); VERIFY(waitForSignal(spyPromptCompleted, 1)); + VERIFY(accessControlDialogWorks); { auto args = spyPromptCompleted.takeFirst(); COMPARE(args.size(), 2); @@ -649,11 +663,15 @@ void TestGuiFdoSecrets::testServiceUnlockItems() DBUS_COMPARE(item->locked(), true); // drive the prompt + bool accessControlDialogWorks = false; + QObject guard; + QTimer::singleShot(50, &guard, [&]() { accessControlDialogWorks = driveAccessControlDialog(true); }); + DBUS_VERIFY(prompt->Prompt("")); // only allow and remember - VERIFY(driveAccessControlDialog(true)); VERIFY(waitForSignal(spyPromptCompleted, 1)); + VERIFY(accessControlDialogWorks); { auto args = spyPromptCompleted.takeFirst(); COMPARE(args.size(), 2); @@ -702,11 +720,15 @@ void TestGuiFdoSecrets::testServiceUnlockItemsIncludeFutureEntries() DBUS_COMPARE(item->locked(), true); // drive the prompt + bool accessControlDialogWorks = false; + QObject guard; + QTimer::singleShot(50, &guard, [&]() { accessControlDialogWorks = driveAccessControlDialog(true, true); }); + DBUS_VERIFY(prompt->Prompt("")); // remember and include future entries - VERIFY(driveAccessControlDialog(true, true)); VERIFY(waitForSignal(spyPromptCompleted, 1)); + VERIFY(accessControlDialogWorks); { auto args = spyPromptCompleted.takeFirst(); COMPARE(args.size(), 2); @@ -1075,7 +1097,7 @@ void TestGuiFdoSecrets::testHiddenFilename() auto coll = getDefaultCollection(service); auto collObj = m_plugin->dbus()->pathToObject(QDBusObjectPath(coll->path())); VERIFY(collObj); - COMPARE(collObj->name(), QStringLiteral(".Name")); + COMPARE(collObj->dbusName(), QStringLiteral(".Name")); } void TestGuiFdoSecrets::testDuplicateName() @@ -1374,7 +1396,7 @@ void TestGuiFdoSecrets::testItemSecret() // get secret with notification FdoSecrets::settings()->setShowNotification(true); { - QSignalSpy spyShowNotification(m_plugin, SIGNAL(requestShowNotification(QString, QString, int))); + QSignalSpy spyShowNotification(m_plugin.data(), SIGNAL(requestShowNotification(QString, QString, int))); VERIFY(spyShowNotification.isValid()); DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path()))); @@ -1521,7 +1543,7 @@ void TestGuiFdoSecrets::testItemRejectSetReferenceFields() VERIFY(rootEntry); auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup"); VERIFY(subgroup); - FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid()); + FdoSecrets::settings()->setExposedGroup(m_db.data(), subgroup->uuid()); auto service = enableService(); VERIFY(service); auto coll = getDefaultCollection(service); @@ -1605,7 +1627,7 @@ void TestGuiFdoSecrets::testExposeSubgroup() { auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup"); VERIFY(subgroup); - FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid()); + FdoSecrets::settings()->setExposedGroup(m_db.data(), subgroup->uuid()); auto service = enableService(); VERIFY(service); @@ -1626,7 +1648,7 @@ void TestGuiFdoSecrets::testModifyingExposedGroup() // test when exposed group is removed the collection is not exposed anymore auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking"); VERIFY(subgroup); - FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid()); + FdoSecrets::settings()->setExposedGroup(m_db.data(), subgroup->uuid()); auto service = enableService(); VERIFY(service); @@ -1645,7 +1667,7 @@ void TestGuiFdoSecrets::testModifyingExposedGroup() } // test setting another exposed group, the collection will be exposed again - FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid()); + FdoSecrets::settings()->setExposedGroup(m_db.data(), m_db->rootGroup()->uuid()); processEvents(); { DBUS_GET(collPaths, service->collections()); @@ -1658,7 +1680,7 @@ void TestGuiFdoSecrets::testNoExposeRecycleBin() // when the recycle bin is underneath the exposed group // be careful not to expose entries in there - FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid()); + FdoSecrets::settings()->setExposedGroup(m_db.data(), m_db->rootGroup()->uuid()); m_db->metadata()->setRecycleBinEnabled(true); auto entry = m_db->rootGroup()->entries().first(); @@ -1790,21 +1812,33 @@ QSharedPointer TestGuiFdoSecrets::createItem(const QSharedPointerPrompt("")); - bool unlockFound = driveUnlockDialog(); - COMPARE(unlockFound, expectUnlockPrompt); - - bool found = driveAccessControlDialog(); - COMPARE(found, expectPrompt); - VERIFY(waitForSignal(spyPromptCompleted, 1)); + COMPARE(unlockDialogFound, expectUnlockPrompt); + COMPARE(accessControlDialogFound, expectPrompt); + auto args = spyPromptCompleted.takeFirst(); COMPARE(args.size(), 2); COMPARE(args.at(0).toBool(), false); itemPath = getSignalVariantArgument(args.at(1)); - return getProxy(itemPath); } diff --git a/tests/gui/TestGuiFdoSecrets.h b/tests/gui/TestGuiFdoSecrets.h index 1624eed49..0cd8433ce 100644 --- a/tests/gui/TestGuiFdoSecrets.h +++ b/tests/gui/TestGuiFdoSecrets.h @@ -30,7 +30,7 @@ class Database; class DatabaseTabWidget; class DatabaseWidget; class TemporaryFile; -class FdoSecretsPlugin; +class FdoSecretsPluginGUI; namespace FdoSecrets { class Service; @@ -150,7 +150,7 @@ private: QPointer m_dbWidget; QSharedPointer m_db; - QPointer m_plugin; + QPointer m_plugin; QSharedPointer m_client; QScopedPointer m_clientCipher;