Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CustomData from KDBX4 to Group and Entry #1477

Closed

Conversation

ckieschnick
Copy link
Contributor

Added support for CustomData from KDBX 4 format in Entry and Group.

Description

Added a CustomData-class to manage custom fields from Metadata, Groups and Entries. Update of timestamps by changing CustomData is implemented through forwarding the modified signal and the self-update-timestamp mechanism which was previously only used in Entry.
Management of CustomData for Group and Entry is similar to KeePass which allows to delete the attributes within the Edit dialog

Motivation and context

Change is required since opening and storing a valid kdbx4-database from KeePass should not loose information. The format version 4 requires to load and store custom data at entries and groups.

How has this been tested?

Added tests to the Testsuite and extended the NewDatabase.xml fixture to contain CustomData at Metadata, Group, Entry and History level.

Types of changes

  • ✅ Bug fix (non-breaking change which fixes an issue)

Checklist:

  • ✅ I have read the CONTRIBUTING document. [REQUIRED]
  • ✅ My code follows the code style of this project. [REQUIRED]
  • ✅ All new and existing tests passed. [REQUIRED]
  • ✅ I have compiled and verified my code with -DWITH_ASAN=ON. [REQUIRED]
  • ✅ I have added tests to cover my changes.

Christian Kieschnick added 3 commits February 12, 2018 12:37
Introduce missing CustomData-attributes of KDBX4 format to allow
storing of plugin data for groups and entries - adopt Metadata to use
the same storage mechanism
Add simple view for CustomData as part of EditWidgetProperties
Tracking of CustomData-Modification using SIGNAL-SLOT update-mechanism
Small improvements to existing tests
Create test to check modification of CustomData in Entry, Group and Metadata
HistoryItem size calculation uses the correct CustomData object
@@ -340,6 +342,16 @@ const EntryAttachments* Entry::attachments() const
return m_attachments;
}

CustomData *Entry::customData()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomData* Entry

return m_customData;
}

const CustomData *Entry::customData() const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomData* Entry

src/core/Entry.h Outdated
@@ -107,6 +108,8 @@ class Entry : public QObject
const EntryAttributes* attributes() const;
EntryAttachments* attachments();
const EntryAttachments* attachments() const;
CustomData *customData();
const CustomData *customData() const;
Copy link
Member

@louib louib Feb 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomData* customData. There's more occurrences of this in the PR, I did not comment on all of them.

if (config()->get("IgnoreGroupExpansion").toBool()) {
updateTimeinfo();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckieschnick I'm not even sure we should be updating the time info in that case. @phoerious thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that depends on what KeePass does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite sure if it is required by KeePass - the change was necessary due to retain the behavior after changing the timestamp update to use the self-listen mechanism with modified-signal (which is explicitly not emitted when "IgnoreGroupExpansion" is on). I think, if the behavior deviates from KeePass, it should be fixed separately.

@@ -743,7 +747,10 @@ Entry* KdbxXmlReader::parseEntry(bool history)
}
continue;
}

if (m_xml.name() == "CustomData" ){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckieschnick space before {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And no space before )

@@ -277,6 +276,10 @@ void KdbxXmlWriter::writeGroup(const Group* group)

writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry());

if (!group->customData()->isEmpty()){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckieschnick the check for emptiness could be moved into writeCustomData

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space before {

@@ -41,12 +41,12 @@
</Binaries>
<CustomData>
<Item>
<Key>A Sample Test Key</Key>
<Value>valu</Value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckieschnick looks like tabs are used in the rest of the file, maybe it would be better to keep it that way for consistency.

Copy link
Member

@louib louib left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckieschnick I added some comments, but it looks good in general. Could not test it though, since I'm blocked by #1472.

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
Copy link
Member

@phoerious phoerious Feb 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this copyright. debfx is the KeePassX maintainer and not involved in KeePassXC.

bool addAttribute = !m_data.contains(key);
bool changeValue = !addAttribute && (m_data.value(key) != value);

if (addAttribute ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove space before )

bool emitModified = false;

bool addAttribute = !m_data.contains(key);
bool changeValue = !addAttribute && (m_data.value(key) != value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value() will return a default-constructed element if the key does not exist. Avoid that by checking if the key exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case is already covered since !addAttributes equals m_data.contains(key) - m_data.value(key) is only called when the key exists.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True.


if (addAttribute || changeValue) {
m_data.insert(key, value);
emitModified = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not emit directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I derived the code from an existing class (I think it was EntryAttributes) to reduce style issues. This part was already part of the existing class, so I took it for a code convention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just emit the signal right away. There's no point in doing it later.

void CustomData::rename(const QString& oldKey, const QString& newKey)
{
if (!m_data.contains(oldKey)) {
Q_ASSERT(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid Q_ASSERT(false). Replace with

bool exists = m_data.contains(oldKey);
Q_ASSERT(exists);
if (!exists) {
    return;
]

@@ -35,11 +38,21 @@ class EditWidgetProperties : public QWidget
explicit EditWidgetProperties(QWidget* parent = nullptr);
~EditWidgetProperties();

void setFields(TimeInfo timeInfo, Uuid uuid);
void setFields(const TimeInfo &timeInfo, const Uuid &uuid);
void setCustomData(const CustomData *customData);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const TimeInfo&
const Uuid&
CustomData*

@@ -53,6 +53,7 @@ void TestKeePass2Format::initTestCase()
entry->setPassword(QString::fromUtf8("\xc3\xa4\xa3\xb6\xc3\xbc\xe9\x9b\xbb\xe7\xb4\x85"));
entry->setUuid(Uuid::random());
entry->attributes()->set("test", "protectedTest", true);
entry->customData()->set("CustomDataEntryKey", "CustomDataValue" );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove space before )

@@ -315,7 +313,7 @@ void TestModified::testHistoryItems()
entry->setTitle("b");
entry->endUpdate();
QCOMPARE(entry->historyItems().size(), ++historyItemsSize);
historyEntry = entry->historyItems().at(historyItemsSize - 1);
Entry *historyEntry = entry->historyItems().at(historyItemsSize - 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto*

void TestModified::testCustomData()
{
int spyCount = 0;
Database* db = new Database();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use QScopedPointer or allocate on the stack


Group* group = new Group();
group->setParent(root);
Entry* entry = new Entry();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto* for all these to avoid class name repetition.


void CustomData::copyDataFrom(const CustomData* other)
{
if (*this != *other) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckieschnick is this really a possibility? shouldn't we assert this doesn't happen instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not that familiar with the KeePassXC code. Skimming the code, I think an assert would be correct - but the check it is a pattern in different data classes (EntryAttributes, EntryAttachments) which suggests that there is or there was a case of self assignment (since CustomData are used in Entry, Group and Metadata the risk is even higher). So I pass the question back to you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add an assert in addition to the check. The assert is for debug purposes only.

Fixed several code style issues (whitespace, position of * and &, assert
style)
Added several QPointer and QScopedPointer where appropriate
@@ -397,20 +397,20 @@ void KdbxXmlReader::parseBinaries()
}
}

void KdbxXmlReader::parseCustomData()
void KdbxXmlReader::parseCustomData(CustomData *customData)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You missed a few of these.

const QString key = index.data().toString();
m_customData->remove(key);
}
this->updateModel();
Copy link
Member

@phoerious phoerious Feb 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also remove the explicit this pointer (I might have missed that last time).

@phoerious
Copy link
Member

Since I can't push to the private source repository, please continue here: #1494

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants