Improvements to confirm access dialog

* Disable access to entries immediately within the dialog
* Use checkboxes instead of row selection
* Add button to deny all access immediately
This commit is contained in:
Jonathan White 2020-01-05 12:07:18 -05:00
parent 7d8072bf8f
commit 0383aa104c
5 changed files with 162 additions and 77 deletions

View File

@ -25,29 +25,54 @@ BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent)
: QDialog(parent) : QDialog(parent)
, m_ui(new Ui::BrowserAccessControlDialog()) , m_ui(new Ui::BrowserAccessControlDialog())
{ {
this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
m_ui->setupUi(this); m_ui->setupUi(this);
connect(m_ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(m_ui->denyButton, SIGNAL(clicked()), this, SLOT(reject())); connect(m_ui->allowButton, SIGNAL(clicked()), SLOT(accept()));
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject()));
} }
BrowserAccessControlDialog::~BrowserAccessControlDialog() BrowserAccessControlDialog::~BrowserAccessControlDialog()
{ {
} }
void BrowserAccessControlDialog::setUrl(const QString& url) void BrowserAccessControlDialog::setItems(const QList<Entry*>& items, const QString& hostname, bool httpAuth)
{ {
m_ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n" m_ui->siteLabel->setText(m_ui->siteLabel->text().arg(hostname));
"Please select whether you want to allow access."))
.arg(QUrl(url).host()));
}
void BrowserAccessControlDialog::setItems(const QList<Entry*>& items) m_ui->rememberDecisionCheckBox->setVisible(!httpAuth);
{ m_ui->rememberDecisionCheckBox->setChecked(false);
for (Entry* entry : items) {
m_ui->itemsList->addItem(entry->title() + " - " + entry->username()); m_ui->itemsTable->setRowCount(items.count());
m_ui->itemsTable->setColumnCount(2);
int row = 0;
for (const auto& entry : items) {
auto item = new QTableWidgetItem();
item->setText(entry->title() + " - " + entry->username());
item->setData(Qt::UserRole, row);
item->setCheckState(Qt::Checked);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
m_ui->itemsTable->setItem(row, 0, item);
auto disableButton = new QPushButton(tr("Disable for this site"));
connect(disableButton, &QAbstractButton::pressed, [&, item] {
emit disableAccess(item);
m_ui->itemsTable->removeRow(item->row());
if (m_ui->itemsTable->rowCount() == 0) {
reject();
}
});
m_ui->itemsTable->setCellWidget(row, 1, disableButton);
++row;
} }
m_ui->itemsTable->resizeColumnsToContents();
m_ui->itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
m_ui->allowButton->setFocus();
} }
bool BrowserAccessControlDialog::remember() const bool BrowserAccessControlDialog::remember() const
@ -55,12 +80,26 @@ bool BrowserAccessControlDialog::remember() const
return m_ui->rememberDecisionCheckBox->isChecked(); return m_ui->rememberDecisionCheckBox->isChecked();
} }
void BrowserAccessControlDialog::setRemember(bool r) QList<QTableWidgetItem*> BrowserAccessControlDialog::getSelectedEntries() const
{ {
m_ui->rememberDecisionCheckBox->setChecked(r); QList<QTableWidgetItem*> selected;
for (int i = 0; i < m_ui->itemsTable->rowCount(); ++i) {
auto item = m_ui->itemsTable->item(i, 0);
if (item->checkState() == Qt::Checked) {
selected.append(item);
}
}
return selected;
} }
void BrowserAccessControlDialog::setHTTPAuth(bool httpAuth) QList<QTableWidgetItem*> BrowserAccessControlDialog::getNonSelectedEntries() const
{ {
m_ui->rememberDecisionCheckBox->setVisible(!httpAuth); QList<QTableWidgetItem*> notSelected;
for (int i = 0; i < m_ui->itemsTable->rowCount(); ++i) {
auto item = m_ui->itemsTable->item(i, 0);
if (item->checkState() != Qt::Checked) {
notSelected.append(item);
}
}
return notSelected;
} }

View File

@ -21,6 +21,7 @@
#include <QDialog> #include <QDialog>
#include <QScopedPointer> #include <QScopedPointer>
#include <QTableWidgetItem>
class Entry; class Entry;
@ -35,13 +36,16 @@ class BrowserAccessControlDialog : public QDialog
public: public:
explicit BrowserAccessControlDialog(QWidget* parent = nullptr); explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
~BrowserAccessControlDialog(); ~BrowserAccessControlDialog() override;
void setUrl(const QString& url); void setItems(const QList<Entry*>& items, const QString& hostname, bool httpAuth);
void setItems(const QList<Entry*>& items);
bool remember() const; bool remember() const;
void setRemember(bool r);
void setHTTPAuth(bool httpAuth); QList<QTableWidgetItem*> getSelectedEntries() const;
QList<QTableWidgetItem*> getNonSelectedEntries() const;
signals:
void disableAccess(QTableWidgetItem* item);
private: private:
QScopedPointer<Ui::BrowserAccessControlDialog> m_ui; QScopedPointer<Ui::BrowserAccessControlDialog> m_ui;

View File

