Merge pull request #5660 from Aetf/fix/fdosecrets-5279

Secret Service: cleanup and fix crash
This commit is contained in:
Aetf 2020-11-15 22:34:14 -05:00 committed by GitHub
commit 3d10f31211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 564 additions and 230 deletions

View File

@ -26,8 +26,6 @@
#include <QFile> #include <QFile>
#include <utility>
using FdoSecrets::Service; using FdoSecrets::Service;
// TODO: Only used for testing. Need to split service functions away from settings page. // TODO: Only used for testing. Need to split service functions away from settings page.
@ -65,12 +63,8 @@ void FdoSecretsPlugin::updateServiceState()
{ {
if (FdoSecrets::settings()->isEnabled()) { if (FdoSecrets::settings()->isEnabled()) {
if (!m_secretService && m_dbTabs) { if (!m_secretService && m_dbTabs) {
m_secretService.reset(new Service(this, m_dbTabs)); m_secretService = Service::Create(this, m_dbTabs);
connect(m_secretService.data(), &Service::error, this, [this](const QString& msg) { if (!m_secretService) {
emit error(tr("<b>Fdo Secret Service:</b> %1").arg(msg));
});
if (!m_secretService->initialize()) {
m_secretService.reset();
FdoSecrets::settings()->setEnabled(false); FdoSecrets::settings()->setEnabled(false);
return; return;
} }
@ -107,6 +101,12 @@ void FdoSecretsPlugin::emitRequestShowNotification(const QString& msg, const QSt
emit requestShowNotification(msg, title, 10000); emit requestShowNotification(msg, title, 10000);
} }
void FdoSecretsPlugin::emitError(const QString& msg)
{
emit error(tr("<b>Fdo Secret Service:</b> %1").arg(msg));
qDebug() << msg;
}
QString FdoSecretsPlugin::reportExistingService() const QString FdoSecretsPlugin::reportExistingService() const
{ {
auto pidStr = tr("Unknown", "Unknown PID"); auto pidStr = tr("Unknown", "Unknown PID");

View File

@ -22,7 +22,8 @@
#include "gui/Icons.h" #include "gui/Icons.h"
#include <QPointer> #include <QPointer>
#include <QScopedPointer>
#include <memory>
class DatabaseTabWidget; class DatabaseTabWidget;
@ -77,6 +78,12 @@ public slots:
void emitRequestSwitchToDatabases(); void emitRequestSwitchToDatabases();
void emitRequestShowNotification(const QString& msg, const QString& title = {}); void emitRequestShowNotification(const QString& msg, const QString& title = {});
/**
* @brief Show error in the GUI
* @param msg
*/
void emitError(const QString& msg);
signals: signals:
void error(const QString& msg); void error(const QString& msg);
void requestSwitchToDatabases(); void requestSwitchToDatabases();
@ -86,7 +93,7 @@ signals:
private: private:
QPointer<DatabaseTabWidget> m_dbTabs; QPointer<DatabaseTabWidget> m_dbTabs;
QScopedPointer<FdoSecrets::Service> m_secretService; QSharedPointer<FdoSecrets::Service> m_secretService;
}; };
#endif // KEEPASSXC_FDOSECRETSPLUGIN_H #endif // KEEPASSXC_FDOSECRETSPLUGIN_H

View File

@ -17,6 +17,7 @@
#include "Collection.h" #include "Collection.h"
#include "fdosecrets/FdoSecretsPlugin.h"
#include "fdosecrets/FdoSecretsSettings.h" #include "fdosecrets/FdoSecretsSettings.h"
#include "fdosecrets/objects/Item.h" #include "fdosecrets/objects/Item.h"
#include "fdosecrets/objects/Prompt.h" #include "fdosecrets/objects/Prompt.h"
@ -24,7 +25,6 @@
#include "fdosecrets/objects/Session.h" #include "fdosecrets/objects/Session.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/Database.h"
#include "core/Tools.h" #include "core/Tools.h"
#include "gui/DatabaseTabWidget.h" #include "gui/DatabaseTabWidget.h"
#include "gui/DatabaseWidget.h" #include "gui/DatabaseWidget.h"
@ -34,16 +34,19 @@
namespace FdoSecrets namespace FdoSecrets
{ {
Collection* Collection::Create(Service* parent, DatabaseWidget* backend)
{
return new Collection(parent, backend);
}
Collection::Collection(Service* parent, DatabaseWidget* backend) Collection::Collection(Service* parent, DatabaseWidget* backend)
: DBusObject(parent) : DBusObjectHelper(parent)
, m_backend(backend) , m_backend(backend)
, m_exposedGroup(nullptr) , m_exposedGroup(nullptr)
, m_registered(false)
{ {
// whenever the file path or the database object itself change, we do a full reload. // whenever the file path or the database object itself change, we do a full reload.
connect(backend, &DatabaseWidget::databaseFilePathChanged, this, &Collection::reloadBackend); connect(backend, &DatabaseWidget::databaseFilePathChanged, this, &Collection::reloadBackendOrDelete);
connect(backend, &DatabaseWidget::databaseReplaced, this, &Collection::reloadBackend); connect(backend, &DatabaseWidget::databaseReplaced, this, &Collection::reloadBackendOrDelete);
// also remember to clear/populate the database when lock state changes. // also remember to clear/populate the database when lock state changes.
connect(backend, &DatabaseWidget::databaseUnlocked, this, &Collection::onDatabaseLockChanged); connect(backend, &DatabaseWidget::databaseUnlocked, this, &Collection::onDatabaseLockChanged);
@ -56,37 +59,36 @@ namespace FdoSecrets
} }
emit doneUnlockCollection(accepted); emit doneUnlockCollection(accepted);
}); });
reloadBackend();
} }
void Collection::reloadBackend() bool Collection::reloadBackend()
{ {
if (m_registered) {
// delete all items
// this has to be done because the backend is actually still there, just we don't expose them
// NOTE: Do NOT use a for loop, because Item::doDelete will remove itself from m_items.
while (!m_items.isEmpty()) {
m_items.first()->doDelete();
}
cleanupConnections();
unregisterCurrentPath();
m_registered = false;
}
Q_ASSERT(m_backend); Q_ASSERT(m_backend);
// make sure we have updated copy of the filepath, which is used to identify the database. // delete all items
m_backendPath = m_backend->database()->filePath(); // this has to be done because the backend is actually still there, just we don't expose them
// NOTE: Do NOT use a for loop, because Item::doDelete will remove itself from m_items.
while (!m_items.isEmpty()) {
m_items.first()->doDelete();
}
cleanupConnections();
unregisterPrimaryPath();
// the database may not have a name (derived from filePath) yet, which may happen if it's newly created. // make sure we have updated copy of the filepath, which is used to identify the database.
// defer the registration to next time a file path change happens. m_backendPath = m_backend->database()->canonicalFilePath();
if (!name().isEmpty()) {
registerWithPath( // register the object, handling potentially duplicated name
QStringLiteral(DBUS_PATH_TEMPLATE_COLLECTION).arg(p()->objectPath().path(), encodePath(name())), auto name = encodePath(this->name());
new CollectionAdaptor(this)); auto path = QStringLiteral(DBUS_PATH_TEMPLATE_COLLECTION).arg(p()->objectPath().path(), name);
m_registered = true; if (!registerWithPath(path)) {
// try again with a suffix
name += QStringLiteral("_%1").arg(Tools::uuidToHex(QUuid::createUuid()).left(4));
path = QStringLiteral(DBUS_PATH_TEMPLATE_COLLECTION).arg(p()->objectPath().path(), name);
if (!registerWithPath(path)) {
service()->plugin()->emitError(tr("Failed to register database on DBus under the name '%1'").arg(name));
return false;
}
} }
// populate contents after expose on dbus, because items rely on parent's dbus object path // populate contents after expose on dbus, because items rely on parent's dbus object path
@ -95,6 +97,15 @@ namespace FdoSecrets
} else { } else {
cleanupConnections(); cleanupConnections();
} }
return true;
}
void Collection::reloadBackendOrDelete()
{
if (!reloadBackend()) {
doDelete();
}
} }
DBusReturn<void> Collection::ensureBackend() const DBusReturn<void> Collection::ensureBackend() const
@ -197,7 +208,11 @@ namespace FdoSecrets
} }
// Delete means close database // Delete means close database
auto prompt = new DeleteCollectionPrompt(service(), this); auto dpret = DeleteCollectionPrompt::Create(service(), this);
if (dpret.isError()) {
return dpret;
}
auto prompt = dpret.value();
if (backendLocked()) { if (backendLocked()) {
// this won't raise a dialog, immediate execute // this won't raise a dialog, immediate execute
auto pret = prompt->prompt({}); auto pret = prompt->prompt({});
@ -311,12 +326,12 @@ namespace FdoSecrets
itemPath = attributes.value(ItemAttributes::PathKey); itemPath = attributes.value(ItemAttributes::PathKey);
// check existing item using attributes // check existing item using attributes
auto existings = searchItems(attributes); auto existing = searchItems(attributes);
if (existings.isError()) { if (existing.isError()) {
return existings; return existing;
} }
if (!existings.value().isEmpty() && replace) { if (!existing.value().isEmpty() && replace) {
item = existings.value().front(); item = existing.value().front();
newlyCreated = false; newlyCreated = false;
} }
} }
@ -400,11 +415,13 @@ namespace FdoSecrets
emit aliasAboutToAdd(alias); emit aliasAboutToAdd(alias);
bool ok = QDBusConnection::sessionBus().registerObject( bool ok =
QStringLiteral(DBUS_PATH_TEMPLATE_ALIAS).arg(p()->objectPath().path(), alias), this); registerWithPath(QStringLiteral(DBUS_PATH_TEMPLATE_ALIAS).arg(p()->objectPath().path(), alias), false);
if (ok) { if (ok) {
m_aliases.insert(alias); m_aliases.insert(alias);
emit aliasAdded(alias); emit aliasAdded(alias);
} else {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
} }
return {}; return {};
@ -435,16 +452,15 @@ namespace FdoSecrets
QString Collection::name() const QString Collection::name() const
{ {
if (m_backendPath.isEmpty()) { if (m_backendPath.isEmpty()) {
// This is a newly created db without saving to file. // This is a newly created db without saving to file,
// This name is also used to register dbus path. // but we have to give a name, which is used to register dbus path.
// For simplicity, we don't monitor the name change. // 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 // So the dbus object path is not updated if the db name changes.
// changes. This should not be a problem because once the database // This should not be a problem because once the database gets saved,
// gets saved, the dbus path will be updated to use filename and // the dbus path will be updated to use filename and everything back to normal.
// everything back to normal.
return m_backend->database()->metadata()->name(); return m_backend->database()->metadata()->name();
} }
return QFileInfo(m_backendPath).baseName(); return QFileInfo(m_backendPath).completeBaseName();
} }
DatabaseWidget* Collection::backend() const DatabaseWidget* Collection::backend() const
@ -466,7 +482,7 @@ namespace FdoSecrets
void Collection::populateContents() void Collection::populateContents()
{ {
if (!m_registered) { if (!m_backend) {
return; return;
} }
@ -558,7 +574,7 @@ namespace FdoSecrets
return; return;
} }
auto item = new Item(this, entry); auto item = Item::Create(this, entry);
m_items << item; m_items << item;
m_entryToItem[entry] = item; m_entryToItem[entry] = item;
@ -625,7 +641,7 @@ namespace FdoSecrets
emit collectionAboutToDelete(); emit collectionAboutToDelete();
unregisterCurrentPath(); unregisterPrimaryPath();
// remove alias manually to trigger signal // remove alias manually to trigger signal
for (const auto& a : aliases()) { for (const auto& a : aliases()) {
@ -643,6 +659,8 @@ namespace FdoSecrets
// reset backend and delete self // reset backend and delete self
m_backend = nullptr; m_backend = nullptr;
deleteLater(); deleteLater();
// items will be removed automatically as they are children objects
} }
void Collection::cleanupConnections() void Collection::cleanupConnections()

