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:
committed by
Jonathan White
parent
e50261a99c
commit
f726d7501f
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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/>
|
||||
|
||||
120
src/gui/entry/EntryURLModel.cpp
Normal file
120
src/gui/entry/EntryURLModel.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/gui/entry/EntryURLModel.h
Normal file
46
src/gui/entry/EntryURLModel.h
Normal 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
|
||||
Reference in New Issue
Block a user