diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9ba5d8f53..921dafc97 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -80,7 +80,7 @@ parts: - libxtst6 - libqt5x11extras5 - libqt5svg5 - - libqrencode3 + - try: [libqrencode3, libqrencode4] - libqt5concurrent5 - libquazip5-1 - libusb-1.0-0 diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 7366ebbec..1868d5260 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -47,7 +47,7 @@ AutoType::AutoType(QObject* parent, bool test) , m_pluginLoader(new QPluginLoader(this)) , m_plugin(nullptr) , m_executor(nullptr) - , m_windowFromGlobal(0) + , m_windowForGlobal(0) { // prevent crash when the plugin has unresolved symbols m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint); @@ -90,7 +90,7 @@ void AutoType::loadPlugin(const QString& pluginPath) if (m_plugin) { if (m_plugin->isAvailable()) { m_executor = m_plugin->createExecutor(); - connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered())); + connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SLOT(startGlobalAutoType())); } else { unloadPlugin(); } @@ -222,6 +222,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt())); + // Used only for selected entry auto-type if (!window) { window = m_plugin->activeWindow(); } @@ -240,6 +241,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c QCoreApplication::processEvents(QEventLoop::AllEvents, 10); } + m_windowForGlobal = 0; + m_windowTitleForGlobal.clear(); + // emit signal only if autotype performed correctly emit autotypePerformed(); @@ -264,6 +268,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) executeAutoTypeActions(entry, hideWindow, sequences.first()); } +void AutoType::startGlobalAutoType() +{ + m_windowForGlobal = m_plugin->activeWindow(); + m_windowTitleForGlobal = m_plugin->activeWindowTitle(); + emit globalAutoTypeTriggered(); +} + /** * Global Autotype entry-point function * Perform global Auto-Type on the active window @@ -278,9 +289,7 @@ void AutoType::performGlobalAutoType(const QList>& dbLi return; } - QString windowTitle = m_plugin->activeWindowTitle(); - - if (windowTitle.isEmpty()) { + if (m_windowTitleForGlobal.isEmpty()) { m_inGlobalAutoTypeDialog.unlock(); return; } @@ -290,7 +299,7 @@ void AutoType::performGlobalAutoType(const QList>& dbLi for (const auto& db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { - const QSet sequences = autoTypeSequences(entry, windowTitle).toSet(); + const QSet sequences = autoTypeSequences(entry, m_windowTitleForGlobal).toSet(); for (const QString& sequence : sequences) { if (!sequence.isEmpty()) { matchList << AutoTypeMatch(entry, sequence); @@ -304,8 +313,9 @@ void AutoType::performGlobalAutoType(const QList>& dbLi auto* msgBox = new QMessageBox(); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setWindowTitle(tr("Auto-Type - KeePassXC")); - msgBox->setText( - tr("Couldn't find an entry that matches the window title:").append("\n\n").append(windowTitle)); + msgBox->setText(tr("Couldn't find an entry that matches the window title:") + .append("\n\n") + .append(m_windowTitleForGlobal)); msgBox->setIcon(QMessageBox::Information); msgBox->setStandardButtons(QMessageBox::Ok); msgBox->show(); @@ -316,10 +326,9 @@ void AutoType::performGlobalAutoType(const QList>& dbLi m_inGlobalAutoTypeDialog.unlock(); emit autotypeRejected(); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { - executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); + executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence, m_windowForGlobal); m_inGlobalAutoTypeDialog.unlock(); } else { - m_windowFromGlobal = m_plugin->activeWindow(); auto* selectDialog = new AutoTypeSelectDialog(); // connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex @@ -327,11 +336,12 @@ void AutoType::performGlobalAutoType(const QList>& dbLi connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal())); selectDialog->setMatchList(matchList); -#if defined(Q_OS_MACOS) +#ifdef Q_OS_MACOS m_plugin->raiseOwnWindow(); - Tools::wait(500); + Tools::wait(200); #endif selectDialog->show(); + selectDialog->raise(); // necessary when the main window is minimized selectDialog->activateWindow(); } @@ -339,8 +349,8 @@ void AutoType::performGlobalAutoType(const QList>& dbLi void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) { - m_plugin->raiseWindow(m_windowFromGlobal); - executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); + m_plugin->raiseWindow(m_windowForGlobal); + executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowForGlobal); // make sure the mutex is definitely locked before we unlock it Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); @@ -353,6 +363,8 @@ void AutoType::autoTypeRejectedFromGlobal() // so make sure the mutex is locked before we try unlocking it Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); m_inGlobalAutoTypeDialog.unlock(); + m_windowForGlobal = 0; + m_windowTitleForGlobal.clear(); emit autotypeRejected(); } diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index f58a1c0c1..ad8607529 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -62,18 +62,19 @@ public slots: void raiseWindow(); signals: - void globalShortcutTriggered(); + void globalAutoTypeTriggered(); void autotypePerformed(); void autotypeRejected(); private slots: + void startGlobalAutoType(); void performAutoTypeFromGlobal(AutoTypeMatch match); void autoTypeRejectedFromGlobal(); void unloadPlugin(); private: explicit AutoType(QObject* parent = nullptr, bool test = false); - ~AutoType(); + ~AutoType() override; void loadPlugin(const QString& pluginPath); void executeAutoTypeActions(const Entry* entry, QWidget* hideWindow = nullptr, @@ -94,9 +95,11 @@ private: QPluginLoader* m_pluginLoader; AutoTypePlatformInterface* m_plugin; AutoTypeExecutor* m_executor; - WId m_windowFromGlobal; static AutoType* m_instance; + QString m_windowTitleForGlobal; + WId m_windowForGlobal; + Q_DISABLE_COPY(AutoType) }; diff --git a/src/autotype/test/AutoTypeTest.cpp b/src/autotype/test/AutoTypeTest.cpp index 9a1b65013..225698e62 100644 --- a/src/autotype/test/AutoTypeTest.cpp +++ b/src/autotype/test/AutoTypeTest.cpp @@ -68,6 +68,11 @@ AutoTypeExecutor* AutoTypePlatformTest::createExecutor() return new AutoTypeExecutorTest(this); } +void AutoTypePlatformTest::triggerGlobalAutoType() +{ + emit globalShortcutTriggered(); +} + void AutoTypePlatformTest::setActiveWindowTitle(const QString& title) { m_activeWindowTitle = title; diff --git a/src/autotype/test/AutoTypeTest.h b/src/autotype/test/AutoTypeTest.h index 87d19491a..ef19c1dd7 100644 --- a/src/autotype/test/AutoTypeTest.h +++ b/src/autotype/test/AutoTypeTest.h @@ -48,6 +48,7 @@ public: bool raiseOwnWindow() override; #endif + void triggerGlobalAutoType() override; void setActiveWindowTitle(const QString& title) override; QString actionChars() override; diff --git a/src/autotype/test/AutoTypeTestInterface.h b/src/autotype/test/AutoTypeTestInterface.h index 7681f2ecb..4595b0eb8 100644 --- a/src/autotype/test/AutoTypeTestInterface.h +++ b/src/autotype/test/AutoTypeTestInterface.h @@ -26,6 +26,7 @@ public: virtual ~AutoTypeTestInterface() { } + virtual void triggerGlobalAutoType() = 0; virtual void setActiveWindowTitle(const QString& title) = 0; virtual QString actionChars() = 0; diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index 535170939..3c8df5ffc 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -94,6 +94,8 @@ private: QString m_publicKey; QString m_secretKey; bool m_associated; + + friend class TestBrowser; }; #endif // BROWSERACTION_H diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp index 9eecc63f9..e212fc6b4 100644 --- a/src/browser/BrowserOptionDialog.cpp +++ b/src/browser/BrowserOptionDialog.cpp @@ -120,6 +120,7 @@ void BrowserOptionDialog::loadSettings() m_ui->useCustomProxy->setChecked(settings->useCustomProxy()); m_ui->customProxyLocation->setText(settings->customProxyLocation()); m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath()); + m_ui->allowExpiredCredentials->setChecked(settings->allowExpiredCredentials()); m_ui->chromeSupport->setChecked(settings->chromeSupport()); m_ui->chromiumSupport->setChecked(settings->chromiumSupport()); m_ui->firefoxSupport->setChecked(settings->firefoxSupport()); @@ -176,6 +177,7 @@ void BrowserOptionDialog::saveSettings() settings->setCustomProxyLocation(m_ui->customProxyLocation->text()); settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked()); + settings->setAllowExpiredCredentials(m_ui->allowExpiredCredentials->isChecked()); settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked()); settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked()); settings->setHttpAuthPermission(m_ui->httpAuthPermission->isChecked()); diff --git a/src/browser/BrowserOptionDialog.ui b/src/browser/BrowserOptionDialog.ui index 50fd9d205..0229649f1 100755 --- a/src/browser/BrowserOptionDialog.ui +++ b/src/browser/BrowserOptionDialog.ui @@ -219,6 +219,16 @@ + + + + Returns expired credentials. String [expired] is added to the title. + + + &Allow returning expired credentials. + + + diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index d9d1b7507..362f23727 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -172,9 +172,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group) return groupList; } -QJsonObject BrowserService::getDatabaseGroups() +QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer& selectedDb) { - auto db = getDatabase(); + auto db = selectedDb ? selectedDb : getDatabase(); if (!db) { return {}; } @@ -453,11 +453,6 @@ void BrowserService::addEntry(const QString& id, return; } - auto* addEntryGroup = findCreateAddEntryGroup(db); - if (!addEntryGroup) { - return; - } - auto* entry = new Entry(); entry->setUuid(QUuid::createUuid()); entry->setTitle(QUrl(url).host()); @@ -465,16 +460,19 @@ void BrowserService::addEntry(const QString& id, entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON); entry->setUsername(login); entry->setPassword(password); - entry->setGroup(addEntryGroup); // Select a group for the entry if (!group.isEmpty()) { if (db->rootGroup()) { auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid)); - if (selectedGroup && selectedGroup->name() == group) { + if (selectedGroup) { entry->setGroup(selectedGroup); + } else { + entry->setGroup(getDefaultEntryGroup(db)); } } + } else { + entry->setGroup(getDefaultEntryGroup(db)); } const QString host = QUrl(url).host(); @@ -818,6 +816,10 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry) res["totp"] = entry->totp(); } + if (entry->isExpired()) { + res["expired"] = "true"; + } + if (browserSettings()->supportKphFields()) { const EntryAttributes* attr = entry->attributes(); QJsonArray stringFields; @@ -841,7 +843,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri return Unknown; } if (entry->isExpired()) { - return Denied; + return browserSettings()->allowExpiredCredentials() ? Allowed : Denied; } if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) { return Allowed; @@ -855,7 +857,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri return Unknown; } -Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer& selectedDb) +Group* BrowserService::getDefaultEntryGroup(const QSharedPointer& selectedDb) { auto db = selectedDb ? selectedDb : getDatabase(); if (!db) { @@ -868,7 +870,7 @@ Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer& s } const QString groupName = - QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); // TODO: setting to decide where new keys are created + QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); for (auto* g : rootGroup->groupsRecursive(true)) { if (g->name() == groupName && !g->isRecycled()) { diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index a8f04262f..77d94b6bd 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -44,7 +44,7 @@ public: bool openDatabase(bool triggerUnlock); QString getDatabaseRootUuid(); QString getDatabaseRecycleBinUuid(); - QJsonObject getDatabaseGroups(); + QJsonObject getDatabaseGroups(const QSharedPointer& selectedDb = {}); QJsonObject createNewGroup(const QString& groupName); QString getKey(const QString& id); void addEntry(const QString& id, @@ -114,7 +114,7 @@ private: const QString& realm); QJsonObject prepareEntry(const Entry* entry); Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm); - Group* findCreateAddEntryGroup(const QSharedPointer& selectedDb = {}); + Group* getDefaultEntryGroup(const QSharedPointer& selectedDb = {}); int sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const; bool matchUrlScheme(const QString& url); @@ -135,6 +135,8 @@ private: bool m_bringToFrontRequested; WindowState m_prevWindowState; QUuid m_keepassBrowserUUID; + + friend class TestBrowser; }; #endif // BROWSERSERVICE_H diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index fbb194974..667812d1f 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -194,6 +194,16 @@ void BrowserSettings::setUpdateBinaryPath(bool enabled) config()->set("Browser/UpdateBinaryPath", enabled); } +bool BrowserSettings::allowExpiredCredentials() +{ + return config()->get("Browser/AllowExpiredCredentials", false).toBool(); +} + +void BrowserSettings::setAllowExpiredCredentials(bool enabled) +{ + config()->set("Browser/AllowExpiredCredentials", enabled); +} + bool BrowserSettings::chromeSupport() { return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME); diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index ba74ff53e..b47e92866 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -64,6 +64,8 @@ public: void setCustomProxyLocation(const QString& location); bool updateBinaryPath(); void setUpdateBinaryPath(bool enabled); + bool allowExpiredCredentials(); + void setAllowExpiredCredentials(bool enabled); bool chromeSupport(); void setChromeSupport(bool enabled); bool chromiumSupport(); diff --git a/src/browser/NativeMessagingBase.cpp b/src/browser/NativeMessagingBase.cpp index 9be399644..b44875b14 100644 --- a/src/browser/NativeMessagingBase.cpp +++ b/src/browser/NativeMessagingBase.cpp @@ -19,6 +19,8 @@ #include "NativeMessagingBase.h" #include +#include "config-keepassx.h" + #if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) #include #include @@ -138,7 +140,7 @@ QString NativeMessagingBase::getLocalServerPath() const { const QString serverPath = "/kpxc_server"; #if defined(KEEPASSXC_DIST_SNAP) - return QProcessEnvironment::systemEnvironment().value("SNAP_COMMON") + serverPath; + return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverPath; #elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // Use XDG_RUNTIME_DIR instead of /tmp if it's available QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 4bb0202b1..d0914a04e 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -35,7 +35,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device, { Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4); - m_binaryPoolInverse.clear(); + m_binaryPool.clear(); if (hasError()) { return false; @@ -273,11 +273,7 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device) return false; } auto data = fieldData.mid(1); - if (m_binaryPoolInverse.contains(data)) { - qWarning("Skipping duplicate binary record"); - break; - } - m_binaryPoolInverse.insert(data, QString::number(m_binaryPoolInverse.size())); + m_binaryPool.insert(QString::number(m_binaryPool.size()), data); break; } } @@ -422,17 +418,5 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device) */ QHash Kdbx4Reader::binaryPool() const { - QHash binaryPool; - for (auto it = m_binaryPoolInverse.cbegin(); it != m_binaryPoolInverse.cend(); ++it) { - binaryPool.insert(it.value(), it.key()); - } - return binaryPool; -} - -/** - * @return mapping from binary data to attachment keys - */ -QHash Kdbx4Reader::binaryPoolInverse() const -{ - return m_binaryPoolInverse; + return m_binaryPool; } diff --git a/src/format/Kdbx4Reader.h b/src/format/Kdbx4Reader.h index 3afb5bf69..fc73a1fbd 100644 --- a/src/format/Kdbx4Reader.h +++ b/src/format/Kdbx4Reader.h @@ -34,7 +34,6 @@ public: const QByteArray& headerData, QSharedPointer key, Database* db) override; - QHash binaryPoolInverse() const; QHash binaryPool() const; protected: @@ -44,7 +43,7 @@ private: bool readInnerHeaderField(QIODevice* device); QVariantMap readVariantMap(QIODevice* device); - QHash m_binaryPoolInverse; + QHash m_binaryPool; }; #endif // KEEPASSX_KDBX4READER_H diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index ce2697802..d24196502 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -59,7 +59,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*))); - connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType())); + connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType())); connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase())); // clang-format on @@ -580,7 +580,7 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget, #ifdef Q_OS_MACOS if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) { macUtils()->raiseOwnWindow(); - Tools::wait(500); + Tools::wait(200); } #endif diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index c51594460..099d38986 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -178,7 +178,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged())); connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged())); connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode))); - connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString))); + connect(m_previewView, SIGNAL(errorOccurred(QString)), SLOT(showErrorMessage(QString))); + connect(m_previewView, SIGNAL(entryUrlActivated(Entry*)), SLOT(openUrlForEntry(Entry*))); connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged())); connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SLOT(onGroupChanged(Group*))); connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SIGNAL(groupChanged())); @@ -195,7 +196,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool))); connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool))); connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile())); - connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); + connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged())); // clang-format on connectDatabaseSignals(); @@ -648,6 +649,10 @@ void DatabaseWidget::openUrl() void DatabaseWidget::openUrlForEntry(Entry* entry) { Q_ASSERT(entry); + if (!entry) { + return; + } + QString cmdString = entry->resolveMultiplePlaceholders(entry->url()); if (cmdString.startsWith("cmd://")) { // check if decision to execute command was stored @@ -691,9 +696,9 @@ void DatabaseWidget::openUrlForEntry(Entry* entry) } } } else { - QString urlString = entry->webUrl(); - if (!urlString.isEmpty()) { - QDesktopServices::openUrl(urlString); + QUrl url = QUrl(entry->url()); + if (!url.isEmpty()) { + QDesktopServices::openUrl(url); } } } diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index 0fd8826c8..c90d0aa67 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -54,11 +54,13 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent) m_ui->entryAttachmentsWidget->setReadOnly(true); m_ui->entryAttachmentsWidget->setButtonsVisible(false); + connect(m_ui->entryUrlLabel, SIGNAL(linkActivated(QString)), SLOT(openEntryUrl())); + connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool))); connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide())); connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool))); connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection); - connect(&m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel())); + connect(&m_totpTimer, SIGNAL(timeout()), SLOT(updateTotpLabel())); // Group m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close")); @@ -197,11 +199,12 @@ void EntryPreviewWidget::updateEntryGeneralTab() } m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl()); - const QString url = m_currentEntry->webUrl(); + const QString url = m_currentEntry->url(); if (!url.isEmpty()) { // URL is well formed and can be opened in a browser m_ui->entryUrlLabel->setUrl(url); m_ui->entryUrlLabel->setCursor(Qt::PointingHandCursor); + m_ui->entryUrlLabel->setOpenExternalLinks(false); } else { m_ui->entryUrlLabel->setUrl({}); m_ui->entryUrlLabel->setCursor(Qt::ArrowCursor); @@ -327,6 +330,13 @@ void EntryPreviewWidget::updateTabIndexes() m_selectedTabGroup = m_ui->groupTabWidget->currentIndex(); } +void EntryPreviewWidget::openEntryUrl() +{ + if (m_currentEntry) { + emit entryUrlActivated(m_currentEntry); + } +} + void EntryPreviewWidget::setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled) { const int tabIndex = tabWidget->indexOf(widget); diff --git a/src/gui/EntryPreviewWidget.h b/src/gui/EntryPreviewWidget.h index 5bfd9dbd6..6a50c0feb 100644 --- a/src/gui/EntryPreviewWidget.h +++ b/src/gui/EntryPreviewWidget.h @@ -43,6 +43,7 @@ public slots: signals: void errorOccurred(const QString& error); + void entryUrlActivated(Entry* entry); private slots: void updateEntryHeaderLine(); @@ -63,6 +64,7 @@ private slots: void updateTotpLabel(); void updateTabIndexes(); + void openEntryUrl(); private: void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled); diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index 810b4bde5..9610cc05b 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -7,9 +7,11 @@ #include #include #include +#include #include #include "EntryAttachmentsModel.h" +#include "config-keepassx.h" #include "core/Config.h" #include "core/EntryAttachments.h" #include "core/Tools.h" @@ -324,7 +326,12 @@ bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& e const QByteArray attachmentData = m_entryAttachments->value(filename); // tmp file will be removed once the database (or the application) has been closed +#ifdef KEEPASSXC_DIST_SNAP + const QString tmpFileTemplate = + QString("%1/XXXXXX.%2").arg(QProcessEnvironment::systemEnvironment().value("SNAP_USER_DATA"), filename); +#else const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); +#endif QScopedPointer tmpFile(new QTemporaryFile(tmpFileTemplate, this)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2092151fe..6d2867bb6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -231,6 +231,11 @@ if(WITH_XC_FDOSECRETS) LIBS testsupport ${TEST_LIBRARIES}) endif() +if(WITH_XC_BROWSER) + add_unit_test(NAME testbrowser SOURCES TestBrowser.cpp + LIBS ${TEST_LIBRARIES}) +endif() + if(WITH_GUI_TESTS) # CLI clip tests need X environment on Linux diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 9cc22f828..69ae955b3 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -157,6 +157,7 @@ void TestAutoType::testGlobalAutoTypeWithNoMatch() void TestAutoType::testGlobalAutoTypeWithOneMatch() { m_test->setActiveWindowTitle("custom window"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password())); @@ -167,6 +168,7 @@ void TestAutoType::testGlobalAutoTypeTitleMatch() config()->set("AutoTypeEntryTitleMatch", true); m_test->setActiveWindowTitle("An Entry Title!"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); @@ -177,6 +179,7 @@ void TestAutoType::testGlobalAutoTypeUrlMatch() config()->set("AutoTypeEntryTitleMatch", true); m_test->setActiveWindowTitle("Dummy - http://example.org/ - "); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); @@ -187,6 +190,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch() config()->set("AutoTypeEntryTitleMatch", true); m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - "); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); @@ -195,6 +199,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch() void TestAutoType::testGlobalAutoTypeTitleMatchDisabled() { m_test->setActiveWindowTitle("An Entry Title!"); + m_test->triggerGlobalAutoType(); MessageBox::setNextAnswer(MessageBox::Ok); m_autoType->performGlobalAutoType(m_dbList); @@ -205,58 +210,68 @@ void TestAutoType::testGlobalAutoTypeRegExp() { // substring matches are ok m_test->setActiveWindowTitle("lorem REGEX1 ipsum"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex1")); m_test->clearActions(); // should be case-insensitive m_test->setActiveWindowTitle("lorem regex1 ipsum"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex1")); m_test->clearActions(); // exact match m_test->setActiveWindowTitle("REGEX2"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex2")); m_test->clearActions(); // a bit more complicated regex m_test->setActiveWindowTitle("REGEX3-R2D2"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex3")); m_test->clearActions(); // with custom attributes m_test->setActiveWindowTitle("CustomAttr1"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute")); m_test->clearActions(); // with (non uppercase) undefined custom attributes m_test->setActiveWindowTitle("CustomAttr2"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("")); m_test->clearActions(); // with mixedcase default attributes m_test->setActiveWindowTitle("CustomAttr3"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr")); m_test->clearActions(); // with resolve placeholders in window association title m_test->setActiveWindowTitle("AttrValueFirst"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr_first")); m_test->clearActions(); m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second")); m_test->clearActions(); m_test->setActiveWindowTitle("lorem AttrValueThird ipsum"); + m_test->triggerGlobalAutoType(); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr_third")); m_test->clearActions(); diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp new file mode 100644 index 000000000..b2ec6d6dd --- /dev/null +++ b/tests/TestBrowser.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#include "TestBrowser.h" +#include "TestGlobal.h" +#include "crypto/Crypto.h" +#include "sodium/crypto_box.h" +#include "browser/BrowserSettings.h" +#include + +QTEST_GUILESS_MAIN(TestBrowser) + +const QString PUBLICKEY = "UIIPObeoya1G8g1M5omgyoPR/j1mR1HlYHu0wHCgMhA="; +const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI="; +const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ="; +const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q="; +const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ"; +const QString CLIENTID = "testClient"; + +void TestBrowser::initTestCase() +{ + QVERIFY(Crypto::init()); + m_browserService.reset(new BrowserService(nullptr)); + m_browserAction.reset(new BrowserAction(*m_browserService.data())); +} + +void TestBrowser::cleanupTestCase() +{ + +} + +/** + * Tests for BrowserAction + */ + +void TestBrowser::testChangePublicKeys() +{ + QJsonObject json; + json["action"] = "change-public-keys"; + json["publicKey"] = PUBLICKEY; + json["nonce"] = NONCE; + + auto response = m_browserAction->handleAction(json); + QCOMPARE(response["action"].toString(), QString("change-public-keys")); + QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false); + QCOMPARE(response["success"].toString(), QString("true")); +} + +void TestBrowser::testEncryptMessage() +{ + QJsonObject message; + message["action"] = "test-action"; + + m_browserAction->m_publicKey = SERVERPUBLICKEY; + m_browserAction->m_secretKey = SERVERSECRETKEY; + m_browserAction->m_clientPublicKey = PUBLICKEY; + auto encrypted = m_browserAction->encryptMessage(message, NONCE); + + QCOMPARE(encrypted, QString("+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP")); +} + +void TestBrowser::testDecryptMessage() +{ + QString message = "+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP"; + m_browserAction->m_publicKey = SERVERPUBLICKEY; + m_browserAction->m_secretKey = SERVERSECRETKEY; + m_browserAction->m_clientPublicKey = PUBLICKEY; + auto decrypted = m_browserAction->decryptMessage(message, NONCE); + + QCOMPARE(decrypted["action"].toString(), QString("test-action")); +} + +void TestBrowser::testGetBase64FromKey() +{ + unsigned char pk[crypto_box_PUBLICKEYBYTES]; + + for (unsigned int i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) { + pk[i] = i; + } + + auto response = m_browserAction->getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES); + QCOMPARE(response, QString("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=")); +} + +void TestBrowser::testIncrementNonce() +{ + auto result = m_browserAction->incrementNonce(NONCE); + QCOMPARE(result, QString("zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ")); +} + +/** + * Tests for BrowserService + */ +void TestBrowser::testBaseDomain() +{ + QString url1 = "https://another.example.co.uk"; + QString url2 = "https://www.example.com"; + QString url3 = "http://test.net"; + QString url4 = "http://so.many.subdomains.co.jp"; + + QString res1 = m_browserService->baseDomain(url1); + QString res2 = m_browserService->baseDomain(url2); + QString res3 = m_browserService->baseDomain(url3); + QString res4 = m_browserService->baseDomain(url4); + + QCOMPARE(res1, QString("example.co.uk")); + QCOMPARE(res2, QString("example.com")); + QCOMPARE(res3, QString("test.net")); + QCOMPARE(res4, QString("subdomains.co.jp")); +} + +void TestBrowser::testSortPriority() +{ + QString host = "github.com"; + QString submitUrl = "https://github.com/session"; + QString baseSubmitUrl = "https://github.com"; + + QScopedPointer entry1(new Entry()); + QScopedPointer entry2(new Entry()); + QScopedPointer entry3(new Entry()); + QScopedPointer entry4(new Entry()); + QScopedPointer entry5(new Entry()); + QScopedPointer entry6(new Entry()); + QScopedPointer entry7(new Entry()); + QScopedPointer entry8(new Entry()); + QScopedPointer entry9(new Entry()); + QScopedPointer entry10(new Entry()); + + entry1->setUrl("https://github.com/login"); + entry2->setUrl("https://github.com/login"); + entry3->setUrl("https://github.com/"); + entry4->setUrl("github.com/login"); + entry5->setUrl("http://github.com"); + entry6->setUrl("http://github.com/login"); + entry7->setUrl("github.com"); + entry8->setUrl("github.com/login"); + entry9->setUrl("https://github"); + entry10->setUrl("github.com"); + + // The extension uses the submitUrl as default for comparison + auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", baseSubmitUrl); + auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl); + auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl); + auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl); + auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl); + auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl); + auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl); + auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl); + auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl); + auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl); + + QCOMPARE(res1, 100); + QCOMPARE(res2, 40); + QCOMPARE(res3, 90); + QCOMPARE(res4, 0); + QCOMPARE(res5, 0); + QCOMPARE(res6, 0); + QCOMPARE(res7, 0); + QCOMPARE(res8, 0); + QCOMPARE(res9, 90); + QCOMPARE(res10, 0); +} + +void TestBrowser::testSearchEntries() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QList urls; + urls.push_back("https://github.com/login_page"); + urls.push_back("https://github.com/login"); + urls.push_back("https://github.com/"); + urls.push_back("github.com/login"); + urls.push_back("http://github.com"); + urls.push_back("http://github.com/login"); + urls.push_back("github.com"); + urls.push_back("github.com/login"); + urls.push_back("https://github"); + urls.push_back("github.com"); + + for (int i = 0; i < urls.length(); ++i) { + auto entry = new Entry(); + entry->setGroup(root); + entry->beginUpdate(); + entry->setUrl(urls[i]); + entry->setUsername(QString("User %1").arg(i)); + entry->endUpdate(); + } + + browserSettings()->setMatchUrlScheme(false); + auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url + QCOMPARE(result.length(), 7); + QCOMPARE(result[0]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[1]->url(), QString("https://github.com/login")); + QCOMPARE(result[2]->url(), QString("https://github.com/")); + QCOMPARE(result[3]->url(), QString("http://github.com")); + QCOMPARE(result[4]->url(), QString("http://github.com/login")); + QCOMPARE(result[5]->url(), QString("github.com")); + QCOMPARE(result[6]->url(), QString("github.com")) ; + + // With matching there should be only 5 results + browserSettings()->setMatchUrlScheme(true); + result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url + QCOMPARE(result.length(), 5); + QCOMPARE(result[0]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[1]->url(), QString("https://github.com/login")); + QCOMPARE(result[2]->url(), QString("https://github.com/")); + QCOMPARE(result[3]->url(), QString("github.com")); + QCOMPARE(result[4]->url(), QString("github.com")); +} + +void TestBrowser::testSearchEntriesWithPort() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QList urls; + urls.push_back("http://127.0.0.1:443"); + urls.push_back("http://127.0.0.1:80"); + + for (int i = 0; i < urls.length(); ++i) { + auto entry = new Entry(); + entry->setGroup(root); + entry->beginUpdate(); + entry->setUrl(urls[i]); + entry->setUsername(QString("User %1").arg(i)); + entry->endUpdate(); + } + + auto result = m_browserService->searchEntries(db, "127.0.0.1", "http://127.0.0.1:443"); // db, hostname, url + QCOMPARE(result.length(), 1); + QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443")); +} + +void TestBrowser::testSortEntries() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QList urls; + urls.push_back("https://github.com/login_page"); + urls.push_back("https://github.com/login"); + urls.push_back("https://github.com/"); + urls.push_back("github.com/login"); + urls.push_back("http://github.com"); + urls.push_back("http://github.com/login"); + urls.push_back("github.com"); + urls.push_back("github.com/login"); + urls.push_back("https://github"); + urls.push_back("github.com"); + + QList entries; + for (int i = 0; i < urls.length(); ++i) { + auto entry = new Entry(); + entry->setGroup(root); + entry->beginUpdate(); + entry->setUrl(urls[i]); + entry->setUsername(QString("User %1").arg(i)); + entry->endUpdate(); + entries.push_back(entry); + } + + browserSettings()->setBestMatchOnly(false); + auto result = m_browserService->sortEntries(entries, "github.com", "https://github.com/session"); // entries, host, submitUrl + QCOMPARE(result.size(), 10); + QCOMPARE(result[0]->username(), QString("User 2")); + QCOMPARE(result[0]->url(), QString("https://github.com/")); + QCOMPARE(result[1]->username(), QString("User 8")); + QCOMPARE(result[1]->url(), QString("https://github")); + QCOMPARE(result[2]->username(), QString("User 0")); + QCOMPARE(result[2]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[3]->username(), QString("User 1")); + QCOMPARE(result[3]->url(), QString("https://github.com/login")); +} + +void TestBrowser::testGetDatabaseGroups() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QScopedPointer group1(new Group()); + group1->setParent(root); + group1->setName("group1"); + + QScopedPointer group2(new Group()); + group2->setParent(root); + group2->setName("group2"); + + QScopedPointer group3(new Group()); + group3->setParent(root); + group3->setName("group3"); + + QScopedPointer group2_1(new Group()); + group2_1->setParent(group2.data()); + group2_1->setName("group2_1"); + + QScopedPointer group2_2(new Group()); + group2_2->setParent(group2.data()); + group2_2->setName("group2_2"); + + QScopedPointer group2_1_1(new Group()); + group2_1_1->setParent(group2_1.data()); + group2_1_1->setName("group2_1_1"); + + auto result = m_browserService->getDatabaseGroups(db); + QCOMPARE(result.length(), 1); + + auto groups = result["groups"].toArray(); + auto first = groups.at(0); + auto children = first.toObject()["children"].toArray(); + QCOMPARE(first.toObject()["name"].toString(), QString("Root")); + QCOMPARE(children.size(), 3); + + auto firstChild = children.at(0); + auto secondChild = children.at(1); + auto thirdChild = children.at(2); + QCOMPARE(firstChild.toObject()["name"].toString(), QString("group1")); + QCOMPARE(secondChild.toObject()["name"].toString(), QString("group2")); + QCOMPARE(thirdChild.toObject()["name"].toString(), QString("group3")); + + auto childrenOfSecond = secondChild.toObject()["children"].toArray(); + auto firstOfCOS = childrenOfSecond.at(0); + auto secondOfCOS = childrenOfSecond.at(1); + QCOMPARE(firstOfCOS.toObject()["name"].toString(), QString("group2_1")); + QCOMPARE(secondOfCOS.toObject()["name"].toString(), QString("group2_2")); + + auto lastChildren = firstOfCOS.toObject()["children"].toArray(); + auto lastChild = lastChildren.at(0); + QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1")); + } diff --git a/tests/TestBrowser.h b/tests/TestBrowser.h new file mode 100644 index 000000000..0b939b077 --- /dev/null +++ b/tests/TestBrowser.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_TESTBROWSER_H +#define KEEPASSXC_TESTBROWSER_H + +#include + +#include "browser/BrowserAction.h" +#include "browser/BrowserService.h" +#include "core/Group.h" + +class TestBrowser : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void testChangePublicKeys(); + void testEncryptMessage(); + void testDecryptMessage(); + void testGetBase64FromKey(); + void testIncrementNonce(); + + void testBaseDomain(); + void testSortPriority(); + void testSearchEntries(); + void testSearchEntriesWithPort(); + void testSortEntries(); + void testGetDatabaseGroups(); + +private: + QScopedPointer m_browserAction; + QScopedPointer m_browserService; +}; + +#endif // KEEPASSXC_TESTBROWSER_H