diff --git a/docs/topics/ImportExport.adoc b/docs/topics/ImportExport.adoc index bcae4ce57..5786c2ab1 100644 --- a/docs/topics/ImportExport.adoc +++ b/docs/topics/ImportExport.adoc @@ -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. @@ -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. diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 36de355aa..2ef44aa50 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -4490,6 +4490,14 @@ You can enable the DuckDuckGo website icon service in the security section of th Url + + Could not load key file. + + + + Could not open remote database. Password or key file may be incorrect. + + ImportWizardPageSelect @@ -4593,6 +4601,36 @@ You can enable the DuckDuckGo website icon service in the security section of th KeePass1 Database + + Temporary Database + + + + Command: + + + + e.g.: "sftp user@hostname" or "scp user@hostname:DatabaseOnRemote.kdbx {TEMP_DATABASE}" + + + + Input: + + + + Remote Database (.kdbx) + + + + 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 + + + KMessageWidget diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index b9b26d377..f5cfd70d8 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -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); @@ -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; - } - - 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; + connect(m_importWizard.data(), &QWizard::finished, [&](int result) { + if (result != QDialog::Accepted) { + 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; + + auto db = m_importWizard->database(); + if (!db) { + // Import wizard was cancelled + return; + } + + 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() diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index aa8542dd9..eb5e06b30 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -21,6 +21,7 @@ #include "DatabaseOpenDialog.h" #include "config-keepassx.h" #include "gui/MessageWidget.h" +#include "wizard/ImportWizard.h" #include #include @@ -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); @@ -123,6 +124,7 @@ private: QPointer m_dbWidgetStateSync; QPointer m_dbWidgetPendingLock; QPointer m_databaseOpenDialog; + QPointer m_importWizard; QTimer m_lockDelayTimer; bool m_databaseOpenInProgress; }; diff --git a/src/gui/wizard/ImportWizard.cpp b/src/gui/wizard/ImportWizard.cpp index 05fdbfe38..ff3769347 100644 --- a/src/gui/wizard/ImportWizard.cpp +++ b/src/gui/wizard/ImportWizard.cpp @@ -69,6 +69,11 @@ bool ImportWizard::validateCurrentPage() return ret; } +ImportWizard::ImportIntoType ImportWizard::importIntoType() +{ + return static_cast(field("ImportIntoType").toInt()); +} + QPair ImportWizard::importInto() { auto list = field("ImportInto").toList(); diff --git a/src/gui/wizard/ImportWizard.h b/src/gui/wizard/ImportWizard.h index b7e9de68d..95bf8b998 100644 --- a/src/gui/wizard/ImportWizard.h +++ b/src/gui/wizard/ImportWizard.h @@ -19,6 +19,7 @@ #define KEEPASSXC_IMPORTWIZARD_H #include +#include #include class Database; @@ -39,7 +40,6 @@ public: bool validateCurrentPage() override; QSharedPointer database(); - QPair importInto(); enum ImportType { @@ -48,9 +48,20 @@ public: 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 importInto(); + private: QSharedPointer m_db; QPointer m_pageSelect; diff --git a/src/gui/wizard/ImportWizardPageReview.cpp b/src/gui/wizard/ImportWizardPageReview.cpp index 2cb56791b..55492a67f 100644 --- a/src/gui/wizard/ImportWizardPageReview.cpp +++ b/src/gui/wizard/ImportWizardPageReview.cpp @@ -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 #include #include #include +#include "gui/remote/RemoteSettings.h" + +struct RemoteParams; + ImportWizardPageReview::ImportWizardPageReview(QWidget* parent) : QWizardPage(parent) , m_ui(new Ui::ImportWizardPageReview) + , m_remoteHandler(new RemoteHandler(this)) { } @@ -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; } @@ -200,3 +215,43 @@ ImportWizardPageReview::importKeePass1(const QString& filename, const QString& p return db; } + +QSharedPointer 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::create(); + + if (!password.isEmpty()) { + key->addKey(QSharedPointer::create(password)); + } + if (!keyfile.isEmpty()) { + QSharedPointer fileKey = QSharedPointer::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 remoteDb = QSharedPointer::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; +} diff --git a/src/gui/wizard/ImportWizardPageReview.h b/src/gui/wizard/ImportWizardPageReview.h index 87f501c85..c1df3efff 100644 --- a/src/gui/wizard/ImportWizardPageReview.h +++ b/src/gui/wizard/ImportWizardPageReview.h @@ -21,6 +21,12 @@ #include #include +#include +#include +#include + +#include "../remote/RemoteHandler.h" + class CsvImportWidget; class Database; namespace Ui @@ -48,6 +54,10 @@ private: QSharedPointer importBitwarden(const QString& filename, const QString& password); QSharedPointer importOPVault(const QString& filename, const QString& password); QSharedPointer importKeePass1(const QString& filename, const QString& password, const QString& keyfile); + QSharedPointer importRemote(const QString& downloadCommand, + const QString& downloadInput, + const QString& password, + const QString& keyfile); void setupDatabasePreview(); @@ -55,6 +65,7 @@ private: QSharedPointer m_db; QPointer m_csvWidget; + QPointer m_remoteHandler; }; #endif diff --git a/src/gui/wizard/ImportWizardPageSelect.cpp b/src/gui/wizard/ImportWizardPageSelect.cpp index 43b0e8f2a..e7bdacc9f 100644 --- a/src/gui/wizard/ImportWizardPageSelect.cpp +++ b/src/gui/wizard/ImportWizardPageSelect.cpp @@ -25,6 +25,8 @@ #include "gui/Icons.h" #include "gui/MainWindow.h" +#include + ImportWizardPageSelect::ImportWizardPageSelect(QWidget* parent) : QWizardPage(parent) , m_ui(new Ui::ImportWizardPageSelect()) @@ -36,12 +38,14 @@ ImportWizardPageSelect::ImportWizardPageSelect(QWidget* parent) new QListWidgetItem(icons()->icon("onepassword"), tr("1Password Vault (.opvault)"), m_ui->importTypeList); new QListWidgetItem(icons()->icon("bitwarden"), tr("Bitwarden (.json)"), m_ui->importTypeList); new QListWidgetItem(icons()->icon("object-locked"), tr("KeePass 1 Database (.kdb)"), m_ui->importTypeList); + new QListWidgetItem(icons()->icon("web"), tr("Remote Database (.kdbx)"), m_ui->importTypeList); m_ui->importTypeList->item(0)->setData(Qt::UserRole, ImportWizard::IMPORT_CSV); m_ui->importTypeList->item(1)->setData(Qt::UserRole, ImportWizard::IMPORT_OPUX); m_ui->importTypeList->item(2)->setData(Qt::UserRole, ImportWizard::IMPORT_OPVAULT); m_ui->importTypeList->item(3)->setData(Qt::UserRole, ImportWizard::IMPORT_BITWARDEN); m_ui->importTypeList->item(4)->setData(Qt::UserRole, ImportWizard::IMPORT_KEEPASS1); + m_ui->importTypeList->item(5)->setData(Qt::UserRole, ImportWizard::IMPORT_REMOTE); connect(m_ui->importTypeList, &QListWidget::currentItemChanged, this, &ImportWizardPageSelect::itemSelected); m_ui->importTypeList->setCurrentRow(0); @@ -54,11 +58,22 @@ ImportWizardPageSelect::ImportWizardPageSelect(QWidget* parent) updateDatabaseChoices(); + m_ui->downloadCommandHelpButton->setIcon(icons()->icon("system-help")); + connect(m_ui->downloadCommandHelpButton, &QToolButton::clicked, this, [] { + QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/KeePassXC_UserGuide#_remote_database_support")); + }); + + connect(m_ui->importFileEdit, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); + connect(m_ui->downloadCommand, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); + registerField("ImportType", this); - registerField("ImportFile*", m_ui->importFileEdit); - registerField("ImportInto", m_ui->importIntoLabel); + registerField("ImportFile", m_ui->importFileEdit); + registerField("ImportIntoType", m_ui->importIntoGroupBox); // This is intentional + registerField("ImportInto", m_ui->importIntoLabel); // This is intentional registerField("ImportPassword", m_ui->passwordEdit, "text", "textChanged"); registerField("ImportKeyFile", m_ui->keyFileEdit); + registerField("DownloadCommand", m_ui->downloadCommand); + registerField("DownloadInput", m_ui->downloadCommandInput, "plainText", "textChanged"); } ImportWizardPageSelect::~ImportWizardPageSelect() @@ -77,14 +92,27 @@ bool ImportWizardPageSelect::validatePage() if (m_ui->existingDatabaseChoice->currentIndex() == -1) { return false; } + setField("ImportIntoType", ImportWizard::EXISTING_DATABASE); setField("ImportInto", m_ui->existingDatabaseChoice->currentData()); + } else if (m_ui->temporaryDatabaseRadio->isChecked()) { + setField("ImportIntoType", ImportWizard::TEMPORARY_DATABASE); + setField("ImportInto", {}); } else { + setField("ImportIntoType", ImportWizard::NEW_DATABASE); setField("ImportInto", {}); } return true; } +bool ImportWizardPageSelect::isComplete() const +{ + if (field("ImportType").toInt() == ImportWizard::IMPORT_REMOTE) { + return !field("DownloadCommand").toString().isEmpty(); + } + return !field("ImportFile").toString().isEmpty(); +} + void ImportWizardPageSelect::itemSelected(QListWidgetItem* current, QListWidgetItem* previous) { Q_UNUSED(previous) @@ -105,15 +133,22 @@ void ImportWizardPageSelect::itemSelected(QListWidgetItem* current, QListWidgetI case ImportWizard::IMPORT_CSV: case ImportWizard::IMPORT_OPUX: setCredentialState(false); + setDownloadCommand(false); break; // Password may be required case ImportWizard::IMPORT_BITWARDEN: case ImportWizard::IMPORT_OPVAULT: setCredentialState(true); + setDownloadCommand(false); break; // Password and/or Key File may be required case ImportWizard::IMPORT_KEEPASS1: setCredentialState(true, true); + setDownloadCommand(false); + break; + case ImportWizard::IMPORT_REMOTE: + setCredentialState(true, true); + setDownloadCommand(true); break; default: Q_ASSERT(false); @@ -228,6 +263,33 @@ void ImportWizardPageSelect::setCredentialState(bool passwordEnabled, bool keyFi } } +void ImportWizardPageSelect::setDownloadCommand(bool downloadCommandEnabled) +{ + bool downloadCommandStateChanged = m_ui->downloadCommandLabel->isVisible() != downloadCommandEnabled; + m_ui->downloadCommandLabel->setVisible(downloadCommandEnabled); + m_ui->downloadCommand->setVisible(downloadCommandEnabled); + m_ui->downloadCommandInputLabel->setVisible(downloadCommandEnabled); + m_ui->downloadCommandInput->setVisible(downloadCommandEnabled); + m_ui->downloadCommandHelpButton->setVisible(downloadCommandEnabled); + + m_ui->temporaryDatabaseRadio->setVisible(downloadCommandEnabled); + + m_ui->importFileLabel->setVisible(!downloadCommandEnabled); + m_ui->importFileEdit->setVisible(!downloadCommandEnabled); + m_ui->importFileButton->setVisible(!downloadCommandEnabled); + + // Workaround Qt bug where the wizard window is not updated when the internal layout changes + if (window()) { + int height = window()->height(); + if (downloadCommandStateChanged) { + auto diff = m_ui->downloadCommand->height() + m_ui->downloadCommandInput->height() + + m_ui->temporaryDatabaseRadio->height() + m_ui->inputFields->layout()->spacing(); + height += downloadCommandEnabled ? diff : -diff; + } + window()->resize(window()->width(), height); + } +} + QString ImportWizardPageSelect::importFileFilter() { switch (field("ImportType").toInt()) { diff --git a/src/gui/wizard/ImportWizardPageSelect.h b/src/gui/wizard/ImportWizardPageSelect.h index 029865a56..5c4024ea5 100644 --- a/src/gui/wizard/ImportWizardPageSelect.h +++ b/src/gui/wizard/ImportWizardPageSelect.h @@ -39,6 +39,7 @@ public: void initializePage() override; bool validatePage() override; + bool isComplete() const override; private slots: void itemSelected(QListWidgetItem* current, QListWidgetItem* previous); @@ -49,6 +50,7 @@ private slots: private: QString importFileFilter(); void setCredentialState(bool passwordEnabled, bool keyFileEnable = false); + void setDownloadCommand(bool downloadCommandEnabled); QScopedPointer m_ui; }; diff --git a/src/gui/wizard/ImportWizardPageSelect.ui b/src/gui/wizard/ImportWizardPageSelect.ui index 6a7c8adad..ca37fec71 100644 --- a/src/gui/wizard/ImportWizardPageSelect.ui +++ b/src/gui/wizard/ImportWizardPageSelect.ui @@ -94,14 +94,14 @@ QLayout::SetMinimumSize - + Import File: - + @@ -115,24 +115,24 @@ - + Password: - + - + Key File: - + @@ -146,7 +146,7 @@ - + Qt::Vertical @@ -162,7 +162,7 @@ - + Import Into: @@ -172,7 +172,7 @@ - + @@ -239,9 +239,64 @@ + + + + Temporary Database + + + + + + + Command: + + + + + + + + + e.g.: "sftp user@hostname" or "scp user@hostname:DatabaseOnRemote.kdbx {TEMP_DATABASE}" + + + + + + + + + + + + + + + + Input: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + 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 + + + + diff --git a/tests/data/SyncDatabase.kdbx b/tests/data/SyncDatabase.kdbx index f72e6fb9e..1a8ad9375 100644 Binary files a/tests/data/SyncDatabase.kdbx and b/tests/data/SyncDatabase.kdbx differ diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 6799cc64d..6ae559c36 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -397,14 +398,10 @@ void TestGui::prepareAndTriggerRemoteSync(const QString& sourceToSync) QVERIFY(saveSettingsButton != nullptr); QTest::mouseClick(saveSettingsButton, Qt::LeftButton); - // find and click dialog OK button - auto buttons = dbSettingsDialog->findChild()->findChildren(); - for (QPushButton* b : buttons) { - if (b->text() == "OK") { - QTest::mouseClick(b, Qt::LeftButton); - break; - } - } + auto okButton = dbSettingsDialog->findChild("buttonBox")->button(QDialogButtonBox::Ok); + QVERIFY(okButton); + QTest::mouseClick(okButton, Qt::LeftButton); + QTRY_COMPARE(m_dbWidget->getRemoteParams().size(), 1); // trigger aboutToShow to create remote actions @@ -477,6 +474,56 @@ void TestGui::testRemoteSyncDatabaseRequiresPassword() QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1); } +void TestGui::testOpenRemoteDatabase() +{ + // close current database + cleanup(); + + QString sourceToSync = "sftp user@server:Database.kdbx"; + RemoteHandler::setRemoteProcessFunc([sourceToSync](QObject* parent) { + return QScopedPointer( + new MockRemoteProcess(parent, QString(KEEPASSX_TEST_DATA_DIR).append("/SyncDatabase.kdbx"))); + }); + auto* openRemoteButton = QApplication::activeWindow()->findChild("buttonImport"); + QVERIFY(openRemoteButton); + QVERIFY(openRemoteButton->isVisible()); + QTest::mouseClick(openRemoteButton, Qt::LeftButton); + QApplication::processEvents(); + + TEST_MODAL_NO_WAIT( + ImportWizard * wizard; QTRY_VERIFY(wizard = m_tabWidget->findChild()); + + auto* importTypeList = wizard->currentPage()->findChild("importTypeList"); + QVERIFY(importTypeList); + importTypeList->scrollToBottom(); + + QListWidgetItem* remoteOption = importTypeList->item(importTypeList->count() - 1); + QRect remoteOptionRect = importTypeList->visualItemRect(remoteOption); + QTest::mouseClick(importTypeList->viewport(), Qt::LeftButton, nullptr, remoteOptionRect.center()); + + auto* downloadCommandEdit = wizard->currentPage()->findChild("downloadCommand"); + QVERIFY(downloadCommandEdit); + QTest::keyClicks(downloadCommandEdit, sourceToSync); + + auto* temporaryDatabaseRadio = wizard->currentPage()->findChild("temporaryDatabaseRadio"); + QVERIFY(temporaryDatabaseRadio); + QTest::mouseClick(temporaryDatabaseRadio, Qt::LeftButton); + + auto* passwordEdit = wizard->currentPage()->findChild("passwordEdit"); + QVERIFY(passwordEdit); + QTest::keyClicks(passwordEdit, "a"); + QTest::keyClick(passwordEdit, Qt::Key_Enter); + + QApplication::processEvents(); + + QVERIFY(wizard->currentPage()->findChildren().count() > 0); + + QTest::keyClick(passwordEdit, Qt::Key_Enter);); + + // remote database has been opened + QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("SyncDatabase [Temporary]")); +} + void TestGui::testAutoreloadDatabase() { config()->set(Config::AutoReloadOnChange, false); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 70ff5dfc5..720e3e2a6 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -42,6 +42,7 @@ private slots: void testMergeDatabase(); void testRemoteSyncDatabaseSameKey(); void testRemoteSyncDatabaseRequiresPassword(); + void testOpenRemoteDatabase(); void testAutoreloadDatabase(); void testTabs(); void testEditEntry();