fdosecrets: separate plugin internals from GUI

This commit is contained in:
Maciej Szeptuch 2022-08-14 15:20:32 +02:00
parent ea2e36c676
commit 6557970fd9
No known key found for this signature in database
GPG Key ID: E4F948F71FEE7375
30 changed files with 1229 additions and 1126 deletions

View File

@ -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

View File

@ -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

View File

@ -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<FdoSecretsPlugin> 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<SettingsWidgetFdoSecrets*>(widget)->loadSettings();
}
void FdoSecretsPlugin::saveSettings(QWidget* widget)
{
qobject_cast<SettingsWidgetFdoSecrets*>(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<FdoSecrets::DBusMgr>& 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<Database> db)
{
if (name.isEmpty()) {
return;
}
serviceInstance()->databaseUnlocked(name, db);
}
void FdoSecretsPlugin::emitError(const QString& msg)
{
emit error(tr("<b>Fdo Secret Service:</b> %1").arg(msg));

View File

@ -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 <QPointer>
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<FdoSecrets::DBusMgr>& 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<Database> 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<DatabaseTabWidget> m_dbTabs;
virtual size_t requestEntriesRemove(const QSharedPointer<FdoSecrets::DBusClient>& client,
const QString& name,
const QList<Entry*>& entries,
bool permanent) const = 0;
virtual bool requestEntriesUnlock(const QSharedPointer<FdoSecrets::DBusClient>& client,
const QString& windowId,
const QList<Entry*>& entries,
QHash<Entry*, AuthDecision>& decisions,
AuthDecision& forFutureEntries) const = 0;
virtual bool doLockDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client, const QString& name) = 0;
virtual bool doUnlockDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client, const QString& name) = 0;
virtual bool requestUnlockAnyDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client) const = 0;
virtual QString requestNewDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client) = 0;
virtual QString overrideMessageBoxParent(const QString& windowId) const = 0;
private:
QSharedPointer<FdoSecrets::DBusMgr> m_dbus;
QSharedPointer<FdoSecrets::Service> m_secretService;
};

View File

