From 53165ea2bc9749d33e6ebd7588c27cc59f95d558 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Mon, 19 Feb 2018 13:51:06 +0200 Subject: [PATCH] Browser connection keys and rules are stored in custom data instead of attributes --- src/browser/BrowserEntryConfig.cpp | 6 +- src/browser/BrowserOptionDialog.cpp | 68 ++++++++++++- src/browser/BrowserOptionDialog.h | 17 +++- src/browser/BrowserOptionDialog.ui | 69 +++++++++++++ src/browser/BrowserService.cpp | 149 ++++++++++++++++++---------- src/browser/BrowserService.h | 3 +- src/browser/NativeMessagingHost.cpp | 6 ++ src/browser/NativeMessagingHost.h | 1 + src/gui/MainWindow.cpp | 14 ++- 9 files changed, 270 insertions(+), 63 deletions(-) diff --git a/src/browser/BrowserEntryConfig.cpp b/src/browser/BrowserEntryConfig.cpp index 36d0c73397..248e92c06c 100644 --- a/src/browser/BrowserEntryConfig.cpp +++ b/src/browser/BrowserEntryConfig.cpp @@ -21,7 +21,7 @@ #include "core/Entry.h" #include "core/EntryAttributes.h" -static const char KEEPASSBROWSER_NAME[] = "KeePassXC-Browser Settings"; +static const char KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings"; BrowserEntryConfig::BrowserEntryConfig(QObject* parent) : @@ -83,7 +83,7 @@ void BrowserEntryConfig::setRealm(const QString& realm) bool BrowserEntryConfig::load(const Entry* entry) { - QString s = entry->attributes()->value(KEEPASSBROWSER_NAME); + QString s = entry->customData()->value(KEEPASSXCBROWSER_NAME); if (s.isEmpty()) { return false; } @@ -105,5 +105,5 @@ void BrowserEntryConfig::save(Entry* entry) QVariantMap v = qo2qv(this); QJsonObject o = QJsonObject::fromVariantMap(v); QByteArray json = QJsonDocument(o).toJson(QJsonDocument::Compact); - entry->attributes()->set(KEEPASSBROWSER_NAME, json); + entry->customData()->set(KEEPASSXCBROWSER_NAME, json); } diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp index 693e62d26e..bb1b7b97a5 100755 --- a/src/browser/BrowserOptionDialog.cpp +++ b/src/browser/BrowserOptionDialog.cpp @@ -23,14 +23,23 @@ #include "BrowserSettings.h" #include "core/FilePath.h" -#include #include +#include "gui/MessageBox.h" -BrowserOptionDialog::BrowserOptionDialog(QWidget* parent) : +BrowserOptionDialog::BrowserOptionDialog(DatabaseTabWidget* parent) : QWidget(parent), - m_ui(new Ui::BrowserOptionDialog()) + m_ui(new Ui::BrowserOptionDialog()), + m_customData(new CustomData(this)), + m_customDataModel(new QStandardItemModel(this)), + m_dbTabWidget(parent) { m_ui->setupUi(this); + m_ui->removeCustomDataButton->setEnabled(false); + m_ui->customDataTable->setModel(m_customDataModel); + connect(m_ui->customDataTable->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + SLOT(toggleRemoveButton(QItemSelection))); + connect(m_ui->removeCustomDataButton, SIGNAL(clicked()), SLOT(removeSelectedKey())); + connect(m_ui->convertToCustomData, SIGNAL(clicked()), this, SIGNAL(convertAttributesToCustomData())); connect(m_ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys())); connect(m_ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions())); @@ -54,6 +63,17 @@ BrowserOptionDialog::~BrowserOptionDialog() { } +CustomData* BrowserOptionDialog::customData() const +{ + // Returns the current database customData from metadata. Otherwise return an empty customData member. + if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) { + if (Database* db = dbWidget->database()) { + return db->metadata()->customData(); + } + } + return m_customData; +} + void BrowserOptionDialog::loadSettings() { BrowserSettings settings; @@ -88,6 +108,9 @@ void BrowserOptionDialog::loadSettings() m_ui->firefoxSupport->setChecked(settings.firefoxSupport()); m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport()); + // Update the stored key list every time settings are loaded + updateModel(); + #if defined(KEEPASSXC_DIST_APPIMAGE) m_ui->supportBrowserProxy->setChecked(true); m_ui->supportBrowserProxy->setEnabled(false); @@ -139,3 +162,42 @@ void BrowserOptionDialog::showProxyLocationFileDialog() fileTypeFilter); m_ui->customProxyLocation->setText(proxyLocation); } + +void BrowserOptionDialog::removeSelectedKey() +{ + if (QMessageBox::Yes != MessageBox::question(this, + tr("Delete the selected key?"), + tr("Do you really want to delete the selected key?\n" + "This may cause the affected plugins to malfunction."), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel)) { + return; + } + + const QItemSelectionModel* itemSelectionModel = m_ui->customDataTable->selectionModel(); + if (itemSelectionModel) { + for (const QModelIndex& index : itemSelectionModel->selectedRows(0)) { + const QString key = index.data().toString(); + customData()->remove(key); + } + updateModel(); + } +} + +void BrowserOptionDialog::toggleRemoveButton(const QItemSelection& selected) +{ + m_ui->removeCustomDataButton->setEnabled(!selected.isEmpty()); +} + +void BrowserOptionDialog::updateModel() +{ + m_customDataModel->clear(); + m_customDataModel->setHorizontalHeaderLabels({tr("Key"), tr("Value")}); + + for (const QString& key : customData()->keys()) { + m_customDataModel->appendRow(QList() + << new QStandardItem(key) + << new QStandardItem(customData()->value(key))); + } + + m_ui->removeCustomDataButton->setEnabled(false); +} diff --git a/src/browser/BrowserOptionDialog.h b/src/browser/BrowserOptionDialog.h index b562f6c18a..bf4c39bb4f 100755 --- a/src/browser/BrowserOptionDialog.h +++ b/src/browser/BrowserOptionDialog.h @@ -22,6 +22,11 @@ #include #include +#include +#include +#include +#include "core/CustomData.h" +#include "gui/DatabaseTabWidget.h" namespace Ui { class BrowserOptionDialog; @@ -32,9 +37,11 @@ class BrowserOptionDialog : public QWidget Q_OBJECT public: - explicit BrowserOptionDialog(QWidget* parent = nullptr); + explicit BrowserOptionDialog(DatabaseTabWidget* parent = nullptr); ~BrowserOptionDialog(); + CustomData* customData() const; + public slots: void loadSettings(); void saveSettings(); @@ -42,12 +49,20 @@ public slots: signals: void removeSharedEncryptionKeys(); void removeStoredPermissions(); + void convertAttributesToCustomData(); private slots: void showProxyLocationFileDialog(); + void removeSelectedKey(); + void toggleRemoveButton(const QItemSelection& selected); private: + void updateModel(); QScopedPointer m_ui; + + QPointer m_customData; + QPointer m_customDataModel; + DatabaseTabWidget* const m_dbTabWidget; }; #endif // BROWSEROPTIONDIALOG_H diff --git a/src/browser/BrowserOptionDialog.ui b/src/browser/BrowserOptionDialog.ui index e823794524..f4ef971174 100755 --- a/src/browser/BrowserOptionDialog.ui +++ b/src/browser/BrowserOptionDialog.ui @@ -230,6 +230,75 @@ + + + + + + + 0 + 0 + + + + Move KeePassHTTP attributes to KeePassXC-Browser &custom data + + + + + + + + + Stored Keys + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + 200 + + + true + + + false + + + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index fb89e8bcb6..814547db0c 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -44,6 +44,9 @@ static const char KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings"; static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: "; static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords"; static int KEEPASSXCBROWSER_DEFAULT_ICON = 1; +// These are for the settings and password conversion +static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings"; +static const char KEEPASSHTTP_GROUP_NAME[] = "KeePassHttp Passwords"; BrowserService::BrowserService(DatabaseTabWidget* parent) : m_dbTabWidget(parent), @@ -107,12 +110,12 @@ QString BrowserService::getDatabaseRootUuid() { Database* db = getDatabase(); if (!db) { - return QString(); + return {}; } Group* rootGroup = db->rootGroup(); if (!rootGroup) { - return QString(); + return {}; } return rootGroup->uuid().toHex(); @@ -122,46 +125,16 @@ QString BrowserService::getDatabaseRecycleBinUuid() { Database* db = getDatabase(); if (!db) { - return QString(); + return {}; } Group* recycleBin = db->metadata()->recycleBin(); if (!recycleBin) { - return QString(); + return {}; } return recycleBin->uuid().toHex(); } -Entry* BrowserService::getConfigEntry(bool create) -{ - Entry* entry = nullptr; - Database* db = getDatabase(); - if (!db) { - return nullptr; - } - - entry = db->resolveEntry(KEEPASSXCBROWSER_UUID); - if (!entry && create) { - entry = new Entry(); - entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME)); - entry->setUuid(KEEPASSXCBROWSER_UUID); - entry->setAutoTypeEnabled(false); - entry->setGroup(db->rootGroup()); - return entry; - } - - if (entry && entry->group() == db->metadata()->recycleBin()) { - if (!create) { - return nullptr; - } else { - entry->setGroup(db->rootGroup()); - return entry; - } - } - - return entry; -} - QString BrowserService::storeKey(const QString& key) { QString id; @@ -173,8 +146,8 @@ QString BrowserService::storeKey(const QString& key) return id; } - Entry* config = getConfigEntry(true); - if (!config) { + Database* db = getDatabase(); + if (!db) { return {}; } @@ -200,7 +173,7 @@ QString BrowserService::storeKey(const QString& key) return {}; } - contains = config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id); + contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id); if (contains) { dialogResult = QMessageBox::warning(nullptr, tr("KeePassXC: Overwrite existing key?"), tr("A shared encryption key with the name \"%1\" " @@ -210,18 +183,18 @@ QString BrowserService::storeKey(const QString& key) } } while (contains && dialogResult == QMessageBox::No); - config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true); + db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key); return id; } QString BrowserService::getKey(const QString& id) { - Entry* config = getConfigEntry(); - if (!config) { - return QString(); + Database* db = getDatabase(); + if (!db) { + return {}; } - return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id); + return db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id); } QJsonArray BrowserService::findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm) @@ -420,8 +393,8 @@ void BrowserService::removeSharedEncryptionKeys() return; } - Entry* entry = getConfigEntry(); - if (!entry) { + Database* db = getDatabase(); + if (!db) { QMessageBox::information(0, tr("KeePassXC: Settings not available!"), tr("The active database does not contain a settings entry."), QMessageBox::Ok); @@ -429,7 +402,7 @@ void BrowserService::removeSharedEncryptionKeys() } QStringList keysToRemove; - for (const QString& key : entry->attributes()->keys()) { + for (const QString& key : db->metadata()->customData()->keys()) { if (key.startsWith(ASSOCIATE_KEY_PREFIX)) { keysToRemove << key; } @@ -442,11 +415,9 @@ void BrowserService::removeSharedEncryptionKeys() return; } - entry->beginUpdate(); for (const QString& key : keysToRemove) { - entry->attributes()->remove(key); + db->metadata()->customData()->remove(key); } - entry->endUpdate(); const int count = keysToRemove.count(); QMessageBox::information(0, tr("KeePassXC: Removed keys from database"), @@ -481,9 +452,9 @@ void BrowserService::removeStoredPermissions() return; } - if (entry->attributes()->contains(KEEPASSXCBROWSER_NAME)) { + if (entry->customData()->contains(KEEPASSXCBROWSER_NAME)) { entry->beginUpdate(); - entry->attributes()->remove(KEEPASSXCBROWSER_NAME); + entry->customData()->remove(KEEPASSXCBROWSER_NAME); entry->endUpdate(); ++counter; } @@ -502,6 +473,69 @@ void BrowserService::removeStoredPermissions() } } +void BrowserService::convertAttributesToCustomData() +{ + if (!isDatabaseOpened()) { + QMessageBox::critical(0, tr("KeePassXC: Database locked!"), + tr("The active database is locked!\n" + "Please unlock the selected database or choose another one which is unlocked."), + QMessageBox::Ok); + return; + } + + Database* db = m_dbTabWidget->currentDatabaseWidget()->database(); + if (!db) { + return; + } + + QList entries = db->rootGroup()->entriesRecursive(); + + QProgressDialog progress(tr("Converting attributes to custom data…"), tr("Abort"), 0, entries.count()); + progress.setWindowModality(Qt::WindowModal); + + uint counter = 0; + for (Entry* entry : entries) { + if (progress.wasCanceled()) { + return; + } + + if (moveSettingsToCustomData(entry, KEEPASSHTTP_NAME)) { + ++counter; + } + if (moveSettingsToCustomData(entry, KEEPASSXCBROWSER_NAME)) { + ++counter; + } + progress.setValue(progress.value() + 1); + } + progress.reset(); + + if (counter > 0) { + QMessageBox::information(0, tr("KeePassXC: Converted KeePassHTTP attributes"), + tr("Successfully converted attributes from %n entry(s).", "", counter), + QMessageBox::Ok); + } else { + QMessageBox::information(0, tr("KeePassXC: No entry with KeePassHTTP attributes found!"), + tr("The active database does not contain an entry with KeePassHTTP attributes."), + QMessageBox::Ok); + } + + // Rename password groupName + Group* rootGroup = db->rootGroup(); + if (!rootGroup) { + 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); + break; + } + } +} + QList BrowserService::sortEntries(QList& pwEntries, const QString& host, const QString& entryUrl) { QUrl url(entryUrl); @@ -717,6 +751,21 @@ Database* BrowserService::getDatabase() return nullptr; } +bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name) const +{ + if (entry->attributes()->contains(name)) { + QString attr = entry->attributes()->value(name); + entry->beginUpdate(); + if (!attr.isEmpty()) { + entry->customData()->set(KEEPASSXCBROWSER_NAME, attr); + } + entry->attributes()->remove(name); + entry->endUpdate(); + return true; + } + return false; +} + void BrowserService::databaseLocked(DatabaseWidget* dbWidget) { if (dbWidget) { diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index 5a96e1ecd2..6e7a88a575 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -38,13 +38,13 @@ class BrowserService : public QObject bool openDatabase(bool triggerUnlock); QString getDatabaseRootUuid(); QString getDatabaseRecycleBinUuid(); - Entry* getConfigEntry(bool create = false); QString getKey(const QString& id); void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm); QList searchEntries(Database* db, const QString& hostname); QList searchEntries(const QString& text); void removeSharedEncryptionKeys(); void removeStoredPermissions(); + void convertAttributesToCustomData(); public slots: QJsonArray findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm); @@ -73,6 +73,7 @@ public slots: bool matchUrlScheme(const QString& url); bool removeFirstDomain(QString& hostname); Database* getDatabase(); + bool moveSettingsToCustomData(Entry* entry, const QString& name) const; private: DatabaseTabWidget* const m_dbTabWidget; diff --git a/src/browser/NativeMessagingHost.cpp b/src/browser/NativeMessagingHost.cpp index 4dfa87d511..9d2c36136c 100755 --- a/src/browser/NativeMessagingHost.cpp +++ b/src/browser/NativeMessagingHost.cpp @@ -193,6 +193,12 @@ void NativeMessagingHost::removeStoredPermissions() m_browserService.removeStoredPermissions(); } +void NativeMessagingHost::convertAttributesToCustomData() +{ + QMutexLocker locker(&m_mutex); + m_browserService.convertAttributesToCustomData(); +} + void NativeMessagingHost::databaseLocked() { QJsonObject response; diff --git a/src/browser/NativeMessagingHost.h b/src/browser/NativeMessagingHost.h index 80825237b3..9db062badb 100755 --- a/src/browser/NativeMessagingHost.h +++ b/src/browser/NativeMessagingHost.h @@ -40,6 +40,7 @@ class NativeMessagingHost : public NativeMessagingBase public slots: void removeSharedEncryptionKeys(); void removeStoredPermissions(); + void convertAttributesToCustomData(); signals: void quit(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 7f732facaa..e7fe40874d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -116,7 +116,9 @@ class HttpPlugin: public ISettingsPage class BrowserPlugin: public ISettingsPage { public: - BrowserPlugin(DatabaseTabWidget* tabWidget) { + BrowserPlugin(DatabaseTabWidget* tabWidget) : + m_dbTabWidget(tabWidget) + { m_nativeMessagingHost = QSharedPointer(new NativeMessagingHost(tabWidget)); } @@ -135,9 +137,10 @@ class BrowserPlugin: public ISettingsPage } QWidget* createWidget() override { - BrowserOptionDialog* dlg = new BrowserOptionDialog(); + BrowserOptionDialog* dlg = new BrowserOptionDialog(m_dbTabWidget); QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_nativeMessagingHost.data(), SLOT(removeSharedEncryptionKeys())); QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_nativeMessagingHost.data(), SLOT(removeStoredPermissions())); + QObject::connect(dlg, SIGNAL(convertAttributesToCustomData()), m_nativeMessagingHost.data(), SLOT(convertAttributesToCustomData())); return dlg; } @@ -156,7 +159,8 @@ class BrowserPlugin: public ISettingsPage } } private: - QSharedPointer m_nativeMessagingHost; + QSharedPointer m_nativeMessagingHost; + DatabaseTabWidget* const m_dbTabWidget; }; #endif @@ -169,7 +173,7 @@ MainWindow::MainWindow() , m_appExiting(false) { m_ui->setupUi(this); - + #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_DBUS) new MainWindowAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); @@ -891,7 +895,7 @@ void MainWindow::updateTrayIcon() connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow())); m_trayIcon->setContextMenu(menu); - + m_trayIcon->setIcon(filePath()->applicationIcon()); m_trayIcon->show(); }