Browser: Asynchronous Access Confirm dialog

This commit is contained in:
varjolintu 2022-04-24 10:39:12 +03:00 committed by Jonathan White
parent 612c1098ea
commit ef6d8f1138
7 changed files with 389 additions and 197 deletions

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2013 Francois Ferrand * Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -21,25 +21,41 @@
#include <QUrl> #include <QUrl>
#include "core/Entry.h" #include "core/Entry.h"
#include <QCloseEvent>
BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent) BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent)
: QDialog(parent) : QDialog(parent)
, m_ui(new Ui::BrowserAccessControlDialog()) , m_ui(new Ui::BrowserAccessControlDialog())
, m_entriesAccepted(false)
{ {
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
m_ui->setupUi(this); m_ui->setupUi(this);
connect(m_ui->allowButton, SIGNAL(clicked()), SLOT(accept())); connect(m_ui->allowButton, SIGNAL(clicked()), SLOT(acceptSelections()));
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject())); connect(m_ui->denyButton, SIGNAL(clicked()), SLOT(rejectSelections()));
connect(this, SIGNAL(rejected()), this, SIGNAL(closed()));
} }
BrowserAccessControlDialog::~BrowserAccessControlDialog() BrowserAccessControlDialog::~BrowserAccessControlDialog()
{ {
} }
void BrowserAccessControlDialog::setItems(const QList<Entry*>& 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<Entry*>& entriesToConfirm,
const QList<Entry*>& allowedEntries,
const QString& urlString,
bool httpAuth)
{
m_entriesToConfirm = entriesToConfirm;
m_allowedEntries = allowedEntries;
QUrl url(urlString); QUrl url(urlString);
m_ui->siteLabel->setText(m_ui->siteLabel->text().arg( m_ui->siteLabel->setText(m_ui->siteLabel->text().arg(
url.toDisplayString(QUrl::RemoveUserInfo | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment))); url.toDisplayString(QUrl::RemoveUserInfo | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment)));
@ -47,11 +63,11 @@ void BrowserAccessControlDialog::setItems(const QList<Entry*>& items, const QStr
m_ui->rememberDecisionCheckBox->setVisible(!httpAuth); m_ui->rememberDecisionCheckBox->setVisible(!httpAuth);
m_ui->rememberDecisionCheckBox->setChecked(false); m_ui->rememberDecisionCheckBox->setChecked(false);
m_ui->itemsTable->setRowCount(items.count()); m_ui->itemsTable->setRowCount(entriesToConfirm.count());
m_ui->itemsTable->setColumnCount(2); m_ui->itemsTable->setColumnCount(2);
int row = 0; int row = 0;
for (const auto& entry : items) { for (const auto& entry : entriesToConfirm) {
auto item = new QTableWidgetItem(); auto item = new QTableWidgetItem();
item->setText(entry->title() + " - " + entry->username()); item->setText(entry->title() + " - " + entry->username());
item->setData(Qt::UserRole, row); item->setData(Qt::UserRole, row);
@ -61,11 +77,13 @@ void BrowserAccessControlDialog::setItems(const QList<Entry*>& items, const QStr
auto disableButton = new QPushButton(tr("Disable for this site")); auto disableButton = new QPushButton(tr("Disable for this site"));
disableButton->setAutoDefault(false); disableButton->setAutoDefault(false);
connect(disableButton, &QAbstractButton::pressed, [&, item] { connect(disableButton, &QAbstractButton::pressed, [&, item] {
emit disableAccess(item); emit disableAccess(item);
m_ui->itemsTable->removeRow(item->row()); m_ui->itemsTable->removeRow(item->row());
if (m_ui->itemsTable->rowCount() == 0) { if (m_ui->itemsTable->rowCount() == 0) {
reject(); emit closed();
} }
}); });
m_ui->itemsTable->setCellWidget(row, 1, disableButton); m_ui->itemsTable->setCellWidget(row, 1, disableButton);
@ -84,6 +102,11 @@ bool BrowserAccessControlDialog::remember() const
return m_ui->rememberDecisionCheckBox->isChecked(); return m_ui->rememberDecisionCheckBox->isChecked();
} }
bool BrowserAccessControlDialog::entriesAccepted() const
{
return m_entriesAccepted;
}
QList<QTableWidgetItem*> BrowserAccessControlDialog::getSelectedEntries() const QList<QTableWidgetItem*> BrowserAccessControlDialog::getSelectedEntries() const
{ {
QList<QTableWidgetItem*> selected; QList<QTableWidgetItem*> selected;
@ -107,3 +130,19 @@ QList<QTableWidgetItem*> BrowserAccessControlDialog::getNonSelectedEntries() con
} }
return notSelected; 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();
}

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2013 Francois Ferrand * Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -37,17 +37,34 @@ public:
explicit BrowserAccessControlDialog(QWidget* parent = nullptr); explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
~BrowserAccessControlDialog() override; ~BrowserAccessControlDialog() override;
void setItems(const QList<Entry*>& items, const QString& urlString, bool httpAuth); void setItems(const QList<Entry*>& entriesToConfirm,
const QList<Entry*>& allowedEntries,
const QString& urlString,
bool httpAuth);
bool remember() const; bool remember() const;
bool entriesAccepted() const;
QList<QTableWidgetItem*> getSelectedEntries() const; QList<QTableWidgetItem*> getSelectedEntries() const;
QList<QTableWidgetItem*> getNonSelectedEntries() const; QList<QTableWidgetItem*> getNonSelectedEntries() const;
signals: signals:
void disableAccess(QTableWidgetItem* item); void disableAccess(QTableWidgetItem* item);
void acceptEntries(QList<QTableWidgetItem*> items, QList<Entry*> entriesToConfirm, QList<Entry*> allowedEntries);
void rejectEntries(QList<QTableWidgetItem*> items, QList<Entry*> entriesToConfirm);
void closed();
public slots:
void acceptSelections();
void rejectSelections();
private:
void closeEvent(QCloseEvent* event) override;
private: private:
QScopedPointer<Ui::BrowserAccessControlDialog> m_ui; QScopedPointer<Ui::BrowserAccessControlDialog> m_ui;
QList<Entry*> m_entriesToConfirm;
QList<Entry*> m_allowedEntries;
bool m_entriesAccepted;
}; };
#endif // BROWSERACCESSCONTROLDIALOG_H #endif // BROWSERACCESSCONTROLDIALOG_H

