From 1fe75f94209a69acf60879a5a7ea2c1cf4a72610 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Mon, 27 Feb 2017 20:25:56 -0500 Subject: [PATCH 1/2] Add feature to handle references, resolves #75 - Create popup for clone options - Add ability to resolve references for autotype/http/copying --- src/CMakeLists.txt | 2 + src/autotype/AutoType.cpp | 10 ++--- src/core/Entry.cpp | 33 ++++++++++++++++ src/core/Entry.h | 10 +++-- src/core/EntrySearcher.cpp | 8 ++-- src/gui/CloneDialog.cpp | 71 +++++++++++++++++++++++++++++++++++ src/gui/CloneDialog.h | 51 +++++++++++++++++++++++++ src/gui/CloneDialog.ui | 77 ++++++++++++++++++++++++++++++++++++++ src/gui/DatabaseWidget.cpp | 27 +++++++------ src/gui/DatabaseWidget.h | 2 +- src/http/Service.cpp | 2 +- 11 files changed, 264 insertions(+), 29 deletions(-) create mode 100644 src/gui/CloneDialog.cpp create mode 100644 src/gui/CloneDialog.h create mode 100644 src/gui/CloneDialog.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53b62ae75a..94a685737e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,7 @@ set(keepassx_SOURCES gui/CategoryListWidget.cpp gui/ChangeMasterKeyWidget.cpp gui/Clipboard.cpp + gui/CloneDialog.cpp gui/DatabaseOpenWidget.cpp gui/DatabaseRepairWidget.cpp gui/DatabaseSettingsWidget.cpp @@ -131,6 +132,7 @@ set(keepassx_SOURCES_MAINEXE set(keepassx_FORMS gui/AboutDialog.ui gui/ChangeMasterKeyWidget.ui + gui/CloneDialog.ui gui/DatabaseOpenWidget.ui gui/DatabaseSettingsWidget.ui gui/CategoryListWidget.ui diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 40ece6e138..25afccfe9f 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -565,8 +565,8 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->title().isEmpty() - && windowTitle.contains(entry->title(), Qt::CaseInsensitive)) { + if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->resolvePlaceholder(entry->title()).isEmpty() + && windowTitle.contains(entry->resolvePlaceholder(entry->title()), Qt::CaseInsensitive)) { sequence = entry->defaultAutoTypeSequence(); match = true; } @@ -597,11 +597,11 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl group = group->parentGroup(); } while (group && (!enableSet || sequence.isEmpty())); - if (sequence.isEmpty() && (!entry->username().isEmpty() || !entry->password().isEmpty())) { - if (entry->username().isEmpty()) { + if (sequence.isEmpty() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || !entry->resolvePlaceholder(entry->password()).isEmpty())) { + if (entry->resolvePlaceholder(entry->username()).isEmpty()) { sequence = "{PASSWORD}{ENTER}"; } - else if (entry->password().isEmpty()) { + else if (entry->resolvePlaceholder(entry->password()).isEmpty()) { sequence = "{USERNAME}{ENTER}"; } else { diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 162d3f089c..99f119f663 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -494,6 +494,18 @@ Entry* Entry::clone(CloneFlags flags) const entry->m_data = m_data; entry->m_attributes->copyDataFrom(m_attributes); entry->m_attachments->copyDataFrom(m_attachments); + + if (flags & CloneUserAsRef) { + // Build the username refrence + QString username = "{REF:U@I:" + m_uuid.toHex() + "}"; + entry->m_attributes->set(EntryAttributes::UserNameKey, username.toUpper(), m_attributes->isProtected(EntryAttributes::UserNameKey)); + } + + if (flags & ClonePassAsRef) { + QString password = "{REF:P@I:" + m_uuid.toHex() + "}"; + entry->m_attributes->set(EntryAttributes::PasswordKey, password.toUpper(), m_attributes->isProtected(EntryAttributes::PasswordKey)); + } + entry->m_autoTypeAssociations->copyDataFrom(this->m_autoTypeAssociations); if (flags & CloneIncludeHistory) { for (Entry* historyItem : m_history) { @@ -663,5 +675,26 @@ QString Entry::resolvePlaceholder(const QString& str) const } } + // resolving references in format: {REF:@I:} + // using format from http://keepass.info/help/base/fieldrefs.html at the time of writing, + // but supporting lookups of standard fields and references by UUID only + + QRegExp tmpRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2); + if (tmpRegExp.indexIn(result) != -1) { + // cap(0) contains the whole reference + // cap(1) contains which field is wanted + // cap(2) contains the uuid of the referenced entry + Entry* tmpRefEntry = m_group->database()->resolveEntry(Uuid(QByteArray::fromHex(tmpRegExp.cap(2).toLatin1()))); + if (tmpRefEntry) { + // entry found, get the relevant field + QString tmpRefField = tmpRegExp.cap(1).toLower(); + if (tmpRefField == "t") result.replace(tmpRegExp.cap(0), tmpRefEntry->title(), Qt::CaseInsensitive); + else if (tmpRefField == "u") result.replace(tmpRegExp.cap(0), tmpRefEntry->username(), Qt::CaseInsensitive); + else if (tmpRefField == "p") result.replace(tmpRegExp.cap(0), tmpRefEntry->password(), Qt::CaseInsensitive); + else if (tmpRefField == "a") result.replace(tmpRegExp.cap(0), tmpRefEntry->url(), Qt::CaseInsensitive); + else if (tmpRefField == "n") result.replace(tmpRegExp.cap(0), tmpRefEntry->notes(), Qt::CaseInsensitive); + } + } + return result; } diff --git a/src/core/Entry.h b/src/core/Entry.h index ae60b596cb..d08c7217cd 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -113,10 +113,12 @@ class Entry : public QObject enum CloneFlag { CloneNoFlags = 0, - CloneNewUuid = 1, // generate a random uuid for the clone - CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time - CloneIncludeHistory = 4, // clone the history items - CloneRenameTitle = 8 // add "-Clone" after the original title + CloneNewUuid = 1, // generate a random uuid for the clone + CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time + CloneIncludeHistory = 4, // clone the history items + CloneRenameTitle = 8, // add "-Clone" after the original title + CloneUserAsRef = 16, // Add the user as a refrence to the origional entry + ClonePassAsRef = 32, // Add the password as a refrence to the origional entry }; Q_DECLARE_FLAGS(CloneFlags, CloneFlag) diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index 01e152e2ab..df05711ac0 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -68,10 +68,10 @@ QList EntrySearcher::matchEntry(const QString& searchTerm, Entry* entry, bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity) { - return entry->title().contains(word, caseSensitivity) || - entry->username().contains(word, caseSensitivity) || - entry->url().contains(word, caseSensitivity) || - entry->notes().contains(word, caseSensitivity); + return entry->resolvePlaceholder(entry->title()).contains(word, caseSensitivity) || + entry->resolvePlaceholder(entry->username()).contains(word, caseSensitivity) || + entry->resolvePlaceholder(entry->url()).contains(word, caseSensitivity) || + entry->resolvePlaceholder(entry->notes()).contains(word, caseSensitivity); } bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity) diff --git a/src/gui/CloneDialog.cpp b/src/gui/CloneDialog.cpp new file mode 100644 index 0000000000..6c8f83117a --- /dev/null +++ b/src/gui/CloneDialog.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 . + */ + +#include "CloneDialog.h" +#include "ui_CloneDialog.h" + +#include "config-keepassx.h" +#include "version.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/FilePath.h" +#include "crypto/Crypto.h" +#include "gui/DatabaseWidget.h" + +CloneDialog::CloneDialog(DatabaseWidget* parent, Database* db, Entry* entry) + : QDialog(parent) + , m_ui(new Ui::CloneDialog()) +{ + m_db = db; + m_entry = entry; + m_parent = parent; + + m_ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(cloneEntry())); +} + +void CloneDialog::cloneEntry() +{ + Entry::CloneFlags flags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo; + + if (m_ui->titleClone->isChecked()) { + flags |= Entry::CloneRenameTitle; + } + + if (m_ui->referencesClone->isChecked()) { + flags |= Entry::CloneUserAsRef; + flags |= Entry::ClonePassAsRef; + } + + if (m_ui->historyClone->isChecked()) { + flags |= Entry::CloneIncludeHistory; + } + + Entry* entry = m_entry->clone(flags); + entry->setGroup(m_entry->group()); + + emit m_parent->refreshSearch(); + close(); +} + +CloneDialog::~CloneDialog() +{ +} diff --git a/src/gui/CloneDialog.h b/src/gui/CloneDialog.h new file mode 100644 index 0000000000..277da4a825 --- /dev/null +++ b/src/gui/CloneDialog.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_CLONEDIALOG_H +#define KEEPASSX_CLONEDIALOG_H + +#include +#include +#include "core/Entry.h" +#include "core/Database.h" +#include "gui/DatabaseWidget.h" + +namespace Ui { + class CloneDialog; +} + +class CloneDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CloneDialog(DatabaseWidget* parent = nullptr, Database* db = nullptr, Entry* entry = nullptr); + ~CloneDialog(); + +private: + QScopedPointer m_ui; + +private Q_SLOTS: + void cloneEntry(); + +protected: + Database* m_db; + Entry* m_entry; + DatabaseWidget* m_parent; +}; + +#endif // KEEPASSX_CLONEDIALOG_H diff --git a/src/gui/CloneDialog.ui b/src/gui/CloneDialog.ui new file mode 100644 index 0000000000..3fef5222d6 --- /dev/null +++ b/src/gui/CloneDialog.ui @@ -0,0 +1,77 @@ + + + CloneDialog + + + + 0 + 0 + 338 + 120 + + + + Clone Options + + + + + 12 + 12 + 323 + 62 + + + + + + + + 170 + 16777215 + + + + Append ' - Copy' to title + + + true + + + + + + + Replace username and password with references + + + + + + + Copy history + + + true + + + + + + + + + 160 + 90 + 164 + 32 + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 3da461cadc..1cb1882d30 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -41,6 +41,7 @@ #include "format/KeePass2Reader.h" #include "gui/ChangeMasterKeyWidget.h" #include "gui/Clipboard.h" +#include "gui/CloneDialog.h" #include "gui/DatabaseOpenWidget.h" #include "gui/DatabaseSettingsWidget.h" #include "gui/KeePass1OpenWidget.h" @@ -320,11 +321,9 @@ void DatabaseWidget::cloneEntry() return; } - Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle); - entry->setGroup(currentEntry->group()); - refreshSearch(); - m_entryView->setFocus(); - m_entryView->setCurrentEntry(entry); + CloneDialog* cloneDialog = new CloneDialog(this, m_db, currentEntry); + cloneDialog->show(); + return; } void DatabaseWidget::deleteEntries() @@ -408,7 +407,7 @@ void DatabaseWidget::copyTitle() return; } - setClipboardTextAndMinimize(currentEntry->title()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->title())); } void DatabaseWidget::copyUsername() @@ -419,7 +418,7 @@ void DatabaseWidget::copyUsername() return; } - setClipboardTextAndMinimize(currentEntry->username()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->username())); } void DatabaseWidget::copyPassword() @@ -430,7 +429,7 @@ void DatabaseWidget::copyPassword() return; } - setClipboardTextAndMinimize(currentEntry->password()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->password())); } void DatabaseWidget::copyURL() @@ -441,7 +440,7 @@ void DatabaseWidget::copyURL() return; } - setClipboardTextAndMinimize(currentEntry->url()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->url())); } void DatabaseWidget::copyNotes() @@ -452,7 +451,7 @@ void DatabaseWidget::copyNotes() return; } - setClipboardTextAndMinimize(currentEntry->notes()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->notes())); } void DatabaseWidget::copyAttribute(QAction* action) @@ -1172,7 +1171,7 @@ bool DatabaseWidget::currentEntryHasUsername() Q_ASSERT(false); return false; } - return !currentEntry->username().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->username()).isEmpty(); } bool DatabaseWidget::currentEntryHasPassword() @@ -1182,7 +1181,7 @@ bool DatabaseWidget::currentEntryHasPassword() Q_ASSERT(false); return false; } - return !currentEntry->password().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->password()).isEmpty(); } bool DatabaseWidget::currentEntryHasUrl() @@ -1192,7 +1191,7 @@ bool DatabaseWidget::currentEntryHasUrl() Q_ASSERT(false); return false; } - return !currentEntry->url().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->url()).isEmpty(); } bool DatabaseWidget::currentEntryHasNotes() @@ -1202,7 +1201,7 @@ bool DatabaseWidget::currentEntryHasNotes() Q_ASSERT(false); return false; } - return !currentEntry->notes().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->notes()).isEmpty(); } GroupView* DatabaseWidget::groupView() { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 7bd4b6b497..66ece0537f 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -99,6 +99,7 @@ class DatabaseWidget : public QStackedWidget void showUnlockDialog(); void closeUnlockDialog(); void ignoreNextAutoreload(); + void refreshSearch(); Q_SIGNALS: void closeRequest(); @@ -179,7 +180,6 @@ private Q_SLOTS: void setClipboardTextAndMinimize(const QString& text); void setIconFromParent(); void replaceDatabase(Database* db); - void refreshSearch(); Database* m_db; QWidget* m_mainWidget; diff --git a/src/http/Service.cpp b/src/http/Service.cpp index aac5d6b0ad..1a89b73284 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -244,7 +244,7 @@ Service::Access Service::checkAccess(const Entry *entry, const QString & host, c KeepassHttpProtocol::Entry Service::prepareEntry(const Entry* entry) { - KeepassHttpProtocol::Entry res(entry->title(), entry->username(), entry->password(), entry->uuid().toHex()); + KeepassHttpProtocol::Entry res(entry->resolvePlaceholder(entry->title()), entry->resolvePlaceholder(entry->username()), entry->resolvePlaceholder(entry->password()), entry->uuid().toHex()); if (HttpSettings::supportKphFields()) { const EntryAttributes * attr = entry->attributes(); Q_FOREACH (const QString& key, attr->keys()) From 97150034bcc1207eab0b9c198c81e79b86412ee6 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Sat, 4 Mar 2017 19:42:21 -0500 Subject: [PATCH 2/2] Fix clone entry gui test --- tests/gui/TestGui.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 0c776e0218..3893038bac 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -45,6 +45,7 @@ #include "format/KeePass2Reader.h" #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" +#include "gui/CloneDialog.h" #include "gui/FileDialog.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" @@ -563,6 +564,10 @@ void TestGui::testCloneEntry() triggerAction("actionEntryClone"); + CloneDialog* cloneDialog = m_dbWidget->findChild("CloneDialog"); + QDialogButtonBox* cloneButtonBox = cloneDialog->findChild("buttonBox"); + QTest::mouseClick(cloneButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + QCOMPARE(entryView->model()->rowCount(), 2); Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1)); QVERIFY(entryOrg->uuid() != entryClone->uuid());