Implement KDBX 4.1 PreviousParentGroup flag

This commit is contained in:
Janek Bevendorff 2021-11-09 23:52:54 +01:00
parent ffaeac130f
commit cd9ef58e98
10 changed files with 200 additions and 18 deletions

View File

@ -1169,7 +1169,7 @@ const Group* Entry::group() const
return m_group; return m_group;
} }
void Entry::setGroup(Group* group) void Entry::setGroup(Group* group, bool trackPrevious)
{ {
Q_ASSERT(group); Q_ASSERT(group);
@ -1180,6 +1180,7 @@ void Entry::setGroup(Group* group)
if (m_group) { if (m_group) {
m_group->removeEntry(this); m_group->removeEntry(this);
if (m_group->database() && m_group->database() != group->database()) { if (m_group->database() && m_group->database() != group->database()) {
setPreviousParentGroup(nullptr);
m_group->database()->addDeletedObject(m_uuid); m_group->database()->addDeletedObject(m_uuid);
// copy custom icon to the new database // copy custom icon to the new database
@ -1188,6 +1189,8 @@ void Entry::setGroup(Group* group)
group->database()->metadata()->addCustomIcon(iconUuid(), group->database()->metadata()->addCustomIcon(iconUuid(),
m_group->database()->metadata()->customIcon(iconUuid())); m_group->database()->metadata()->customIcon(iconUuid()));
} }
} else if (trackPrevious && m_group->database() && group != m_group) {
setPreviousParentGroup(m_group);
} }
} }
@ -1375,7 +1378,30 @@ QString Entry::resolveUrl(const QString& url) const
} }
// No valid http URL's found // No valid http URL's found
return QString(""); return {};
}
const Group* Entry::previousParentGroup() const
{
if (!database() || !database()->rootGroup()) {
return nullptr;
}
return database()->rootGroup()->findGroupByUuid(m_data.previousParentGroupUuid);
}
QUuid Entry::previousParentGroupUuid() const
{
return m_data.previousParentGroupUuid;
}
void Entry::setPreviousParentGroupUuid(const QUuid& uuid)
{
set(m_data.previousParentGroupUuid, uuid);
}
void Entry::setPreviousParentGroup(const Group* group)
{
setPreviousParentGroupUuid(group ? group->uuid() : QUuid());
} }
bool EntryData::operator==(const EntryData& other) const bool EntryData::operator==(const EntryData& other) const
@ -1438,6 +1464,9 @@ bool EntryData::equals(const EntryData& other, CompareItemOptions options) const
if (::compare(excludeFromReports, other.excludeFromReports, options) != 0) { if (::compare(excludeFromReports, other.excludeFromReports, options) != 0) {
return false; return false;
} }
if (::compare(previousParentGroupUuid, other.previousParentGroupUuid, options) != 0) {
return false;
}
return true; return true;
} }

View File

@ -66,6 +66,7 @@ struct EntryData
QSharedPointer<Totp::Settings> totpSettings; QSharedPointer<Totp::Settings> totpSettings;
QSharedPointer<PasswordHealth> passwordHealth; QSharedPointer<PasswordHealth> passwordHealth;
bool excludeFromReports; bool excludeFromReports;
QUuid previousParentGroupUuid;
bool operator==(const EntryData& other) const; bool operator==(const EntryData& other) const;
bool operator!=(const EntryData& other) const; bool operator!=(const EntryData& other) const;
@ -106,6 +107,8 @@ public:
QString totp() const; QString totp() const;
QString totpSettingsString() const; QString totpSettingsString() const;
QSharedPointer<Totp::Settings> totpSettings() const; QSharedPointer<Totp::Settings> totpSettings() const;
const Group* previousParentGroup() const;
QUuid previousParentGroupUuid() const;
int size() const; int size() const;
QString path() const; QString path() const;
const QSharedPointer<PasswordHealth>& passwordHealth(); const QSharedPointer<PasswordHealth>& passwordHealth();
@ -147,6 +150,8 @@ public:
void setExpires(const bool& value); void setExpires(const bool& value);
void setExpiryTime(const QDateTime& dateTime); void setExpiryTime(const QDateTime& dateTime);
void setTotp(QSharedPointer<Totp::Settings> settings); void setTotp(QSharedPointer<Totp::Settings> settings);
void setPreviousParentGroup(const Group* group);
void setPreviousParentGroupUuid(const QUuid& uuid);
QList<Entry*> historyItems(); QList<Entry*> historyItems();
const QList<Entry*>& historyItems() const; const QList<Entry*>& historyItems() const;
@ -243,7 +248,7 @@ public:
Group* group(); Group* group();
const Group* group() const; const Group* group() const;
void setGroup(Group* group); void setGroup(Group* group, bool trackPrevious = true);
const Database* database() const; const Database* database() const;
Database* database(); Database* database();

