mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user