Support opening remote databases (#10896)

* Use the import wizard to support opening a remote database

---------

Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
sforst 2024-10-07 04:39:50 +02:00 committed by GitHub
parent abcb1414a3
commit d2da13da20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 379 additions and 62 deletions

View File

@ -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.

View File

@ -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>
@ -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>

View File

@ -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()

View File

@ -21,6 +21,7 @@
#include "DatabaseOpenDialog.h"
#include "config-keepassx.h"
#include "gui/MessageWidget.h"
#include "wizard/ImportWizard.h"
#include <QTabWidget>
#include <QTimer>
@ -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<DatabaseWidgetStateSync> m_dbWidgetStateSync;
QPointer<DatabaseWidget> m_dbWidgetPendingLock;
QPointer<DatabaseOpenDialog> m_databaseOpenDialog;
QPointer<ImportWizard> m_importWizard;
QTimer m_lockDelayTimer;
bool m_databaseOpenInProgress;
};

View File

@ -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();

View File

@ -19,6 +19,7 @@
#define KEEPASSXC_IMPORTWIZARD_H
#include <QPointer>
#include <QUuid>
#include <QWizard>
class Database;
@ -39,7 +40,6 @@ public:
bool validateCurrentPage() override;
QSharedPointer<Database> database();
QPair<QUuid, QUuid> 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<QUuid, QUuid> importInto();
private:
QSharedPointer<Database> m_db;
QPointer<ImportWizardPageSelect> m_pageSelect;

View File

@ -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))
{
}
@ -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<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;
}

View File

@ -21,6 +21,12 @@
#include <QPointer>
#include <QWizardPage>
#include <QLabel>
#include <QProgressBar>
#include <QStatusBar>
#include "../remote/RemoteHandler.h"
class CsvImportWidget;
class Database;
namespace Ui
@ -48,6 +54,10 @@ private:
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();
@ -55,6 +65,7 @@ private:
QSharedPointer<Database> m_db;
QPointer<CsvImportWidget> m_csvWidget;
QPointer<RemoteHandler> m_remoteHandler;
};
#endif

View File

@ -25,6 +25,8 @@
#include "gui/Icons.h"
#include "gui/MainWindow.h"
#include <QDesktopServices>
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()) {

View File

@ -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<Ui::ImportWizardPageSelect> m_ui;
};

View File

@ -94,14 +94,14 @@
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item row="0" column="0">
<item row="3" column="0">
<widget class="QLabel" name="importFileLabel">
<property name="text">
<string>Import File:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="3" column="1">
<layout class="QHBoxLayout" name="importFileLayout">
<item>
<widget class="QLineEdit" name="importFileEdit"/>
@ -115,24 +115,24 @@
</item>
</layout>
</item>
<item row="1" column="0">
<item row="4" column="0">
<widget class="QLabel" name="passwordLabel">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="4" column="1">
<widget class="PasswordWidget" name="passwordEdit" native="true"/>
</item>
<item row="2" column="0">
<item row="5" column="0">
<widget class="QLabel" name="keyFileLabel">
<property name="text">
<string>Key File:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="5" column="1">
<layout class="QHBoxLayout" name="keyFileLayout">
<item>
<widget class="QLineEdit" name="keyFileEdit"/>
@ -146,7 +146,7 @@
</item>
</layout>
</item>
<item row="3" column="1">
<item row="6" column="1">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -162,7 +162,7 @@
</property>
</spacer>
</item>
<item row="4" column="0">
<item row="7" column="0">
<widget class="QLabel" name="importIntoLabel">
<property name="text">
<string>Import Into:</string>
@ -172,7 +172,7 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="7" column="1">
<widget class="QGroupBox" name="importIntoGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
@ -239,9 +239,64 @@
</item>
</layout>
</item>
<item>
<widget class="QRadioButton" name="temporaryDatabaseRadio">
<property name="text">
<string>Temporary Database</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="downloadCommandLabel">
<property name="text">
<string>Command:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="downloadCommandLayout">
<item>
<widget class="QLineEdit" name="downloadCommand">
<property name="placeholderText">
<string>e.g.: &quot;sftp user@hostname&quot; or &quot;scp user@hostname:DatabaseOnRemote.kdbx {TEMP_DATABASE}&quot;</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="downloadCommandHelpButton">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="downloadCommandInputLabel">
<property name="text">
<string>Input:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPlainTextEdit" name="downloadCommandInput">
<property name="placeholderText">
<string>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
</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

Binary file not shown.

View File

@ -30,6 +30,7 @@
#include <QRadioButton>
#include <QSignalSpy>
#include <QSpinBox>
#include <QTableWidget>
#include <QTest>
#include <QToolBar>
@ -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<QDialogButtonBox*>()->findChildren<QPushButton*>();
for (QPushButton* b : buttons) {
if (b->text() == "OK") {
QTest::mouseClick(b, Qt::LeftButton);
break;
}
}
auto okButton = dbSettingsDialog->findChild<QDialogButtonBox*>("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<RemoteProcess>(
new MockRemoteProcess(parent, QString(KEEPASSX_TEST_DATA_DIR).append("/SyncDatabase.kdbx")));
});
auto* openRemoteButton = QApplication::activeWindow()->findChild<QPushButton*>("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<ImportWizard*>());
auto* importTypeList = wizard->currentPage()->findChild<QListWidget*>("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<QLineEdit*>("downloadCommand");
QVERIFY(downloadCommandEdit);
QTest::keyClicks(downloadCommandEdit, sourceToSync);
auto* temporaryDatabaseRadio = wizard->currentPage()->findChild<QRadioButton*>("temporaryDatabaseRadio");
QVERIFY(temporaryDatabaseRadio);
QTest::mouseClick(temporaryDatabaseRadio, Qt::LeftButton);
auto* passwordEdit = wizard->currentPage()->findChild<QLineEdit*>("passwordEdit");
QVERIFY(passwordEdit);
QTest::keyClicks(passwordEdit, "a");
QTest::keyClick(passwordEdit, Qt::Key_Enter);
QApplication::processEvents();
QVERIFY(wizard->currentPage()->findChildren<QTableWidget*>().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);

View File

@ -42,6 +42,7 @@ private slots:
void testMergeDatabase();
void testRemoteSyncDatabaseSameKey();
void testRemoteSyncDatabaseRequiresPassword();
void testOpenRemoteDatabase();
void testAutoreloadDatabase();
void testTabs();
void testEditEntry();