Bitwarden import: Add support for timestamps and password history (#12588)

* Closes #12587

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: droidmonkey <2809491+droidmonkey@users.noreply.github.com>
Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
Copilot
2025-10-26 08:10:01 -04:00
committed by GitHub
parent 6ca42103b8
commit 682269f622
3 changed files with 80 additions and 0 deletions

View File

@@ -56,6 +56,7 @@ namespace
// Create entry and assign basic values
QScopedPointer<Entry> entry(new Entry());
entry->setEmitModified(false);
entry->setUuid(QUuid::createUuid());
entry->setTitle(itemMap.value("name").toString());
entry->setNotes(itemMap.value("notes").toString());
@@ -205,9 +206,58 @@ namespace
entry->attributes()->set(name, value, type == 1);
}
// Parse timestamps
auto timeInfo = entry->timeInfo();
if (itemMap.contains("creationDate")) {
const auto creationDate = QDateTime::fromString(itemMap.value("creationDate").toString(), Qt::ISODate);
if (creationDate.isValid()) {
timeInfo.setCreationTime(creationDate);
}
}
if (itemMap.contains("revisionDate")) {
const auto revisionDate = QDateTime::fromString(itemMap.value("revisionDate").toString(), Qt::ISODate);
if (revisionDate.isValid()) {
timeInfo.setLastModificationTime(revisionDate);
timeInfo.setLastAccessTime(revisionDate);
}
}
entry->setTimeInfo(timeInfo);
// Collapse any accumulated history
entry->removeHistoryItems(entry->historyItems());
// Parse password history, if present
if (itemMap.contains("passwordHistory")) {
const auto passwordHistory = itemMap.value("passwordHistory").toList();
for (const auto& historyItem : passwordHistory) {
const auto historyMap = historyItem.toMap();
const auto password = historyMap.value("password").toString();
const auto lastUsedDate =
QDateTime::fromString(historyMap.value("lastUsedDate").toString(), Qt::ISODate);
if (!password.isEmpty() && lastUsedDate.isValid()) {
// Create a history entry with the old password
auto historyEntry = new Entry();
historyEntry->setUuid(entry->uuid());
historyEntry->setTitle(entry->title());
historyEntry->setUsername(entry->username());
historyEntry->setPassword(password);
historyEntry->setUrl(entry->url());
historyEntry->setNotes(entry->notes());
// Set the timestamp for this history item
auto historyTimeInfo = historyEntry->timeInfo();
historyTimeInfo.setCreationTime(entry->timeInfo().creationTime());
historyTimeInfo.setLastModificationTime(lastUsedDate);
historyTimeInfo.setLastAccessTime(lastUsedDate);
historyEntry->setTimeInfo(historyTimeInfo);
entry->addHistoryItem(historyEntry);
}
}
}
entry->setEmitModified(true);
return entry.take();
}
@@ -240,7 +290,9 @@ namespace
for (const auto& item : items) {
auto entry = readItem(item.toObject(), folderId);
if (entry) {
entry->setUpdateTimeinfo(false);
entry->setGroup(folderMap.value(folderId, db->rootGroup()), false);
entry->setUpdateTimeinfo(true);
}
}
}

View File

@@ -228,6 +228,16 @@ void TestImports::testBitwarden()
QCOMPARE(entry->attribute("KP2A_URL_2"), QStringLiteral("https://gmail.com"));
// Check TOTP
QVERIFY(entry->hasTotp());
// Check Modified and Created timestamps
QCOMPARE(entry->timeInfo().lastModificationTime(),
QDateTime::fromString(QStringLiteral("2024-12-25T12:00:00Z"), Qt::ISODate));
QCOMPARE(entry->timeInfo().creationTime(),
QDateTime::fromString(QStringLiteral("2024-12-01T12:00:00Z"), Qt::ISODate));
// Check Password History
QCOMPARE(entry->historyItems().size(), 1);
QCOMPARE(entry->historyItems().first()->password(), QStringLiteral("oldpassword"));
QCOMPARE(entry->historyItems().first()->timeInfo().lastModificationTime(),
QDateTime::fromString(QStringLiteral("2024-12-01T12:00:00Z"), Qt::ISODate));
// NOTE: Bitwarden does not export attachments
// NOTE: Bitwarden does not export expiration dates

View File

@@ -11,6 +11,9 @@
],
"items": [
{
"revisionDate": "2024-12-25T12:00:00.000Z",
"creationDate": "2024-12-01T12:00:00.000Z",
"deletedDate": null,
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa",
"organizationId": null,
"folderId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
@@ -43,6 +46,9 @@
]
},
{
"revisionDate": "2024-12-25T12:00:00.000Z",
"creationDate": "2024-12-01T12:00:00.000Z",
"deletedDate": null,
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"organizationId": null,
"folderId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
@@ -80,6 +86,9 @@
]
},
{
"revisionDate": "2024-12-25T12:00:00.000Z",
"creationDate": "2024-12-01T12:00:00.000Z",
"deletedDate": null,
"id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"organizationId": null,
"folderId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
@@ -129,6 +138,15 @@
]
},
{
"passwordHistory": [
{
"lastUsedDate": "2024-12-01T12:00:00.000Z",
"password": "oldpassword"
}
],
"revisionDate": "2024-12-25T12:00:00.000Z",
"creationDate": "2024-12-01T12:00:00.000Z",
"deletedDate": null,
"id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
"organizationId": null,
"folderId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",