mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-25 22:16:01 -05:00
fdosecrets: separate plugin internals from GUI
This commit is contained in:
parent
ea2e36c676
commit
6557970fd9
@ -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
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
};
|
||||
|
349
src/fdosecrets/FdoSecretsPluginGUI.cpp
Normal file
349
src/fdosecrets/FdoSecretsPluginGUI.cpp
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
72
src/fdosecrets/FdoSecretsPluginGUI.h
Normal file
72
src/fdosecrets/FdoSecretsPluginGUI.h
Normal 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
|
@ -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)};
|
||||
|
@ -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);
|
||||
|
||||
|
71
src/fdosecrets/FdoSecretsSettingsPage.cpp
Normal file
71
src/fdosecrets/FdoSecretsSettingsPage.cpp
Normal 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();
|
||||
}
|
66
src/fdosecrets/FdoSecretsSettingsPage.h
Normal file
66
src/fdosecrets/FdoSecretsSettingsPage.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user