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;
}
void Entry::setGroup(Group* group)
void Entry::setGroup(Group* group, bool trackPrevious)
{
Q_ASSERT(group);
@ -1180,6 +1180,7 @@ void Entry::setGroup(Group* group)
if (m_group) {
m_group->removeEntry(this);
if (m_group->database() && m_group->database() != group->database()) {
setPreviousParentGroup(nullptr);
m_group->database()->addDeletedObject(m_uuid);
// copy custom icon to the new database
@ -1188,6 +1189,8 @@ void Entry::setGroup(Group* group)
group->database()->metadata()->addCustomIcon(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
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
@ -1438,6 +1464,9 @@ bool EntryData::equals(const EntryData& other, CompareItemOptions options) const
if (::compare(excludeFromReports, other.excludeFromReports, options) != 0) {
return false;
}
if (::compare(previousParentGroupUuid, other.previousParentGroupUuid, options) != 0) {
return false;
}
return true;
}

View File

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

View File

@ -403,7 +403,7 @@ const Group* Group::parentGroup() const
return m_parent;
}
void Group::setParent(Group* parent, int index)
void Group::setParent(Group* parent, int index, bool trackPrevious)
{
Q_ASSERT(parent);
Q_ASSERT(index >= -1 && index <= parent->children().size());
@ -428,6 +428,7 @@ void Group::setParent(Group* parent, int index)
cleanupParent();
m_parent = parent;
if (m_db) {
setPreviousParentGroup(nullptr);
recCreateDelObjects();
// copy custom icon to the new database
@ -445,6 +446,9 @@ void Group::setParent(Group* parent, int index)
parent->m_children.insert(index, this);
} else {
emit aboutToMove(this, parent, index);
if (trackPrevious && m_parent != parent) {
setPreviousParentGroup(m_parent);
}
m_parent->m_children.removeAll(this);
m_parent = parent;
QObject::setParent(parent);
@ -585,7 +589,7 @@ Entry* Group::findEntryByUuid(const QUuid& uuid, bool recursive) const
return nullptr;
}
Entry* Group::findEntryByPath(const QString& entryPath)
Entry* Group::findEntryByPath(const QString& entryPath) const
{
if (entryPath.isEmpty()) {
return nullptr;
@ -647,7 +651,7 @@ Entry* Group::findEntryBySearchTerm(const QString& term, EntryReferenceType refe
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
// slash, return the first entry title that matches
@ -843,6 +847,21 @@ Group* Group::findGroupByUuid(const QUuid& uuid)
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)
{
for (Group* group : asConst(m_children)) {
@ -1168,6 +1187,29 @@ void Group::sortChildrenRecursively(bool reverse)
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
{
return equals(other, CompareItemDefault);

View File

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

View File

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

View File

@ -276,6 +276,9 @@ void KdbxXmlWriter::writeGroup(const Group* group)
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(group->customData());
if (!group->previousParentGroupUuid().isNull()) {
writeUuid("PreviousParentGroup", group->previousParentGroupUuid());
}
}
const QList<Entry*>& entryList = group->entries();
@ -344,8 +347,14 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
writeString("OverrideURL", entry->overrideUrl());
writeString("Tags", entry->tags());
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();

View File

@ -614,7 +614,7 @@ void TestEntry::testIsRecycled()
QVERIFY(entry1->isRecycled());
}
void TestEntry::testMove()
void TestEntry::testMoveUpDown()
{
Database db;
Group* root = db.rootGroup();
@ -724,3 +724,47 @@ void TestEntry::testMove()
QCOMPARE(root->entries().at(2), entry1);
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 testResolveClonedEntry();
void testIsRecycled();
void testMove();
void testMoveUpDown();
void testPreviousParentGroup();
};
#endif // KEEPASSX_TESTENTRY_H

View File

@ -1143,7 +1143,7 @@ void TestGroup::testUsernamesRecursive()
QVERIFY(usernames.indexOf("Name2") < usernames.indexOf("Name1"));
}
void TestGroup::testMove()
void TestGroup::testMoveUpDown()
{
Database database;
Group* root = database.rootGroup();
@ -1253,3 +1253,40 @@ void TestGroup::testMove()
QCOMPARE(root->entries().at(2), entry1);
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 testApplyGroupIconRecursively();
void testUsernamesRecursive();
void testMove();
void testMoveUpDown();
void testPreviousParentGroup();
};
#endif // KEEPASSX_TESTGROUP_H