@ -0,0 +1,349 @@
/*
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "FdoSecretsPluginGUI.h"
#include <QWindow>
#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<Database> oldDb, QSharedPointer<Database> 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<FdoSecrets::DBusClient>&,
const QString& name,
const QList<Entry*>& 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<FdoSecrets::DBusClient>& client,
const QString& windowId,
const QList<Entry*>& entries,
QHash<Entry*, AuthDecision>& 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<Entry*, AuthDecision>& dialogDecisions, AuthDecision dialogForFutureEntries) {
decisions = dialogDecisions;
forFutureEntries = dialogForFutureEntries;
});
ac->exec();
ac->deleteLater();
return true;
}
bool FdoSecretsPluginGUI::doLockDatabase(const QSharedPointer<FdoSecrets::DBusClient>&, 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<FdoSecrets::DBusClient>&, 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<FdoSecrets::DBusClient>&) 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<FdoSecrets::DBusClient>&)
{
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<Database>& 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);
}
});
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_FDOSECRETSPLUGINGUI_H
#define KEEPASSXC_FDOSECRETSPLUGINGUI_H
#include "fdosecrets/FdoSecretsPlugin.h"
#include <QSet>
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<FdoSecrets::DBusClient>& client,
const QString& name,
const QList<Entry*>& entries,
bool permanent) const override;
bool requestEntriesUnlock(const QSharedPointer<FdoSecrets::DBusClient>& client,
const QString& windowId,
const QList<Entry*>& entries,
QHash<Entry*, AuthDecision>& decisions,
AuthDecision& forFutureEntries) const override;
bool doLockDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client, const QString& name) override;
bool doUnlockDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client, const QString& name) override;
bool requestUnlockAnyDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client) const override;
QString requestNewDatabase(const QSharedPointer<FdoSecrets::DBusClient>& client) override;
QString overrideMessageBoxParent(const QString& windowId) const override;
QWindow* findWindow(const QString& windowId) const;
QString getDatabaseName(const QSharedPointer<Database>& db) const;
void monitorDatabaseExposedGroup(DatabaseWidget* dbWidget);
private:
QHash<QString, DatabaseWidget*> m_nameToWidget;
DatabaseTabWidget* m_databases;
QSet<const DatabaseWidget*> m_lockingDb; // list of dbs being locked
QSet<const DatabaseWidget*> m_unlockingDb; // list of dbs being unlocked
};
#endif // KEEPASSXC_FDOSECRETSPLUGINGUI_H

View File

@ -84,16 +84,6 @@ namespace FdoSecrets
config()->set(Config::FdoSecrets_UnlockBeforeSearch, unlockBeforeSearch);
}
QUuid FdoSecretsSettings::exposedGroup(const QSharedPointer<Database>& db) const
{
return exposedGroup(db.data());
}
void FdoSecretsSettings::setExposedGroup(const QSharedPointer<Database>& db, const QUuid& group)
{
setExposedGroup(db.data(), group);
}
QUuid FdoSecretsSettings::exposedGroup(Database* db) const
{
return {db->metadata()->customData()->value(CustomData::FdoSecretsExposedGroup)};

View File

@ -48,9 +48,6 @@ namespace FdoSecrets
void setUnlockBeforeSearch(bool unlockBeforeSearch);
// Per db settings
QUuid exposedGroup(const QSharedPointer<Database>& db) const;
void setExposedGroup(const QSharedPointer<Database>& db, const QUuid& group);
QUuid exposedGroup(Database* db) const;
void setExposedGroup(Database* db, const QUuid& group);

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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<SettingsWidgetFdoSecrets*>(widget)->loadSettings();
}
void FdoSecretsSettingsPage::saveSettings(QWidget* widget)
{
qobject_cast<SettingsWidgetFdoSecrets*>(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();
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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);
}

View File

@ -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 <QEventLoop>
#include <QFileInfo>
namespace FdoSecrets
{
Collection* Collection::Create(Service* parent, DatabaseWidget* backend)
Collection* Collection::Create(Service* parent, const QString& name)
{
return new Collection(parent, backend);
QScopedPointer<Collection> 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<Item*>& 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<qulonglong>(
m_backend->database()->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000);
created = static_cast<qulonglong>(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<qulonglong>(
m_backend->database()->rootGroup()->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000);
modified = static_cast<qulonglong>(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<CreateItemPrompt>(service(), this, properties, secret, replace);
@ -320,84 +266,6 @@ namespace FdoSecrets
return {};
}
const QSet<QString> 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<Service*>(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<Database> db)
{
Q_ASSERT(!m_backend);
m_backend = db;
reloadBackend();
emit collectionLockChanged(true);
}
} // namespace FdoSecrets

View File

@ -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<Item*>& 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<QString> 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<Service*>(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<Database> 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<DatabaseWidget> m_backend;
QString m_backendPath;
QSharedPointer<Database> m_backend;
QString m_name;
QPointer<Group> m_exposedGroup;
QSet<QString> m_aliases;
QList<Item*> m_items;
QMap<const Entry*, Item*> m_entryToItem;
};

View File

@ -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<Collection*>(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();

View File

@ -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<QString> 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<Collection*>(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<Entry> m_backend;
};

View File

@ -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 <QThread>
#include <QTimer>
#include <QWindow>
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<Service*>(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<Collection*>& colls, const QSet<Item*>& 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<Collection*>(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<Entry*> 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<Entry*, AuthDecision>& 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<Entry*, AuthDecision> 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<UnlockPrompt>(service(), QSet<Collection*>{m_coll.data()}, QSet<Item*>{});
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

View File

@ -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<Collection> 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<Collection*>& colls);
PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override;
DBusResult promptSync(const DBusClientPtr& client, const QString& windowId) override;
QVariant currentResult() const override;
QList<QPointer<Collection>> m_collections;
@ -165,23 +131,15 @@ namespace FdoSecrets
explicit UnlockPrompt(Service* parent, const QSet<Collection*>& colls, const QSet<Item*>& 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<Entry*, AuthDecision>& results, AuthDecision forFutureEntries);
void unlockItems();
QList<QPointer<Collection>> m_collections;
QHash<Collection*, QList<QPointer<Item>>> m_items;
QHash<QUuid, Item*> m_entryToItems;
QList<QDBusObjectPath> m_unlocked;
int m_numRejected = 0;
// info about calling client
QWeakPointer<DBusClient> 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<Item> 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<Collection> m_coll;
QVariantMap m_properties;
@ -220,9 +177,6 @@ namespace FdoSecrets
bool m_replace;
QPointer<Item> m_item;
QPointer<const Session> m_sess;
QWeakPointer<DBusClient> m_client;
};
} // namespace FdoSecrets

View File

@ -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>
Service::Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs, QSharedPointer<DBusMgr> dbus)
QSharedPointer<Service> Service::Create(FdoSecretsPlugin* plugin, QSharedPointer<DBusMgr> dbus)
{
QSharedPointer<Service> res{new Service(plugin, std::move(dbTabs), std::move(dbus))};
if (!res->initialize()) {
return {};
QSharedPointer<Service> res{new Service(plugin, std::move(dbus))};
if (!res->dbus()->registerObject(res.data())) {
return nullptr;
}
return res;
}
Service::Service(FdoSecretsPlugin* plugin,
QPointer<DatabaseTabWidget> dbTabs,
QSharedPointer<DBusMgr> 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<Collection*>& 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<CreateCollectionPrompt>(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<Collection*>(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<Collection*>(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<Session*> 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<Database> 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

View File

@ -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<DatabaseTabWidget> dbTabs, QSharedPointer<DBusMgr> dbus);
explicit Service(FdoSecretsPlugin* plugin, QSharedPointer<DBusMgr> dbus);
public:
/**
@ -49,10 +49,13 @@ namespace FdoSecrets
* This may be caused by
* - failed initialization
*/
static QSharedPointer<Service>
Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs, QSharedPointer<DBusMgr> dbus);
static QSharedPointer<Service> Create(FdoSecretsPlugin* plugin, QSharedPointer<DBusMgr> 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<Collection*>& 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<Session*> 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<Database> 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<Collection*>& unlocked) const;
Collection* createCollection(const QString& name);
private:
FdoSecretsPlugin* m_plugin{nullptr};
QPointer<DatabaseTabWidget> m_databases{};
QHash<QString, Collection*> m_aliases{};
QHash<QString, Collection*> m_aliasToCollection{};
QHash<QString, Collection*> m_nameToCollection{};
QList<Collection*> m_collections{};
QHash<const DatabaseWidget*, Collection*> m_dbToCollection{};
QList<Session*> m_sessions{};
bool m_insideEnsureDefaultAlias{false};
bool m_unlockingAnyDatabase{false};
// list of db currently has unlock dialog shown
QHash<const DatabaseWidget*, QMetaObject::Connection> m_unlockingDb{};
QSet<const DatabaseWidget*> m_lockingDb{}; // list of db being locking
};
} // namespace FdoSecrets

