Files
keepassxc/src/gui/wizard/ImportWizardPageReview.cpp
Jonathan White edab0faa94 Add Proton Pass importer
* Closes #10465
2025-01-03 17:45:50 -05:00

279 lines
8.9 KiB
C++

/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ImportWizardPageReview.h"
#include "ui_ImportWizardPageReview.h"
#include "core/Database.h"
#include "core/Group.h"
#include "format/BitwardenReader.h"
#include "format/KeePass1Reader.h"
#include "format/OPUXReader.h"
#include "format/OpVaultReader.h"
#include "format/ProtonPassReader.h"
#include "gui/csvImport/CsvImportWidget.h"
#include "gui/wizard/ImportWizard.h"
#include "cli/Utils.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
#include <QBoxLayout>
#include <QDir>
#include <QHeaderView>
#include <QTableWidget>
#include "gui/remote/RemoteSettings.h"
struct RemoteParams;
ImportWizardPageReview::ImportWizardPageReview(QWidget* parent)
: QWizardPage(parent)
, m_ui(new Ui::ImportWizardPageReview)
, m_remoteHandler(new RemoteHandler(this))
{
}
ImportWizardPageReview::~ImportWizardPageReview()
{
}
void ImportWizardPageReview::initializePage()
{
m_db.reset();
// Reset the widget in case we changed the import type
for (auto child : children()) {
delete child;
}
m_ui->setupUi(this);
auto filename = field("ImportFile").toString();
m_ui->filenameLabel->setText(filename);
m_ui->messageWidget->hideMessage();
m_ui->messageWidget->setAnimate(false);
m_ui->messageWidget->setCloseButtonVisible(false);
auto importType = field("ImportType").toInt();
switch (importType) {
case ImportWizard::IMPORT_CSV:
setupCsvImport(filename);
break;
case ImportWizard::IMPORT_OPVAULT:
m_db = importOPVault(filename, field("ImportPassword").toString());
break;
case ImportWizard::IMPORT_OPUX:
m_db = importOPUX(filename);
break;
case ImportWizard::IMPORT_KEEPASS1:
m_db = importKeePass1(filename, field("ImportPassword").toString(), field("ImportKeyFile").toString());
break;
case ImportWizard::IMPORT_BITWARDEN:
m_db = importBitwarden(filename, field("ImportPassword").toString());
break;
case ImportWizard::IMPORT_PROTONPASS:
m_db = importProtonPass(filename);
break;
case ImportWizard::IMPORT_REMOTE:
m_db = importRemote(field("DownloadCommand").toString(),
field("DownloadInput").toString(),
field("ImportPassword").toString(),
field("ImportKeyFile").toString());
break;
default:
break;
}
setupDatabasePreview();
}
bool ImportWizardPageReview::validatePage()
{
if (isCsvImport()) {
m_db = m_csvWidget->buildDatabase();
}
return !m_db.isNull();
}
QSharedPointer<Database> ImportWizardPageReview::database()
{
return m_db;
}
void ImportWizardPageReview::setupCsvImport(const QString& filename)
{
// No need for this label with CSV
m_ui->previewLabel->hide();
m_csvWidget = new CsvImportWidget();
connect(m_csvWidget, &CsvImportWidget::message, m_ui->messageWidget, [this](QString message) {
m_ui->messageWidget->showMessage(message, KMessageWidget::Error, -1);
});
m_csvWidget->load(filename);
}
void ImportWizardPageReview::setupDatabasePreview()
{
// CSV preview is handled by the import widget
if (isCsvImport()) {
// Qt does not automatically resize a QScrollWidget in a QWizard...
m_ui->scrollAreaContents->layout()->addWidget(m_csvWidget);
m_ui->scrollArea->setMinimumSize(m_csvWidget->width() + 50, m_csvWidget->height() + 100);
return;
}
if (!m_db) {
m_ui->scrollArea->setVisible(false);
return;
}
auto entryList = m_db->rootGroup()->entriesRecursive();
m_ui->previewLabel->setText(tr("Entry count: %1").arg(entryList.count()));
QStringList headerLabels({tr("Group"), tr("Title"), tr("Username"), tr("Password"), tr("Url")});
auto tableWidget = new QTableWidget(entryList.count(), headerLabels.count());
tableWidget->setHorizontalHeaderLabels(headerLabels);
int row = 0;
for (auto entry : entryList) {
QList items({new QTableWidgetItem(entry->group()->name()),
new QTableWidgetItem(entry->title()),
new QTableWidgetItem(entry->username()),
new QTableWidgetItem(entry->password()),
new QTableWidgetItem(entry->url())});
int column = 0;
for (auto item : items) {
tableWidget->setItem(row, column++, item);
}
++row;
}
tableWidget->setSortingEnabled(true);
tableWidget->setSelectionMode(QTableWidget::NoSelection);
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
tableWidget->setWordWrap(true);
tableWidget->horizontalHeader()->setMaximumSectionSize(200);
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
tableWidget->horizontalHeader()->setStretchLastSection(true);
m_ui->scrollAreaContents->layout()->addWidget(tableWidget);
}
QSharedPointer<Database> ImportWizardPageReview::importOPUX(const QString& filename)
{
OPUXReader reader;
auto db = reader.convert(filename);
if (reader.hasError()) {
m_ui->messageWidget->showMessage(reader.errorString(), KMessageWidget::Error, -1);
}
return db;
}
QSharedPointer<Database> ImportWizardPageReview::importBitwarden(const QString& filename, const QString& password)
{
BitwardenReader reader;
auto db = reader.convert(filename, password);
if (reader.hasError()) {
m_ui->messageWidget->showMessage(reader.errorString(), KMessageWidget::Error, -1);
}
return db;
}
QSharedPointer<Database> ImportWizardPageReview::importOPVault(const QString& filename, const QString& password)
{
OpVaultReader reader;
QDir opVault(filename);
auto db = reader.convert(opVault, password);
if (reader.hasError()) {
m_ui->messageWidget->showMessage(reader.errorString(), KMessageWidget::Error, -1);
}
return db;
}
QSharedPointer<Database>
ImportWizardPageReview::importKeePass1(const QString& filename, const QString& password, const QString& keyfile)
{
KeePass1Reader reader;
// TODO: Handle case of empty password?
auto db = reader.readDatabase(filename, password, keyfile);
if (reader.hasError()) {
m_ui->messageWidget->showMessage(reader.errorString(), KMessageWidget::Error, -1);
}
return db;
}
QSharedPointer<Database> ImportWizardPageReview::importProtonPass(const QString& filename)
{
ProtonPassReader reader;
auto db = reader.convert(filename);
if (reader.hasError()) {
m_ui->messageWidget->showMessage(reader.errorString(), KMessageWidget::Error, -1);
}
return db;
}
bool ImportWizardPageReview::isCsvImport() const
{
return m_csvWidget && field("ImportType").toInt() == ImportWizard::IMPORT_CSV;
}
QSharedPointer<Database> ImportWizardPageReview::importRemote(const QString& downloadCommand,
const QString& downloadInput,
const QString& password,
const QString& keyfile)
{
auto* params = new RemoteParams();
params->downloadCommand = downloadCommand;
params->downloadInput = downloadInput;
auto result = m_remoteHandler->download(params);
if (!result.success) {
m_ui->messageWidget->showMessage(result.errorMessage, KMessageWidget::Error, -1);
}
auto key = QSharedPointer<CompositeKey>::create();
if (!password.isEmpty()) {
key->addKey(QSharedPointer<PasswordKey>::create(password));
}
if (!keyfile.isEmpty()) {
QSharedPointer<FileKey> fileKey = QSharedPointer<FileKey>::create();
if (Utils::loadFileKey(keyfile, fileKey)) {
key->addKey(fileKey);
} else {
m_ui->messageWidget->showMessage(tr("Could not load key file."), KMessageWidget::Error, -1);
}
}
QString error;
QSharedPointer<Database> remoteDb = QSharedPointer<Database>::create();
remoteDb->markAsTemporaryDatabase();
if (!remoteDb->open(result.filePath, key, &error)) {
m_ui->messageWidget->showMessage(
tr("Could not open remote database. Password or key file may be incorrect."), KMessageWidget::Error, -1);
}
return remoteDb;
}