Add site permissions table to edit entry view

This commit is contained in:
varjolintu 2026-02-01 17:13:46 +02:00
parent b45cea0d9c
commit 1db0294dfe
No known key found for this signature in database
GPG key ID: 65838D52184C812A
4 changed files with 322 additions and 3 deletions

View file

@ -3095,6 +3095,34 @@ Would you like to correct it?</source>
<source>Failed to decrypt SSH key, ensure password is correct.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Site</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Allowed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>No</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Deny</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Allow</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Are you sure you want to remove this site?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditEntryWidgetAdvanced</name>
@ -3302,6 +3330,22 @@ Would you like to correct it?</source>
<source>Do not send this entry to the browser for HTTP Auth dialogs. If enabled, HTTP Auth dialogs will not show this entry for selection.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Site Permissions</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Allow or deny site</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Allow</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove selected site</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditEntryWidgetHistory</name>

View file

@ -47,7 +47,11 @@
#endif
#ifdef WITH_XC_BROWSER
#include "EntryURLModel.h"
#include "browser/BrowserMessageBuilder.h"
#include "browser/BrowserService.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#endif
#include "gui/Clipboard.h"
#include "gui/EditWidgetIcons.h"
@ -335,6 +339,18 @@ void EditEntryWidget::setupBrowser()
connect(m_additionalURLsDataModel,
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
SLOT(updateCurrentAttribute()));
connect(m_browserUi->sitePermissionsTable->selectionModel(),
SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
this,
SLOT(sitePermissionSelectionChanged()));
connect(m_browserUi->sitePermissionsTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(editSite()));
connect(m_browserUi->sitePermissionsTable,
SIGNAL(itemChanged(QTableWidgetItem*)),
this,
SLOT(editSiteFinished(QTableWidgetItem*)));
connect(m_browserUi->allowDenyButton, SIGNAL(clicked()), SLOT(allowDenyChangedForSite()));
connect(m_browserUi->removeSiteButton, SIGNAL(clicked()), SLOT(removeCurrentSite()));
connect(m_browserUi->editSiteButton, SIGNAL(clicked()), SLOT(editSite()));
// clang-format on
}
}
@ -342,6 +358,7 @@ void EditEntryWidget::setupBrowser()
void EditEntryWidget::updateBrowserModified()
{
m_browserSettingsChanged = true;
setModified(true);
}
void EditEntryWidget::updateBrowser()
@ -376,6 +393,27 @@ void EditEntryWidget::updateBrowser()
if (m_browserUi->notHttpAuthCheckbox->isEnabled()) {
changeValue(BrowserService::OPTION_NOT_HTTP_AUTH, m_browserUi->notHttpAuthCheckbox->isChecked());
}
// Update site permissions
if (!m_sitePermissions.isEmpty()) {
QJsonArray allowedSites;
QJsonArray deniedSites;
for (const auto& site : m_sitePermissions) {
if (site.second) {
allowedSites.append(site.first);
} else {
deniedSites.append(site.first);
}
}
QJsonObject sitePermissions;
sitePermissions["Allow"] = allowedSites;
sitePermissions["Deny"] = deniedSites;
sitePermissions["Realm"] = QString();
const auto sitePermissionsStr = QString(QJsonDocument(sitePermissions).toJson(QJsonDocument::Compact));
m_customData->set(BrowserService::KEEPASSXCBROWSER_NAME, sitePermissionsStr);
}
}
void EditEntryWidget::insertURL()
@ -459,6 +497,153 @@ void EditEntryWidget::entryURLEdited(const QString& url)
{
m_additionalURLsDataModel->setEntryUrl(url);
}
void EditEntryWidget::initializeSitePermissionsTable()
{
if (!m_customData->hasKey(BrowserService::KEEPASSXCBROWSER_NAME)) {
return;
}
// Get JSON from custom data
const auto sitePermissions = m_customData->value(BrowserService::KEEPASSXCBROWSER_NAME);
const auto sitePermissionsJson = browserMessageBuilder()->getJsonObject(sitePermissions.toUtf8());
if (!sitePermissionsJson["Allow"].isArray() || !sitePermissionsJson["Deny"].isArray()) {
return;
}
m_sitePermissions.clear();
// Parse allowed URLs
const auto allowedUrls = sitePermissionsJson["Allow"].toArray();
for (const auto& allowedUrl : allowedUrls) {
if (!allowedUrl.isString()) {
continue;
}
m_sitePermissions.append(qMakePair(allowedUrl.toString(), true));
}
// Parse denied URLs
const auto deniedUrls = sitePermissionsJson["Deny"].toArray();
for (const auto& deniedUrl : deniedUrls) {
if (!deniedUrl.isString()) {
continue;
}
m_sitePermissions.append(qMakePair(deniedUrl.toString(), false));
}
m_browserUi->sitePermissionsTable->setColumnCount(2);
m_browserUi->sitePermissionsTable->setSelectionMode(QAbstractItemView::SingleSelection);
m_browserUi->sitePermissionsTable->setHorizontalHeaderLabels({tr("Site"), tr("Allowed")});
m_browserUi->sitePermissionsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
m_browserUi->sitePermissionsTable->setColumnWidth(1, 100);
m_browserUi->sitePermissionsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
updateSitePermissionsTable();
}
void EditEntryWidget::updateSitePermissionsTable()
{
m_browserUi->sitePermissionsTable->setRowCount(m_sitePermissions.size());
int row = 0;
for (const auto& sitePermission : m_sitePermissions) {
const auto urlItem = new QTableWidgetItem(sitePermission.first);
const auto allowDenyItem = new QTableWidgetItem(sitePermission.second ? tr("Yes") : tr("No"));
// Allow edit on site only
urlItem->setData(Qt::UserRole, row);
urlItem->setFlags(urlItem->flags() | Qt::ItemIsEditable);
allowDenyItem->setData(Qt::UserRole, row);
allowDenyItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
m_browserUi->sitePermissionsTable->setItem(row, 0, urlItem);
m_browserUi->sitePermissionsTable->setItem(row, 1, allowDenyItem);
m_browserUi->sitePermissionsTable->item(row, 1)->setTextAlignment(Qt::AlignCenter | Qt::AlignHCenter);
++row;
}
}
void EditEntryWidget::sitePermissionSelectionChanged()
{
const auto currentItem = m_browserUi->sitePermissionsTable->currentIndex();
if (!currentItem.isValid()) {
m_browserUi->allowDenyButton->setEnabled(false);
m_browserUi->removeSiteButton->setEnabled(false);
m_browserUi->editSiteButton->setEnabled(false);
return;
}
const auto currentSite = m_sitePermissions.at(currentItem.row());
m_browserUi->allowDenyButton->setText(currentSite.second ? tr("Deny") : tr("Allow"));
m_browserUi->allowDenyButton->setEnabled(true);
m_browserUi->removeSiteButton->setEnabled(true);
m_browserUi->editSiteButton->setEnabled(true);
}
void EditEntryWidget::allowDenyChangedForSite()
{
const auto currentItem = m_browserUi->sitePermissionsTable->currentIndex();
if (!currentItem.isValid()) {
return;
}
const auto currentSite = m_sitePermissions.at(currentItem.row());
const auto newValue = !currentSite.second;
m_sitePermissions.replace(currentItem.row(), qMakePair(currentSite.first, newValue));
m_browserUi->allowDenyButton->setText(newValue ? tr("Deny") : tr("Allow"));
updateSitePermissionsTable();
updateBrowserModified();
}
void EditEntryWidget::removeCurrentSite()
{
const auto index = m_browserUi->sitePermissionsTable->currentIndex();
if (index.isValid()) {
const auto result = MessageBox::question(this,
tr("Confirm Removal"),
tr("Are you sure you want to remove this site?"),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result != MessageBox::Remove) {
return;
}
m_sitePermissions.removeAt(index.row());
if (m_sitePermissions.isEmpty()) {
m_browserUi->allowDenyButton->setEnabled(false);
m_browserUi->removeSiteButton->setEnabled(false);
m_browserUi->editSiteButton->setEnabled(false);
}
updateSitePermissionsTable();
updateBrowserModified();
}
}
void EditEntryWidget::editSite()
{
const auto currentItem = m_browserUi->sitePermissionsTable->currentItem();
if (!currentItem) {
return;
}
m_browserUi->sitePermissionsTable->editItem(currentItem);
}
void EditEntryWidget::editSiteFinished(QTableWidgetItem* item)
{
if (item->column() == 0 && item->row() <= m_sitePermissions.size()) {
const auto currentSite = m_sitePermissions.at(item->row());
if (currentSite.first != item->text()) {
m_sitePermissions.replace(item->row(), qMakePair(item->text(), currentSite.second));
updateSitePermissionsTable();
updateBrowserModified();
}
}
}
#endif
void EditEntryWidget::setupProperties()
@ -556,6 +741,8 @@ void EditEntryWidget::setupEntryUpdate()
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->allowDenyButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->removeSiteButton, SIGNAL(toggled(bool)), SLOT(setModified()));
}
#endif
}
@ -1145,6 +1332,7 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
}
}
initializeSitePermissionsTable();
setPageHidden(m_browserWidget, !config()->get(Config::Browser_Enabled).toBool());
#endif

