mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge pull request #370 from mrrsm/feature/references
Add feature to handle field references, resolves #75
This commit is contained in:
commit
1e1428c73d
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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:<WantedField>@I:<uuid of referenced entry>}
|
||||
// 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;
|
||||
}
|
||||
|
@ -113,10 +113,12 @@ public:
|
||||
|
||||
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)
|
||||
|
||||
|
@ -68,10 +68,10 @@ QList<Entry*> 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)
|
||||
|
71
src/gui/CloneDialog.cpp
Normal file
71
src/gui/CloneDialog.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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 "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()
|
||||
{
|
||||
}
|
51
src/gui/CloneDialog.h
Normal file
51
src/gui/CloneDialog.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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 KEEPASSX_CLONEDIALOG_H
|
||||
#define KEEPASSX_CLONEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
#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<Ui::CloneDialog> m_ui;
|
||||
|
||||
private Q_SLOTS:
|
||||
void cloneEntry();
|
||||
|
||||
protected:
|
||||
Database* m_db;
|
||||
Entry* m_entry;
|
||||
DatabaseWidget* m_parent;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_CLONEDIALOG_H
|
77
src/gui/CloneDialog.ui
Normal file
77
src/gui/CloneDialog.ui
Normal file
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CloneDialog</class>
|
||||
<widget class="QDialog" name="CloneDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>338</width>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Clone Options</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="formLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>12</x>
|
||||
<y>12</y>
|
||||
<width>323</width>
|
||||
<height>62</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="titleClone">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>170</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Append ' - Copy' to title</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="referencesClone">
|
||||
<property name="text">
|
||||
<string>Replace username and password with references</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="historyClone">
|
||||
<property name="text">
|
||||
<string>Copy history</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>160</x>
|
||||
<y>90</y>
|
||||
<width>164</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -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() {
|
||||
|
@ -99,6 +99,7 @@ public:
|
||||
void showUnlockDialog();
|
||||
void closeUnlockDialog();
|
||||
void ignoreNextAutoreload();
|
||||
void refreshSearch();
|
||||
|
||||
Q_SIGNALS:
|
||||
void closeRequest();
|
||||
@ -179,7 +180,6 @@ private:
|
||||
void setClipboardTextAndMinimize(const QString& text);
|
||||
void setIconFromParent();
|
||||
void replaceDatabase(Database* db);
|
||||
void refreshSearch();
|
||||
|
||||
Database* m_db;
|
||||
QWidget* m_mainWidget;
|
||||
|
@ -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())
|
||||
|
@ -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*>("CloneDialog");
|
||||
QDialogButtonBox* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("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());
|
||||
|
Loading…
Reference in New Issue
Block a user