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.
This commit is contained in:
varjolintu
2019-08-15 12:35:11 +03:00
committed by Jonathan White
parent e50261a99c
commit f726d7501f
19 changed files with 568 additions and 104 deletions

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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/>

View File

@@ -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);
}
}
}

View File

@@ -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