Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorvarjolintu <sami.vanttinen@protonmail.com>2019-08-15 12:35:11 +0300
committerJonathan White <support@dmapps.us>2019-10-17 05:20:57 +0300
commitf726d7501ff7e8a66ae974719042f23010716595 (patch)
tree9eb923dde877609866bc6c2684ace6e44f090289 /src
parente50261a99c0ed6911aa16331dde730223fe4a2e1 (diff)
Add support for multiple URLs in an entry
* Fixes #398 The new Browser Integration entry settings page has a list view with any additional URL's. These URL's are added to the entry attributes with KP2A_URL_<counter>, which means those are directly compatible with Keepass2Android.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/browser/BrowserOptionDialog.cpp16
-rw-r--r--src/browser/BrowserService.cpp120
-rw-r--r--src/browser/BrowserService.h14
-rw-r--r--src/core/Bootstrap.cpp2
-rw-r--r--src/core/Entry.cpp3
-rw-r--r--src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp12
-rw-r--r--src/gui/entry/EditEntryWidget.cpp126
-rw-r--r--src/gui/entry/EditEntryWidget.h10
-rw-r--r--src/gui/entry/EditEntryWidgetBrowser.ui88
-rw-r--r--src/gui/entry/EntryURLModel.cpp120
-rw-r--r--src/gui/entry/EntryURLModel.h46
12 files changed, 468 insertions, 90 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index acc94785d..77acf290e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -218,6 +218,7 @@ add_subdirectory(proxy)
if(WITH_XC_BROWSER)
set(keepassxcbrowser_LIB keepassxcbrowser)
set(keepassx_SOURCES ${keepassx_SOURCES} gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp)
+ set(keepassx_SOURCES ${keepassx_SOURCES} gui/entry/EntryURLModel.cpp)
endif()
add_subdirectory(autotype)
diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp
index eea5cb4b1..a5bb921da 100644
--- a/src/browser/BrowserOptionDialog.cpp
+++ b/src/browser/BrowserOptionDialog.cpp
@@ -53,12 +53,6 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
m_ui->scriptWarningWidget->setVisible(false);
m_ui->scriptWarningWidget->setAutoHideTimeout(-1);
- m_ui->scriptWarningWidget->showMessage(
- tr("<b>Warning</b>, the keepassxc-proxy application was not found!"
- "<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options."
- "<br />Browser integration WILL NOT WORK without the proxy application."
- "<br />Expected Path: "),
- MessageWidget::Warning);
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"),
MessageWidget::Warning);
@@ -154,9 +148,13 @@ void BrowserOptionDialog::loadSettings()
// Check for native messaging host location errors
QString path;
if (!settings->checkIfProxyExists(path)) {
- QString text = m_ui->scriptWarningWidget->text();
- text.append(path);
- m_ui->scriptWarningWidget->setText(text);
+ auto text =
+ tr("<b>Warning</b>, the keepassxc-proxy application was not found!"
+ "<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options."
+ "<br />Browser integration WILL NOT WORK without the proxy application."
+ "<br />Expected Path: %1")
+ .arg(path);
+ m_ui->scriptWarningWidget->showMessage(text, MessageWidget::Warning);
m_ui->scriptWarningWidget->setVisible(true);
} else {
m_ui->scriptWarningWidget->setVisible(false);
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
index 7b7d7a3af..8a6ad0ec5 100644
--- a/src/browser/BrowserService.cpp
+++ b/src/browser/BrowserService.cpp
@@ -41,18 +41,20 @@
#include "gui/macutils/MacUtils.h"
#endif
-const char BrowserService::KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
-const char BrowserService::KEEPASSXCBROWSER_OLD_NAME[] = "keepassxc-browser Settings";
-const char BrowserService::ASSOCIATE_KEY_PREFIX[] = "KPXC_BROWSER_";
-static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
+const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
+const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
+const QString BrowserService::ASSOCIATE_KEY_PREFIX = QStringLiteral("KPXC_BROWSER_");
+static const QString KEEPASSXCBROWSER_GROUP_NAME = QStringLiteral("KeePassXC-Browser Passwords");
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
// These are for the settings and password conversion
-const char BrowserService::LEGACY_ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
-static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings";
-static const char KEEPASSHTTP_GROUP_NAME[] = "KeePassHttp Passwords";
+const QString BrowserService::LEGACY_ASSOCIATE_KEY_PREFIX = QStringLiteral("Public Key: ");
+static const QString KEEPASSHTTP_NAME = QStringLiteral("KeePassHttp Settings");
+static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwords");
// Extra entry related options saved in custom data
-const char BrowserService::OPTION_SKIP_AUTO_SUBMIT[] = "BrowserSkipAutoSubmit";
-const char BrowserService::OPTION_HIDE_ENTRY[] = "BrowserHideEntry";
+const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
+const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
+// Multiple URL's
+const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
BrowserService::BrowserService(DatabaseTabWidget* parent)
: m_dbTabWidget(parent)
@@ -320,7 +322,7 @@ QString BrowserService::storeKey(const QString& key)
return {};
}
- contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+ contains = db->metadata()->customData()->contains(ASSOCIATE_KEY_PREFIX + id);
if (contains) {
dialogResult = MessageBox::warning(nullptr,
tr("KeePassXC: Overwrite existing key?"),
@@ -333,7 +335,7 @@ QString BrowserService::storeKey(const QString& key)
} while (contains && dialogResult == MessageBox::Cancel);
hideWindow();
- db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key);
+ db->metadata()->customData()->set(ASSOCIATE_KEY_PREFIX + id, key);
return id;
}
@@ -344,7 +346,7 @@ QString BrowserService::getKey(const QString& id)
return {};
}
- return db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+ return db->metadata()->customData()->value(ASSOCIATE_KEY_PREFIX + id);
}
QJsonArray BrowserService::findMatchingEntries(const QString& id,
@@ -377,9 +379,9 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
// Check entries for authorization
QList<Entry*> pwEntriesToConfirm;
QList<Entry*> pwEntries;
- for (Entry* entry : searchEntries(url, keyList)) {
- if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY) &&
- entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
+ for (auto* entry : searchEntries(url, keyList)) {
+ if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
+ && entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
continue;
}
@@ -425,7 +427,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
pwEntries = sortEntries(pwEntries, host, submitUrl);
// Fill the list
- for (Entry* entry : pwEntries) {
+ for (auto* entry : pwEntries) {
result.append(prepareEntry(entry));
}
@@ -588,22 +590,30 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
return entries;
}
- for (Entry* entry : EntrySearcher().search(baseDomain(hostname), rootGroup)) {
- QString entryUrl = entry->url();
- QUrl entryQUrl(entryUrl);
- QString entryScheme = entryQUrl.scheme();
- QUrl qUrl(url);
-
- // Ignore entry if port or scheme defined in the URL doesn't match
- if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port())
- || (browserSettings()->matchUrlScheme() && !entryScheme.isEmpty()
- && entryScheme.compare(qUrl.scheme()) != 0)) {
+ for (const auto& group : rootGroup->groupsRecursive(true)) {
+ if (group->isRecycled() || !group->resolveSearchingEnabled()) {
continue;
}
- // Filter to match hostname in URL field
- if ((!entryUrl.isEmpty() && hostname.contains(entryUrl))
- || (matchUrlScheme(entryUrl) && hostname.endsWith(entryQUrl.host()))) {
+ for (auto* entry : group->entries()) {
+ if (entry->isRecycled()) {
+ continue;
+ }
+
+ // Search for additional URL's starting with KP2A_URL
+ if (entry->attributes()->keys().contains(ADDITIONAL_URL)) {
+ for (const auto& key : entry->attributes()->keys()) {
+ if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), hostname, url)) {
+ entries.append(entry);
+ continue;
+ }
+ }
+ }
+
+ if (!handleURL(entry->url(), hostname, url)) {
+ continue;
+ }
+
entries.append(entry);
}
}
@@ -616,7 +626,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
// Check if database is connected with KeePassXC-Browser
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
for (const StringPair& keyPair : keyList) {
- QString key = db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
+ QString key = db->metadata()->customData()->value(ASSOCIATE_KEY_PREFIX + keyPair.first);
if (!key.isEmpty() && keyPair.second == key) {
return true;
}
@@ -668,7 +678,7 @@ void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database
int counter = 0;
int keyCounter = 0;
- for (Entry* entry : entries) {
+ for (auto* entry : entries) {
if (progress.wasCanceled()) {
return;
}
@@ -721,12 +731,9 @@ void BrowserService::convertAttributesToCustomData(const QSharedPointer<Database
return;
}
- const QString keePassBrowserGroupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
- const QString keePassHttpGroupName = QLatin1String(KEEPASSHTTP_GROUP_NAME);
-
- for (Group* g : rootGroup->groupsRecursive(true)) {
- if (g->name() == keePassHttpGroupName) {
- g->setName(keePassBrowserGroupName);
+ for (auto* g : rootGroup->groupsRecursive(true)) {
+ if (g->name() == KEEPASSHTTP_GROUP_NAME) {
+ g->setName(KEEPASSXCBROWSER_GROUP_NAME);
break;
}
}
@@ -745,7 +752,7 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
// Build map of prioritized entries
QMultiMap<int, Entry*> priorities;
- for (Entry* entry : pwEntries) {
+ for (auto* entry : pwEntries) {
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
}
@@ -801,7 +808,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
int res = accessControlDialog.exec();
if (accessControlDialog.remember()) {
- for (Entry* entry : pwEntriesToConfirm) {
+ for (auto* entry : pwEntriesToConfirm) {
BrowserEntryConfig config;
config.load(entry);
if (res == QDialog::Accepted) {
@@ -853,8 +860,8 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
if (browserSettings()->supportKphFields()) {
const EntryAttributes* attr = entry->attributes();
QJsonArray stringFields;
- for (const QString& key : attr->keys()) {
- if (key.startsWith(QLatin1String("KPH: "))) {
+ for (const auto& key : attr->keys()) {
+ if (key.startsWith("KPH: ")) {
QJsonObject sField;
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
stringFields.append(sField);
@@ -899,17 +906,15 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& sele
return nullptr;
}
- const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
-
for (auto* g : rootGroup->groupsRecursive(true)) {
- if (g->name() == groupName && !g->isRecycled()) {
+ if (g->name() == KEEPASSXCBROWSER_GROUP_NAME && !g->isRecycled()) {
return db->rootGroup()->findGroupByUuid(g->uuid());
}
}
auto* group = new Group();
group->setUuid(QUuid::createUuid());
- group->setName(groupName);
+ group->setName(KEEPASSXCBROWSER_GROUP_NAME);
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
group->setParent(rootGroup);
return group;
@@ -987,6 +992,26 @@ bool BrowserService::removeFirstDomain(QString& hostname)
return false;
}
+bool BrowserService::handleURL(const QString& entryUrl, const QString& hostname, const QString& url)
+{
+ QUrl entryQUrl(entryUrl);
+ QString entryScheme = entryQUrl.scheme();
+ QUrl qUrl(url);
+
+ // Ignore entry if port or scheme defined in the URL doesn't match
+ if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port())
+ || (browserSettings()->matchUrlScheme() && !entryScheme.isEmpty() && entryScheme.compare(qUrl.scheme()) != 0)) {
+ return false;
+ }
+
+ // Filter to match hostname in URL field
+ if ((!entryUrl.isEmpty() && hostname.contains(entryUrl))
+ || (matchUrlScheme(entryUrl) && hostname.endsWith(entryQUrl.host()))) {
+ return true;
+ }
+ return false;
+};
+
/**
* Gets the base domain of URL.
*
@@ -1080,9 +1105,8 @@ int BrowserService::moveKeysToCustomData(Entry* entry, const QSharedPointer<Data
publicKey.remove(LEGACY_ASSOCIATE_KEY_PREFIX);
// Add key to database custom data
- if (db && !db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey)) {
- db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + publicKey,
- entry->attributes()->value(key));
+ if (db && !db->metadata()->customData()->contains(ASSOCIATE_KEY_PREFIX + publicKey)) {
+ db->metadata()->customData()->set(ASSOCIATE_KEY_PREFIX + publicKey, entry->attributes()->value(key));
++keyCounter;
}
}
diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h
index 2958155b4..a18a97448 100644
--- a/src/browser/BrowserService.h
+++ b/src/browser/BrowserService.h
@@ -68,12 +68,13 @@ public:
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
public:
- static const char KEEPASSXCBROWSER_NAME[];
- static const char KEEPASSXCBROWSER_OLD_NAME[];
- static const char ASSOCIATE_KEY_PREFIX[];
- static const char LEGACY_ASSOCIATE_KEY_PREFIX[];
- static const char OPTION_SKIP_AUTO_SUBMIT[];
- static const char OPTION_HIDE_ENTRY[];
+ static const QString KEEPASSXCBROWSER_NAME;
+ static const QString KEEPASSXCBROWSER_OLD_NAME;
+ static const QString ASSOCIATE_KEY_PREFIX;
+ static const QString LEGACY_ASSOCIATE_KEY_PREFIX;
+ static const QString OPTION_SKIP_AUTO_SUBMIT;
+ static const QString OPTION_HIDE_ENTRY;
+ static const QString ADDITIONAL_URL;
public slots:
QJsonArray findMatchingEntries(const QString& id,
@@ -129,6 +130,7 @@ private:
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
bool matchUrlScheme(const QString& url);
bool removeFirstDomain(QString& hostname);
+ bool handleURL(const QString& entryUrl, const QString& hostname, const QString& url);
QString baseDomain(const QString& url) const;
QSharedPointer<Database> getDatabase();
QSharedPointer<Database> selectedDatabase();
diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp
index 2d1a3e087..204942f4d 100644
--- a/src/core/Bootstrap.cpp
+++ b/src/core/Bootstrap.cpp
@@ -257,7 +257,7 @@ namespace Bootstrap
nullptr, // do not change owner or group
pACL, // DACL specified
nullptr // do not change SACL
- );
+ );
Cleanup:
diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp
index 60d64167a..677c7b387 100644
--- a/src/core/Entry.cpp
+++ b/src/core/Entry.cpp
@@ -762,7 +762,8 @@ Entry* Entry::clone(CloneFlags flags) const
entry->m_autoTypeAssociations->copyDataFrom(m_autoTypeAssociations);
if (flags & CloneIncludeHistory) {
for (Entry* historyItem : m_history) {
- Entry* historyItemClone = historyItem->clone(flags & ~CloneIncludeHistory & ~CloneNewUuid & ~CloneResetTimeInfo);
+ Entry* historyItemClone =
+ historyItem->clone(flags & ~CloneIncludeHistory & ~CloneNewUuid & ~CloneResetTimeInfo);
historyItemClone->setUpdateTimeinfo(false);
historyItemClone->setUuid(entry->uuid());
historyItemClone->setUpdateTimeinfo(true);
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp
index fac85c21e..4ea30c1f6 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp
@@ -242,12 +242,12 @@ void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData()
{
if (MessageBox::Yes
!= MessageBox::question(
- this,
- tr("Move KeePassHTTP attributes to custom data"),
- tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
- "This is necessary to maintain compatibility with the browser plugin."),
- MessageBox::Yes | MessageBox::Cancel,
- MessageBox::Cancel)) {
+ this,
+ tr("Move KeePassHTTP attributes to custom data"),
+ tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
+ "This is necessary to maintain compatibility with the browser plugin."),
+ MessageBox::Yes | MessageBox::Cancel,
+ MessageBox::Cancel)) {
return;
}
diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp
index 0238131f5..3345848af 100644
--- a/src/gui/entry/EditEntryWidget.cpp
+++ b/src/gui/entry/EditEntryWidget.cpp
@@ -51,6 +51,7 @@
#include "sshagent/SSHAgent.h"
#endif
#ifdef WITH_XC_BROWSER
+#include "EntryURLModel.h"
#include "browser/BrowserService.h"
#endif
#include "gui/Clipboard.h"
@@ -82,7 +83,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
, m_sshAgentWidget(new QWidget())
#endif
#ifdef WITH_XC_BROWSER
+ , m_browserSettingsChanged(false)
, m_browserWidget(new QWidget())
+ , m_additionalURLsDataModel(new EntryURLModel(this))
#endif
, m_editWidgetProperties(new EditWidgetProperties())
, m_historyWidget(new QWidget())
@@ -265,18 +268,112 @@ void EditEntryWidget::setupBrowser()
if (config()->get("Browser/Enabled", false).toBool()) {
addPage(tr("Browser Integration"), FilePath::instance()->icon("apps", "internet-web-browser"), m_browserWidget);
- connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowser()));
- connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowser()));
+ m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
+ m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
+
+ // clang-format off
+ connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
+ connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
+ connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
+ connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
+ connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
+ connect(m_browserUi->additionalURLsView->selectionModel(),
+ SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ SLOT(updateCurrentURL()));
+ connect(m_additionalURLsDataModel,
+ SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
+ SLOT(updateCurrentAttribute()));
+ // clang-format on
}
}
+void EditEntryWidget::updateBrowserModified()
+{
+ m_browserSettingsChanged = true;
+}
+
void EditEntryWidget::updateBrowser()
{
+ if (!m_browserSettingsChanged) {
+ return;
+ }
+
auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
auto hide = m_browserUi->hideEntryCheckbox->isChecked();
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? QString("true") : QString("false")));
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? QString("true") : QString("false")));
}
+
+void EditEntryWidget::insertURL()
+{
+ Q_ASSERT(!m_history);
+
+ QString name("KP2A_URL");
+ int i = 1;
+
+ while (m_entryAttributes->keys().contains(name)) {
+ name = QString("KP2A_URL_%1").arg(i);
+ i++;
+ }
+
+ m_entryAttributes->set(name, tr("<empty URL>"));
+ QModelIndex index = m_additionalURLsDataModel->indexByKey(name);
+
+ m_browserUi->additionalURLsView->setCurrentIndex(index);
+ m_browserUi->additionalURLsView->edit(index);
+
+ setModified(true);
+}
+
+void EditEntryWidget::removeCurrentURL()
+{
+ Q_ASSERT(!m_history);
+
+ QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
+
+ if (index.isValid()) {
+ auto result = MessageBox::question(this,
+ tr("Confirm Removal"),
+ tr("Are you sure you want to remove this URL?"),
+ MessageBox::Remove | MessageBox::Cancel,
+ MessageBox::Cancel);
+
+ if (result == MessageBox::Remove) {
+ m_entryAttributes->remove(m_additionalURLsDataModel->keyByIndex(index));
+ if (m_additionalURLsDataModel->rowCount() == 0) {
+ m_browserUi->editURLButton->setEnabled(false);
+ m_browserUi->removeURLButton->setEnabled(false);
+ }
+ setModified(true);
+ }
+ }
+}
+
+void EditEntryWidget::editCurrentURL()
+{
+ Q_ASSERT(!m_history);
+
+ QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
+
+ if (index.isValid()) {
+ m_browserUi->additionalURLsView->edit(index);
+ setModified(true);
+ }
+}
+
+void EditEntryWidget::updateCurrentURL()
+{
+ QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
+
+ if (index.isValid()) {
+ // Don't allow editing in history view
+ m_browserUi->editURLButton->setEnabled(!m_history);
+ m_browserUi->removeURLButton->setEnabled(!m_history);
+ } else {
+ m_browserUi->editURLButton->setEnabled(false);
+ m_browserUi->removeURLButton->setEnabled(false);
+ }
+}
#endif
void EditEntryWidget::setupProperties()
@@ -366,8 +463,11 @@ void EditEntryWidget::setupEntryUpdate()
#ifdef WITH_XC_BROWSER
if (config()->get("Browser/Enabled", false).toBool()) {
- connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), this, SLOT(setModified()));
- connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), this, SLOT(setModified()));
+ connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
+ connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
+ 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()));
}
#endif
}
@@ -862,7 +962,8 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
#ifdef WITH_XC_BROWSER
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
- m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == "true");
+ m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT)
+ == "true");
} else {
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
}
@@ -872,6 +973,15 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
} else {
m_browserUi->hideEntryCheckbox->setChecked(false);
}
+
+ m_browserUi->addURLButton->setEnabled(!m_history);
+ m_browserUi->removeURLButton->setEnabled(false);
+ m_browserUi->editURLButton->setEnabled(false);
+ m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
+
+ if (m_additionalURLsDataModel->rowCount() != 0) {
+ m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
+ }
#endif
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
@@ -946,6 +1056,12 @@ bool EditEntryWidget::commitEntry()
}
#endif
+#ifdef WITH_XC_BROWSER
+ if (config()->get("Browser/Enabled", false).toBool()) {
+ updateBrowser();
+ }
+#endif
+
if (!m_create) {
m_entry->beginUpdate();
}
diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h
index e70c548c3..8c6fee0bc 100644
--- a/src/gui/entry/EditEntryWidget.h
+++ b/src/gui/entry/EditEntryWidget.h
@@ -46,6 +46,9 @@ class QStringListModel;
#include "sshagent/KeeAgentSettings.h"
class OpenSSHKey;
#endif
+#ifdef WITH_XC_BROWSER
+class EntryURLModel;
+#endif
namespace Ui
{
@@ -120,7 +123,12 @@ private slots:
void copyPublicKey();
#endif
#ifdef WITH_XC_BROWSER
+ void updateBrowserModified();
void updateBrowser();
+ void insertURL();
+ void removeCurrentURL();
+ void editCurrentURL();
+ void updateCurrentURL();
#endif
private:
@@ -175,7 +183,9 @@ private:
QWidget* const m_sshAgentWidget;
#endif
#ifdef WITH_XC_BROWSER
+ bool m_browserSettingsChanged;
QWidget* const m_browserWidget;
+ EntryURLModel* const m_additionalURLsDataModel;
#endif
EditWidgetProperties* const m_editWidgetProperties;
QWidget* const m_historyWidget;
diff --git a/src/gui/entry/EditEntryWidgetBrowser.ui b/src/gui/entry/EditEntryWidgetBrowser.ui
index 73e64dfb3..4d0d29cf7 100644
--- a/src/gui/entry/EditEntryWidgetBrowser.ui
+++ b/src/gui/entry/EditEntryWidgetBrowser.ui
@@ -54,26 +54,86 @@
</widget>
</item>
<item>
- <spacer name="verticalSpacer_3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Expanding</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Additional URL's</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <item>
+ <widget class="QListView" name="additionalURLsView">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ <property name="resizeMode">
+ <enum>QListView::Adjust</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="additionalURLsButtonLayout">
+ <item>
+ <widget class="QPushButton" name="addURLButton">
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeURLButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="editURLButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Edit</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</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>
<tabstops>
<tabstop>skipAutoSubmitCheckbox</tabstop>
<tabstop>hideEntryCheckbox</tabstop>
+ <tabstop>additionalURLsView</tabstop>
+ <tabstop>addURLButton</tabstop>
+ <tabstop>removeURLButton</tabstop>
+ <tabstop>editURLButton</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/src/gui/entry/EntryURLModel.cpp b/src/gui/entry/EntryURLModel.cpp
new file mode 100644
index 000000000..3667c78f0
--- /dev/null
+++ b/src/gui/entry/EntryURLModel.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
+ * Copyright (C) 2019 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "EntryURLModel.h"
+
+#include "core/Entry.h"
+#include "core/Tools.h"
+
+#include <algorithm>
+
+EntryURLModel::EntryURLModel(QObject* parent)
+ : QStandardItemModel(parent)
+ , m_entryAttributes(nullptr)
+{
+}
+
+void EntryURLModel::setEntryAttributes(EntryAttributes* entryAttributes)
+{
+ beginResetModel();
+
+ if (m_entryAttributes) {
+ m_entryAttributes->disconnect(this);
+ }
+
+ m_entryAttributes = entryAttributes;
+
+ if (m_entryAttributes) {
+ updateAttributes();
+ // clang-format off
+ connect(m_entryAttributes, SIGNAL(added(QString)), SLOT(updateAttributes()));
+ connect(m_entryAttributes, SIGNAL(customKeyModified(QString)), SLOT(updateAttributes()));
+ connect(m_entryAttributes, SIGNAL(removed(QString)), SLOT(updateAttributes()));
+ connect(m_entryAttributes, SIGNAL(renamed(QString,QString)), SLOT(updateAttributes()));
+ connect(m_entryAttributes, SIGNAL(reset()), SLOT(updateAttributes()));
+ // clang-format on
+ }
+
+ endResetModel();
+}
+
+bool EntryURLModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ if (!index.isValid() || role != Qt::EditRole || value.type() != QVariant::String || value.toString().isEmpty()) {
+ return false;
+ }
+
+ const int row = index.row();
+ const QString key = m_urls.at(row).first;
+ const QString oldValue = m_urls.at(row).second;
+
+ if (EntryAttributes::isDefaultAttribute(key) || m_entryAttributes->containsValue(value.toString())) {
+ return false;
+ }
+
+ m_entryAttributes->set(key, value.toString());
+
+ emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1));
+ return true;
+}
+
+QModelIndex EntryURLModel::indexByKey(const QString& key) const
+{
+ int row = -1;
+ for (int i = 0; i < m_urls.size(); ++i) {
+ if (m_urls.at(i).first == key) {
+ row = i;
+ break;
+ }
+ }
+
+ if (row == -1) {
+ return QModelIndex();
+ } else {
+ return index(row, 0);
+ }
+}
+
+QString EntryURLModel::keyByIndex(const QModelIndex& index) const
+{
+ if (!index.isValid()) {
+ return QString();
+ } else {
+ return m_urls.at(index.row()).first;
+ }
+}
+
+void EntryURLModel::updateAttributes()
+{
+ clear();
+ m_urls.clear();
+
+ const QList<QString> attributesKeyList = m_entryAttributes->keys();
+ for (const QString& key : attributesKeyList) {
+ if (!EntryAttributes::isDefaultAttribute(key) && key.contains("KP2A_URL")) {
+ const auto value = m_entryAttributes->value(key);
+ m_urls.append(qMakePair(key, value));
+
+ auto* item = new QStandardItem(value);
+ if (m_entryAttributes->isProtected(key)) {
+ item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+ }
+ appendRow(item);
+ }
+ }
+}
diff --git a/src/gui/entry/EntryURLModel.h b/src/gui/entry/EntryURLModel.h
new file mode 100644
index 000000000..09344d92a
--- /dev/null
+++ b/src/gui/entry/EntryURLModel.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
+ * Copyright (C) 2019 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSXC_ENTRYURLMODEL_H
+#define KEEPASSXC_ENTRYURLMODEL_H
+
+#include <QStandardItemModel>
+
+class EntryAttributes;
+
+class EntryURLModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit EntryURLModel(QObject* parent = nullptr);
+ void setEntryAttributes(EntryAttributes* entryAttributes);
+ void insertRow(const QString& key, const QString& value);
+ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
+ QModelIndex indexByKey(const QString& key) const;
+ QString keyByIndex(const QModelIndex& index) const;
+
+private slots:
+ void updateAttributes();
+
+private:
+ QList<QPair<QString, QString>> m_urls;
+ EntryAttributes* m_entryAttributes;
+};
+
+#endif // KEEPASSXC_ENTRYURLMODEL_H