FdoSecrets: add smarter handling of database unlock requests

This commit implements the following logic:
* If there're already unlocked collections, just use those,
* otherwise, show the unlock dialog until there's an unlocked and exposed collection.

* Fixes #7574
This commit is contained in:
Aetf 2022-05-07 03:38:10 -04:00 committed by Jonathan White
parent 8711d31f24
commit 07755c324a
6 changed files with 151 additions and 32 deletions

View file

@ -210,8 +210,7 @@ namespace FdoSecrets
return {};
}
DBusResult
Collection::searchItems(const DBusClientPtr& client, const StringStringMap& attributes, QList<Item*>& items)
DBusResult Collection::searchItems(const DBusClientPtr&, const StringStringMap& attributes, QList<Item*>& items)
{
items.clear();
@ -220,24 +219,6 @@ namespace FdoSecrets
return ret;
}
if (backendLocked() && settings()->unlockBeforeSearch()) {
// do a blocking unlock prompt first.
// while technically not correct, this should improve compatibility.
// see issue #4443
auto prompt = PromptBase::Create<UnlockPrompt>(service(), QSet<Collection*>{this}, QSet<Item*>{});
if (!prompt) {
return QDBusError::InternalError;
}
// we don't know the windowId to show the prompt correctly,
// but the default of showing over the KPXC main window should be good enough
ret = prompt->prompt(client, "");
// blocking wait
QEventLoop loop;
connect(prompt, &PromptBase::completed, &loop, &QEventLoop::quit);
loop.exec();
}
// check again because the prompt may be cancelled
if (backendLocked()) {
// searchItems should work, whether `this` is locked or not.
// however, we can't search items the same way as in gnome-keying,

View file

@ -184,6 +184,30 @@ namespace FdoSecrets
return {};
}
DBusResult Service::unlockedCollections(QList<Collection*>& unlocked) const
{
auto ret = collections(unlocked);
if (ret.err()) {
return ret;
}
// filter out locked collections
auto it = unlocked.begin();
while (it != unlocked.end()) {
bool isLocked = true;
ret = (*it)->locked(isLocked);
if (ret.err()) {
return ret;
}
if (isLocked) {
it = unlocked.erase(it);
} else {
++it;
}
}
return {};
}
DBusResult Service::openSession(const DBusClientPtr& client,
const QString& algorithm,
const QVariant& input,
@ -242,15 +266,43 @@ namespace FdoSecrets
DBusResult Service::searchItems(const DBusClientPtr& client,
const StringStringMap& attributes,
QList<Item*>& unlocked,
QList<Item*>& locked) const
QList<Item*>& locked)
{
QList<Collection*> colls;
auto ret = collections(colls);
// we can only search unlocked collections
QList<Collection*> unlockedColls;
auto ret = unlockedCollections(unlockedColls);
if (ret.err()) {
return ret;
}
for (const auto& coll : asConst(colls)) {
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();
if (!wasAccepted) {
// user cancelled, do not proceed
qWarning() << "user cancelled";
return {};
}
// need to recompute this because collections may disappear while in event loop
ret = unlockedCollections(unlockedColls);
if (ret.err()) {
return ret;
}
}
for (const auto& coll : asConst(unlockedColls)) {
QList<Item*> items;
ret = coll->searchItems(client, attributes, items);
if (ret.err()) {
@ -524,7 +576,7 @@ namespace FdoSecrets
}
// check if the db is already being unlocked to prevent multiple dialogs for the same db
if (m_unlockingDb.contains(dbWidget)) {
if (m_unlockingAnyDatabase || m_unlockingDb.contains(dbWidget)) {
return;
}
@ -536,21 +588,33 @@ namespace FdoSecrets
m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None);
}
void Service::doUnlockAnyDatabaseInDialog()
{
if (m_unlockingAnyDatabase || !m_unlockingDb.isEmpty()) {
return;
}
m_unlockingAnyDatabase = true;
m_databases->unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::None);
}
void Service::onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
{
if (!m_unlockingDb.contains(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;

View file

@ -65,7 +65,7 @@ namespace FdoSecrets
Q_INVOKABLE DBusResult searchItems(const DBusClientPtr& client,
const StringStringMap& attributes,
QList<Item*>& unlocked,
QList<Item*>& locked) const;
QList<Item*>& locked);
Q_INVOKABLE DBusResult unlock(const DBusClientPtr& client,
const QList<DBusObject*>& objects,
@ -125,6 +125,12 @@ namespace FdoSecrets
*/
void doUnlockDatabaseInDialog(DatabaseWidget* dbWidget);
/**
* Async, connect to signal doneUnlockDatabaseInDialog for finish notification
* @param dbWidget
*/
void doUnlockAnyDatabaseInDialog();
private slots:
void ensureDefaultAlias();
@ -154,6 +160,8 @@ namespace FdoSecrets
*/
Collection* findCollection(const DatabaseWidget* db) const;
DBusResult unlockedCollections(QList<Collection*>& unlocked) const;
private:
FdoSecretsPlugin* m_plugin{nullptr};
QPointer<DatabaseTabWidget> m_databases{};
@ -165,6 +173,7 @@ namespace FdoSecrets
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

View file

@ -75,6 +75,7 @@ public slots:
void closeDatabaseFromSender();
void unlockDatabaseInDialog(DatabaseWidget* dbWidget, DatabaseOpenDialog::Intent intent);
void unlockDatabaseInDialog(DatabaseWidget* dbWidget, DatabaseOpenDialog::Intent intent, const QString& filePath);
void unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent);
void relockPendingDatabase();
void showDatabaseSecurity();
@ -106,7 +107,6 @@ private:
QSharedPointer<Database> execNewDatabaseWizard();
void updateLastDatabases(const QString& filename);
bool warnOnExport();
void unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent);
void displayUnlockDialog();
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;