Merge branch 'develop' into feature/yubikey

This commit is contained in:
Janek Bevendorff 2017-03-06 13:49:48 +01:00
commit 3c1271b1c4
No known key found for this signature in database
GPG key ID: CFEC2F6850BFFA53
13 changed files with 280 additions and 33 deletions

View file

@ -59,7 +59,7 @@ To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassH
### Contributing ### Contributing
We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or write to our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum. We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
You can of course also directly contribute your own code. We are happy to accept your pull requests. You can of course also directly contribute your own code. We are happy to accept your pull requests.

View file

@ -73,6 +73,7 @@ set(keepassx_SOURCES
gui/CategoryListWidget.cpp gui/CategoryListWidget.cpp
gui/ChangeMasterKeyWidget.cpp gui/ChangeMasterKeyWidget.cpp
gui/Clipboard.cpp gui/Clipboard.cpp
gui/CloneDialog.cpp
gui/DatabaseOpenWidget.cpp gui/DatabaseOpenWidget.cpp
gui/DatabaseRepairWidget.cpp gui/DatabaseRepairWidget.cpp
gui/DatabaseSettingsWidget.cpp gui/DatabaseSettingsWidget.cpp
@ -133,6 +134,7 @@ set(keepassx_SOURCES_MAINEXE
set(keepassx_FORMS set(keepassx_FORMS
gui/AboutDialog.ui gui/AboutDialog.ui
gui/ChangeMasterKeyWidget.ui gui/ChangeMasterKeyWidget.ui
gui/CloneDialog.ui
gui/DatabaseOpenWidget.ui gui/DatabaseOpenWidget.ui
gui/DatabaseSettingsWidget.ui gui/DatabaseSettingsWidget.ui
gui/CategoryListWidget.ui gui/CategoryListWidget.ui

View file

@ -565,8 +565,8 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl
} }
} }
if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->title().isEmpty() if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->resolvePlaceholder(entry->title()).isEmpty()
&& windowTitle.contains(entry->title(), Qt::CaseInsensitive)) { && windowTitle.contains(entry->resolvePlaceholder(entry->title()), Qt::CaseInsensitive)) {
sequence = entry->defaultAutoTypeSequence(); sequence = entry->defaultAutoTypeSequence();
match = true; match = true;
} }
@ -597,11 +597,11 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl
group = group->parentGroup(); group = group->parentGroup();
} while (group && (!enableSet || sequence.isEmpty())); } while (group && (!enableSet || sequence.isEmpty()));
if (sequence.isEmpty() && (!entry->username().isEmpty() || !entry->password().isEmpty())) { if (sequence.isEmpty() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || !entry->resolvePlaceholder(entry->password()).isEmpty())) {
if (entry->username().isEmpty()) { if (entry->resolvePlaceholder(entry->username()).isEmpty()) {
sequence = "{PASSWORD}{ENTER}"; sequence = "{PASSWORD}{ENTER}";
} }
else if (entry->password().isEmpty()) { else if (entry->resolvePlaceholder(entry->password()).isEmpty()) {
sequence = "{USERNAME}{ENTER}"; sequence = "{USERNAME}{ENTER}";
} }
else { else {

View file

@ -494,6 +494,18 @@ Entry* Entry::clone(CloneFlags flags) const
entry->m_data = m_data; entry->m_data = m_data;
entry->m_attributes->copyDataFrom(m_attributes); entry->m_attributes->copyDataFrom(m_attributes);
entry->m_attachments->copyDataFrom(m_attachments); 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); entry->m_autoTypeAssociations->copyDataFrom(this->m_autoTypeAssociations);
if (flags & CloneIncludeHistory) { if (flags & CloneIncludeHistory) {
for (Entry* historyItem : m_history) { 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; return result;
} }

View file

@ -116,7 +116,9 @@ public:
CloneNewUuid = 1, // generate a random uuid for the clone CloneNewUuid = 1, // generate a random uuid for the clone
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
CloneIncludeHistory = 4, // clone the history items CloneIncludeHistory = 4, // clone the history items
CloneRenameTitle = 8 // add "-Clone" after the original title 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) Q_DECLARE_FLAGS(CloneFlags, CloneFlag)

View file

@ -68,10 +68,10 @@ QList<Entry*> EntrySearcher::matchEntry(const QString& searchTerm, Entry* entry,
bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity) bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity)
{ {
return entry->title().contains(word, caseSensitivity) || return entry->resolvePlaceholder(entry->title()).contains(word, caseSensitivity) ||
entry->username().contains(word, caseSensitivity) || entry->resolvePlaceholder(entry->username()).contains(word, caseSensitivity) ||
entry->url().contains(word, caseSensitivity) || entry->resolvePlaceholder(entry->url()).contains(word, caseSensitivity) ||
entry->notes().contains(word, caseSensitivity); entry->resolvePlaceholder(entry->notes()).contains(word, caseSensitivity);
} }
bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity) bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)