View file

@ -1,6 +1,6 @@
/*
* Copyright (C) 2026 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
*
* 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
@ -44,6 +44,7 @@ class QMenu;
class QScrollArea;
class QSortFilterProxyModel;
class QStringListModel;
class QTableWidgetItem;
#ifdef WITH_XC_SSHAGENT
#include "sshagent/KeeAgentSettings.h"
class OpenSSHKey;
@ -148,6 +149,11 @@ private slots:
void editCurrentURL();
void updateCurrentURL();
void entryURLEdited(const QString& url);
void sitePermissionSelectionChanged();
void allowDenyChangedForSite();
void removeCurrentSite();
void editSite();
void editSiteFinished(QTableWidgetItem* item);
#endif
private:
@ -157,6 +163,8 @@ private:
void setupAutoType();
#ifdef WITH_XC_BROWSER
void setupBrowser();
void initializeSitePermissionsTable();
void updateSitePermissionsTable();
#endif
#ifdef WITH_XC_SSHAGENT
void setupSSHAgent();
@ -207,6 +215,7 @@ private:
bool m_browserSettingsChanged;
QWidget* const m_browserWidget;
EntryURLModel* const m_additionalURLsDataModel;
QList<QPair<QString, bool>> m_sitePermissions;
#endif
EditWidgetProperties* const m_editWidgetProperties;
QWidget* const m_historyWidget;

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>374</width>
<height>348</height>
<width>532</width>
<height>469</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
@ -155,6 +155,84 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Site Permissions</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableWidget" name="sitePermissionsTable">
<property name="accessibleName">
<string>Site Permissions</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="allowDenyButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="accessibleName">
<string>Allow or deny site</string>
</property>
<property name="text">
<string>Allow</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeSiteButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="accessibleName">
<string>Remove selected site</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editSiteButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>