Skip to content

Commit

Permalink
Support opening remote databases (#10896)
Browse files Browse the repository at this point in the history
* Use the import wizard to support opening a remote database

---------

Co-authored-by: Jonathan White <[email protected]>
  • Loading branch information
t-h-e and droidmonkey authored Oct 7, 2024
1 parent abcb141 commit d2da13d
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 61 deletions.
16 changes: 16 additions & 0 deletions docs/topics/ImportExport.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ KeePassXC allows you to import external databases from the following options:
* 1Password Vault (.opvault)
* Bitwarden (.json)
* KeePass 1 Database (.kdb)
* Remote database (.kdbx)

To import any of these files, start KeePassXC and either click the `Import File` button on the welcome screen or use the menu Database > Import... to launch the Import Wizard.

Expand Down Expand Up @@ -67,6 +68,21 @@ To import a KeePass 1 database file in KeePassXC, perform the following steps:

3. Click `Continue` to unlock and preview the import. Click `Done` to complete the import.

=== Importing Remote Database
Database files that are stored in a remote location can be imported or opened with KeePassXC if you provide a command to download the file from the remote location.

To import (or temporarily open) a remote database file in KeePassXC, perform the following steps:

1. Open the Import Wizard as shown above. Select the Remote Database option.

2. Enter a command to download the remote database. If necessary, enter input that needs to be passed to the command. The command and/or input need a `{TEMP_DATABASE}` placeholder specified where the remote database is temporarily stored.

3. Enter the password for your database and optionally provide a key file.

4. Click `Continue` to unlock and preview the import. Click `Done` to complete the import.

Opening without importing a remote database is possible by selecting Temporary Database in the Import Into section of the wizard.

== Exporting Databases
KeePassXC supports multiple ways to export your database for transfer to another program or to print out and archive.

Expand Down
38 changes: 38 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4490,6 +4490,14 @@ You can enable the DuckDuckGo website icon service in the security section of th
<source>Url</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not load key file.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not open remote database. Password or key file may be incorrect.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ImportWizardPageSelect</name>
Expand Down Expand Up @@ -4593,6 +4601,36 @@ You can enable the DuckDuckGo website icon service in the security section of th
<source>KeePass1 Database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Temporary Database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Command:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>e.g.: &quot;sftp user@hostname&quot; or &quot;scp user@hostname:DatabaseOnRemote.kdbx {TEMP_DATABASE}&quot;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Input:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remote Database (.kdbx)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>e.g.:
get DatabaseOnRemote.kdbx {TEMP_DATABASE}
exit
---
{TEMP_DATABASE} is used as placeholder to store the database in a temporary location
The command has to exit. In case of `sftp` as last commend `exit` has to be sent
</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>KMessageWidget</name>
Expand Down
90 changes: 51 additions & 39 deletions src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
#include "gui/osutils/macutils/MacUtils.h"
#endif
#include "gui/wizard/NewDatabaseWizard.h"
#include "wizard/ImportWizard.h"

DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
: QTabWidget(parent)
, m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
, m_dbWidgetPendingLock(nullptr)
, m_databaseOpenDialog(new DatabaseOpenDialog(this))
, m_importWizard(nullptr)
, m_databaseOpenInProgress(false)
{
auto* tabBar = new QTabBar(this);
Expand Down Expand Up @@ -255,53 +255,65 @@ void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackgrou
&DatabaseTabWidget::unlockDatabaseInDialogForSync);
}

