From ef6d8f113876d794803583962e74972aa9986bd5 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sun, 24 Apr 2022 10:39:12 +0300 Subject: [PATCH] Browser: Asynchronous Access Confirm dialog --- src/browser/BrowserAccessControlDialog.cpp | 53 ++- src/browser/BrowserAccessControlDialog.h | 21 +- src/browser/BrowserAccessControlDialog.ui | 2 +- src/browser/BrowserAction.cpp | 39 +- src/browser/BrowserAction.h | 2 +- src/browser/BrowserService.cpp | 403 +++++++++++++-------- src/browser/BrowserService.h | 66 +++- 7 files changed, 389 insertions(+), 197 deletions(-) diff --git a/src/browser/BrowserAccessControlDialog.cpp b/src/browser/BrowserAccessControlDialog.cpp index 85c940254..f55ea8b83 100644 --- a/src/browser/BrowserAccessControlDialog.cpp +++ b/src/browser/BrowserAccessControlDialog.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2013 Francois Ferrand - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2022 KeePassXC Team * * 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 @@ -21,25 +21,41 @@ #include #include "core/Entry.h" +#include BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent) : QDialog(parent) , m_ui(new Ui::BrowserAccessControlDialog()) + , m_entriesAccepted(false) { setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); m_ui->setupUi(this); - connect(m_ui->allowButton, SIGNAL(clicked()), SLOT(accept())); - connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject())); + connect(m_ui->allowButton, SIGNAL(clicked()), SLOT(acceptSelections())); + connect(m_ui->denyButton, SIGNAL(clicked()), SLOT(rejectSelections())); + connect(this, SIGNAL(rejected()), this, SIGNAL(closed())); } BrowserAccessControlDialog::~BrowserAccessControlDialog() { } -void BrowserAccessControlDialog::setItems(const QList& items, const QString& urlString, bool httpAuth) +void BrowserAccessControlDialog::closeEvent(QCloseEvent* event) { + // Emits closed signal when clicking X from title bar + emit closed(); + event->accept(); +} + +void BrowserAccessControlDialog::setItems(const QList& entriesToConfirm, + const QList& allowedEntries, + const QString& urlString, + bool httpAuth) +{ + m_entriesToConfirm = entriesToConfirm; + m_allowedEntries = allowedEntries; + QUrl url(urlString); m_ui->siteLabel->setText(m_ui->siteLabel->text().arg( url.toDisplayString(QUrl::RemoveUserInfo | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment))); @@ -47,11 +63,11 @@ void BrowserAccessControlDialog::setItems(const QList& items, const QStr m_ui->rememberDecisionCheckBox->setVisible(!httpAuth); m_ui->rememberDecisionCheckBox->setChecked(false); - m_ui->itemsTable->setRowCount(items.count()); + m_ui->itemsTable->setRowCount(entriesToConfirm.count()); m_ui->itemsTable->setColumnCount(2); int row = 0; - for (const auto& entry : items) { + for (const auto& entry : entriesToConfirm) { auto item = new QTableWidgetItem(); item->setText(entry->title() + " - " + entry->username()); item->setData(Qt::UserRole, row); @@ -61,11 +77,13 @@ void BrowserAccessControlDialog::setItems(const QList& items, const QStr auto disableButton = new QPushButton(tr("Disable for this site")); disableButton->setAutoDefault(false); + connect(disableButton, &QAbstractButton::pressed, [&, item] { emit disableAccess(item); m_ui->itemsTable->removeRow(item->row()); + if (m_ui->itemsTable->rowCount() == 0) { - reject(); + emit closed(); } }); m_ui->itemsTable->setCellWidget(row, 1, disableButton); @@ -84,6 +102,11 @@ bool BrowserAccessControlDialog::remember() const return m_ui->rememberDecisionCheckBox->isChecked(); } +bool BrowserAccessControlDialog::entriesAccepted() const +{ + return m_entriesAccepted; +} + QList BrowserAccessControlDialog::getSelectedEntries() const { QList selected; @@ -107,3 +130,19 @@ QList BrowserAccessControlDialog::getNonSelectedEntries() con } return notSelected; } + +void BrowserAccessControlDialog::acceptSelections() +{ + auto selectedEntries = getSelectedEntries(); + + m_entriesAccepted = true; + emit acceptEntries(selectedEntries, m_entriesToConfirm, m_allowedEntries); + emit closed(); +} + +void BrowserAccessControlDialog::rejectSelections() +{ + auto rejectedEntries = getNonSelectedEntries(); + emit rejectEntries(rejectedEntries, m_entriesToConfirm); + emit closed(); +} diff --git a/src/browser/BrowserAccessControlDialog.h b/src/browser/BrowserAccessControlDialog.h index 0bf14ecc4..c41c6c77e 100644 --- a/src/browser/BrowserAccessControlDialog.h +++ b/src/browser/BrowserAccessControlDialog.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2013 Francois Ferrand - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2022 KeePassXC Team * * 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 @@ -37,17 +37,34 @@ public: explicit BrowserAccessControlDialog(QWidget* parent = nullptr); ~BrowserAccessControlDialog() override; - void setItems(const QList& items, const QString& urlString, bool httpAuth); + void setItems(const QList& entriesToConfirm, + const QList& allowedEntries, + const QString& urlString, + bool httpAuth); bool remember() const; + bool entriesAccepted() const; QList getSelectedEntries() const; QList getNonSelectedEntries() const; signals: void disableAccess(QTableWidgetItem* item); + void acceptEntries(QList items, QList entriesToConfirm, QList allowedEntries); + void rejectEntries(QList items, QList entriesToConfirm); + void closed(); + +public slots: + void acceptSelections(); + void rejectSelections(); + +private: + void closeEvent(QCloseEvent* event) override; private: QScopedPointer m_ui; + QList m_entriesToConfirm; + QList m_allowedEntries; + bool m_entriesAccepted; }; #endif // BROWSERACCESSCONTROLDIALOG_H diff --git a/src/browser/BrowserAccessControlDialog.ui b/src/browser/BrowserAccessControlDialog.ui index bed26e6ab..4224c1633 100755 --- a/src/browser/BrowserAccessControlDialog.ui +++ b/src/browser/BrowserAccessControlDialog.ui @@ -97,7 +97,7 @@ - + Deny All diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 70a0fb588..1d789d470 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -76,7 +76,7 @@ QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject& } else if (action.compare("test-associate") == 0) { return handleTestAssociate(json, action); } else if (action.compare("get-logins") == 0) { - return handleGetLogins(json, action); + return handleGetLogins(socket, json, action); } else if (action.compare("generate-password") == 0) { return handleGeneratePassword(socket, json, action); } else if (action.compare("set-login") == 0) { @@ -231,10 +231,11 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS return buildResponse(action, message, newNonce); } -QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action) +QJsonObject BrowserAction::handleGetLogins(QLocalSocket* socket, const QJsonObject& json, const QString& action) { const QString hash = browserService()->getDatabaseHash(); const QString nonce = json.value("nonce").toString(); + const auto incrementedNonce = browserMessageBuilder()->incrementNonce(nonce); const QString encrypted = json.value("message").toString(); if (!m_associated) { @@ -263,21 +264,31 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin const QString formUrl = decrypted.value("submitUrl").toString(); const QString auth = decrypted.value("httpAuth").toString(); const bool httpAuth = auth.compare(TRUE_STR) == 0; - const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth); + auto requestId = decrypted.value("requestID").toString(); - if (users.isEmpty()) { - return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND); + if (browserService()->isAccessConfirmRequested()) { + auto errorReply = getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED); + + if (!requestId.isEmpty()) { + errorReply["requestID"] = requestId; + } + + return errorReply; } - const QString newNonce = browserMessageBuilder()->incrementNonce(nonce); - - QJsonObject message = browserMessageBuilder()->buildMessage(newNonce); - message["count"] = users.count(); - message["entries"] = users; - message["hash"] = hash; - message["id"] = id; - - return buildResponse(action, message, newNonce); + browserService()->findEntries(socket, + incrementedNonce, + m_clientPublicKey, + m_secretKey, + id, + hash, + requestId, + siteUrl, + formUrl, + "", + keyList, + httpAuth); + return QJsonObject(); } QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action) diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index 49c66b644..6f0b9a6cf 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -37,7 +37,7 @@ private: QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action); QJsonObject handleAssociate(const QJsonObject& json, const QString& action); QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action); - QJsonObject handleGetLogins(const QJsonObject& json, const QString& action); + QJsonObject handleGetLogins(QLocalSocket* socket, const QJsonObject& json, const QString& action); QJsonObject handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action); QJsonObject handleSetLogin(const QJsonObject& json, const QString& action); QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action); diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 38601de5b..cee32bb08 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -18,7 +18,6 @@ */ #include "BrowserService.h" -#include "BrowserAccessControlDialog.h" #include "BrowserAction.h" #include "BrowserEntryConfig.h" #include "BrowserEntrySaveDialog.h" @@ -65,9 +64,9 @@ Q_GLOBAL_STATIC(BrowserService, s_browserService); BrowserService::BrowserService() : QObject() , m_browserHost(new BrowserHost) - , m_dialogActive(false) , m_bringToFrontRequested(false) , m_passwordGeneratorRequested(false) + , m_accessConfirmRequested(false) , m_prevWindowState(WindowState::Normal) , m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224")) { @@ -315,6 +314,219 @@ QString BrowserService::getCurrentTotp(const QString& uuid) return {}; } +void BrowserService::findEntries(QLocalSocket* socket, + const QString& nonce, + const QString& publicKey, + const QString& secretKey, + const QString& dbid, + const QString& hash, + const QString& requestId, + const QString& siteUrl, + const QString& formUrl, + const QString& realm, + const StringPairList& keyList, + const bool httpAuth) +{ + Q_UNUSED(dbid); + const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess(); + const bool ignoreHttpAuth = browserSettings()->httpAuthPermission(); + const QString siteHost = QUrl(siteUrl).host(); + const QString formHost = QUrl(formUrl).host(); + + // Check entries for authorization + QList entriesToConfirm; + QList allowedEntries; + for (auto* entry : searchEntries(siteUrl, formUrl, keyList)) { + auto entryCustomData = entry->customData(); + + if (!httpAuth + && ((entryCustomData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH) + && entryCustomData->value(BrowserService::OPTION_ONLY_HTTP_AUTH) == TRUE_STR) + || entry->group()->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Enable)) { + continue; + } + + if (httpAuth + && ((entryCustomData->contains(BrowserService::OPTION_NOT_HTTP_AUTH) + && entryCustomData->value(BrowserService::OPTION_NOT_HTTP_AUTH) == TRUE_STR) + || entry->group()->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Enable)) { + continue; + } + + // HTTP Basic Auth always needs a confirmation + if (!ignoreHttpAuth && httpAuth) { + entriesToConfirm.append(entry); + continue; + } + + switch (checkAccess(entry, siteHost, formHost, realm)) { + case Denied: + continue; + + case Unknown: + if (alwaysAllowAccess) { + allowedEntries.append(entry); + } else { + entriesToConfirm.append(entry); + } + break; + + case Allowed: + allowedEntries.append(entry); + break; + } + } + + if (entriesToConfirm.isEmpty()) { + sendCredentialsToClient(allowedEntries, socket, nonce, publicKey, secretKey, hash, dbid, siteUrl, formUrl); + return; + } + + confirmEntries(socket, + nonce, + publicKey, + secretKey, + dbid, + hash, + requestId, + allowedEntries, + entriesToConfirm, + siteUrl, + siteHost, + formHost, + realm, + httpAuth); +} + +void BrowserService::confirmEntries(QLocalSocket* socket, + const QString& incrementedNonce, + const QString& publicKey, + const QString& secretKey, + const QString& id, + const QString& hash, + const QString& requestId, + QList& allowedEntries, + QList& entriesToConfirm, + const QString& siteUrl, + const QString& siteHost, + const QString& formUrl, + const QString& realm, + const bool httpAuth) +{ + if (entriesToConfirm.isEmpty() || m_accessConfirmRequested) { + return; + } + + if (!m_accessControlDialog) { + + m_accessControlDialog.reset(new BrowserAccessControlDialog()); + + connect( + m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), m_accessControlDialog.data(), SIGNAL(closed())); + + connect(m_accessControlDialog.data(), + &BrowserAccessControlDialog::disableAccess, + m_accessControlDialog.data(), + [=](QTableWidgetItem* item) { + auto entry = entriesToConfirm[item->row()]; + denyEntry(entry, siteHost, formUrl, realm); + }); + + connect(m_accessControlDialog.data(), &BrowserAccessControlDialog::closed, m_accessControlDialog.data(), [=] { + if (!m_accessControlDialog->entriesAccepted()) { + auto errorMessage = + browserMessageBuilder()->getErrorReply("get-logins", ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED); + errorMessage["requestID"] = requestId; + m_browserHost->sendClientMessage(socket, errorMessage); + } + + m_accessControlDialog.reset(); + hideWindow(); + m_accessConfirmRequested = false; + }); + + connect(m_accessControlDialog.data(), + &BrowserAccessControlDialog::acceptEntries, + m_accessControlDialog.data(), + [=](QList items, QList entries, QList allowed) { + QList selectedEntries; + + for (auto item : items) { + auto entry = entries[item->row()]; + if (m_accessControlDialog->remember()) { + allowEntry(entry, siteHost, formUrl, realm); + } + + selectedEntries.append(entry); + } + + hideWindow(); + + if (!selectedEntries.isEmpty()) { + allowed.append(selectedEntries); + } + + if (allowed.isEmpty()) { + return; + } + + // Ensure that database is not locked when the popup was visible + if (!isDatabaseOpened()) { + return; + } + + sendCredentialsToClient( + allowed, socket, incrementedNonce, publicKey, secretKey, hash, id, siteUrl, formUrl); + }); + + connect(m_accessControlDialog.data(), + &BrowserAccessControlDialog::rejectEntries, + m_accessControlDialog.data(), + [=](QList items, QList entries) { + Q_UNUSED(items); // We might need this later if single entries can be denied from the list + for (auto entry : entries) { + if (m_accessControlDialog->remember()) { + denyEntry(entry, siteHost, formUrl, realm); + } + } + }); + + m_accessControlDialog->setItems(entriesToConfirm, allowedEntries, siteUrl, httpAuth); + m_accessControlDialog->show(); + } + + m_accessConfirmRequested = true; + updateWindowState(); +} + +void BrowserService::sendCredentialsToClient(QList& allowedEntries, + QLocalSocket* socket, + const QString& incrementedNonce, + const QString& publicKey, + const QString& secretKey, + const QString& hash, + const QString& id, + const QString siteUrl, + const QString& formUrl) +{ + allowedEntries = sortEntries(allowedEntries, siteUrl, formUrl); + + QJsonArray result; + for (auto* entry : allowedEntries) { + result.append(prepareEntry(entry)); + } + + QJsonObject message = browserMessageBuilder()->buildMessage(incrementedNonce); + message["count"] = result.count(); + message["entries"] = result; + message["hash"] = hash; + message["id"] = id; + + m_browserHost->sendClientMessage( + socket, browserMessageBuilder()->buildResponse("get-logins", message, incrementedNonce, publicKey, secretKey)); + hideWindow(); +} + void BrowserService::showPasswordGenerator(QLocalSocket* socket, const QString& incrementedNonce, const QString& publicKey, @@ -341,9 +553,11 @@ void BrowserService::showPasswordGenerator(QLocalSocket* socket, [=](const QString& password) { QJsonObject message = browserMessageBuilder()->buildMessage(incrementedNonce); message["password"] = password; - sendPassword(socket, - browserMessageBuilder()->buildResponse( - "generate-password", message, incrementedNonce, publicKey, secretKey)); + m_browserHost->sendClientMessage( + socket, + browserMessageBuilder()->buildResponse( + "generate-password", message, incrementedNonce, publicKey, secretKey)); + hideWindow(); }); } @@ -353,17 +567,16 @@ void BrowserService::showPasswordGenerator(QLocalSocket* socket, m_passwordGenerator->activateWindow(); } -void BrowserService::sendPassword(QLocalSocket* socket, const QJsonObject& message) -{ - m_browserHost->sendClientMessage(socket, message); - hideWindow(); -} - bool BrowserService::isPasswordGeneratorRequested() const { return m_passwordGeneratorRequested; } +bool BrowserService::isAccessConfirmRequested() const +{ + return m_accessConfirmRequested; +} + QString BrowserService::storeKey(const QString& key) { auto db = getDatabase(); @@ -426,91 +639,6 @@ QString BrowserService::getKey(const QString& id) return db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + id); } -QJsonArray BrowserService::findMatchingEntries(const QString& dbid, - const QString& siteUrl, - const QString& formUrl, - const QString& realm, - const StringPairList& keyList, - const bool httpAuth) -{ - Q_UNUSED(dbid); - const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess(); - const bool ignoreHttpAuth = browserSettings()->httpAuthPermission(); - const QString siteHost = QUrl(siteUrl).host(); - const QString formHost = QUrl(formUrl).host(); - - // Check entries for authorization - QList pwEntriesToConfirm; - QList pwEntries; - for (auto* entry : searchEntries(siteUrl, formUrl, keyList)) { - auto entryCustomData = entry->customData(); - - if (!httpAuth - && ((entryCustomData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH) - && entryCustomData->value(BrowserService::OPTION_ONLY_HTTP_AUTH) == TRUE_STR) - || entry->group()->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Enable)) { - continue; - } - - if (httpAuth - && ((entryCustomData->contains(BrowserService::OPTION_NOT_HTTP_AUTH) - && entryCustomData->value(BrowserService::OPTION_NOT_HTTP_AUTH) == TRUE_STR) - || entry->group()->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Enable)) { - continue; - } - - // HTTP Basic Auth always needs a confirmation - if (!ignoreHttpAuth && httpAuth) { - pwEntriesToConfirm.append(entry); - continue; - } - - switch (checkAccess(entry, siteHost, formHost, realm)) { - case Denied: - continue; - - case Unknown: - if (alwaysAllowAccess) { - pwEntries.append(entry); - } else { - pwEntriesToConfirm.append(entry); - } - break; - - case Allowed: - pwEntries.append(entry); - break; - } - } - - // Confirm entries - QList selectedEntriesToConfirm = - confirmEntries(pwEntriesToConfirm, siteUrl, siteHost, formHost, realm, httpAuth); - if (!selectedEntriesToConfirm.isEmpty()) { - pwEntries.append(selectedEntriesToConfirm); - } - - if (pwEntries.isEmpty()) { - return {}; - } - - // Ensure that database is not locked when the popup was visible - if (!isDatabaseOpened()) { - return {}; - } - - // Sort results - pwEntries = sortEntries(pwEntries, siteUrl, formUrl); - - // Fill the list - QJsonArray result; - for (auto* entry : pwEntries) { - result.append(prepareEntry(entry)); - } - - return result; -} - void BrowserService::addEntry(const QString& dbid, const QString& login, const QString& password, @@ -822,13 +950,12 @@ void BrowserService::requestGlobalAutoType(const QString& search) emit osUtils->globalShortcutTriggered("autotype", search); } -QList -BrowserService::sortEntries(QList& pwEntries, const QString& siteUrlStr, const QString& formUrlStr) +QList BrowserService::sortEntries(QList& pwEntries, const QString& siteUrl, const QString& formUrl) { // Build map of prioritized entries QMultiMap priorities; for (auto* entry : pwEntries) { - priorities.insert(sortPriority(getEntryURLs(entry), siteUrlStr, formUrlStr), entry); + priorities.insert(sortPriority(getEntryURLs(entry), siteUrl, formUrl), entry); } auto keys = priorities.uniqueKeys(); @@ -847,66 +974,38 @@ BrowserService::sortEntries(QList& pwEntries, const QString& siteUrlStr, return results; } -QList BrowserService::confirmEntries(QList& pwEntriesToConfirm, - const QString& siteUrl, - const QString& siteHost, - const QString& formUrl, - const QString& realm, - const bool httpAuth) +void BrowserService::allowEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm) { - if (pwEntriesToConfirm.isEmpty() || m_dialogActive) { - return {}; + BrowserEntryConfig config; + config.load(entry); + config.allow(siteHost); + + if (!formUrl.isEmpty() && siteHost != formUrl) { + config.allow(formUrl); } - m_dialogActive = true; - updateWindowState(); - BrowserAccessControlDialog accessControlDialog; - - connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &accessControlDialog, SLOT(reject())); - - connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) { - auto entry = pwEntriesToConfirm[item->row()]; - BrowserEntryConfig config; - config.load(entry); - config.deny(siteHost); - if (!formUrl.isEmpty() && siteHost != formUrl) { - config.deny(formUrl); - } - if (!realm.isEmpty()) { - config.setRealm(realm); - } - config.save(entry); - }); - - accessControlDialog.setItems(pwEntriesToConfirm, siteUrl, httpAuth); - - QList allowedEntries; - if (accessControlDialog.exec() == QDialog::Accepted) { - const auto selectedEntries = accessControlDialog.getSelectedEntries(); - for (auto item : accessControlDialog.getSelectedEntries()) { - auto entry = pwEntriesToConfirm[item->row()]; - if (accessControlDialog.remember()) { - BrowserEntryConfig config; - config.load(entry); - config.allow(siteHost); - if (!formUrl.isEmpty() && siteHost != formUrl) { - config.allow(formUrl); - } - if (!realm.isEmpty()) { - config.setRealm(realm); - } - config.save(entry); - } - allowedEntries.append(entry); - } + if (!realm.isEmpty()) { + config.setRealm(realm); } - // Re-hide the application if it wasn't visible before - hideWindow(); + config.save(entry); +} - m_dialogActive = false; +void BrowserService::denyEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm) +{ + BrowserEntryConfig config; + config.load(entry); + config.deny(siteHost); - return allowedEntries; + if (!formUrl.isEmpty() && siteHost != formUrl) { + config.deny(formUrl); + } + + if (!realm.isEmpty()) { + config.setRealm(realm); + } + + config.save(entry); } QJsonObject BrowserService::prepareEntry(const Entry* entry) diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index 1ab3342fb..27b977257 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2013 Francois Ferrand * Copyright (C) 2017 Sami Vänttinen - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2022 KeePassXC Team * * 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 @@ -20,6 +20,7 @@ #ifndef BROWSERSERVICE_H #define BROWSERSERVICE_H +#include "BrowserAccessControlDialog.h" #include "core/Entry.h" #include "gui/PasswordGeneratorWidget.h" @@ -64,12 +65,13 @@ public: const QString& secretKey); void sendPassword(QLocalSocket* socket, const QJsonObject& message); bool isPasswordGeneratorRequested() const; + bool isAccessConfirmRequested() const; void addEntry(const QString& dbid, const QString& login, const QString& password, - const QString& siteUrlStr, - const QString& formUrlStr, + const QString& siteUrl, + const QString& formUrl, const QString& realm, const QString& group, const QString& groupUuid, @@ -79,17 +81,21 @@ public: const QString& uuid, const QString& login, const QString& password, - const QString& siteUrlStr, - const QString& formUrlStr); + const QString& siteUrl, + const QString& formUrl); bool deleteEntry(const QString& uuid); - - QJsonArray findMatchingEntries(const QString& dbid, - const QString& siteUrlStr, - const QString& formUrlStr, - const QString& realm, - const StringPairList& keyList, - const bool httpAuth = false); - + void findEntries(QLocalSocket* socket, + const QString& nonce, + const QString& publicKey, + const QString& secretKey, + const QString& dbid, + const QString& hash, + const QString& requestId, + const QString& siteUrl, + const QString& formUrl, + const QString& realm, + const StringPairList& keyList, + const bool httpAuth = false); void requestGlobalAutoType(const QString& search); static void convertAttributesToCustomData(QSharedPointer db); @@ -132,13 +138,32 @@ private: QList searchEntries(const QSharedPointer& db, const QString& siteUrl, const QString& formUrl); QList searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList); QList sortEntries(QList& pwEntries, const QString& siteUrl, const QString& formUrl); - QList confirmEntries(QList& pwEntriesToConfirm, - const QString& siteUrl, - const QString& siteHost, - const QString& formUrl, - const QString& realm, - const bool httpAuth); + void confirmEntries(QLocalSocket* socket, + const QString& incrementedNonce, + const QString& publicKey, + const QString& secretKey, + const QString& id, + const QString& hash, + const QString& requestId, + QList& allowedEntries, + QList& entriesToConfirm, + const QString& siteUrl, + const QString& siteHost, + const QString& formUrl, + const QString& realm, + const bool httpAuth); + void sendCredentialsToClient(QList& allowedEntries, + QLocalSocket* socket, + const QString& incrementedNonce, + const QString& publicKey, + const QString& secretKey, + const QString& hash, + const QString& id, + const QString siteUrl, + const QString& formUrl); QJsonObject prepareEntry(const Entry* entry); + void allowEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm); + void denyEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm); QJsonArray getChildrenFromGroup(Group* group); Access checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm); Group* getDefaultEntryGroup(const QSharedPointer& selectedDb = {}); @@ -171,14 +196,15 @@ private: QPointer m_browserHost; QHash> m_browserClients; - bool m_dialogActive; bool m_bringToFrontRequested; bool m_passwordGeneratorRequested; + bool m_accessConfirmRequested; WindowState m_prevWindowState; QUuid m_keepassBrowserUUID; QPointer m_currentDatabaseWidget; QScopedPointer m_passwordGenerator; + QScopedPointer m_accessControlDialog; Q_DISABLE_COPY(BrowserService);