mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-17 19:54:33 -05:00
FdoSecrets: ask to unlock the database when creating items
Also only emit databaseUnlockFinished after the database is unlocked Fix #7989
This commit is contained in:
parent
c5467c43bf
commit
e2bf537c4a
@ -210,7 +210,6 @@ namespace FdoSecrets
|
|||||||
m_collections.reserve(colls.size());
|
m_collections.reserve(colls.size());
|
||||||
for (const auto& coll : asConst(colls)) {
|
for (const auto& coll : asConst(colls)) {
|
||||||
m_collections << coll;
|
m_collections << coll;
|
||||||
connect(coll, &Collection::doneUnlockCollection, this, &UnlockPrompt::collectionUnlockFinished);
|
|
||||||
}
|
}
|
||||||
for (const auto& item : asConst(items)) {
|
for (const auto& item : asConst(items)) {
|
||||||
m_items[item->collection()] << item;
|
m_items[item->collection()] << item;
|
||||||
@ -234,6 +233,7 @@ namespace FdoSecrets
|
|||||||
bool waitingForCollections = false;
|
bool waitingForCollections = false;
|
||||||
for (const auto& c : asConst(m_collections)) {
|
for (const auto& c : asConst(m_collections)) {
|
||||||
if (c) {
|
if (c) {
|
||||||
|
connect(c, &Collection::doneUnlockCollection, this, &UnlockPrompt::collectionUnlockFinished);
|
||||||
// doUnlock is nonblocking, execution will continue in 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
|
// it is ok to call doUnlock multiple times before it's actually unlocked by the user
|
||||||
c->doUnlock();
|
c->doUnlock();
|
||||||
@ -242,7 +242,7 @@ namespace FdoSecrets
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unlock items directly if no collection unlocking pending
|
// unlock items directly if no collection unlocking pending
|
||||||
// o.w. do it in collectionUnlockFinished
|
// o.w. doing it in collectionUnlockFinished
|
||||||
if (!waitingForCollections) {
|
if (!waitingForCollections) {
|
||||||
unlockItems();
|
unlockItems();
|
||||||
}
|
}
|
||||||
@ -400,18 +400,49 @@ namespace FdoSecrets
|
|||||||
return PromptResult::accepted(false);
|
return PromptResult::accepted(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool locked = true;
|
|
||||||
auto ret = m_coll->locked(locked);
|
|
||||||
if (locked) {
|
|
||||||
// collection was locked
|
|
||||||
return DBusResult{DBUS_ERROR_SECRET_IS_LOCKED};
|
|
||||||
}
|
|
||||||
|
|
||||||
// save a weak reference to the client which may be used asynchronously later
|
// save a weak reference to the client which may be used asynchronously later
|
||||||
m_client = client;
|
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};
|
||||||
|
}
|
||||||
|
// 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);
|
||||||
|
if (ret.err()) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return PromptResult::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusResult CreateItemPrompt::createItem(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
|
// get itemPath to create item and
|
||||||
// try finding an existing item using attributes
|
// try to find an existing item using attributes
|
||||||
QString itemPath{};
|
QString itemPath{};
|
||||||
auto iterAttr = m_properties.find(DBUS_INTERFACE_SECRET_ITEM + ".Attributes");
|
auto iterAttr = m_properties.find(DBUS_INTERFACE_SECRET_ITEM + ".Attributes");
|
||||||
if (iterAttr != m_properties.end()) {
|
if (iterAttr != m_properties.end()) {
|
||||||
@ -425,7 +456,7 @@ namespace FdoSecrets
|
|||||||
|
|
||||||
// check existing item using attributes
|
// check existing item using attributes
|
||||||
QList<Item*> existing;
|
QList<Item*> existing;
|
||||||
ret = m_coll->searchItems(client, attributes, existing);
|
auto ret = m_coll->searchItems(client, attributes, existing);
|
||||||
if (ret.err()) {
|
if (ret.err()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -444,31 +475,29 @@ namespace FdoSecrets
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the item may be locked due to authorization
|
// the item may be locked due to authorization
|
||||||
ret = m_item->locked(client, locked);
|
// give the user a chance to unlock the item
|
||||||
|
auto prompt = PromptBase::Create<UnlockPrompt>(service(), QSet<Collection*>{}, QSet<Item*>{m_item});
|
||||||
|
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);
|
||||||
if (ret.err()) {
|
if (ret.err()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (locked) {
|
return {};
|
||||||
// give the user a chance to unlock the item
|
|
||||||
auto prompt = PromptBase::Create<UnlockPrompt>(service(), QSet<Collection*>{}, QSet<Item*>{m_item});
|
|
||||||
if (!prompt) {
|
|
||||||
return DBusResult{QDBusError::InternalError};
|
|
||||||
}
|
|
||||||
// postpone anything after the confirmation
|
|
||||||
connect(prompt, &PromptBase::completed, this, [this]() {
|
|
||||||
auto res = updateItem();
|
|
||||||
finishPrompt(res.err());
|
|
||||||
});
|
|
||||||
|
|
||||||
ret = prompt->prompt(client, windowId);
|
|
||||||
if (ret.err()) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return PromptResult::Pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the item can be updated directly
|
|
||||||
return updateItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DBusResult CreateItemPrompt::updateItem()
|
DBusResult CreateItemPrompt::updateItem()
|
||||||
@ -493,6 +522,9 @@ namespace FdoSecrets
|
|||||||
if (ret.err()) {
|
if (ret.err()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finally can finish the prompt without dismissing it
|
||||||
|
finishPrompt(false);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
} // namespace FdoSecrets
|
} // namespace FdoSecrets
|
||||||
|
@ -211,6 +211,7 @@ namespace FdoSecrets
|
|||||||
PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override;
|
PromptResult promptSync(const DBusClientPtr& client, const QString& windowId) override;
|
||||||
QVariant currentResult() const override;
|
QVariant currentResult() const override;
|
||||||
|
|
||||||
|
DBusResult createItem(const QString& windowId);
|
||||||
DBusResult updateItem();
|
DBusResult updateItem();
|
||||||
|
|
||||||
QPointer<Collection> m_coll;
|
QPointer<Collection> m_coll;
|
||||||
|
@ -523,18 +523,37 @@ namespace FdoSecrets
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark the db as being unlocked to prevent multiple dialogs for the same db
|
// check if the db is already being unlocked to prevent multiple dialogs for the same db
|
||||||
if (m_unlockingDb.contains(dbWidget)) {
|
if (m_unlockingDb.contains(dbWidget)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_unlockingDb.insert(dbWidget);
|
|
||||||
|
|
||||||
|
// 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);
|
m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Service::onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
|
void Service::onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
|
||||||
{
|
{
|
||||||
m_unlockingDb.remove(dbWidget);
|
if (!m_unlockingDb.contains(dbWidget)) {
|
||||||
emit doneUnlockDatabaseInDialog(accepted, dbWidget);
|
// not our concern
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accepted) {
|
||||||
|
emit doneUnlockDatabaseInDialog(false, dbWidget);
|
||||||
|
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);
|
||||||
|
disconnect(m_unlockingDb.take(dbWidget));
|
||||||
|
});
|
||||||
|
m_unlockingDb[dbWidget] = conn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace FdoSecrets
|
} // namespace FdoSecrets
|
||||||
|
@ -128,6 +128,7 @@ namespace FdoSecrets
|
|||||||
private slots:
|
private slots:
|
||||||
void ensureDefaultAlias();
|
void ensureDefaultAlias();
|
||||||
|
|
||||||
|
void onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
||||||
void onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal);
|
void onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal);
|
||||||
void monitorDatabaseExposedGroup(DatabaseWidget* dbWidget);
|
void monitorDatabaseExposedGroup(DatabaseWidget* dbWidget);
|
||||||
|
|
||||||
@ -136,8 +137,6 @@ namespace FdoSecrets
|
|||||||
|
|
||||||
void onCollectionAliasRemoved(const QString& alias);
|
void onCollectionAliasRemoved(const QString& alias);
|
||||||
|
|
||||||
void onDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initialize();
|
bool initialize();
|
||||||
|
|
||||||
@ -166,7 +165,8 @@ namespace FdoSecrets
|
|||||||
QList<Session*> m_sessions{};
|
QList<Session*> m_sessions{};
|
||||||
|
|
||||||
bool m_insideEnsureDefaultAlias{false};
|
bool m_insideEnsureDefaultAlias{false};
|
||||||
QSet<const DatabaseWidget*> m_unlockingDb{}; // list of db being unlocking
|
// 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
|
QSet<const DatabaseWidget*> m_lockingDb{}; // list of db being locking
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1094,6 +1094,31 @@ void TestGuiFdoSecrets::testItemCreate()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestGuiFdoSecrets::testItemCreateUnlock()
|
||||||
|
{
|
||||||
|
auto service = enableService();
|
||||||
|
VERIFY(service);
|
||||||
|
auto coll = getDefaultCollection(service);
|
||||||
|
VERIFY(coll);
|
||||||
|
auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
|
||||||
|
VERIFY(sess);
|
||||||
|
|
||||||
|
// NOTE: entries are no longer valid after locking
|
||||||
|
lockDatabaseInBackend();
|
||||||
|
|
||||||
|
QSignalSpy spyItemCreated(coll.data(), SIGNAL(ItemCreated(QDBusObjectPath)));
|
||||||
|
VERIFY(spyItemCreated.isValid());
|
||||||
|
|
||||||
|
// create item
|
||||||
|
StringStringMap attributes{
|
||||||
|
{"application", "fdosecrets-test"},
|
||||||
|
{"attr-i[bute]", "![some] -value*"},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto item = createItem(sess, coll, "abc", "Password", attributes, false, false, true);
|
||||||
|
VERIFY(item);
|
||||||
|
}
|
||||||
|
|
||||||
void TestGuiFdoSecrets::testItemChange()
|
void TestGuiFdoSecrets::testItemChange()
|
||||||
{
|
{
|
||||||
auto service = enableService();
|
auto service = enableService();
|
||||||
@ -1678,7 +1703,8 @@ QSharedPointer<ItemProxy> TestGuiFdoSecrets::createItem(const QSharedPointer<Ses
|
|||||||
const QString& pass,
|
const QString& pass,
|
||||||
const StringStringMap& attr,
|
const StringStringMap& attr,
|
||||||
bool replace,
|
bool replace,
|
||||||
bool expectPrompt)
|
bool expectPrompt,
|
||||||
|
bool expectUnlockPrompt)
|
||||||
{
|
{
|
||||||
VERIFY(sess);
|
VERIFY(sess);
|
||||||
VERIFY(coll);
|
VERIFY(coll);
|
||||||
@ -1703,6 +1729,10 @@ QSharedPointer<ItemProxy> TestGuiFdoSecrets::createItem(const QSharedPointer<Ses
|
|||||||
|
|
||||||
// drive the prompt
|
// drive the prompt
|
||||||
DBUS_VERIFY(prompt->Prompt(""));
|
DBUS_VERIFY(prompt->Prompt(""));
|
||||||
|
|
||||||
|
bool unlockFound = driveUnlockDialog();
|
||||||
|
COMPARE(unlockFound, expectUnlockPrompt);
|
||||||
|
|
||||||
bool found = driveAccessControlDialog();
|
bool found = driveAccessControlDialog();
|
||||||
COMPARE(found, expectPrompt);
|
COMPARE(found, expectPrompt);
|
||||||
|
|
||||||
@ -1800,6 +1830,9 @@ bool TestGuiFdoSecrets::driveUnlockDialog()
|
|||||||
processEvents();
|
processEvents();
|
||||||
auto dbOpenDlg = m_tabWidget->findChild<DatabaseOpenDialog*>();
|
auto dbOpenDlg = m_tabWidget->findChild<DatabaseOpenDialog*>();
|
||||||
VERIFY(dbOpenDlg);
|
VERIFY(dbOpenDlg);
|
||||||
|
if (!dbOpenDlg->isVisible()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto editPassword = dbOpenDlg->findChild<PasswordWidget*>("editPassword")->findChild<QLineEdit*>("passwordEdit");
|
auto editPassword = dbOpenDlg->findChild<PasswordWidget*>("editPassword")->findChild<QLineEdit*>("passwordEdit");
|
||||||
VERIFY(editPassword);
|
VERIFY(editPassword);
|
||||||
editPassword->setFocus();
|
editPassword->setFocus();
|
||||||
|
@ -84,6 +84,7 @@ private slots:
|
|||||||
void testCollectionChange();
|
void testCollectionChange();
|
||||||
|
|
||||||
void testItemCreate();
|
void testItemCreate();
|
||||||
|
void testItemCreateUnlock();
|
||||||
void testItemChange();
|
void testItemChange();
|
||||||
void testItemReplace();
|
void testItemReplace();
|
||||||
void testItemReplaceExistingLocked();
|
void testItemReplaceExistingLocked();
|
||||||
@ -122,7 +123,8 @@ private:
|
|||||||
const QString& pass,
|
const QString& pass,
|
||||||
const FdoSecrets::wire::StringStringMap& attr,
|
const FdoSecrets::wire::StringStringMap& attr,
|
||||||
bool replace,
|
bool replace,
|
||||||
bool expectPrompt = false);
|
bool expectPrompt = false,
|
||||||
|
bool expectUnlockPrompt = false);
|
||||||
FdoSecrets::wire::Secret
|
FdoSecrets::wire::Secret
|
||||||
encryptPassword(QByteArray value, QString contentType, const QSharedPointer<SessionProxy>& sess);
|
encryptPassword(QByteArray value, QString contentType, const QSharedPointer<SessionProxy>& sess);
|
||||||
template <typename Proxy> QSharedPointer<Proxy> getProxy(const QDBusObjectPath& path) const
|
template <typename Proxy> QSharedPointer<Proxy> getProxy(const QDBusObjectPath& path) const
|
||||||
|
Loading…
Reference in New Issue
Block a user