diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 0147ec0f9..ddca7b4fe 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -1369,6 +1369,9 @@ void Entry::setGroup(Group* group, bool trackPrevious) setPreviousParentGroup(nullptr); m_group->database()->addDeletedObject(m_uuid); + // Resolve references before moving to a different database + resolveReferencesBeforeDatabaseMove(); + // copy custom icon to the new database if (!iconUuid().isNull() && group->database() && m_group->database()->metadata()->hasCustomIcon(iconUuid()) && !group->database()->metadata()->hasCustomIcon(iconUuid())) { @@ -1411,6 +1414,32 @@ Database* Entry::database() return nullptr; } +void Entry::resolveReferencesBeforeDatabaseMove() +{ + if (!m_group || !m_group->database()) { + return; + } + + // Resolve references in all default attributes + for (const QString& key : EntryAttributes::DefaultAttributes) { + if (m_attributes->contains(key) && m_attributes->isReference(key)) { + QString resolvedValue = resolveMultiplePlaceholdersRecursive(m_attributes->value(key), 10); + bool isProtected = m_attributes->isProtected(key); + m_attributes->set(key, resolvedValue, isProtected); + } + } + + // Resolve references in custom attributes + const QList customKeys = m_attributes->customKeys(); + for (const QString& key : customKeys) { + if (m_attributes->isReference(key)) { + QString resolvedValue = resolveMultiplePlaceholdersRecursive(m_attributes->value(key), 10); + bool isProtected = m_attributes->isProtected(key); + m_attributes->set(key, resolvedValue, isProtected); + } + } +} + QString Entry::maskPasswordPlaceholders(const QString& str) const { return QString{str}.replace(QStringLiteral("{PASSWORD}"), QStringLiteral("******"), Qt::CaseInsensitive); diff --git a/src/core/Entry.h b/src/core/Entry.h index 3c9a0f3de..3fe28e583 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -273,6 +273,8 @@ public: bool canUpdateTimeinfo() const; void setUpdateTimeinfo(bool value); + void resolveReferencesBeforeDatabaseMove(); + signals: /** * Emitted when a default attribute has been changed. diff --git a/tests/TestEntry.cpp b/tests/TestEntry.cpp index 51cb4799c..493f75650 100644 --- a/tests/TestEntry.cpp +++ b/tests/TestEntry.cpp @@ -20,6 +20,9 @@ #include "TestEntry.h" #include "core/Clock.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/EntryAttributes.h" #include "core/Group.h" #include "core/Metadata.h" #include "core/TimeInfo.h" @@ -672,6 +675,55 @@ void TestEntry::testResolveClonedEntry() QCOMPARE(cclone4->resolveMultiplePlaceholders(cclone4->password()), original->password()); } +void TestEntry::testCrossDatabaseReferences() +{ + // Test that references are resolved when moving entries between databases + Database db1; + auto* root1 = db1.rootGroup(); + Database db2; + auto* root2 = db2.rootGroup(); + + // Create original entry in database 1 + auto* originalEntry = new Entry(); + originalEntry->setGroup(root1); + originalEntry->setUuid(QUuid::createUuid()); + originalEntry->setTitle("OriginalTitle"); + originalEntry->setUsername("OriginalUsername"); + originalEntry->setPassword("OriginalPassword"); + originalEntry->setUrl("http://original.com"); + + // Create entry with references to original entry in database 1 + auto* refEntry = new Entry(); + refEntry->setGroup(root1); + refEntry->setUuid(QUuid::createUuid()); + refEntry->setTitle(QString("{REF:T@I:%1}").arg(originalEntry->uuidToHex())); + refEntry->setUsername(QString("{REF:U@I:%1}").arg(originalEntry->uuidToHex())); + refEntry->setPassword(QString("{REF:P@I:%1}").arg(originalEntry->uuidToHex())); + refEntry->setUrl(QString("{REF:A@I:%1}").arg(originalEntry->uuidToHex())); + + // Verify references work within same database + QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->title()), QString("OriginalTitle")); + QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->username()), QString("OriginalUsername")); + QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->password()), QString("OriginalPassword")); + QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->url()), QString("http://original.com")); + + // Move the referenced entry to database 2 + // This should resolve the references before the move + refEntry->setGroup(root2); + + // After move, the entry should have resolved values instead of references + QCOMPARE(refEntry->title(), QString("OriginalTitle")); + QCOMPARE(refEntry->username(), QString("OriginalUsername")); + QCOMPARE(refEntry->password(), QString("OriginalPassword")); + QCOMPARE(refEntry->url(), QString("http://original.com")); + + // Verify that the references have been replaced with actual values + QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::TitleKey)); + QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::UserNameKey)); + QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::PasswordKey)); + QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::URLKey)); +} + void TestEntry::testIsRecycled() { auto entry = new Entry(); diff --git a/tests/TestEntry.h b/tests/TestEntry.h index 69f5b0d46..e1a4a6478 100644 --- a/tests/TestEntry.h +++ b/tests/TestEntry.h @@ -39,6 +39,7 @@ private slots: void testResolveConversionPlaceholders(); void testResolveReplacePlaceholders(); void testResolveClonedEntry(); + void testCrossDatabaseReferences(); void testIsRecycled(); void testMoveUpDown(); void testPreviousParentGroup();