View File

@ -36,12 +36,24 @@ namespace FdoSecrets
class Item; class Item;
class PromptBase; class PromptBase;
class Service; class Service;
class Collection : public DBusObject class Collection : public DBusObjectHelper<Collection, CollectionAdaptor>
{ {
Q_OBJECT Q_OBJECT
public:
explicit Collection(Service* parent, DatabaseWidget* backend); explicit Collection(Service* parent, DatabaseWidget* backend);
public:
/**
* @brief Create a new instance of `Collection`
* @param parent the owning Service
* @param backend the widget containing the database
* @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);
DBusReturn<const QList<Item*>> items() const; DBusReturn<const QList<Item*>> items() const;
DBusReturn<QString> label() const; DBusReturn<QString> label() const;
@ -101,7 +113,7 @@ namespace FdoSecrets
static EntrySearcher::SearchTerm attributeToTerm(const QString& key, const QString& value); static EntrySearcher::SearchTerm attributeToTerm(const QString& key, const QString& value);
public slots: public slots:
// expose some methods for Prmopt to use // expose some methods for Prompt to use
bool doLock(); bool doLock();
void doUnlock(); void doUnlock();
// will remove self // will remove self
@ -110,11 +122,15 @@ namespace FdoSecrets
// delete the Entry in backend from this collection // delete the Entry in backend from this collection
void doDeleteEntries(QList<Entry*> entries); void doDeleteEntries(QList<Entry*> entries);
// force reload info from backend, potentially delete self
bool reloadBackend();
private slots: private slots:
void onDatabaseLockChanged(); void onDatabaseLockChanged();
void onDatabaseExposedGroupChanged(); void onDatabaseExposedGroupChanged();
// force reload info from backend, potentially delete self
void reloadBackend(); // calls reloadBackend, delete self when error
void reloadBackendOrDelete();
private: private:
friend class DeleteCollectionPrompt; friend class DeleteCollectionPrompt;
@ -154,8 +170,6 @@ namespace FdoSecrets
QSet<QString> m_aliases; QSet<QString> m_aliases;
QList<Item*> m_items; QList<Item*> m_items;
QMap<const Entry*, Item*> m_entryToItem; QMap<const Entry*, Item*> m_entryToItem;
bool m_registered;
}; };
} // namespace FdoSecrets } // namespace FdoSecrets

View File

@ -17,31 +17,28 @@
#include "DBusObject.h" #include "DBusObject.h"
#include <QDBusAbstractAdaptor>
#include <QFile> #include <QFile>
#include <QRegularExpression> #include <QRegularExpression>
#include <QTextStream> #include <QTextStream>
#include <QUrl> #include <QUrl>
#include <QUuid> #include <QUuid>
#include <utility>
namespace FdoSecrets namespace FdoSecrets
{ {
DBusObject::DBusObject(DBusObject* parent) DBusObject::DBusObject(DBusObject* parent)
: QObject(parent) : QObject(parent)
, m_dbusAdaptor(nullptr)
{ {
} }
void DBusObject::registerWithPath(const QString& path, QDBusAbstractAdaptor* adaptor) bool DBusObject::registerWithPath(const QString& path, bool primary)
{ {
m_objectPath.setPath(path); if (primary) {
m_dbusAdaptor = adaptor; m_objectPath.setPath(path);
adaptor->setParent(this); }
auto ok = QDBusConnection::sessionBus().registerObject(m_objectPath.path(), this);
Q_UNUSED(ok); return QDBusConnection::sessionBus().registerObject(path, this);
Q_ASSERT(ok);
} }
QString DBusObject::callingPeerName() const QString DBusObject::callingPeerName() const
@ -57,7 +54,10 @@ namespace FdoSecrets
QString encodePath(const QString& value) QString encodePath(const QString& value)
{ {
// force "-.~_" to be encoded // toPercentEncoding encodes everything except those in the unreserved group:
// ALPHA / DIGIT / "-" / "." / "_" / "~"
// we want only ALPHA / DIGIT / "_", with "_" as the escape character
// so force "-.~_" to be encoded
return QUrl::toPercentEncoding(value, "", "-.~_").replace('%', '_'); return QUrl::toPercentEncoding(value, "", "-.~_").replace('%', '_');
} }

View File

@ -21,6 +21,7 @@
#include "fdosecrets/objects/DBusReturn.h" #include "fdosecrets/objects/DBusReturn.h"
#include "fdosecrets/objects/DBusTypes.h" #include "fdosecrets/objects/DBusTypes.h"
#include <QDBusAbstractAdaptor>
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusConnectionInterface> #include <QDBusConnectionInterface>
#include <QDBusContext> #include <QDBusContext>
@ -31,21 +32,19 @@
#include <QObject> #include <QObject>
#include <QScopedPointer> #include <QScopedPointer>
class QDBusAbstractAdaptor;
namespace FdoSecrets namespace FdoSecrets
{ {
class Service; class Service;
/** /**
* @brief A common base class for all dbus-exposed objects * @brief A common base class for all dbus-exposed objects.
* However, derived class should inherit from `DBusObjectHelper`, which is
* the only way to set DBus adaptor and enforces correct adaptor creation.
*/ */
class DBusObject : public QObject, public QDBusContext class DBusObject : public QObject, public QDBusContext
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit DBusObject(DBusObject* parent = nullptr);
const QDBusObjectPath& objectPath() const const QDBusObjectPath& objectPath() const
{ {
return m_objectPath; return m_objectPath;
@ -57,12 +56,21 @@ namespace FdoSecrets
} }
protected: protected:
void registerWithPath(const QString& path, QDBusAbstractAdaptor* adaptor); /**
* @brief Register this object at given DBus path
* @param path DBus path to register at
* @param primary whether this path to be considered primary. The primary path is the one to be returned by
* `DBusObject::objectPath`.
* @return true on success
*/
bool registerWithPath(const QString& path, bool primary = true);
void unregisterCurrentPath() void unregisterPrimaryPath()
{ {
if (m_objectPath.path() == QStringLiteral("/")) {
return;
}
QDBusConnection::sessionBus().unregisterObject(m_objectPath.path()); QDBusConnection::sessionBus().unregisterObject(m_objectPath.path());
m_dbusAdaptor = nullptr;
m_objectPath.setPath(QStringLiteral("/")); m_objectPath.setPath(QStringLiteral("/"));
} }
@ -85,6 +93,8 @@ namespace FdoSecrets
} }
private: private:
explicit DBusObject(DBusObject* parent);
/** /**
* Derived class should not directly use sendErrorReply. * Derived class should not directly use sendErrorReply.
* Instead, use raiseError * Instead, use raiseError
@ -92,12 +102,26 @@ namespace FdoSecrets
using QDBusContext::sendErrorReply; using QDBusContext::sendErrorReply;
template <typename U> friend class DBusReturn; template <typename U> friend class DBusReturn;
template <typename Object, typename Adaptor> friend class DBusObjectHelper;
private:
QDBusAbstractAdaptor* m_dbusAdaptor; QDBusAbstractAdaptor* m_dbusAdaptor;
QDBusObjectPath m_objectPath; QDBusObjectPath m_objectPath;
}; };
template <typename Object, typename Adaptor> class DBusObjectHelper : public DBusObject
{
protected:
explicit DBusObjectHelper(DBusObject* parent)
: DBusObject(parent)
{
// creating new Adaptor has to be delayed into constructor's body,
// and can't be simply moved to initializer list, because at that
// point the base QObject class hasn't been initialized and will sigfault.
m_dbusAdaptor = new Adaptor(static_cast<Object*>(this));
m_dbusAdaptor->setParent(this);
}
};
/** /**
* Return the object path of the pointed DBusObject, or "/" if the pointer is null * Return the object path of the pointed DBusObject, or "/" if the pointer is null
* @tparam T * @tparam T

View File

@ -43,6 +43,7 @@
#define DBUS_PATH_TEMPLATE_SESSION "%1/session/%2" #define DBUS_PATH_TEMPLATE_SESSION "%1/session/%2"
#define DBUS_PATH_TEMPLATE_COLLECTION "%1/collection/%2" #define DBUS_PATH_TEMPLATE_COLLECTION "%1/collection/%2"
#define DBUS_PATH_TEMPLATE_ITEM "%1/%2" #define DBUS_PATH_TEMPLATE_ITEM "%1/%2"
#define DBUS_PATH_TEMPLATE_PROMPT "%1/prompt/%2"
namespace FdoSecrets namespace FdoSecrets
{ {

View File

@ -27,10 +27,10 @@
#include "core/EntryAttributes.h" #include "core/EntryAttributes.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "core/Tools.h"
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QRegularExpression> #include <QRegularExpression>
#include <QScopedPointer>
#include <QSet> #include <QSet>
#include <QTextCodec> #include <QTextCodec>
@ -48,18 +48,36 @@ namespace FdoSecrets
constexpr auto FDO_SECRETS_CONTENT_TYPE = "FDO_SECRETS_CONTENT_TYPE"; constexpr auto FDO_SECRETS_CONTENT_TYPE = "FDO_SECRETS_CONTENT_TYPE";
} // namespace } // namespace
Item* Item::Create(Collection* parent, Entry* backend)
{
QScopedPointer<Item> res{new Item(parent, backend)};
if (!res->registerSelf()) {
return nullptr;
}
return res.take();
}
Item::Item(Collection* parent, Entry* backend) Item::Item(Collection* parent, Entry* backend)
: DBusObject(parent) : DBusObjectHelper(parent)
, m_backend(backend) , m_backend(backend)
{ {
Q_ASSERT(!p()->objectPath().path().isEmpty()); Q_ASSERT(!p()->objectPath().path().isEmpty());
registerWithPath(QStringLiteral(DBUS_PATH_TEMPLATE_ITEM).arg(p()->objectPath().path(), m_backend->uuidToHex()),
new ItemAdaptor(this));
connect(m_backend.data(), &Entry::entryModified, this, &Item::itemChanged); connect(m_backend.data(), &Entry::entryModified, this, &Item::itemChanged);
} }
bool Item::registerSelf()
{
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_ITEM).arg(p()->objectPath().path(), m_backend->uuidToHex());
bool ok = registerWithPath(path);
if (!ok) {
service()->plugin()->emitError(tr("Failed to register item on DBus at path '%1'").arg(path));
}
return ok;
}
DBusReturn<bool> Item::locked() const DBusReturn<bool> Item::locked() const
{ {
auto ret = ensureBackend(); auto ret = ensureBackend();
@ -206,8 +224,8 @@ namespace FdoSecrets
if (ret.isError()) { if (ret.isError()) {
return ret; return ret;
} }
auto prompt = new DeleteItemPrompt(service(), this); auto prompt = DeleteItemPrompt::Create(service(), this);
return prompt; return prompt.value();
} }
DBusReturn<SecretStruct> Item::getSecret(Session* session) DBusReturn<SecretStruct> Item::getSecret(Session* session)
@ -322,7 +340,7 @@ namespace FdoSecrets
// Unregister current path early, do not rely on deleteLater's call to destructor // Unregister current path early, do not rely on deleteLater's call to destructor
// as in case of Entry moving between groups, new Item will be created at the same DBus path // as in case of Entry moving between groups, new Item will be created at the same DBus path
// before the current Item is deleted in the event loop. // before the current Item is deleted in the event loop.
unregisterCurrentPath(); unregisterPrimaryPath();
m_backend = nullptr; m_backend = nullptr;
deleteLater(); deleteLater();

View File

@ -38,12 +38,23 @@ namespace FdoSecrets
class Collection; class Collection;
class PromptBase; class PromptBase;
class Item : public DBusObject class Item : public DBusObjectHelper<Item, ItemAdaptor>
{ {
Q_OBJECT Q_OBJECT
public:
explicit Item(Collection* parent, Entry* backend); explicit Item(Collection* parent, Entry* backend);
public:
/**
* @brief Create a new instance of `Item`.
* @param parent the owning `Collection`
* @param backend the `Entry` containing the data
* @return pointer to newly created Item, or nullptr if error
* This may be caused by
* - DBus path registration error
*/
static Item* Create(Collection* parent, Entry* backend);
DBusReturn<bool> locked() const; DBusReturn<bool> locked() const;
DBusReturn<const StringStringMap> attributes() const; DBusReturn<const StringStringMap> attributes() const;
@ -90,7 +101,12 @@ namespace FdoSecrets
public slots: public slots:
void doDelete(); void doDelete();
private: /**
* @brief Register self on DBus
* @return
*/
bool registerSelf();
/** /**
* Check if the backend is a valid object, send error reply if not. * 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 backend is valid.

View File

@ -17,6 +17,7 @@
#include "Prompt.h" #include "Prompt.h"
#include "fdosecrets/FdoSecretsPlugin.h"
#include "fdosecrets/FdoSecretsSettings.h" #include "fdosecrets/FdoSecretsSettings.h"
#include "fdosecrets/objects/Collection.h" #include "fdosecrets/objects/Collection.h"
#include "fdosecrets/objects/Item.h" #include "fdosecrets/objects/Item.h"
@ -33,15 +34,22 @@ namespace FdoSecrets
{ {
PromptBase::PromptBase(Service* parent) PromptBase::PromptBase(Service* parent)
: DBusObject(parent) : DBusObjectHelper(parent)
{ {
registerWithPath(
QStringLiteral("%1/prompt/%2").arg(p()->objectPath().path(), Tools::uuidToHex(QUuid::createUuid())),
new PromptAdaptor(this));
connect(this, &PromptBase::completed, this, &PromptBase::deleteLater); connect(this, &PromptBase::completed, this, &PromptBase::deleteLater);
} }
bool PromptBase::registerSelf()
{
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_PROMPT)
.arg(p()->objectPath().path(), Tools::uuidToHex(QUuid::createUuid()));
bool ok = registerWithPath(path);
if (!ok) {
service()->plugin()->emitError(tr("Failed to register item on DBus at path '%1'").arg(path));
}
return ok;
}
QWindow* PromptBase::findWindow(const QString& windowId) QWindow* PromptBase::findWindow(const QString& windowId)
{ {
// find parent window, or nullptr if not found // find parent window, or nullptr if not found
@ -69,6 +77,15 @@ namespace FdoSecrets
return {}; return {};
} }
DBusReturn<DeleteCollectionPrompt*> DeleteCollectionPrompt::Create(Service* parent, Collection* coll)
{
QScopedPointer<DeleteCollectionPrompt> res{new DeleteCollectionPrompt(parent, coll)};
if (!res->registerSelf()) {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
}
return res.take();
}
DeleteCollectionPrompt::DeleteCollectionPrompt(Service* parent, Collection* coll) DeleteCollectionPrompt::DeleteCollectionPrompt(Service* parent, Collection* coll)
: PromptBase(parent) : PromptBase(parent)
, m_collection(coll) , m_collection(coll)
@ -100,6 +117,15 @@ namespace FdoSecrets
return {}; return {};
} }
DBusReturn<CreateCollectionPrompt*> CreateCollectionPrompt::Create(Service* parent)
{
QScopedPointer<CreateCollectionPrompt> res{new CreateCollectionPrompt(parent)};
if (!res->registerSelf()) {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
}
return res.take();
}
CreateCollectionPrompt::CreateCollectionPrompt(Service* parent) CreateCollectionPrompt::CreateCollectionPrompt(Service* parent)
: PromptBase(parent) : PromptBase(parent)
{ {
@ -136,6 +162,15 @@ namespace FdoSecrets
return {}; return {};
} }
DBusReturn<LockCollectionsPrompt*> LockCollectionsPrompt::Create(Service* parent, const QList<Collection*>& colls)
{
QScopedPointer<LockCollectionsPrompt> res{new LockCollectionsPrompt(parent, colls)};
if (!res->registerSelf()) {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
}
return res.take();
}
LockCollectionsPrompt::LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls) LockCollectionsPrompt::LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
: PromptBase(parent) : PromptBase(parent)
{ {
@ -179,6 +214,16 @@ namespace FdoSecrets
return {}; return {};
} }
DBusReturn<UnlockCollectionsPrompt*> UnlockCollectionsPrompt::Create(Service* parent,
const QList<Collection*>& coll)
{
QScopedPointer<UnlockCollectionsPrompt> res{new UnlockCollectionsPrompt(parent, coll)};
if (!res->registerSelf()) {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
}
return res.take();
}
UnlockCollectionsPrompt::UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& colls) UnlockCollectionsPrompt::UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
: PromptBase(parent) : PromptBase(parent)
{ {
@ -246,6 +291,15 @@ namespace FdoSecrets
return {}; return {};
} }
DBusReturn<DeleteItemPrompt*> DeleteItemPrompt::Create(Service* parent, Item* item)
{
QScopedPointer<DeleteItemPrompt> res{new DeleteItemPrompt(parent, item)};
if (!res->registerSelf()) {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
}
return res.take();
}
DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item) DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item)
: PromptBase(parent) : PromptBase(parent)
, m_item(item) , m_item(item)

View File

@ -32,12 +32,10 @@ namespace FdoSecrets
class Service; class Service;
class PromptBase : public DBusObject class PromptBase : public DBusObjectHelper<PromptBase, PromptAdaptor>
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit PromptBase(Service* parent);
virtual DBusReturn<void> prompt(const QString& windowId) = 0; virtual DBusReturn<void> prompt(const QString& windowId) = 0;
virtual DBusReturn<void> dismiss(); virtual DBusReturn<void> dismiss();
@ -46,6 +44,9 @@ namespace FdoSecrets
void completed(bool dismissed, const QVariant& result); void completed(bool dismissed, const QVariant& result);
protected: protected:
explicit PromptBase(Service* parent);
bool registerSelf();
QWindow* findWindow(const QString& windowId); QWindow* findWindow(const QString& windowId);
Service* service() const; Service* service() const;
}; };
@ -56,9 +57,11 @@ namespace FdoSecrets
{ {
Q_OBJECT Q_OBJECT
public:
explicit DeleteCollectionPrompt(Service* parent, Collection* coll); explicit DeleteCollectionPrompt(Service* parent, Collection* coll);
public:
static DBusReturn<DeleteCollectionPrompt*> Create(Service* parent, Collection* coll);
DBusReturn<void> prompt(const QString& windowId) override; DBusReturn<void> prompt(const QString& windowId) override;
private: private:
@ -69,9 +72,11 @@ namespace FdoSecrets
{ {
Q_OBJECT Q_OBJECT
public:
explicit CreateCollectionPrompt(Service* parent); explicit CreateCollectionPrompt(Service* parent);
public:
static DBusReturn<CreateCollectionPrompt*> Create(Service* parent);
DBusReturn<void> prompt(const QString& windowId) override; DBusReturn<void> prompt(const QString& windowId) override;
DBusReturn<void> dismiss() override; DBusReturn<void> dismiss() override;
@ -82,9 +87,12 @@ namespace FdoSecrets
class LockCollectionsPrompt : public PromptBase class LockCollectionsPrompt : public PromptBase
{ {
Q_OBJECT Q_OBJECT
public:
explicit LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls); explicit LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls);
public:
static DBusReturn<LockCollectionsPrompt*> Create(Service* parent, const QList<Collection*>& colls);
DBusReturn<void> prompt(const QString& windowId) override; DBusReturn<void> prompt(const QString& windowId) override;
DBusReturn<void> dismiss() override; DBusReturn<void> dismiss() override;
@ -96,9 +104,12 @@ namespace FdoSecrets
class UnlockCollectionsPrompt : public PromptBase class UnlockCollectionsPrompt : public PromptBase
{ {
Q_OBJECT Q_OBJECT
public:
explicit UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& coll); explicit UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& coll);
public:
static DBusReturn<UnlockCollectionsPrompt*> Create(Service* parent, const QList<Collection*>& coll);
DBusReturn<void> prompt(const QString& windowId) override; DBusReturn<void> prompt(const QString& windowId) override;
DBusReturn<void> dismiss() override; DBusReturn<void> dismiss() override;
@ -116,9 +127,11 @@ namespace FdoSecrets
{ {
Q_OBJECT Q_OBJECT
public:
explicit DeleteItemPrompt(Service* parent, Item* item); explicit DeleteItemPrompt(Service* parent, Item* item);
public:
static DBusReturn<DeleteItemPrompt*> Create(Service* parent, Item* item);
DBusReturn<void> prompt(const QString& windowId) override; DBusReturn<void> prompt(const QString& windowId) override;
private: private:

View File

@ -30,6 +30,7 @@
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusServiceWatcher> #include <QDBusServiceWatcher>
#include <QDebug> #include <QDebug>
#include <QSharedPointer>
namespace namespace
{ {
@ -38,13 +39,21 @@ namespace
namespace FdoSecrets namespace FdoSecrets
{ {
QSharedPointer<Service> Service::Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs)
{
QSharedPointer<Service> res{new Service(plugin, std::move(dbTabs))};
if (!res->initialize()) {
return {};
}
return res;
}
Service::Service(FdoSecretsPlugin* plugin, Service::Service(FdoSecretsPlugin* plugin,
QPointer<DatabaseTabWidget> dbTabs) // clazy: exclude=ctor-missing-parent-argument QPointer<DatabaseTabWidget> dbTabs) // clazy: exclude=ctor-missing-parent-argument
: DBusObject(nullptr) : DBusObjectHelper(nullptr)
, m_plugin(plugin) , m_plugin(plugin)
, m_databases(std::move(dbTabs)) , m_databases(std::move(dbTabs))
, m_insdieEnsureDefaultAlias(false) , m_insideEnsureDefaultAlias(false)
, m_serviceWatcher(nullptr) , m_serviceWatcher(nullptr)
{ {
connect( connect(
@ -59,19 +68,21 @@ namespace FdoSecrets
bool Service::initialize() bool Service::initialize()
{ {
if (!QDBusConnection::sessionBus().registerService(QStringLiteral(DBUS_SERVICE_SECRET))) { if (!QDBusConnection::sessionBus().registerService(QStringLiteral(DBUS_SERVICE_SECRET))) {
emit error(tr("Failed to register DBus service at %1.<br/>").arg(QLatin1String(DBUS_SERVICE_SECRET)) plugin()->emitError(
+ m_plugin->reportExistingService()); tr("Failed to register DBus service at %1.<br/>").arg(QLatin1String(DBUS_SERVICE_SECRET))
+ m_plugin->reportExistingService());
return false; return false;
} }
registerWithPath(QStringLiteral(DBUS_PATH_SECRETS), new ServiceAdaptor(this)); if (!registerWithPath(QStringLiteral(DBUS_PATH_SECRETS))) {
plugin()->emitError(tr("Failed to register DBus path %1.<br/>").arg(QStringLiteral(DBUS_PATH_SECRETS)));
return false;
}
// Connect to service unregistered signal // Connect to service unregistered signal
m_serviceWatcher.reset(new QDBusServiceWatcher()); m_serviceWatcher.reset(new QDBusServiceWatcher());
connect(m_serviceWatcher.data(), connect(
&QDBusServiceWatcher::serviceUnregistered, m_serviceWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, this, &Service::dbusServiceUnregistered);
this,
&Service::dbusServiceUnregistered);
m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
@ -99,31 +110,32 @@ namespace FdoSecrets
// When the Collection finds that no exposed group, it will delete itself. // 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 // Thus the service also needs to monitor it and recreate the collection if the user changes
// from no exposed to exposed something. // from no exposed to exposed something.
connect(dbWidget, &DatabaseWidget::databaseReplaced, this, [this, dbWidget]() {
monitorDatabaseExposedGroup(dbWidget);
});
if (!dbWidget->isLocked()) { if (!dbWidget->isLocked()) {
monitorDatabaseExposedGroup(dbWidget); monitorDatabaseExposedGroup(dbWidget);
} }
connect(dbWidget, &DatabaseWidget::databaseUnlocked, this, [this, dbWidget]() {
monitorDatabaseExposedGroup(dbWidget);
});
auto coll = new Collection(this, dbWidget); auto coll = Collection::Create(this, dbWidget);
// Creation may fail if the database is not exposed. if (!coll) {
// This is okay, because we monitor the expose settings above
if (!coll->isValid()) {
coll->deleteLater();
return; return;
} }
m_collections << coll; m_collections << coll;
m_dbToCollection[dbWidget] = coll; m_dbToCollection[dbWidget] = coll;
// handle alias // 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::aliasAboutToAdd, this, &Service::onCollectionAliasAboutToAdd);
connect(coll, &Collection::aliasAdded, this, &Service::onCollectionAliasAdded); connect(coll, &Collection::aliasAdded, this, &Service::onCollectionAliasAdded);
connect(coll, &Collection::aliasRemoved, this, &Service::onCollectionAliasRemoved); connect(coll, &Collection::aliasRemoved, this, &Service::onCollectionAliasRemoved);
ensureDefaultAlias();
// Forward delete signal, we have to rely on filepath to identify the database being closed, // 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, // but we can not access m_backend safely because during the databaseClosed signal,
// m_backend may already be reset to nullptr // m_backend may already be reset to nullptr
@ -138,14 +150,22 @@ namespace FdoSecrets
} }
}); });
// relay signals // actual load, must after updates to m_collections, because the reload may trigger
connect(coll, &Collection::collectionChanged, this, [this, coll]() { emit collectionChanged(coll); }); // another onDatabaseTabOpen, and m_collections will be used to prevent recursion.
connect(coll, &Collection::collectionAboutToDelete, this, [this, coll]() { if (!coll->reloadBackend()) {
m_collections.removeAll(coll); // error in dbus
m_dbToCollection.remove(coll->backend()); return;
emit collectionDeleted(coll); }
}); 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) { if (emitSignal) {
emit collectionCreated(coll); emit collectionCreated(coll);
} }
@ -164,11 +184,11 @@ namespace FdoSecrets
void Service::ensureDefaultAlias() void Service::ensureDefaultAlias()
{ {
if (m_insdieEnsureDefaultAlias) { if (m_insideEnsureDefaultAlias) {
return; return;
} }
m_insdieEnsureDefaultAlias = true; m_insideEnsureDefaultAlias = true;
auto coll = findCollection(m_databases->currentDatabaseWidget()); auto coll = findCollection(m_databases->currentDatabaseWidget());
if (coll) { if (coll) {
@ -176,7 +196,7 @@ namespace FdoSecrets
coll->addAlias(DEFAULT_ALIAS).okOrDie(); coll->addAlias(DEFAULT_ALIAS).okOrDie();
} }
m_insdieEnsureDefaultAlias = false; m_insideEnsureDefaultAlias = false;
} }
void Service::dbusServiceUnregistered(const QString& service) void Service::dbusServiceUnregistered(const QString& service)
@ -184,8 +204,9 @@ namespace FdoSecrets
Q_ASSERT(m_serviceWatcher); Q_ASSERT(m_serviceWatcher);
auto removed = m_serviceWatcher->removeWatchedService(service); auto removed = m_serviceWatcher->removeWatchedService(service);
Q_UNUSED(removed); if (!removed) {
Q_ASSERT(removed); qDebug("FdoSecrets: Failed to remove service watcher");
}
Session::CleanupNegotiation(service); Session::CleanupNegotiation(service);
auto sess = m_peerToSession.value(service, nullptr); auto sess = m_peerToSession.value(service, nullptr);
@ -218,7 +239,10 @@ namespace FdoSecrets
if (!ciphers) { if (!ciphers) {
return DBusReturn<>::Error(QDBusError::NotSupported); return DBusReturn<>::Error(QDBusError::NotSupported);
} }
result = new Session(std::move(ciphers), callingPeerName(), this); result = Session::Create(std::move(ciphers), callingPeerName(), this);
if (!result) {
return DBusReturn<>::Error(QDBusError::InvalidObjectPath);
}
m_sessions.append(result); m_sessions.append(result);
m_peerToSession[peer] = result; m_peerToSession[peer] = result;
@ -240,17 +264,23 @@ namespace FdoSecrets
// return existing collection if alias is non-empty and exists. // return existing collection if alias is non-empty and exists.
auto collection = findCollection(alias); auto collection = findCollection(alias);
if (!collection) { if (!collection) {
auto cp = new CreateCollectionPrompt(this); auto cp = CreateCollectionPrompt::Create(this);
prompt = cp; if (cp.isError()) {
return cp;
}
prompt = cp.value();
// collection will be created when the prompt complets. // collection will be created when the prompt completes.
// once it's done, we set additional properties on the collection // once it's done, we set additional properties on the collection
connect(cp, &CreateCollectionPrompt::collectionCreated, cp, [alias, properties](Collection* coll) { connect(cp.value(),
coll->setProperties(properties).okOrDie(); &CreateCollectionPrompt::collectionCreated,
if (!alias.isEmpty()) { cp.value(),
coll->addAlias(alias).okOrDie(); [alias, properties](Collection* coll) {
} coll->setProperties(properties).okOrDie();
}); if (!alias.isEmpty()) {
coll->addAlias(alias).okOrDie();
}
});
} }
return collection; return collection;
} }
@ -314,7 +344,11 @@ namespace FdoSecrets
} }
} }
if (!toUnlock.isEmpty()) { if (!toUnlock.isEmpty()) {
prompt = new UnlockCollectionsPrompt(this, toUnlock); auto up = UnlockCollectionsPrompt::Create(this, toUnlock);
if (up.isError()) {
return up;
}
prompt = up.value();
} }
return unlocked; return unlocked;
} }
@ -352,7 +386,11 @@ namespace FdoSecrets
} }
} }
if (!toLock.isEmpty()) { if (!toLock.isEmpty()) {
prompt = new LockCollectionsPrompt(this, toLock); auto lp = LockCollectionsPrompt::Create(this, toLock);
if (lp.isError()) {
return lp;
}
prompt = lp.value();
} }
return locked; return locked;
} }

View File

@ -24,9 +24,10 @@
#include <QHash> #include <QHash>
#include <QObject> #include <QObject>
#include <QPointer> #include <QPointer>
#include <QScopedPointer>
#include <QVariant> #include <QVariant>
#include <memory>
class QDBusServiceWatcher; class QDBusServiceWatcher;
class DatabaseTabWidget; class DatabaseTabWidget;
@ -44,14 +45,21 @@ namespace FdoSecrets
class ServiceAdaptor; class ServiceAdaptor;
class Session; class Session;
class Service : public DBusObject // clazy: exclude=ctor-missing-parent-argument class Service : public DBusObjectHelper<Service, ServiceAdaptor> // clazy: exclude=ctor-missing-parent-argument
{ {
Q_OBJECT Q_OBJECT
public:
explicit Service(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs);
~Service() override;
bool initialize(); explicit Service(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs);
public:
/**
* @brief Create a new instance of `Service`. Its parent is set to `null`
* @return pointer to newly created Service, or nullptr if error
* This may be caused by
* - failed initialization
*/
static QSharedPointer<Service> Create(FdoSecretsPlugin* plugin, QPointer<DatabaseTabWidget> dbTabs);
~Service() override;
DBusReturn<QVariant> openSession(const QString& algorithm, const QVariant& input, Session*& result); DBusReturn<QVariant> openSession(const QString& algorithm, const QVariant& input, Session*& result);
DBusReturn<Collection*> DBusReturn<Collection*>
@ -82,12 +90,6 @@ namespace FdoSecrets
void sessionOpened(Session* sess); void sessionOpened(Session* sess);
void sessionClosed(Session* sess); void sessionClosed(Session* sess);
/**
* Report error message to the GUI
* @param msg
*/
void error(const QString& msg);
/** /**
* Finish signal for async action doUnlockDatabaseInDialog * Finish signal for async action doUnlockDatabaseInDialog
* @param accepted If false, the action is canceled by the user * @param accepted If false, the action is canceled by the user
@ -131,6 +133,8 @@ namespace FdoSecrets
void onCollectionAliasRemoved(const QString& alias); void onCollectionAliasRemoved(const QString& alias);
private: private:
bool initialize();
/** /**
* Find collection by alias name * Find collection by alias name
* @param alias * @param alias
@ -156,9 +160,9 @@ namespace FdoSecrets
QList<Session*> m_sessions; QList<Session*> m_sessions;
QHash<QString, Session*> m_peerToSession; QHash<QString, Session*> m_peerToSession;
bool m_insdieEnsureDefaultAlias; bool m_insideEnsureDefaultAlias;
QScopedPointer<QDBusServiceWatcher> m_serviceWatcher; std::unique_ptr<QDBusServiceWatcher> m_serviceWatcher;
}; };
} // namespace FdoSecrets } // namespace FdoSecrets

View File

@ -16,6 +16,7 @@
*/ */
#include "Session.h" #include "Session.h"
#include "fdosecrets/FdoSecretsPlugin.h"
#include "fdosecrets/objects/SessionCipher.h" #include "fdosecrets/objects/SessionCipher.h"
#include "core/Tools.h" #include "core/Tools.h"
@ -23,21 +24,40 @@
namespace FdoSecrets namespace FdoSecrets
{ {
QHash<QString, QVariant> Session::negoniationState; QHash<QString, QVariant> Session::negotiationState;
Session* Session::Create(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent)
{
QScopedPointer<Session> res{new Session(std::move(cipher), peer, parent)};
if (!res->registerSelf()) {
return nullptr;
}
return res.take();
}
Session::Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent) Session::Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent)
: DBusObject(parent) : DBusObjectHelper(parent)
, m_cipher(std::move(cipher)) , m_cipher(std::move(cipher))
, m_peer(peer) , m_peer(peer)
, m_id(QUuid::createUuid()) , m_id(QUuid::createUuid())
{ {
registerWithPath(QStringLiteral(DBUS_PATH_TEMPLATE_SESSION).arg(p()->objectPath().path(), id()), }
new SessionAdaptor(this));
bool Session::registerSelf()
{
auto path = QStringLiteral(DBUS_PATH_TEMPLATE_SESSION).arg(p()->objectPath().path(), id());
bool ok = registerWithPath(path);
if (!ok) {
service()->plugin()->emitError(tr("Failed to register session on DBus at path '%1'").arg(path));
}
return ok;
} }
void Session::CleanupNegotiation(const QString& peer) void Session::CleanupNegotiation(const QString& peer)
{ {
negoniationState.remove(peer); negotiationState.remove(peer);
} }
DBusReturn<void> Session::close() DBusReturn<void> Session::close()
@ -58,6 +78,11 @@ namespace FdoSecrets
return Tools::uuidToHex(m_id); return Tools::uuidToHex(m_id);
} }
Service* Session::service() const
{
return qobject_cast<Service*>(parent());
}
std::unique_ptr<CipherPair> Session::CreateCiphers(const QString& peer, std::unique_ptr<CipherPair> Session::CreateCiphers(const QString& peer,
const QString& algorithm, const QString& algorithm,
const QVariant& input, const QVariant& input,

View File

@ -34,18 +34,30 @@ namespace FdoSecrets
{ {
class CipherPair; class CipherPair;
class Session : public DBusObject class Session : public DBusObjectHelper<Session, SessionAdaptor>
{ {
Q_OBJECT Q_OBJECT
explicit Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent);
public: public:
static std::unique_ptr<CipherPair> CreateCiphers(const QString& peer, static std::unique_ptr<CipherPair> CreateCiphers(const QString& peer,
const QString& algorithm, const QString& algorithm,
const QVariant& intpu, const QVariant& input,
QVariant& output, QVariant& output,
bool& incomplete); bool& incomplete);
static void CleanupNegotiation(const QString& peer); static void CleanupNegotiation(const QString& peer);
explicit Session(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent); /**
* @brief Create a new instance of `Session`.
* @param cipher the negotiated cipher
* @param peer connecting peer
* @param parent the owning Service
* @return pointer to newly created Session, or nullptr if error
* This may be caused by
* - DBus path registration error
*/
static Session* Create(std::unique_ptr<CipherPair>&& cipher, const QString& peer, Service* parent);
DBusReturn<void> close(); DBusReturn<void> close();
@ -71,6 +83,8 @@ namespace FdoSecrets
QString id() const; QString id() const;
Service* service() const;
signals: signals:
/** /**
* The session is going to be closed * The session is going to be closed
@ -78,12 +92,15 @@ namespace FdoSecrets
*/ */
void aboutToClose(); void aboutToClose();
private:
bool registerSelf();
private: private:
std::unique_ptr<CipherPair> m_cipher; std::unique_ptr<CipherPair> m_cipher;
QString m_peer; QString m_peer;
QUuid m_id; QUuid m_id;
static QHash<QString, QVariant> negoniationState; static QHash<QString, QVariant> negotiationState;
}; };
} // namespace FdoSecrets } // namespace FdoSecrets

View File

@ -27,12 +27,16 @@ namespace FdoSecrets
CollectionAdaptor::CollectionAdaptor(Collection* parent) CollectionAdaptor::CollectionAdaptor(Collection* parent)
: DBusAdaptor(parent) : DBusAdaptor(parent)
{ {
connect( // p() isn't ready yet as this is called in Parent's constructor
p(), &Collection::itemCreated, this, [this](const Item* item) { emit ItemCreated(objectPathSafe(item)); }); connect(parent, &Collection::itemCreated, this, [this](const Item* item) {
connect( emit ItemCreated(objectPathSafe(item));
p(), &Collection::itemDeleted, this, [this](const Item* item) { emit ItemDeleted(objectPathSafe(item)); }); });
connect( connect(parent, &Collection::itemDeleted, this, [this](const Item* item) {
p(), &Collection::itemChanged, this, [this](const Item* item) { emit ItemChanged(objectPathSafe(item)); }); emit ItemDeleted(objectPathSafe(item));
});
connect(parent, &Collection::itemChanged, this, [this](const Item* item) {
emit ItemChanged(objectPathSafe(item));
});
} }
const QList<QDBusObjectPath> CollectionAdaptor::items() const const QList<QDBusObjectPath> CollectionAdaptor::items() const

View File

@ -32,7 +32,7 @@ namespace FdoSecrets
template <typename Parent> class DBusAdaptor : public QDBusAbstractAdaptor template <typename Parent> class DBusAdaptor : public QDBusAbstractAdaptor
{ {
public: public:
explicit DBusAdaptor(QObject* parent = nullptr) explicit DBusAdaptor(Parent* parent = nullptr)
: QDBusAbstractAdaptor(parent) : QDBusAbstractAdaptor(parent)
{ {
} }

View File

@ -25,7 +25,8 @@ namespace FdoSecrets
PromptAdaptor::PromptAdaptor(PromptBase* parent) PromptAdaptor::PromptAdaptor(PromptBase* parent)
: DBusAdaptor(parent) : DBusAdaptor(parent)
{ {
connect(p(), &PromptBase::completed, this, [this](bool dismissed, QVariant result) { // p() isn't ready yet as this is called in Parent's constructor
connect(parent, &PromptBase::completed, this, [this](bool dismissed, QVariant result) {
// make sure the result contains a valid value, otherwise QDBusVariant refuses to marshall it. // make sure the result contains a valid value, otherwise QDBusVariant refuses to marshall it.
if (!result.isValid()) { if (!result.isValid()) {
result = QString{}; result = QString{};

View File

@ -29,13 +29,14 @@ namespace FdoSecrets
ServiceAdaptor::ServiceAdaptor(Service* parent) ServiceAdaptor::ServiceAdaptor(Service* parent)
: DBusAdaptor(parent) : DBusAdaptor(parent)
{ {
connect(p(), &Service::collectionCreated, this, [this](Collection* coll) { // p() isn't ready yet as this is called in Parent's constructor
connect(parent, &Service::collectionCreated, this, [this](Collection* coll) {
emit CollectionCreated(objectPathSafe(coll)); emit CollectionCreated(objectPathSafe(coll));
}); });
connect(p(), &Service::collectionDeleted, this, [this](Collection* coll) { connect(parent, &Service::collectionDeleted, this, [this](Collection* coll) {
emit CollectionDeleted(objectPathSafe(coll)); emit CollectionDeleted(objectPathSafe(coll));
}); });
connect(p(), &Service::collectionChanged, this, [this](Collection* coll) { connect(parent, &Service::collectionChanged, this, [this](Collection* coll) {
emit CollectionChanged(objectPathSafe(coll)); emit CollectionChanged(objectPathSafe(coll));
}); });
} }

View File

@ -48,6 +48,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QPointer> #include <QPointer>
#include <QSignalSpy> #include <QSignalSpy>
#include <QTemporaryDir>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <memory> #include <memory>
@ -173,18 +174,10 @@ namespace
using namespace FdoSecrets; using namespace FdoSecrets;
// for use in QSignalSpy
Q_DECLARE_METATYPE(Collection*);
Q_DECLARE_METATYPE(Item*);
TestGuiFdoSecrets::~TestGuiFdoSecrets() = default; TestGuiFdoSecrets::~TestGuiFdoSecrets() = default;
void TestGuiFdoSecrets::initTestCase() void TestGuiFdoSecrets::initTestCase()
{ {
// for use in QSignalSpy
qRegisterMetaType<Collection*>();
qRegisterMetaType<Item*>();
QVERIFY(Crypto::init()); QVERIFY(Crypto::init());
Config::createTempFileInstance(); Config::createTempFileInstance();
config()->set(Config::AutoSaveAfterEveryChange, false); config()->set(Config::AutoSaveAfterEveryChange, false);
@ -267,7 +260,7 @@ void TestGuiFdoSecrets::init()
m_dbWidget = m_tabWidget->currentDatabaseWidget(); m_dbWidget = m_tabWidget->currentDatabaseWidget();
m_db = m_dbWidget->database(); m_db = m_dbWidget->database();
// by default expsoe the root group // by default expose the root group
FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid()); FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid());
QVERIFY(m_dbWidget->save()); QVERIFY(m_dbWidget->save());
} }
@ -457,11 +450,11 @@ void TestGuiFdoSecrets::testServiceUnlock()
auto coll = getDefaultCollection(service); auto coll = getDefaultCollection(service);
QVERIFY(coll); QVERIFY(coll);
QSignalSpy spyCollectionCreated(service, SIGNAL(collectionCreated(Collection*))); QSignalSpy spyCollectionCreated(&service->dbusAdaptor(), SIGNAL(CollectionCreated(QDBusObjectPath)));
QVERIFY(spyCollectionCreated.isValid()); QVERIFY(spyCollectionCreated.isValid());
QSignalSpy spyCollectionDeleted(service, SIGNAL(collectionDeleted(Collection*))); QSignalSpy spyCollectionDeleted(&service->dbusAdaptor(), SIGNAL(CollectionDeleted(QDBusObjectPath)));
QVERIFY(spyCollectionDeleted.isValid()); QVERIFY(spyCollectionDeleted.isValid());
QSignalSpy spyCollectionChanged(service, SIGNAL(collectionChanged(Collection*))); QSignalSpy spyCollectionChanged(&service->dbusAdaptor(), SIGNAL(CollectionChanged(QDBusObjectPath)));
QVERIFY(spyCollectionChanged.isValid()); QVERIFY(spyCollectionChanged.isValid());
PromptBase* prompt = nullptr; PromptBase* prompt = nullptr;
@ -471,7 +464,7 @@ void TestGuiFdoSecrets::testServiceUnlock()
QVERIFY(unlocked.isEmpty()); QVERIFY(unlocked.isEmpty());
} }
QVERIFY(prompt); QVERIFY(prompt);
QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); QSignalSpy spyPromptCompleted(&prompt->dbusAdaptor(), SIGNAL(Completed(bool, QDBusVariant)));
QVERIFY(spyPromptCompleted.isValid()); QVERIFY(spyPromptCompleted.isValid());
// nothing is unlocked yet // nothing is unlocked yet
@ -509,14 +502,14 @@ void TestGuiFdoSecrets::testServiceUnlock()
auto args = spyPromptCompleted.takeFirst(); auto args = spyPromptCompleted.takeFirst();
QCOMPARE(args.size(), 2); QCOMPARE(args.size(), 2);
QCOMPARE(args.at(0).toBool(), false); QCOMPARE(args.at(0).toBool(), false);
QCOMPARE(args.at(1).value<QList<QDBusObjectPath>>(), {coll->objectPath()}); QCOMPARE(args.at(1).value<QDBusVariant>().variant().value<QList<QDBusObjectPath>>(), {coll->objectPath()});
} }
QCOMPARE(spyCollectionCreated.count(), 0); QCOMPARE(spyCollectionCreated.count(), 0);
QCOMPARE(spyCollectionChanged.count(), 1); QCOMPARE(spyCollectionChanged.count(), 1);
{ {
auto args = spyCollectionChanged.takeFirst(); auto args = spyCollectionChanged.takeFirst();
QCOMPARE(args.size(), 1); QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<Collection*>(), coll.data()); QCOMPARE(args.at(0).value<QDBusObjectPath>(), coll->objectPath());
} }
QCOMPARE(spyCollectionDeleted.count(), 0); QCOMPARE(spyCollectionDeleted.count(), 0);
} }
@ -528,11 +521,11 @@ void TestGuiFdoSecrets::testServiceLock()
auto coll = getDefaultCollection(service); auto coll = getDefaultCollection(service);
QVERIFY(coll); QVERIFY(coll);
QSignalSpy spyCollectionCreated(service, SIGNAL(collectionCreated(Collection*))); QSignalSpy spyCollectionCreated(&service->dbusAdaptor(), SIGNAL(CollectionCreated(QDBusObjectPath)));
QVERIFY(spyCollectionCreated.isValid()); QVERIFY(spyCollectionCreated.isValid());
QSignalSpy spyCollectionDeleted(service, SIGNAL(collectionDeleted(Collection*))); QSignalSpy spyCollectionDeleted(&service->dbusAdaptor(), SIGNAL(CollectionDeleted(QDBusObjectPath)));
QVERIFY(spyCollectionDeleted.isValid()); QVERIFY(spyCollectionDeleted.isValid());
QSignalSpy spyCollectionChanged(service, SIGNAL(collectionChanged(Collection*))); QSignalSpy spyCollectionChanged(&service->dbusAdaptor(), SIGNAL(CollectionChanged(QDBusObjectPath)));
QVERIFY(spyCollectionChanged.isValid()); QVERIFY(spyCollectionChanged.isValid());
// if the db is modified, prompt user // if the db is modified, prompt user
@ -542,7 +535,7 @@ void TestGuiFdoSecrets::testServiceLock()
CHECKED_DBUS_LOCAL_CALL(locked, service->lock({coll}, prompt)); CHECKED_DBUS_LOCAL_CALL(locked, service->lock({coll}, prompt));
QCOMPARE(locked.size(), 0); QCOMPARE(locked.size(), 0);
QVERIFY(prompt); QVERIFY(prompt);
QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); QSignalSpy spyPromptCompleted(&prompt->dbusAdaptor(), SIGNAL(Completed(bool, QDBusVariant)));
QVERIFY(spyPromptCompleted.isValid()); QVERIFY(spyPromptCompleted.isValid());
// prompt and click cancel // prompt and click cancel
@ -563,7 +556,7 @@ void TestGuiFdoSecrets::testServiceLock()
CHECKED_DBUS_LOCAL_CALL(locked, service->lock({coll}, prompt)); CHECKED_DBUS_LOCAL_CALL(locked, service->lock({coll}, prompt));
QCOMPARE(locked.size(), 0); QCOMPARE(locked.size(), 0);
QVERIFY(prompt); QVERIFY(prompt);
QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); QSignalSpy spyPromptCompleted(&prompt->dbusAdaptor(), SIGNAL(Completed(bool, QDBusVariant)));
QVERIFY(spyPromptCompleted.isValid()); QVERIFY(spyPromptCompleted.isValid());
// prompt and click save // prompt and click save
@ -577,7 +570,7 @@ void TestGuiFdoSecrets::testServiceLock()
auto args = spyPromptCompleted.takeFirst(); auto args = spyPromptCompleted.takeFirst();
QCOMPARE(args.count(), 2); QCOMPARE(args.count(), 2);
QCOMPARE(args.at(0).toBool(), false); QCOMPARE(args.at(0).toBool(), false);
QCOMPARE(args.at(1).value<QList<QDBusObjectPath>>(), {coll->objectPath()}); QCOMPARE(args.at(1).value<QDBusVariant>().variant().value<QList<QDBusObjectPath>>(), {coll->objectPath()});
} }
QCOMPARE(spyCollectionCreated.count(), 0); QCOMPARE(spyCollectionCreated.count(), 0);
@ -585,7 +578,7 @@ void TestGuiFdoSecrets::testServiceLock()
{ {
auto args = spyCollectionChanged.takeFirst(); auto args = spyCollectionChanged.takeFirst();
QCOMPARE(args.size(), 1); QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<Collection*>(), coll.data()); QCOMPARE(args.at(0).value<QDBusObjectPath>(), coll->objectPath());
} }
QCOMPARE(spyCollectionDeleted.count(), 0); QCOMPARE(spyCollectionDeleted.count(), 0);
@ -641,7 +634,7 @@ void TestGuiFdoSecrets::testCollectionCreate()
auto service = enableService(); auto service = enableService();
QVERIFY(service); QVERIFY(service);
QSignalSpy spyCollectionCreated(service, SIGNAL(collectionCreated(Collection*))); QSignalSpy spyCollectionCreated(&service->dbusAdaptor(), SIGNAL(CollectionCreated(QDBusObjectPath)));
QVERIFY(spyCollectionCreated.isValid()); QVERIFY(spyCollectionCreated.isValid());
// returns existing if alias is nonempty and exists // returns existing if alias is nonempty and exists
@ -663,7 +656,7 @@ void TestGuiFdoSecrets::testCollectionCreate()
QVERIFY(!created); QVERIFY(!created);
QVERIFY(prompt); QVERIFY(prompt);
QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); QSignalSpy spyPromptCompleted(&prompt->dbusAdaptor(), SIGNAL(Completed(bool, QDBusVariant)));
QVERIFY(spyPromptCompleted.isValid()); QVERIFY(spyPromptCompleted.isValid());
QTimer::singleShot(50, this, SLOT(createDatabaseCallback())); QTimer::singleShot(50, this, SLOT(createDatabaseCallback()));
@ -674,7 +667,8 @@ void TestGuiFdoSecrets::testCollectionCreate()
auto args = spyPromptCompleted.takeFirst(); auto args = spyPromptCompleted.takeFirst();
QCOMPARE(args.size(), 2); QCOMPARE(args.size(), 2);
QCOMPARE(args.at(0).toBool(), false); QCOMPARE(args.at(0).toBool(), false);
auto coll = FdoSecrets::pathToObject<Collection>(args.at(1).value<QDBusObjectPath>()); auto coll =
FdoSecrets::pathToObject<Collection>(args.at(1).value<QDBusVariant>().variant().value<QDBusObjectPath>());
QVERIFY(coll); QVERIFY(coll);
QCOMPARE(coll->backend()->database()->metadata()->name(), QStringLiteral("Test NewDB")); QCOMPARE(coll->backend()->database()->metadata()->name(), QStringLiteral("Test NewDB"));
@ -683,7 +677,7 @@ void TestGuiFdoSecrets::testCollectionCreate()
{ {
args = spyCollectionCreated.takeFirst(); args = spyCollectionCreated.takeFirst();
QCOMPARE(args.size(), 1); QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<Collection*>(), coll); QCOMPARE(args.at(0).value<QDBusObjectPath>(), coll->objectPath());
} }
} }
} }
@ -722,18 +716,16 @@ void TestGuiFdoSecrets::testCollectionDelete()
QVERIFY(service); QVERIFY(service);
auto coll = getDefaultCollection(service); auto coll = getDefaultCollection(service);
QVERIFY(coll); QVERIFY(coll);
// closing the tab calls coll->deleteLater() // save the path which will be gone after the deletion.
// but deleteLater is not processed in QApplication::processEvent auto collPath = coll->objectPath();
// see https://doc.qt.io/qt-5/qcoreapplication.html#processEvents
auto rawColl = coll.data();
QSignalSpy spyCollectionDeleted(service, SIGNAL(collectionDeleted(Collection*))); QSignalSpy spyCollectionDeleted(&service->dbusAdaptor(), SIGNAL(CollectionDeleted(QDBusObjectPath)));
QVERIFY(spyCollectionDeleted.isValid()); QVERIFY(spyCollectionDeleted.isValid());
m_db->markAsModified(); m_db->markAsModified();
CHECKED_DBUS_LOCAL_CALL(prompt, coll->deleteCollection()); CHECKED_DBUS_LOCAL_CALL(prompt, coll->deleteCollection());
QVERIFY(prompt); QVERIFY(prompt);
QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); QSignalSpy spyPromptCompleted(&prompt->dbusAdaptor(), SIGNAL(Completed(bool, QDBusVariant)));
QVERIFY(spyPromptCompleted.isValid()); QVERIFY(spyPromptCompleted.isValid());
// prompt and click save // prompt and click save
@ -751,16 +743,60 @@ void TestGuiFdoSecrets::testCollectionDelete()
auto args = spyPromptCompleted.takeFirst(); auto args = spyPromptCompleted.takeFirst();
QCOMPARE(args.count(), 2); QCOMPARE(args.count(), 2);
QCOMPARE(args.at(0).toBool(), false); QCOMPARE(args.at(0).toBool(), false);
QCOMPARE(args.at(1).toString(), QStringLiteral("")); QCOMPARE(args.at(1).value<QDBusVariant>().variant().toString(), QStringLiteral(""));
QCOMPARE(spyCollectionDeleted.count(), 1); QCOMPARE(spyCollectionDeleted.count(), 1);
{ {
args = spyCollectionDeleted.takeFirst(); args = spyCollectionDeleted.takeFirst();
QCOMPARE(args.size(), 1); QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<Collection*>(), rawColl); QCOMPARE(args.at(0).value<QDBusObjectPath>(), collPath);
} }
} }
void TestGuiFdoSecrets::testHiddenFilename()
{
// when file name contains leading dot, all parts excepting the last should be used
// for collection name, and the registration should success
QVERIFY(m_dbFile->rename(QFileInfo(*m_dbFile).path() + "/.Name.kdbx"));
// reset is necessary to not hold database longer and cause connections
// not cleaned up when the database tab is closed.
m_db.reset();
QVERIFY(m_tabWidget->closeAllDatabaseTabs());
m_tabWidget->addDatabaseTab(m_dbFile->fileName(), false, "a");
m_dbWidget = m_tabWidget->currentDatabaseWidget();
m_db = m_dbWidget->database();
// enable the service
auto service = enableService();
QVERIFY(service);
// collection is properly registered
auto coll = getDefaultCollection(service);
QVERIFY(coll->objectPath().path() != "/");
QCOMPARE(coll->name(), QStringLiteral(".Name"));
}
void TestGuiFdoSecrets::testDuplicateName()
{
QTemporaryDir dir;
QVERIFY(dir.isValid());
// create another file under different path but with the same filename
QString anotherFile = dir.path() + "/" + QFileInfo(*m_dbFile).fileName();
m_dbFile->copy(anotherFile);
m_tabWidget->addDatabaseTab(anotherFile, false, "a");
auto service = enableService();
QVERIFY(service);
// when two databases have the same name, one of it will have part of its uuid suffixed
const auto pathNoSuffix = QStringLiteral("/org/freedesktop/secrets/collection/KeePassXC");
CHECKED_DBUS_LOCAL_CALL(colls, service->collections());
QCOMPARE(colls.size(), 2);
QCOMPARE(colls[0]->objectPath().path(), pathNoSuffix);
QVERIFY(colls[1]->objectPath().path() != pathNoSuffix);
}
void TestGuiFdoSecrets::testItemCreate() void TestGuiFdoSecrets::testItemCreate()
{ {
auto service = enableService(); auto service = enableService();
@ -770,6 +806,9 @@ void TestGuiFdoSecrets::testItemCreate()
auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm); auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
QVERIFY(sess); QVERIFY(sess);
QSignalSpy spyItemCreated(&coll->dbusAdaptor(), SIGNAL(ItemCreated(QDBusObjectPath)));
QVERIFY(spyItemCreated.isValid());
// create item // create item
StringStringMap attributes{ StringStringMap attributes{
{"application", "fdosecrets-test"}, {"application", "fdosecrets-test"},
@ -779,6 +818,14 @@ void TestGuiFdoSecrets::testItemCreate()
auto item = createItem(sess, coll, "abc", "Password", attributes, false); auto item = createItem(sess, coll, "abc", "Password", attributes, false);
QVERIFY(item); QVERIFY(item);
// signals
{
QCOMPARE(spyItemCreated.count(), 1);
auto args = spyItemCreated.takeFirst();
QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<QDBusObjectPath>(), item->objectPath());
}
// attributes // attributes
{ {
CHECKED_DBUS_LOCAL_CALL(actual, item->attributes()); CHECKED_DBUS_LOCAL_CALL(actual, item->attributes());
@ -843,28 +890,56 @@ void TestGuiFdoSecrets::testItemReplace()
QCOMPARE(unlocked.size(), 2); QCOMPARE(unlocked.size(), 2);
} }
QSignalSpy spyItemCreated(&coll->dbusAdaptor(), SIGNAL(ItemCreated(QDBusObjectPath)));
QVERIFY(spyItemCreated.isValid());
QSignalSpy spyItemChanged(&coll->dbusAdaptor(), SIGNAL(ItemChanged(QDBusObjectPath)));
QVERIFY(spyItemChanged.isValid());
{ {
// when replace, existing item with matching attr is updated // when replace, existing item with matching attr is updated
auto item3 = createItem(sess, coll, "abc3", "Password", attr2, true); auto item3 = createItem(sess, coll, "abc3", "Password", attr2, true);
QVERIFY(item3); QVERIFY(item3);
QCOMPARE(item2, item3); QCOMPARE(item2, item3);
COMPARE_DBUS_LOCAL_CALL(item3->label(), QStringLiteral("abc3")); COMPARE_DBUS_LOCAL_CALL(item3->label(), QStringLiteral("abc3"));
// there is still 2 entries // there are still 2 entries
QList<Item*> locked; QList<Item*> locked;
CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked)); CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked));
QCOMPARE(unlocked.size(), 2); QCOMPARE(unlocked.size(), 2);
QCOMPARE(spyItemCreated.count(), 0);
// there may be multiple changed signals, due to each item attribute is set separately
QVERIFY(!spyItemChanged.isEmpty());
for (const auto& args : spyItemChanged) {
QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<QDBusObjectPath>(), item3->objectPath());
}
} }
spyItemCreated.clear();
spyItemChanged.clear();
{ {
// when NOT replace, another entry is created // when NOT replace, another entry is created
auto item4 = createItem(sess, coll, "abc4", "Password", attr2, false); auto item4 = createItem(sess, coll, "abc4", "Password", attr2, false);
QVERIFY(item4); QVERIFY(item4);
COMPARE_DBUS_LOCAL_CALL(item2->label(), QStringLiteral("abc3")); COMPARE_DBUS_LOCAL_CALL(item2->label(), QStringLiteral("abc3"));
COMPARE_DBUS_LOCAL_CALL(item4->label(), QStringLiteral("abc4")); COMPARE_DBUS_LOCAL_CALL(item4->label(), QStringLiteral("abc4"));
// there is 3 entries // there are 3 entries
QList<Item*> locked; QList<Item*> locked;
CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked)); CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked));
QCOMPARE(unlocked.size(), 3); QCOMPARE(unlocked.size(), 3);
QCOMPARE(spyItemCreated.count(), 1);
{
auto args = spyItemCreated.takeFirst();
QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<QDBusObjectPath>(), item4->objectPath());
}
// there may be multiple changed signals, due to each item attribute is set separately
QVERIFY(!spyItemChanged.isEmpty());
for (const auto& args : spyItemChanged) {
QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<QDBusObjectPath>(), item4->objectPath());
}
} }
} }
@ -949,15 +1024,16 @@ void TestGuiFdoSecrets::testItemDelete()
QVERIFY(coll); QVERIFY(coll);
auto item = getFirstItem(coll); auto item = getFirstItem(coll);
QVERIFY(item); QVERIFY(item);
auto rawItem = item.data(); // save the path which will be gone after the deletion.
auto itemPath = item->objectPath();
QSignalSpy spyItemDeleted(coll, SIGNAL(itemDeleted(Item*))); QSignalSpy spyItemDeleted(&coll->dbusAdaptor(), SIGNAL(ItemDeleted(QDBusObjectPath)));
QVERIFY(spyItemDeleted.isValid()); QVERIFY(spyItemDeleted.isValid());
CHECKED_DBUS_LOCAL_CALL(prompt, item->deleteItem()); CHECKED_DBUS_LOCAL_CALL(prompt, item->deleteItem());
QVERIFY(prompt); QVERIFY(prompt);
QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); QSignalSpy spyPromptCompleted(&prompt->dbusAdaptor(), SIGNAL(Completed(bool, QDBusVariant)));
QVERIFY(spyPromptCompleted.isValid()); QVERIFY(spyPromptCompleted.isValid());
// prompt and click save // prompt and click save
@ -980,7 +1056,7 @@ void TestGuiFdoSecrets::testItemDelete()
{ {
args = spyItemDeleted.takeFirst(); args = spyItemDeleted.takeFirst();
QCOMPARE(args.size(), 1); QCOMPARE(args.size(), 1);
QCOMPARE(args.at(0).value<Item*>(), rawItem); QCOMPARE(args.at(0).value<QDBusObjectPath>(), itemPath);
} }
} }
@ -1047,7 +1123,7 @@ void TestGuiFdoSecrets::testExposeSubgroup()
QCOMPARE(exposedEntries, subgroup->entries()); QCOMPARE(exposedEntries, subgroup->entries());
} }
void TestGuiFdoSecrets::testModifiyingExposedGroup() void TestGuiFdoSecrets::testModifyingExposedGroup()
{ {
// test when exposed group is removed the collection is not exposed anymore // test when exposed group is removed the collection is not exposed anymore
auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking"); auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking");

View File

@ -82,7 +82,10 @@ private slots:
void testDefaultAliasAlwaysPresent(); void testDefaultAliasAlwaysPresent();
void testExposeSubgroup(); void testExposeSubgroup();
void testModifiyingExposedGroup(); void testModifyingExposedGroup();
void testHiddenFilename();
void testDuplicateName();
protected slots: protected slots:
void createDatabaseCallback(); void createDatabaseCallback();