71
src/gui/CloneDialog.cpp Normal file
View 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
View 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
View 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>

View file

@ -41,6 +41,7 @@
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "gui/ChangeMasterKeyWidget.h" #include "gui/ChangeMasterKeyWidget.h"
#include "gui/Clipboard.h" #include "gui/Clipboard.h"
#include "gui/CloneDialog.h"
#include "gui/DatabaseOpenWidget.h" #include "gui/DatabaseOpenWidget.h"
#include "gui/DatabaseSettingsWidget.h" #include "gui/DatabaseSettingsWidget.h"
#include "gui/KeePass1OpenWidget.h" #include "gui/KeePass1OpenWidget.h"
@ -320,12 +321,9 @@ void DatabaseWidget::cloneEntry()
return; return;
} }
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle); CloneDialog* cloneDialog = new CloneDialog(this, m_db, currentEntry);
entry->setGroup(currentEntry->group()); cloneDialog->show();
if (isInSearchMode()) return;
search(m_lastSearchText);
m_entryView->setFocus();
m_entryView->setCurrentEntry(entry);
} }
void DatabaseWidget::deleteEntries() void DatabaseWidget::deleteEntries()
@ -366,6 +364,7 @@ void DatabaseWidget::deleteEntries()
for (Entry* entry : asConst(selectedEntries)) { for (Entry* entry : asConst(selectedEntries)) {
delete entry; delete entry;
} }
refreshSearch();
} }
} }
else { else {
@ -408,7 +407,7 @@ void DatabaseWidget::copyTitle()
return; return;
} }
setClipboardTextAndMinimize(currentEntry->title()); setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->title()));
} }
void DatabaseWidget::copyUsername() void DatabaseWidget::copyUsername()
@ -419,7 +418,7 @@ void DatabaseWidget::copyUsername()
return; return;
} }
setClipboardTextAndMinimize(currentEntry->username()); setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->username()));
} }
void DatabaseWidget::copyPassword() void DatabaseWidget::copyPassword()
@ -430,7 +429,7 @@ void DatabaseWidget::copyPassword()
return; return;
} }
setClipboardTextAndMinimize(currentEntry->password()); setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->password()));
} }
void DatabaseWidget::copyURL() void DatabaseWidget::copyURL()
@ -441,7 +440,7 @@ void DatabaseWidget::copyURL()
return; return;
} }
setClipboardTextAndMinimize(currentEntry->url()); setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->url()));
} }
void DatabaseWidget::copyNotes() void DatabaseWidget::copyNotes()
@ -452,7 +451,7 @@ void DatabaseWidget::copyNotes()
return; return;
} }
setClipboardTextAndMinimize(currentEntry->notes()); setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->notes()));
} }
void DatabaseWidget::copyAttribute(QAction* action) void DatabaseWidget::copyAttribute(QAction* action)
@ -875,6 +874,12 @@ void DatabaseWidget::databaseSaved()
m_databaseModified = false; m_databaseModified = false;
} }
void DatabaseWidget::refreshSearch() {
if (isInSearchMode()) {
search(m_lastSearchText);
}
}
void DatabaseWidget::search(const QString& searchtext) void DatabaseWidget::search(const QString& searchtext)
{ {
if (searchtext.isEmpty()) if (searchtext.isEmpty())
@ -908,9 +913,7 @@ void DatabaseWidget::search(const QString& searchtext)
void DatabaseWidget::setSearchCaseSensitive(bool state) void DatabaseWidget::setSearchCaseSensitive(bool state)
{ {
m_searchCaseSensitive = state; m_searchCaseSensitive = state;
refreshSearch();
if (isInSearchMode())
search(m_lastSearchText);
} }
void DatabaseWidget::onGroupChanged(Group* group) void DatabaseWidget::onGroupChanged(Group* group)
@ -1173,7 +1176,7 @@ bool DatabaseWidget::currentEntryHasUsername()
Q_ASSERT(false); Q_ASSERT(false);
return false; return false;
} }
return !currentEntry->username().isEmpty(); return !currentEntry->resolvePlaceholder(currentEntry->username()).isEmpty();
} }
bool DatabaseWidget::currentEntryHasPassword() bool DatabaseWidget::currentEntryHasPassword()
@ -1183,7 +1186,7 @@ bool DatabaseWidget::currentEntryHasPassword()
Q_ASSERT(false); Q_ASSERT(false);
return false; return false;
} }
return !currentEntry->password().isEmpty(); return !currentEntry->resolvePlaceholder(currentEntry->password()).isEmpty();
} }
bool DatabaseWidget::currentEntryHasUrl() bool DatabaseWidget::currentEntryHasUrl()
@ -1193,7 +1196,7 @@ bool DatabaseWidget::currentEntryHasUrl()
Q_ASSERT(false); Q_ASSERT(false);
return false; return false;
} }
return !currentEntry->url().isEmpty(); return !currentEntry->resolvePlaceholder(currentEntry->url()).isEmpty();
} }
bool DatabaseWidget::currentEntryHasNotes() bool DatabaseWidget::currentEntryHasNotes()
@ -1203,7 +1206,7 @@ bool DatabaseWidget::currentEntryHasNotes()
Q_ASSERT(false); Q_ASSERT(false);
return false; return false;
} }
return !currentEntry->notes().isEmpty(); return !currentEntry->resolvePlaceholder(currentEntry->notes()).isEmpty();
} }
GroupView* DatabaseWidget::groupView() { GroupView* DatabaseWidget::groupView() {

View file

@ -99,6 +99,7 @@ public:
void showUnlockDialog(); void showUnlockDialog();
void closeUnlockDialog(); void closeUnlockDialog();
void blockAutoReload(bool block = true); void blockAutoReload(bool block = true);
void refreshSearch();
signals: signals:
void closeRequest(); void closeRequest();
@ -147,10 +148,12 @@ public slots:
void switchToImportKeepass1(const QString& fileName); void switchToImportKeepass1(const QString& fileName);
void databaseModified(); void databaseModified();
void databaseSaved(); void databaseSaved();
// Search related slots // Search related slots
void search(const QString& searchtext); void search(const QString& searchtext);
void setSearchCaseSensitive(bool state); void setSearchCaseSensitive(bool state);
void endSearch(); void endSearch();
void showMessage(const QString& text, MessageWidget::MessageType type); void showMessage(const QString& text, MessageWidget::MessageType type);
void hideMessage(); void hideMessage();

View file

@ -244,7 +244,7 @@ Service::Access Service::checkAccess(const Entry *entry, const QString & host, c
KeepassHttpProtocol::Entry Service::prepareEntry(const Entry* entry) 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()) { if (HttpSettings::supportKphFields()) {
const EntryAttributes * attr = entry->attributes(); const EntryAttributes * attr = entry->attributes();
Q_FOREACH (const QString& key, attr->keys()) Q_FOREACH (const QString& key, attr->keys())

View file

@ -45,6 +45,7 @@
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "gui/DatabaseTabWidget.h" #include "gui/DatabaseTabWidget.h"
#include "gui/DatabaseWidget.h" #include "gui/DatabaseWidget.h"
#include "gui/CloneDialog.h"
#include "gui/FileDialog.h" #include "gui/FileDialog.h"
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
@ -563,6 +564,10 @@ void TestGui::testCloneEntry()
triggerAction("actionEntryClone"); 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); QCOMPARE(entryView->model()->rowCount(), 2);
Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1)); Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1));
QVERIFY(entryOrg->uuid() != entryClone->uuid()); QVERIFY(entryOrg->uuid() != entryClone->uuid());