View File

@ -403,7 +403,7 @@ const Group* Group::parentGroup() const
return m_parent; return m_parent;
} }
void Group::setParent(Group* parent, int index) void Group::setParent(Group* parent, int index, bool trackPrevious)
{ {
Q_ASSERT(parent); Q_ASSERT(parent);
Q_ASSERT(index >= -1 && index <= parent->children().size()); Q_ASSERT(index >= -1 && index <= parent->children().size());
@ -428,6 +428,7 @@ void Group::setParent(Group* parent, int index)
cleanupParent(); cleanupParent();
m_parent = parent; m_parent = parent;
if (m_db) { if (m_db) {
setPreviousParentGroup(nullptr);
recCreateDelObjects(); recCreateDelObjects();
// copy custom icon to the new database // copy custom icon to the new database
@ -445,6 +446,9 @@ void Group::setParent(Group* parent, int index)
parent->m_children.insert(index, this); parent->m_children.insert(index, this);
} else { } else {
emit aboutToMove(this, parent, index); emit aboutToMove(this, parent, index);
if (trackPrevious && m_parent != parent) {
setPreviousParentGroup(m_parent);
}
m_parent->m_children.removeAll(this); m_parent->m_children.removeAll(this);
m_parent = parent; m_parent = parent;
QObject::setParent(parent); QObject::setParent(parent);
@ -585,7 +589,7 @@ Entry* Group::findEntryByUuid(const QUuid& uuid, bool recursive) const
return nullptr; return nullptr;
} }
Entry* Group::findEntryByPath(const QString& entryPath) Entry* Group::findEntryByPath(const QString& entryPath) const
{ {
if (entryPath.isEmpty()) { if (entryPath.isEmpty()) {
return nullptr; return nullptr;
@ -647,7 +651,7 @@ Entry* Group::findEntryBySearchTerm(const QString& term, EntryReferenceType refe
return nullptr; return nullptr;
} }
Entry* Group::findEntryByPathRecursive(const QString& entryPath, const QString& basePath) Entry* Group::findEntryByPathRecursive(const QString& entryPath, const QString& basePath) const
{ {
// Return the first entry that matches the full path OR if there is no leading // Return the first entry that matches the full path OR if there is no leading
// slash, return the first entry title that matches // slash, return the first entry title that matches
@ -843,6 +847,21 @@ Group* Group::findGroupByUuid(const QUuid& uuid)
return nullptr; return nullptr;
} }
const Group* Group::findGroupByUuid(const QUuid& uuid) const
{
if (uuid.isNull()) {
return nullptr;
}
for (const Group* group : groupsRecursive(true)) {
if (group->uuid() == uuid) {
return group;
}
}
return nullptr;
}
Group* Group::findChildByName(const QString& name) Group* Group::findChildByName(const QString& name)
{ {
for (Group* group : asConst(m_children)) { for (Group* group : asConst(m_children)) {
@ -1168,6 +1187,29 @@ void Group::sortChildrenRecursively(bool reverse)
emitModified(); emitModified();
} }
const Group* Group::previousParentGroup() const
{
if (!database() || !database()->rootGroup()) {
return nullptr;
}
return database()->rootGroup()->findGroupByUuid(m_data.previousParentGroupUuid);
}
QUuid Group::previousParentGroupUuid() const
{
return m_data.previousParentGroupUuid;
}
void Group::setPreviousParentGroupUuid(const QUuid& uuid)
{
set(m_data.previousParentGroupUuid, uuid);
}
void Group::setPreviousParentGroup(const Group* group)
{
setPreviousParentGroupUuid(group ? group->uuid() : QUuid());
}
bool Group::GroupData::operator==(const Group::GroupData& other) const bool Group::GroupData::operator==(const Group::GroupData& other) const
{ {
return equals(other, CompareItemDefault); return equals(other, CompareItemDefault);

View File

@ -69,6 +69,7 @@ public:
Group::TriState autoTypeEnabled; Group::TriState autoTypeEnabled;
Group::TriState searchingEnabled; Group::TriState searchingEnabled;
Group::MergeMode mergeMode; Group::MergeMode mergeMode;
QUuid previousParentGroupUuid;
bool operator==(const GroupData& other) const; bool operator==(const GroupData& other) const;
bool operator!=(const GroupData& other) const; bool operator!=(const GroupData& other) const;
@ -101,6 +102,8 @@ public:
const CustomData* customData() const; const CustomData* customData() const;
Group::TriState resolveCustomDataTriState(const QString& key, bool checkParent = true) const; Group::TriState resolveCustomDataTriState(const QString& key, bool checkParent = true) const;
void setCustomDataTriState(const QString& key, const Group::TriState& value); void setCustomDataTriState(const QString& key, const Group::TriState& value);
const Group* previousParentGroup() const;
QUuid previousParentGroupUuid() const;
bool equals(const Group* other, CompareItemOptions options) const; bool equals(const Group* other, CompareItemOptions options) const;
@ -110,9 +113,10 @@ public:
Group* findChildByName(const QString& name); Group* findChildByName(const QString& name);
Entry* findEntryByUuid(const QUuid& uuid, bool recursive = true) const; Entry* findEntryByUuid(const QUuid& uuid, bool recursive = true) const;
Entry* findEntryByPath(const QString& entryPath); Entry* findEntryByPath(const QString& entryPath) const;
Entry* findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType); Entry* findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType);
Group* findGroupByUuid(const QUuid& uuid); Group* findGroupByUuid(const QUuid& uuid);
const Group* findGroupByUuid(const QUuid& uuid) const;
Group* findGroupByPath(const QString& groupPath); Group* findGroupByPath(const QString& groupPath);
Entry* addEntryWithPath(const QString& entryPath); Entry* addEntryWithPath(const QString& entryPath);
void setUuid(const QUuid& uuid); void setUuid(const QUuid& uuid);
@ -129,13 +133,15 @@ public:
void setExpires(bool value); void setExpires(bool value);
void setExpiryTime(const QDateTime& dateTime); void setExpiryTime(const QDateTime& dateTime);
void setMergeMode(MergeMode newMode); void setMergeMode(MergeMode newMode);
void setPreviousParentGroup(const Group* group);
void setPreviousParentGroupUuid(const QUuid& uuid);
bool canUpdateTimeinfo() const; bool canUpdateTimeinfo() const;
void setUpdateTimeinfo(bool value); void setUpdateTimeinfo(bool value);
Group* parentGroup(); Group* parentGroup();
const Group* parentGroup() const; const Group* parentGroup() const;
void setParent(Group* parent, int index = -1); void setParent(Group* parent, int index = -1, bool trackPrevious = true);
QStringList hierarchy(int height = -1) const; QStringList hierarchy(int height = -1) const;
bool hasChildren() const; bool hasChildren() const;
@ -203,7 +209,7 @@ private:
void cleanupParent(); void cleanupParent();
void recCreateDelObjects(); void recCreateDelObjects();
Entry* findEntryByPathRecursive(const QString& entryPath, const QString& basePath); Entry* findEntryByPathRecursive(const QString& entryPath, const QString& basePath) const;
Group* findGroupByPathRecursive(const QString& groupPath, const QString& basePath); Group* findGroupByPathRecursive(const QString& groupPath, const QString& basePath);
QPointer<Database> m_db; QPointer<Database> m_db;
@ -221,7 +227,7 @@ private:
friend void Database::setRootGroup(Group* group); friend void Database::setRootGroup(Group* group);
friend Entry::~Entry(); friend Entry::~Entry();
friend void Entry::setGroup(Group* group); friend void Entry::setGroup(Group* group, bool trackPrevious);
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(Group::CloneFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(Group::CloneFlags)

View File

@ -583,6 +583,10 @@ Group* KdbxXmlReader::parseGroup()
parseCustomData(group->customData()); parseCustomData(group->customData());
continue; continue;
} }
if (m_xml.name() == "PreviousParentGroup") {
group->setPreviousParentGroupUuid(readUuid());
continue;
}
skipCurrentElement(); skipCurrentElement();
} }
@ -602,11 +606,11 @@ Group* KdbxXmlReader::parseGroup()
} }
for (Group* child : asConst(children)) { for (Group* child : asConst(children)) {
child->setParent(group); child->setParent(group, -1, false);
} }
for (Entry* entry : asConst(entries)) { for (Entry* entry : asConst(entries)) {
entry->setGroup(group); entry->setGroup(group, false);
} }
return group; return group;
@ -760,6 +764,10 @@ Entry* KdbxXmlReader::parseEntry(bool history)
} }
continue; continue;
} }
if (m_xml.name() == "PreviousParentGroup") {
entry->setPreviousParentGroupUuid(readUuid());
continue;
}
skipCurrentElement(); skipCurrentElement();
} }

