Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support opening remote databases #10896

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 @@ -4482,6 +4482,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 @@ -4585,6 +4593,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