DatabaseWidget* DatabaseTabWidget::importFile()
void DatabaseTabWidget::importFile()
{
// Show the import wizard
QScopedPointer wizard(new ImportWizard(this));
if (!wizard->exec()) {
return nullptr;
}
m_importWizard = new ImportWizard(this);

auto db = wizard->database();
if (!db) {
// Import wizard was cancelled
return nullptr;
}
connect(m_importWizard.data(), &QWizard::finished, [&](int result) {
if (result != QDialog::Accepted) {
return;
}

auto importInto = wizard->importInto();
if (importInto.first.isNull()) {
// Start the new database wizard with the imported database
auto newDb = execNewDatabaseWizard();
if (newDb) {
// Merge the imported db into the new one
Merger merger(db.data(), newDb.data());
merger.setSkipDatabaseCustomData(true);
merger.merge();
// Show the new database
auto dbWidget = new DatabaseWidget(newDb, this);
addDatabaseTab(dbWidget);
newDb->markAsModified();
return dbWidget;
auto db = m_importWizard->database();
if (!db) {
// Import wizard was cancelled
return;
}
} else {
for (int i = 0, c = count(); i < c; ++i) {
// Find the database and group to import into based on import wizard choice
auto dbWidget = databaseWidgetFromIndex(i);
if (!dbWidget->isLocked() && dbWidget->database()->uuid() == importInto.first) {
auto group = dbWidget->database()->rootGroup()->findGroupByUuid(importInto.second);
if (group) {
// Extract the root group from the import database
auto importGroup = db->setRootGroup(new Group());
importGroup->setParent(group);
setCurrentIndex(i);
return dbWidget;

switch (m_importWizard->importIntoType()) {
case ImportWizard::EXISTING_DATABASE:
for (int i = 0, c = count(); i < c; ++i) {
auto importInto = m_importWizard->importInto();
// Find the database and group to import into based on import wizard choice
auto dbWidget = databaseWidgetFromIndex(i);
if (!dbWidget->isLocked() && dbWidget->database()->uuid() == importInto.first) {
auto group = dbWidget->database()->rootGroup()->findGroupByUuid(importInto.second);
if (group) {
// Extract the root group from the import database
auto importGroup = db->setRootGroup(new Group());
importGroup->setParent(group);
setCurrentIndex(i);
return;
}
}
}
break;
case ImportWizard::TEMPORARY_DATABASE: {
// Use the already created database as temporary database
auto dbWidget = new DatabaseWidget(db, this);
addDatabaseTab(dbWidget);
return;
}
}
default:
// Start the new database wizard with the imported database
auto newDb = execNewDatabaseWizard();
if (newDb) {
// Merge the imported db into the new one
Merger merger(db.data(), newDb.data());
merger.setSkipDatabaseCustomData(true);
merger.merge();
// Show the new database
auto dbWidget = new DatabaseWidget(newDb, this);
addDatabaseTab(dbWidget);
newDb->markAsModified();
return;
}
}
});

return nullptr;
// use `open` instead of `exec`. `exec` should not be used, see https://doc.qt.io/qt-6/qdialog.html#exec
m_importWizard->show();
}

void DatabaseTabWidget::mergeDatabase()
Expand Down
4 changes: 3 additions & 1 deletion src/gui/DatabaseTabWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "DatabaseOpenDialog.h"
#include "config-keepassx.h"
#include "gui/MessageWidget.h"
#include "wizard/ImportWizard.h"

#include <QTabWidget>
#include <QTimer>
Expand Down Expand Up @@ -64,7 +65,7 @@ public slots:
DatabaseWidget* newDatabase();
void openDatabase();
void mergeDatabase();
DatabaseWidget* importFile();
void importFile();
bool saveDatabase(int index = -1);
bool saveDatabaseAs(int index = -1);
bool saveDatabaseBackup(int index = -1);
Expand Down Expand Up @@ -123,6 +124,7 @@ private slots:
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
QPointer<DatabaseWidget> m_dbWidgetPendingLock;
QPointer<DatabaseOpenDialog> m_databaseOpenDialog;
QPointer<ImportWizard> m_importWizard;
QTimer m_lockDelayTimer;
bool m_databaseOpenInProgress;
};
Expand Down
5 changes: 5 additions & 0 deletions src/gui/wizard/ImportWizard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ bool ImportWizard::validateCurrentPage()
return ret;
}

ImportWizard::ImportIntoType ImportWizard::importIntoType()
{
return static_cast<ImportIntoType>(field("ImportIntoType").toInt());
}

QPair<QUuid, QUuid> ImportWizard::importInto()
{
auto list = field("ImportInto").toList();
Expand Down
15 changes: 13 additions & 2 deletions src/gui/wizard/ImportWizard.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#define KEEPASSXC_IMPORTWIZARD_H

#include <QPointer>
#include <QUuid>
#include <QWizard>

class Database;
Expand All @@ -39,7 +40,6 @@ class ImportWizard : public QWizard
bool validateCurrentPage() override;

QSharedPointer<Database> database();
QPair<QUuid, QUuid> importInto();

enum ImportType
{
Expand All @@ -48,9 +48,20 @@ class ImportWizard : public QWizard
IMPORT_OPVAULT,
IMPORT_OPUX,
IMPORT_BITWARDEN,
IMPORT_KEEPASS1
IMPORT_KEEPASS1,
IMPORT_REMOTE,
};

enum ImportIntoType
{
NEW_DATABASE = 1,
EXISTING_DATABASE,
TEMPORARY_DATABASE,
};

ImportWizard::ImportIntoType importIntoType();
QPair<QUuid, QUuid> importInto();

private:
QSharedPointer<Database> m_db;
QPointer<ImportWizardPageSelect> m_pageSelect;
Expand Down
55 changes: 55 additions & 0 deletions src/gui/wizard/ImportWizardPageReview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,23 @@
#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))
{
}

Expand Down Expand Up @@ -80,6 +89,12 @@ void ImportWizardPageReview::initializePage()
m_db = importBitwarden(filename, field("ImportPassword").toString());
setupDatabasePreview();
break;
case ImportWizard::IMPORT_REMOTE:
m_db = importRemote(field("DownloadCommand").toString(),
field("DownloadInput").toString(),
field("ImportPassword").toString(),
field("ImportKeyFile").toString());
setupDatabasePreview();
default:
break;
}
Expand Down Expand Up @@ -200,3 +215,43 @@ ImportWizardPageReview::importKeePass1(const QString& filename, const QString& p

return db;
}

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;
}
11 changes: 11 additions & 0 deletions src/gui/wizard/ImportWizardPageReview.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
#include <QPointer>
#include <QWizardPage>

#include <QLabel>
#include <QProgressBar>
#include <QStatusBar>

#include "../remote/RemoteHandler.h"

class CsvImportWidget;
class Database;
namespace Ui
Expand Down Expand Up @@ -48,13 +54,18 @@ class ImportWizardPageReview : public QWizardPage
QSharedPointer<Database> importBitwarden(const QString& filename, const QString& password);
QSharedPointer<Database> importOPVault(const QString& filename, const QString& password);
QSharedPointer<Database> importKeePass1(const QString& filename, const QString& password, const QString& keyfile);
QSharedPointer<Database> importRemote(const QString& downloadCommand,
const QString& downloadInput,
const QString& password,
const QString& keyfile);

void setupDatabasePreview();

QScopedPointer<Ui::ImportWizardPageReview> m_ui;

QSharedPointer<Database> m_db;
QPointer<CsvImportWidget> m_csvWidget;
QPointer<RemoteHandler> m_remoteHandler;
};

#endif
Loading

0 comments on commit d2da13d

Please sign in to comment.