View File

@ -276,6 +276,9 @@ void KdbxXmlWriter::writeGroup(const Group* group)
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) { if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(group->customData()); writeCustomData(group->customData());
if (!group->previousParentGroupUuid().isNull()) {
writeUuid("PreviousParentGroup", group->previousParentGroupUuid());
}
} }
const QList<Entry*>& entryList = group->entries(); const QList<Entry*>& entryList = group->entries();
@ -344,8 +347,14 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
writeString("OverrideURL", entry->overrideUrl()); writeString("OverrideURL", entry->overrideUrl());
writeString("Tags", entry->tags()); writeString("Tags", entry->tags());
writeTimes(entry->timeInfo()); writeTimes(entry->timeInfo());
if (entry->excludeFromReports()) {
writeBool("QualityCheck", false); if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
if (entry->excludeFromReports()) {
writeBool("QualityCheck", false);
}
if (!entry->previousParentGroupUuid().isNull()) {
writeUuid("PreviousParentGroup", entry->previousParentGroupUuid());
}
} }
const QList<QString> attributesKeyList = entry->attributes()->keys(); const QList<QString> attributesKeyList = entry->attributes()->keys();

View File

@ -614,7 +614,7 @@ void TestEntry::testIsRecycled()
QVERIFY(entry1->isRecycled()); QVERIFY(entry1->isRecycled());
} }
void TestEntry::testMove() void TestEntry::testMoveUpDown()
{ {
Database db; Database db;
Group* root = db.rootGroup(); Group* root = db.rootGroup();
@ -724,3 +724,47 @@ void TestEntry::testMove()
QCOMPARE(root->entries().at(2), entry1); QCOMPARE(root->entries().at(2), entry1);
QCOMPARE(root->entries().at(3), entry0); QCOMPARE(root->entries().at(3), entry0);
} }
void TestEntry::testPreviousParentGroup()
{
Database db;
auto* root = db.rootGroup();
root->setUuid(QUuid::createUuid());
QVERIFY(!root->uuid().isNull());
auto* group1 = new Group();
group1->setUuid(QUuid::createUuid());
group1->setParent(root);
QVERIFY(!group1->uuid().isNull());
auto* group2 = new Group();
group2->setParent(root);
group2->setUuid(QUuid::createUuid());
QVERIFY(!group2->uuid().isNull());
auto* entry = new Entry();
QVERIFY(entry);
QVERIFY(entry->previousParentGroupUuid().isNull());
QVERIFY(!entry->previousParentGroup());
entry->setGroup(root);
QVERIFY(entry->previousParentGroupUuid().isNull());
QVERIFY(!entry->previousParentGroup());
// Previous parent shouldn't be recorded if new and old parent are the same
entry->setGroup(root);
QVERIFY(entry->previousParentGroupUuid().isNull());
QVERIFY(!entry->previousParentGroup());
entry->setGroup(group1);
QVERIFY(entry->previousParentGroupUuid() == root->uuid());
QVERIFY(entry->previousParentGroup() == root);
entry->setGroup(group2);
QVERIFY(entry->previousParentGroupUuid() == group1->uuid());
QVERIFY(entry->previousParentGroup() == group1);
entry->setGroup(group2);
QVERIFY(entry->previousParentGroupUuid() == group1->uuid());
QVERIFY(entry->previousParentGroup() == group1);
}