View File

@ -122,7 +122,7 @@ void DatabaseSettingsWidgetFdoSecrets::loadSettings(QSharedPointer<Database> 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()

View File

@ -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:

View File

@ -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 <QAction>
#include <QToolButton>
@ -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<DatabaseWidget> 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<ManageSession>(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<ManageDatabase>(
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

View File

@ -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<Ui::SettingsWidgetFdoSecrets> m_ui;
FdoSecretsPlugin* m_plugin;
FdoSecretsSettingsPage* m_page;
QTimer m_checkTimer;
};

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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 <QTest>

View File

@ -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<DatabaseTabWidget*>("tabWidget");
VERIFY(m_tabWidget);
m_plugin = FdoSecretsPlugin::getPlugin();
m_plugin = m_mainWindow->findChild<FdoSecretsPluginGUI*>();
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<Collection>(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<ItemProxy> TestGuiFdoSecrets::createItem(const QSharedPointer<Ses
QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
VERIFY(spyPromptCompleted.isValid());
bool unlockDialogFound = false;
bool accessControlDialogFound = false;
QObject guard;
if (expectUnlockPrompt) {
QTimer::singleShot(50, &guard, [&]() {
unlockDialogFound = driveUnlockDialog();
if (expectPrompt) {
QTimer::singleShot(50, &guard, [&]() { accessControlDialogFound = driveAccessControlDialog(); });
}
});
}
else if (expectPrompt) {
QTimer::singleShot(50, &guard, [&]() { accessControlDialogFound = driveAccessControlDialog(); });
}
// drive the prompt
DBUS_VERIFY(prompt->Prompt(""));
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<QDBusObjectPath>(args.at(1));
return getProxy<ItemProxy>(itemPath);
}

View File

@ -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<DatabaseWidget> m_dbWidget;
QSharedPointer<Database> m_db;
QPointer<FdoSecretsPlugin> m_plugin;
QPointer<FdoSecretsPluginGUI> m_plugin;
QSharedPointer<FdoSecrets::DBusClient> m_client;
QScopedPointer<FdoSecrets::DhIetf1024Sha256Aes128CbcPkcs7> m_clientCipher;