@ -6,29 +6,50 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>405</width>
<height>221</height> <height>200</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>KeePassXC-Browser Confirm Access</string> <string>KeePassXC - Browser Access Request</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="siteLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text"> <property name="text">
<string/> <string>%1 is requesting access to the following entries:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QListWidget" name="itemsList"/> <widget class="QTableWidget" name="itemsTable">
</item> <property name="editTriggers">
<item> <set>QAbstractItemView::NoEditTriggers</set>
<widget class="QCheckBox" name="rememberDecisionCheckBox">
<property name="text">
<string>Remember this decision</string>
</property> </property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget> </widget>
</item> </item>
<item> <item>
@ -47,22 +68,35 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="allowButton"> <widget class="QCheckBox" name="rememberDecisionCheckBox">
<property name="toolTip">
<string>Remember access to checked entries</string>
</property>
<property name="accessibleName"> <property name="accessibleName">
<string>Allow access</string> <string>Remember access to checked entries</string>
</property> </property>
<property name="text"> <property name="text">
<string>Allow</string> <string>Remember</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="denyButton"> <widget class="QPushButton" name="allowButton">
<property name="accessibleName"> <property name="accessibleName">
<string>Deny access</string> <string>Allow access to entries</string>
</property> </property>
<property name="text"> <property name="text">
<string>Deny</string> <string>Allow Selected</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Deny All</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -417,8 +417,9 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
} }
// Confirm entries // Confirm entries
if (confirmEntries(pwEntriesToConfirm, url, host, submitUrl, realm, httpAuth)) { QList<Entry*> selectedEntriesToConfirm = confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm, httpAuth);
pwEntries.append(pwEntriesToConfirm); if (!selectedEntriesToConfirm.isEmpty()) {
pwEntries.append(selectedEntriesToConfirm);
} }
if (pwEntries.isEmpty()) { if (pwEntries.isEmpty()) {
@ -788,59 +789,66 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
return results; return results;
} }
bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& url, const QString& url,
const QString& host, const QString& host,
const QString& submitUrl, const QString& submitHost,
const QString& realm, const QString& realm,
const bool httpAuth) const bool httpAuth)
{ {
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) { if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
return false; return {};
} }
m_dialogActive = true; m_dialogActive = true;
BrowserAccessControlDialog accessControlDialog; BrowserAccessControlDialog accessControlDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject())); connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
accessControlDialog.setUrl(!submitUrl.isEmpty() ? submitUrl : url); connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) {
accessControlDialog.setItems(pwEntriesToConfirm); auto entry = pwEntriesToConfirm[item->row()];
accessControlDialog.setHTTPAuth(httpAuth); BrowserEntryConfig config;
config.load(entry);
config.deny(host);
if (!submitHost.isEmpty() && host != submitHost) {
config.deny(submitHost);
}
if (!realm.isEmpty()) {
config.setRealm(realm);
}
config.save(entry);
});
accessControlDialog.setItems(pwEntriesToConfirm, !submitHost.isEmpty() ? submitHost : url, httpAuth);
raiseWindow(); raiseWindow();
accessControlDialog.show(); accessControlDialog.show();
accessControlDialog.activateWindow(); accessControlDialog.activateWindow();
accessControlDialog.raise(); accessControlDialog.raise();
const QString submitHost = QUrl(submitUrl).host(); QList<Entry*> allowedEntries;
int res = accessControlDialog.exec(); if (accessControlDialog.exec() == QDialog::Accepted) {
if (accessControlDialog.remember()) { const auto selectedEntries = accessControlDialog.getSelectedEntries();
for (auto* entry : pwEntriesToConfirm) { for (auto item : accessControlDialog.getSelectedEntries()) {
BrowserEntryConfig config; auto entry = pwEntriesToConfirm[item->row()];
config.load(entry); if (accessControlDialog.remember()) {
if (res == QDialog::Accepted) { BrowserEntryConfig config;
config.load(entry);
config.allow(host); config.allow(host);
if (!submitHost.isEmpty() && host != submitHost)
config.allow(submitHost);
} else if (res == QDialog::Rejected) {
config.deny(host);
if (!submitHost.isEmpty() && host != submitHost) { if (!submitHost.isEmpty() && host != submitHost) {
config.deny(submitHost); config.allow(submitHost);
} }
if (!realm.isEmpty()) {
config.setRealm(realm);
}
config.save(entry);
} }
if (!realm.isEmpty()) { allowedEntries.append(entry);
config.setRealm(realm);
}
config.save(entry);
} }
} }
m_dialogActive = false; m_dialogActive = false;
hideWindow(); hideWindow();
if (res == QDialog::Accepted) { return allowedEntries;
return true;
}
return false;
} }
QJsonObject BrowserService::prepareEntry(const Entry* entry) QJsonObject BrowserService::prepareEntry(const Entry* entry)

View File

@ -118,12 +118,12 @@ private:
private: private:
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl); QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
bool confirmEntries(QList<Entry*>& pwEntriesToConfirm, QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& url, const QString& url,
const QString& host, const QString& host,
const QString& submitUrl, const QString& submitUrl,
const QString& realm, const QString& realm,
const bool httpAuth); const bool httpAuth);
QJsonObject prepareEntry(const Entry* entry); QJsonObject prepareEntry(const Entry* entry);
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm); Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {}); Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});