View File

@ -38,7 +38,8 @@ private slots:
void testResolveNonIdPlaceholdersToUuid(); void testResolveNonIdPlaceholdersToUuid();
void testResolveClonedEntry(); void testResolveClonedEntry();
void testIsRecycled(); void testIsRecycled();
void testMove(); void testMoveUpDown();
void testPreviousParentGroup();
}; };
#endif // KEEPASSX_TESTENTRY_H #endif // KEEPASSX_TESTENTRY_H

View File

@ -1143,7 +1143,7 @@ void TestGroup::testUsernamesRecursive()
QVERIFY(usernames.indexOf("Name2") < usernames.indexOf("Name1")); QVERIFY(usernames.indexOf("Name2") < usernames.indexOf("Name1"));
} }
void TestGroup::testMove() void TestGroup::testMoveUpDown()
{ {
Database database; Database database;
Group* root = database.rootGroup(); Group* root = database.rootGroup();
@ -1253,3 +1253,40 @@ void TestGroup::testMove()
QCOMPARE(root->entries().at(2), entry1); QCOMPARE(root->entries().at(2), entry1);
QCOMPARE(root->entries().at(3), entry0); QCOMPARE(root->entries().at(3), entry0);
} }
void TestGroup::testPreviousParentGroup()
{
Database db;
auto* root = db.rootGroup();
root->setUuid(QUuid::createUuid());
QVERIFY(!root->uuid().isNull());
QVERIFY(!root->previousParentGroup());
QVERIFY(root->previousParentGroupUuid().isNull());
auto* group1 = new Group();
group1->setUuid(QUuid::createUuid());
group1->setParent(root);
QVERIFY(!group1->uuid().isNull());
QVERIFY(!group1->previousParentGroup());
QVERIFY(group1->previousParentGroupUuid().isNull());
auto* group2 = new Group();
group2->setParent(root);
group2->setUuid(QUuid::createUuid());
QVERIFY(!group2->uuid().isNull());
QVERIFY(!group2->previousParentGroup());
QVERIFY(group2->previousParentGroupUuid().isNull());
group1->setParent(group2);
QVERIFY(group1->previousParentGroupUuid() == root->uuid());
QVERIFY(group1->previousParentGroup() == root);
// Previous parent shouldn't be recorded if new and old parent are the same
group1->setParent(group2);
QVERIFY(group1->previousParentGroupUuid() == root->uuid());
QVERIFY(group1->previousParentGroup() == root);
group1->setParent(root);
QVERIFY(group1->previousParentGroupUuid() == group2->uuid());
QVERIFY(group1->previousParentGroup() == group2);
}

View File

@ -47,7 +47,8 @@ private slots:
void testHierarchy(); void testHierarchy();
void testApplyGroupIconRecursively(); void testApplyGroupIconRecursively();
void testUsernamesRecursive(); void testUsernamesRecursive();
void testMove(); void testMoveUpDown();
void testPreviousParentGroup();
}; };
#endif // KEEPASSX_TESTGROUP_H #endif // KEEPASSX_TESTGROUP_H