Add 1Password 1PUX and Bitwarden JSON Importers

* Closes #7545 - Support 1Password 1PUX import format based on https://support.1password.com/1pux-format/

* Closes #8367 - Support Bitwarden JSON import format (both unencrypted and encrypted) based on https://bitwarden.com/help/encrypted-export/

* Fixes #9577 - OPVault import when fields have the same name or type

* Introduce the import wizard to handle all import tasks (CSV, KDBX1, OPVault, 1PUX, JSON)

* Clean up CSV parser code to make it much more efficient and easier to read

* Combine all importer tests (except CSV) into one test file
This commit is contained in:
Jonathan White 2023-08-27 21:54:53 -04:00
parent a02bceabd2
commit e700195f0a
70 changed files with 3562 additions and 1876 deletions

View file

@ -30,20 +30,20 @@
#include <QSplitter>
#include <QTextDocumentFragment>
#include <QTextEdit>
#include <core/Tools.h>
#include "autotype/AutoType.h"
#include "core/EntrySearcher.h"
#include "core/Merger.h"
#include "core/Tools.h"
#include "gui/Clipboard.h"
#include "gui/CloneDialog.h"
#include "gui/DatabaseOpenDialog.h"
#include "gui/DatabaseOpenWidget.h"
#include "gui/EntryPreviewWidget.h"
#include "gui/FileDialog.h"
#include "gui/GuiTools.h"
#include "gui/KeePass1OpenWidget.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
#include "gui/OpVaultOpenWidget.h"
#include "gui/TotpDialog.h"
#include "gui/TotpExportSettingsDialog.h"
#include "gui/TotpSetupDialog.h"
@ -79,15 +79,12 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
, m_previewSplitter(new QSplitter(m_mainWidget))
, m_searchingLabel(new QLabel(this))
, m_shareLabel(new ElidedLabel(this))
, m_csvImportWizard(new CsvImportWizard(this))
, m_editEntryWidget(new EditEntryWidget(this))
, m_editGroupWidget(new EditGroupWidget(this))
, m_historyEditEntryWidget(new EditEntryWidget(this))
, m_reportsDialog(new ReportsDialog(this))
, m_databaseSettingDialog(new DatabaseSettingsDialog(this))
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
, m_opVaultOpenWidget(new OpVaultOpenWidget(this))
, m_groupView(new GroupView(m_db.data(), this))
, m_tagView(new TagView(this))
, m_saveAttempts(0)
@ -179,12 +176,9 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
m_editEntryWidget->setObjectName("editEntryWidget");
m_editGroupWidget->setObjectName("editGroupWidget");
m_csvImportWizard->setObjectName("csvImportWizard");
m_reportsDialog->setObjectName("reportsDialog");
m_databaseSettingDialog->setObjectName("databaseSettingsDialog");
m_databaseOpenWidget->setObjectName("databaseOpenWidget");
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
m_opVaultOpenWidget->setObjectName("opVaultOpenWidget");
addChildWidget(m_mainWidget);
addChildWidget(m_editEntryWidget);
@ -193,9 +187,6 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
addChildWidget(m_databaseSettingDialog);
addChildWidget(m_historyEditEntryWidget);
addChildWidget(m_databaseOpenWidget);
addChildWidget(m_csvImportWizard);
addChildWidget(m_keepass1OpenWidget);
addChildWidget(m_opVaultOpenWidget);
// clang-format off
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged()));
@ -216,9 +207,6 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_reportsDialog, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
connect(m_databaseSettingDialog, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
connect(this, SIGNAL(requestGlobalAutoType(const QString&)), parent, SLOT(performGlobalAutoType(const QString&)));
// clang-format on
@ -273,10 +261,8 @@ DatabaseWidget::Mode DatabaseWidget::currentMode() const
return Mode::None;
} else if (currentWidget() == m_mainWidget) {
return Mode::ViewMode;
} else if (currentWidget() == m_databaseOpenWidget || currentWidget() == m_keepass1OpenWidget) {
} else if (currentWidget() == m_databaseOpenWidget) {
return Mode::LockedMode;
} else if (currentWidget() == m_csvImportWizard) {
return Mode::ImportMode;
} else {
return Mode::EditMode;
}
@ -327,6 +313,45 @@ bool DatabaseWidget::isEditWidgetModified() const
return false;
}
QString DatabaseWidget::displayName() const
{
if (!m_db) {
return {};
}
auto displayName = m_db->metadata()->name();
if (!m_db->filePath().isEmpty()) {
if (displayName.isEmpty()) {
displayName = displayFileName();
}
} else {
if (displayName.isEmpty()) {
displayName = tr("New Database");
} else {
displayName = tr("%1 [New Database]", "Database tab name modifier").arg(displayName);
}
}
return displayName;
}
QString DatabaseWidget::displayFileName() const
{
if (m_db) {
QFileInfo fileinfo(m_db->filePath());
return fileinfo.fileName();
}
return {};
}
QString DatabaseWidget::displayFilePath() const
{
if (m_db) {
return m_db->canonicalFilePath();
}
return {};
}
QHash<Config::ConfigKey, QList<int>> DatabaseWidget::splitterSizes() const
{
return {{Config::GUI_SplitterState, m_mainSplitter->sizes()},
@ -1341,33 +1366,6 @@ void DatabaseWidget::switchToOpenDatabase(const QString& filePath, const QString
m_databaseOpenWidget->enterKey(password, keyFile);
}
void DatabaseWidget::switchToCsvImport(const QString& filePath)
{
setCurrentWidget(m_csvImportWizard);
m_csvImportWizard->load(filePath, m_db.data());
}
void DatabaseWidget::csvImportFinished(bool accepted)
{
if (!accepted) {
emit closeRequest();
} else {
switchToMainView();
}
}
void DatabaseWidget::switchToImportKeepass1(const QString& filePath)
{
m_keepass1OpenWidget->load(filePath);
setCurrentWidget(m_keepass1OpenWidget);
}
void DatabaseWidget::switchToImportOpVault(const QString& fileName)
{
m_opVaultOpenWidget->load(fileName);
setCurrentWidget(m_opVaultOpenWidget);
}
void DatabaseWidget::switchToEntryEdit()
{
auto entry = m_entryView->currentEntry();