Add CustomData from KDBX4 to Group and Entry#1477
Add CustomData from KDBX4 to Group and Entry#1477ckieschnick wants to merge 4 commits intokeepassxreboot:developfrom
Conversation
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
src/core/Entry.cpp
Outdated
| return m_attachments; | ||
| } | ||
|
|
||
| CustomData *Entry::customData() |
src/core/Entry.cpp
Outdated
| return m_customData; | ||
| } | ||
|
|
||
| const CustomData *Entry::customData() const |
src/core/Entry.h
Outdated
| EntryAttachments* attachments(); | ||
| const EntryAttachments* attachments() const; | ||
| CustomData *customData(); | ||
| const CustomData *customData() const; |
There was a problem hiding this comment.
CustomData* customData. There's more occurrences of this in the PR, I did not comment on all of them.
| m_data.isExpanded = expanded; | ||
| updateTimeinfo(); | ||
| if (config()->get("IgnoreGroupExpansion").toBool()) { | ||
| updateTimeinfo(); |
There was a problem hiding this comment.
@ckieschnick I'm not even sure we should be updating the time info in that case. @phoerious thoughts?
There was a problem hiding this comment.
I would say that depends on what KeePass does.
There was a problem hiding this comment.
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.
src/format/KdbxXmlReader.cpp
Outdated
| continue; | ||
| } | ||
|
|
||
| if (m_xml.name() == "CustomData" ){ |
src/format/KdbxXmlWriter.cpp
Outdated
|
|
||
| writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry()); | ||
|
|
||
| if (!group->customData()->isEmpty()){ |
There was a problem hiding this comment.
@ckieschnick the check for emptiness could be moved into writeCustomData
| <CustomData> | ||
| <Item> | ||
| <Key>A Sample Test Key</Key> | ||
| <Value>valu</Value> |
There was a problem hiding this comment.
@ckieschnick looks like tabs are used in the rest of the file, maybe it would be better to keep it that way for consistency.
louib
left a comment
There was a problem hiding this comment.
@ckieschnick I added some comments, but it looks good in general. Could not test it though, since I'm blocked by #1472.
src/core/CustomData.cpp
Outdated
| @@ -0,0 +1,156 @@ | |||
| /* | |||
| * Copyright (C) 2012 Felix Geyer <debfx@fobos.de> | |||
There was a problem hiding this comment.
Remove this copyright. debfx is the KeePassX maintainer and not involved in KeePassXC.
src/core/CustomData.cpp
Outdated
| bool addAttribute = !m_data.contains(key); | ||
| bool changeValue = !addAttribute && (m_data.value(key) != value); | ||
|
|
||
| if (addAttribute ) { |
| bool emitModified = false; | ||
|
|
||
| bool addAttribute = !m_data.contains(key); | ||
| bool changeValue = !addAttribute && (m_data.value(key) != value); |
There was a problem hiding this comment.
value() will return a default-constructed element if the key does not exist. Avoid that by checking if the key exists.
There was a problem hiding this comment.
The case is already covered since !addAttributes equals m_data.contains(key) - m_data.value(key) is only called when the key exists.
src/core/CustomData.cpp
Outdated
|
|
||
| if (addAttribute || changeValue) { | ||
| m_data.insert(key, value); | ||
| emitModified = true; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Just emit the signal right away. There's no point in doing it later.
src/core/CustomData.cpp
Outdated
| void CustomData::rename(const QString& oldKey, const QString& newKey) | ||
| { | ||
| if (!m_data.contains(oldKey)) { | ||
| Q_ASSERT(false); |
There was a problem hiding this comment.
Avoid Q_ASSERT(false). Replace with
bool exists = m_data.contains(oldKey);
Q_ASSERT(exists);
if (!exists) {
return;
]
src/gui/EditWidgetProperties.h
Outdated
|
|
||
| void setFields(TimeInfo timeInfo, Uuid uuid); | ||
| void setFields(const TimeInfo &timeInfo, const Uuid &uuid); | ||
| void setCustomData(const CustomData *customData); |
There was a problem hiding this comment.
const TimeInfo&
const Uuid&
CustomData*
tests/TestKeePass2Format.cpp
Outdated
| 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" ); |
tests/TestModified.cpp
Outdated
| entry->endUpdate(); | ||
| QCOMPARE(entry->historyItems().size(), ++historyItemsSize); | ||
| historyEntry = entry->historyItems().at(historyItemsSize - 1); | ||
| Entry *historyEntry = entry->historyItems().at(historyItemsSize - 1); |
tests/TestModified.cpp
Outdated
| void TestModified::testCustomData() | ||
| { | ||
| int spyCount = 0; | ||
| Database* db = new Database(); |
There was a problem hiding this comment.
Use QScopedPointer or allocate on the stack
tests/TestModified.cpp
Outdated
|
|
||
| Group* group = new Group(); | ||
| group->setParent(root); | ||
| Entry* entry = new Entry(); |
There was a problem hiding this comment.
auto* for all these to avoid class name repetition.
|
|
||
| void CustomData::copyDataFrom(const CustomData* other) | ||
| { | ||
| if (*this != *other) { |
There was a problem hiding this comment.
@ckieschnick is this really a possibility? shouldn't we assert this doesn't happen instead?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
| } | ||
|
|
||
| void KdbxXmlReader::parseCustomData() | ||
| void KdbxXmlReader::parseCustomData(CustomData *customData) |
| const QString key = index.data().toString(); | ||
| m_customData->remove(key); | ||
| } | ||
| this->updateModel(); |
There was a problem hiding this comment.
Also remove the explicit this pointer (I might have missed that last time).
|
Since I can't push to the private source repository, please continue here: #1494 |
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
Checklist:
-DWITH_ASAN=ON. [REQUIRED]