View File

@ -97,7 +97,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="cancelButton"> <widget class="QPushButton" name="denyButton">
<property name="text"> <property name="text">
<string>Deny All</string> <string>Deny All</string>
</property> </property>

View File

@ -76,7 +76,7 @@ QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject&
} else if (action.compare("test-associate") == 0) { } else if (action.compare("test-associate") == 0) {
return handleTestAssociate(json, action); return handleTestAssociate(json, action);
} else if (action.compare("get-logins") == 0) { } else if (action.compare("get-logins") == 0) {
return handleGetLogins(json, action); return handleGetLogins(socket, json, action);
} else if (action.compare("generate-password") == 0) { } else if (action.compare("generate-password") == 0) {
return handleGeneratePassword(socket, json, action); return handleGeneratePassword(socket, json, action);
} else if (action.compare("set-login") == 0) { } else if (action.compare("set-login") == 0) {
@ -231,10 +231,11 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS
return buildResponse(action, message, newNonce); 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 hash = browserService()->getDatabaseHash();
const QString nonce = json.value("nonce").toString(); const QString nonce = json.value("nonce").toString();
const auto incrementedNonce = browserMessageBuilder()->incrementNonce(nonce);
const QString encrypted = json.value("message").toString(); const QString encrypted = json.value("message").toString();
if (!m_associated) { if (!m_associated) {
@ -263,21 +264,31 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
const QString formUrl = decrypted.value("submitUrl").toString(); const QString formUrl = decrypted.value("submitUrl").toString();
const QString auth = decrypted.value("httpAuth").toString(); const QString auth = decrypted.value("httpAuth").toString();
const bool httpAuth = auth.compare(TRUE_STR) == 0; 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()) { if (browserService()->isAccessConfirmRequested()) {
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND); auto errorReply = getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
if (!requestId.isEmpty()) {
errorReply["requestID"] = requestId;
}
return errorReply;
} }
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce); browserService()->findEntries(socket,
incrementedNonce,
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce); m_clientPublicKey,
message["count"] = users.count(); m_secretKey,
message["entries"] = users; id,
message["hash"] = hash; hash,
message["id"] = id; requestId,
siteUrl,
return buildResponse(action, message, newNonce); formUrl,
"",
keyList,
httpAuth);
return QJsonObject();
} }
QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action) QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action)

View File

@ -37,7 +37,7 @@ private:
QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action); QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
QJsonObject handleAssociate(const QJsonObject& json, const QString& action); QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
QJsonObject handleTestAssociate(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 handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action);
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action); QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action); QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);

View File

@ -18,7 +18,6 @@
*/ */
#include "BrowserService.h" #include "BrowserService.h"
#include "BrowserAccessControlDialog.h"
#include "BrowserAction.h" #include "BrowserAction.h"
#include "BrowserEntryConfig.h" #include "BrowserEntryConfig.h"
#include "BrowserEntrySaveDialog.h" #include "BrowserEntrySaveDialog.h"
@ -65,9 +64,9 @@ Q_GLOBAL_STATIC(BrowserService, s_browserService);
BrowserService::BrowserService() BrowserService::BrowserService()
: QObject() : QObject()
, m_browserHost(new BrowserHost) , m_browserHost(new BrowserHost)
, m_dialogActive(false)
, m_bringToFrontRequested(false) , m_bringToFrontRequested(false)
, m_passwordGeneratorRequested(false) , m_passwordGeneratorRequested(false)
, m_accessConfirmRequested(false)
, m_prevWindowState(WindowState::Normal) , m_prevWindowState(WindowState::Normal)
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224")) , m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
{ {
@ -315,6 +314,219 @@ QString BrowserService::getCurrentTotp(const QString& uuid)
return {}; 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<Entry*> entriesToConfirm;
QList<Entry*> 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<Entry*>& allowedEntries,
QList<Entry*>& 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<QTableWidgetItem*> items, QList<Entry*> entries, QList<Entry*> allowed) {
QList<Entry*> 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<QTableWidgetItem*> items, QList<Entry*> 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<Entry*>& 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, void BrowserService::showPasswordGenerator(QLocalSocket* socket,
const QString& incrementedNonce, const QString& incrementedNonce,
const QString& publicKey, const QString& publicKey,
@ -341,9 +553,11 @@ void BrowserService::showPasswordGenerator(QLocalSocket* socket,
[=](const QString& password) { [=](const QString& password) {
QJsonObject message = browserMessageBuilder()->buildMessage(incrementedNonce); QJsonObject message = browserMessageBuilder()->buildMessage(incrementedNonce);
message["password"] = password; message["password"] = password;
sendPassword(socket, m_browserHost->sendClientMessage(
browserMessageBuilder()->buildResponse( socket,
"generate-password", message, incrementedNonce, publicKey, secretKey)); browserMessageBuilder()->buildResponse(
"generate-password", message, incrementedNonce, publicKey, secretKey));
hideWindow();
}); });
} }
@ -353,17 +567,16 @@ void BrowserService::showPasswordGenerator(QLocalSocket* socket,
m_passwordGenerator->activateWindow(); m_passwordGenerator->activateWindow();
} }
void BrowserService::sendPassword(QLocalSocket* socket, const QJsonObject& message)
{
m_browserHost->sendClientMessage(socket, message);
hideWindow();
}
bool BrowserService::isPasswordGeneratorRequested() const bool BrowserService::isPasswordGeneratorRequested() const
{ {
return m_passwordGeneratorRequested; return m_passwordGeneratorRequested;
} }
bool BrowserService::isAccessConfirmRequested() const
{
return m_accessConfirmRequested;
}
QString BrowserService::storeKey(const QString& key) QString BrowserService::storeKey(const QString& key)
{ {
auto db = getDatabase(); auto db = getDatabase();
@ -426,91 +639,6 @@ QString BrowserService::getKey(const QString& id)
return db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + 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<Entry*> pwEntriesToConfirm;
QList<Entry*> 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<Entry*> 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, void BrowserService::addEntry(const QString& dbid,
const QString& login, const QString& login,
const QString& password, const QString& password,
@ -822,13 +950,12 @@ void BrowserService::requestGlobalAutoType(const QString& search)
emit osUtils->globalShortcutTriggered("autotype", search); emit osUtils->globalShortcutTriggered("autotype", search);
} }
QList<Entry*> QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrl, const QString& formUrl)
BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr, const QString& formUrlStr)
{ {
// Build map of prioritized entries // Build map of prioritized entries
QMultiMap<int, Entry*> priorities; QMultiMap<int, Entry*> priorities;
for (auto* entry : pwEntries) { for (auto* entry : pwEntries) {
priorities.insert(sortPriority(getEntryURLs(entry), siteUrlStr, formUrlStr), entry); priorities.insert(sortPriority(getEntryURLs(entry), siteUrl, formUrl), entry);
} }
auto keys = priorities.uniqueKeys(); auto keys = priorities.uniqueKeys();
@ -847,66 +974,38 @@ BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr,
return results; return results;
} }
QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, void BrowserService::allowEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm)
const QString& siteUrl,
const QString& siteHost,
const QString& formUrl,
const QString& realm,
const bool httpAuth)
{ {
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) { BrowserEntryConfig config;
return {}; config.load(entry);
config.allow(siteHost);
if (!formUrl.isEmpty() && siteHost != formUrl) {
config.allow(formUrl);
} }
m_dialogActive = true; if (!realm.isEmpty()) {
updateWindowState(); config.setRealm(realm);
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<Entry*> 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);
}
} }
// Re-hide the application if it wasn't visible before config.save(entry);
hideWindow(); }
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) QJsonObject BrowserService::prepareEntry(const Entry* entry)

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2013 Francois Ferrand * Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com> * Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,6 +20,7 @@
#ifndef BROWSERSERVICE_H #ifndef BROWSERSERVICE_H
#define BROWSERSERVICE_H #define BROWSERSERVICE_H
#include "BrowserAccessControlDialog.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "gui/PasswordGeneratorWidget.h" #include "gui/PasswordGeneratorWidget.h"
@ -64,12 +65,13 @@ public:
const QString& secretKey); const QString& secretKey);
void sendPassword(QLocalSocket* socket, const QJsonObject& message); void sendPassword(QLocalSocket* socket, const QJsonObject& message);
bool isPasswordGeneratorRequested() const; bool isPasswordGeneratorRequested() const;
bool isAccessConfirmRequested() const;
void addEntry(const QString& dbid, void addEntry(const QString& dbid,
const QString& login, const QString& login,
const QString& password, const QString& password,
const QString& siteUrlStr, const QString& siteUrl,
const QString& formUrlStr, const QString& formUrl,
const QString& realm, const QString& realm,
const QString& group, const QString& group,
const QString& groupUuid, const QString& groupUuid,
@ -79,17 +81,21 @@ public:
const QString& uuid, const QString& uuid,
const QString& login, const QString& login,
const QString& password, const QString& password,
const QString& siteUrlStr, const QString& siteUrl,
const QString& formUrlStr); const QString& formUrl);
bool deleteEntry(const QString& uuid); bool deleteEntry(const QString& uuid);
void findEntries(QLocalSocket* socket,
QJsonArray findMatchingEntries(const QString& dbid, const QString& nonce,
const QString& siteUrlStr, const QString& publicKey,
const QString& formUrlStr, const QString& secretKey,
const QString& realm, const QString& dbid,
const StringPairList& keyList, const QString& hash,
const bool httpAuth = false); const QString& requestId,
const QString& siteUrl,
const QString& formUrl,
const QString& realm,
const StringPairList& keyList,
const bool httpAuth = false);
void requestGlobalAutoType(const QString& search); void requestGlobalAutoType(const QString& search);
static void convertAttributesToCustomData(QSharedPointer<Database> db); static void convertAttributesToCustomData(QSharedPointer<Database> db);
@ -132,13 +138,32 @@ private:
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& siteUrl, const QString& formUrl); QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& siteUrl, const QString& formUrl);
QList<Entry*> searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList); QList<Entry*> searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList);
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& siteUrl, const QString& formUrl); QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& siteUrl, const QString& formUrl);
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm, void confirmEntries(QLocalSocket* socket,
const QString& siteUrl, const QString& incrementedNonce,
const QString& siteHost, const QString& publicKey,
const QString& formUrl, const QString& secretKey,
const QString& realm, const QString& id,
const bool httpAuth); const QString& hash,
const QString& requestId,
QList<Entry*>& allowedEntries,
QList<Entry*>& entriesToConfirm,
const QString& siteUrl,
const QString& siteHost,
const QString& formUrl,
const QString& realm,
const bool httpAuth);
void sendCredentialsToClient(QList<Entry*>& 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); 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); QJsonArray getChildrenFromGroup(Group* group);
Access checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm); Access checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm);
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {}); Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
@ -171,14 +196,15 @@ private:
QPointer<BrowserHost> m_browserHost; QPointer<BrowserHost> m_browserHost;
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients; QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;
bool m_dialogActive;
bool m_bringToFrontRequested; bool m_bringToFrontRequested;
bool m_passwordGeneratorRequested; bool m_passwordGeneratorRequested;
bool m_accessConfirmRequested;
WindowState m_prevWindowState; WindowState m_prevWindowState;
QUuid m_keepassBrowserUUID; QUuid m_keepassBrowserUUID;
QPointer<DatabaseWidget> m_currentDatabaseWidget; QPointer<DatabaseWidget> m_currentDatabaseWidget;
QScopedPointer<PasswordGeneratorWidget> m_passwordGenerator; QScopedPointer<PasswordGeneratorWidget> m_passwordGenerator;
QScopedPointer<BrowserAccessControlDialog> m_accessControlDialog;
Q_DISABLE_COPY(BrowserService); Q_DISABLE_COPY(BrowserService);