Merge branch 'release/2.4.2' into develop

This commit is contained in:
Jonathan White 2019-05-30 17:22:09 -04:00
commit 63aab99b9c
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
25 changed files with 541 additions and 63 deletions

View File

@ -80,7 +80,7 @@ parts:
- libxtst6 - libxtst6
- libqt5x11extras5 - libqt5x11extras5
- libqt5svg5 - libqt5svg5
- libqrencode3 - try: [libqrencode3, libqrencode4]
- libqt5concurrent5 - libqt5concurrent5
- libquazip5-1 - libquazip5-1
- libusb-1.0-0 - libusb-1.0-0

View File

@ -47,7 +47,7 @@ AutoType::AutoType(QObject* parent, bool test)
, m_pluginLoader(new QPluginLoader(this)) , m_pluginLoader(new QPluginLoader(this))
, m_plugin(nullptr) , m_plugin(nullptr)
, m_executor(nullptr) , m_executor(nullptr)
, m_windowFromGlobal(0) , m_windowForGlobal(0)
{ {
// prevent crash when the plugin has unresolved symbols // prevent crash when the plugin has unresolved symbols
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint); m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
@ -90,7 +90,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
if (m_plugin) { if (m_plugin) {
if (m_plugin->isAvailable()) { if (m_plugin->isAvailable()) {
m_executor = m_plugin->createExecutor(); m_executor = m_plugin->createExecutor();
connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered())); connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SLOT(startGlobalAutoType()));
} else { } else {
unloadPlugin(); unloadPlugin();
} }
@ -222,6 +222,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt())); Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt()));
// Used only for selected entry auto-type
if (!window) { if (!window) {
window = m_plugin->activeWindow(); window = m_plugin->activeWindow();
} }
@ -240,6 +241,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
QCoreApplication::processEvents(QEventLoop::AllEvents, 10); QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} }
m_windowForGlobal = 0;
m_windowTitleForGlobal.clear();
// emit signal only if autotype performed correctly // emit signal only if autotype performed correctly
emit autotypePerformed(); emit autotypePerformed();
@ -264,6 +268,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
executeAutoTypeActions(entry, hideWindow, sequences.first()); 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 * Global Autotype entry-point function
* Perform global Auto-Type on the active window * Perform global Auto-Type on the active window
@ -278,9 +289,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
return; return;
} }
QString windowTitle = m_plugin->activeWindowTitle(); if (m_windowTitleForGlobal.isEmpty()) {
if (windowTitle.isEmpty()) {
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
return; return;
} }
@ -290,7 +299,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
for (const auto& db : dbList) { for (const auto& db : dbList) {
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive(); const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
for (Entry* entry : dbEntries) { for (Entry* entry : dbEntries) {
const QSet<QString> sequences = autoTypeSequences(entry, windowTitle).toSet(); const QSet<QString> sequences = autoTypeSequences(entry, m_windowTitleForGlobal).toSet();
for (const QString& sequence : sequences) { for (const QString& sequence : sequences) {
if (!sequence.isEmpty()) { if (!sequence.isEmpty()) {
matchList << AutoTypeMatch(entry, sequence); matchList << AutoTypeMatch(entry, sequence);
@ -304,8 +313,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
auto* msgBox = new QMessageBox(); auto* msgBox = new QMessageBox();
msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setWindowTitle(tr("Auto-Type - KeePassXC")); msgBox->setWindowTitle(tr("Auto-Type - KeePassXC"));
msgBox->setText( msgBox->setText(tr("Couldn't find an entry that matches the window title:")
tr("Couldn't find an entry that matches the window title:").append("\n\n").append(windowTitle)); .append("\n\n")
.append(m_windowTitleForGlobal));
msgBox->setIcon(QMessageBox::Information); msgBox->setIcon(QMessageBox::Information);
msgBox->setStandardButtons(QMessageBox::Ok); msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->show(); msgBox->show();
@ -316,10 +326,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
emit autotypeRejected(); emit autotypeRejected();
} else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { } 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(); m_inGlobalAutoTypeDialog.unlock();
} else { } else {
m_windowFromGlobal = m_plugin->activeWindow();
auto* selectDialog = new AutoTypeSelectDialog(); auto* selectDialog = new AutoTypeSelectDialog();
// connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex // connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex
@ -327,11 +336,12 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal())); connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal()));
selectDialog->setMatchList(matchList); selectDialog->setMatchList(matchList);
#if defined(Q_OS_MACOS) #ifdef Q_OS_MACOS
m_plugin->raiseOwnWindow(); m_plugin->raiseOwnWindow();
Tools::wait(500); Tools::wait(200);
#endif #endif
selectDialog->show(); selectDialog->show();
selectDialog->raise();
// necessary when the main window is minimized // necessary when the main window is minimized
selectDialog->activateWindow(); selectDialog->activateWindow();
} }
@ -339,8 +349,8 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match)
{ {
m_plugin->raiseWindow(m_windowFromGlobal); m_plugin->raiseWindow(m_windowForGlobal);
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowForGlobal);
// make sure the mutex is definitely locked before we unlock it // make sure the mutex is definitely locked before we unlock it
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
@ -353,6 +363,8 @@ void AutoType::autoTypeRejectedFromGlobal()
// so make sure the mutex is locked before we try unlocking it // so make sure the mutex is locked before we try unlocking it
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
m_windowForGlobal = 0;
m_windowTitleForGlobal.clear();
emit autotypeRejected(); emit autotypeRejected();
} }

View File

@ -62,18 +62,19 @@ public slots:
void raiseWindow(); void raiseWindow();
signals: signals:
void globalShortcutTriggered(); void globalAutoTypeTriggered();
void autotypePerformed(); void autotypePerformed();
void autotypeRejected(); void autotypeRejected();
private slots: private slots:
void startGlobalAutoType();
void performAutoTypeFromGlobal(AutoTypeMatch match); void performAutoTypeFromGlobal(AutoTypeMatch match);
void autoTypeRejectedFromGlobal(); void autoTypeRejectedFromGlobal();
void unloadPlugin(); void unloadPlugin();
private: private:
explicit AutoType(QObject* parent = nullptr, bool test = false); explicit AutoType(QObject* parent = nullptr, bool test = false);
~AutoType(); ~AutoType() override;
void loadPlugin(const QString& pluginPath); void loadPlugin(const QString& pluginPath);
void executeAutoTypeActions(const Entry* entry, void executeAutoTypeActions(const Entry* entry,
QWidget* hideWindow = nullptr, QWidget* hideWindow = nullptr,
@ -94,9 +95,11 @@ private:
QPluginLoader* m_pluginLoader; QPluginLoader* m_pluginLoader;
AutoTypePlatformInterface* m_plugin; AutoTypePlatformInterface* m_plugin;
AutoTypeExecutor* m_executor; AutoTypeExecutor* m_executor;
WId m_windowFromGlobal;
static AutoType* m_instance; static AutoType* m_instance;
QString m_windowTitleForGlobal;
WId m_windowForGlobal;
Q_DISABLE_COPY(AutoType) Q_DISABLE_COPY(AutoType)
}; };

View File

@ -68,6 +68,11 @@ AutoTypeExecutor* AutoTypePlatformTest::createExecutor()
return new AutoTypeExecutorTest(this); return new AutoTypeExecutorTest(this);
} }
void AutoTypePlatformTest::triggerGlobalAutoType()
{
emit globalShortcutTriggered();
}
void AutoTypePlatformTest::setActiveWindowTitle(const QString& title) void AutoTypePlatformTest::setActiveWindowTitle(const QString& title)
{ {
m_activeWindowTitle = title; m_activeWindowTitle = title;

View File

@ -48,6 +48,7 @@ public:
bool raiseOwnWindow() override; bool raiseOwnWindow() override;
#endif #endif
void triggerGlobalAutoType() override;
void setActiveWindowTitle(const QString& title) override; void setActiveWindowTitle(const QString& title) override;
QString actionChars() override; QString actionChars() override;

View File

@ -26,6 +26,7 @@ public:
virtual ~AutoTypeTestInterface() virtual ~AutoTypeTestInterface()
{ {
} }
virtual void triggerGlobalAutoType() = 0;
virtual void setActiveWindowTitle(const QString& title) = 0; virtual void setActiveWindowTitle(const QString& title) = 0;
virtual QString actionChars() = 0; virtual QString actionChars() = 0;

View File

@ -94,6 +94,8 @@ private:
QString m_publicKey; QString m_publicKey;
QString m_secretKey; QString m_secretKey;
bool m_associated; bool m_associated;
friend class TestBrowser;
}; };
#endif // BROWSERACTION_H #endif // BROWSERACTION_H

View File

@ -120,6 +120,7 @@ void BrowserOptionDialog::loadSettings()
m_ui->useCustomProxy->setChecked(settings->useCustomProxy()); m_ui->useCustomProxy->setChecked(settings->useCustomProxy());
m_ui->customProxyLocation->setText(settings->customProxyLocation()); m_ui->customProxyLocation->setText(settings->customProxyLocation());
m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath()); m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath());
m_ui->allowExpiredCredentials->setChecked(settings->allowExpiredCredentials());
m_ui->chromeSupport->setChecked(settings->chromeSupport()); m_ui->chromeSupport->setChecked(settings->chromeSupport());
m_ui->chromiumSupport->setChecked(settings->chromiumSupport()); m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
m_ui->firefoxSupport->setChecked(settings->firefoxSupport()); m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
@ -176,6 +177,7 @@ void BrowserOptionDialog::saveSettings()
settings->setCustomProxyLocation(m_ui->customProxyLocation->text()); settings->setCustomProxyLocation(m_ui->customProxyLocation->text());
settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked()); settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
settings->setAllowExpiredCredentials(m_ui->allowExpiredCredentials->isChecked());
settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked()); settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked()); settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
settings->setHttpAuthPermission(m_ui->httpAuthPermission->isChecked()); settings->setHttpAuthPermission(m_ui->httpAuthPermission->isChecked());

View File

@ -219,6 +219,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="allowExpiredCredentials">
<property name="toolTip">
<string>Returns expired credentials. String [expired] is added to the title.</string>
</property>
<property name="text">
<string>&amp;Allow returning expired credentials.</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QRadioButton" name="sortByTitle"> <widget class="QRadioButton" name="sortByTitle">
<property name="text"> <property name="text">

View File

@ -172,9 +172,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group)
return groupList; return groupList;
} }
QJsonObject BrowserService::getDatabaseGroups() QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& selectedDb)
{ {
auto db = getDatabase(); auto db = selectedDb ? selectedDb : getDatabase();
if (!db) { if (!db) {
return {}; return {};
} }
@ -453,11 +453,6 @@ void BrowserService::addEntry(const QString& id,
return; return;
} }
auto* addEntryGroup = findCreateAddEntryGroup(db);
if (!addEntryGroup) {
return;
}
auto* entry = new Entry(); auto* entry = new Entry();
entry->setUuid(QUuid::createUuid()); entry->setUuid(QUuid::createUuid());
entry->setTitle(QUrl(url).host()); entry->setTitle(QUrl(url).host());
@ -465,16 +460,19 @@ void BrowserService::addEntry(const QString& id,
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON); entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
entry->setUsername(login); entry->setUsername(login);
entry->setPassword(password); entry->setPassword(password);
entry->setGroup(addEntryGroup);
// Select a group for the entry // Select a group for the entry
if (!group.isEmpty()) { if (!group.isEmpty()) {
if (db->rootGroup()) { if (db->rootGroup()) {
auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid)); auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid));
if (selectedGroup && selectedGroup->name() == group) { if (selectedGroup) {
entry->setGroup(selectedGroup); entry->setGroup(selectedGroup);
} else {
entry->setGroup(getDefaultEntryGroup(db));
} }
} }
} else {
entry->setGroup(getDefaultEntryGroup(db));
} }
const QString host = QUrl(url).host(); const QString host = QUrl(url).host();
@ -818,6 +816,10 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
res["totp"] = entry->totp(); res["totp"] = entry->totp();
} }
if (entry->isExpired()) {
res["expired"] = "true";
}
if (browserSettings()->supportKphFields()) { if (browserSettings()->supportKphFields()) {
const EntryAttributes* attr = entry->attributes(); const EntryAttributes* attr = entry->attributes();
QJsonArray stringFields; QJsonArray stringFields;
@ -841,7 +843,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
return Unknown; return Unknown;
} }
if (entry->isExpired()) { if (entry->isExpired()) {
return Denied; return browserSettings()->allowExpiredCredentials() ? Allowed : Denied;
} }
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) { if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
return Allowed; return Allowed;
@ -855,7 +857,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
return Unknown; return Unknown;
} }
Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& selectedDb) Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb)
{ {
auto db = selectedDb ? selectedDb : getDatabase(); auto db = selectedDb ? selectedDb : getDatabase();
if (!db) { if (!db) {
@ -868,7 +870,7 @@ Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& s
} }
const QString groupName = 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)) { for (auto* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName && !g->isRecycled()) { if (g->name() == groupName && !g->isRecycled()) {

View File

@ -44,7 +44,7 @@ public:
bool openDatabase(bool triggerUnlock); bool openDatabase(bool triggerUnlock);
QString getDatabaseRootUuid(); QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid(); QString getDatabaseRecycleBinUuid();
QJsonObject getDatabaseGroups(); QJsonObject getDatabaseGroups(const QSharedPointer<Database>& selectedDb = {});
QJsonObject createNewGroup(const QString& groupName); QJsonObject createNewGroup(const QString& groupName);
QString getKey(const QString& id); QString getKey(const QString& id);
void addEntry(const QString& id, void addEntry(const QString& id,
@ -114,7 +114,7 @@ private:
const QString& realm); const QString& realm);
QJsonObject prepareEntry(const Entry* entry); QJsonObject prepareEntry(const Entry* entry);
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm); Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
Group* findCreateAddEntryGroup(const QSharedPointer<Database>& selectedDb = {}); Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
int int
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const; sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
bool matchUrlScheme(const QString& url); bool matchUrlScheme(const QString& url);
@ -135,6 +135,8 @@ private:
bool m_bringToFrontRequested; bool m_bringToFrontRequested;
WindowState m_prevWindowState; WindowState m_prevWindowState;
QUuid m_keepassBrowserUUID; QUuid m_keepassBrowserUUID;
friend class TestBrowser;
}; };
#endif // BROWSERSERVICE_H #endif // BROWSERSERVICE_H

View File

@ -194,6 +194,16 @@ void BrowserSettings::setUpdateBinaryPath(bool enabled)
config()->set("Browser/UpdateBinaryPath", 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() bool BrowserSettings::chromeSupport()
{ {
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME); return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);

View File

@ -64,6 +64,8 @@ public:
void setCustomProxyLocation(const QString& location); void setCustomProxyLocation(const QString& location);
bool updateBinaryPath(); bool updateBinaryPath();
void setUpdateBinaryPath(bool enabled); void setUpdateBinaryPath(bool enabled);
bool allowExpiredCredentials();
void setAllowExpiredCredentials(bool enabled);
bool chromeSupport(); bool chromeSupport();
void setChromeSupport(bool enabled); void setChromeSupport(bool enabled);
bool chromiumSupport(); bool chromiumSupport();

View File

@ -19,6 +19,8 @@
#include "NativeMessagingBase.h" #include "NativeMessagingBase.h"
#include <QStandardPaths> #include <QStandardPaths>
#include "config-keepassx.h"
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) #if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
#include <sys/event.h> #include <sys/event.h>
#include <sys/time.h> #include <sys/time.h>
@ -138,7 +140,7 @@ QString NativeMessagingBase::getLocalServerPath() const
{ {
const QString serverPath = "/kpxc_server"; const QString serverPath = "/kpxc_server";
#if defined(KEEPASSXC_DIST_SNAP) #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) #elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// Use XDG_RUNTIME_DIR instead of /tmp if it's available // Use XDG_RUNTIME_DIR instead of /tmp if it's available
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);

View File

@ -35,7 +35,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
{ {
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4); Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
m_binaryPoolInverse.clear(); m_binaryPool.clear();
if (hasError()) { if (hasError()) {
return false; return false;
@ -273,11 +273,7 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
return false; return false;
} }
auto data = fieldData.mid(1); auto data = fieldData.mid(1);
if (m_binaryPoolInverse.contains(data)) { m_binaryPool.insert(QString::number(m_binaryPool.size()), data);
qWarning("Skipping duplicate binary record");
break;
}
m_binaryPoolInverse.insert(data, QString::number(m_binaryPoolInverse.size()));
break; break;
} }
} }
@ -422,17 +418,5 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
*/ */
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
{ {
QHash<QString, QByteArray> binaryPool; return m_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<QByteArray, QString> Kdbx4Reader::binaryPoolInverse() const
{
return m_binaryPoolInverse;
} }

View File

@ -34,7 +34,6 @@ public:
const QByteArray& headerData, const QByteArray& headerData,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
Database* db) override; Database* db) override;
QHash<QByteArray, QString> binaryPoolInverse() const;
QHash<QString, QByteArray> binaryPool() const; QHash<QString, QByteArray> binaryPool() const;
protected: protected:
@ -44,7 +43,7 @@ private:
bool readInnerHeaderField(QIODevice* device); bool readInnerHeaderField(QIODevice* device);
QVariantMap readVariantMap(QIODevice* device); QVariantMap readVariantMap(QIODevice* device);
QHash<QByteArray, QString> m_binaryPoolInverse; QHash<QString, QByteArray> m_binaryPool;
}; };
#endif // KEEPASSX_KDBX4READER_H #endif // KEEPASSX_KDBX4READER_H

View File

@ -59,7 +59,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged()));
connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
m_dbWidgetStateSync, SLOT(setActive(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(autotypePerformed()), SLOT(relockPendingDatabase()));
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
// clang-format on // clang-format on
@ -580,7 +580,7 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) { if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
macUtils()->raiseOwnWindow(); macUtils()->raiseOwnWindow();
Tools::wait(500); Tools::wait(200);
} }
#endif #endif

View File

@ -178,7 +178,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged())); connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged())); connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged()));
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode))); 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_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SLOT(onGroupChanged(Group*))); connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SLOT(onGroupChanged(Group*)));
connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SIGNAL(groupChanged())); connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SIGNAL(groupChanged()));
@ -195,7 +196,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool))); connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool))); connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile())); 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 // clang-format on
connectDatabaseSignals(); connectDatabaseSignals();
@ -648,6 +649,10 @@ void DatabaseWidget::openUrl()
void DatabaseWidget::openUrlForEntry(Entry* entry) void DatabaseWidget::openUrlForEntry(Entry* entry)
{ {
Q_ASSERT(entry); Q_ASSERT(entry);
if (!entry) {
return;
}
QString cmdString = entry->resolveMultiplePlaceholders(entry->url()); QString cmdString = entry->resolveMultiplePlaceholders(entry->url());
if (cmdString.startsWith("cmd://")) { if (cmdString.startsWith("cmd://")) {
// check if decision to execute command was stored // check if decision to execute command was stored
@ -691,9 +696,9 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
} }
} }
} else { } else {
QString urlString = entry->webUrl(); QUrl url = QUrl(entry->url());
if (!urlString.isEmpty()) { if (!url.isEmpty()) {
QDesktopServices::openUrl(urlString); QDesktopServices::openUrl(url);
} }
} }
} }

View File

@ -54,11 +54,13 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent)
m_ui->entryAttachmentsWidget->setReadOnly(true); m_ui->entryAttachmentsWidget->setReadOnly(true);
m_ui->entryAttachmentsWidget->setButtonsVisible(false); 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->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool)));
connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide())); connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide()));
connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool))); connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool)));
connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection); 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 // Group
m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close")); m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
@ -197,11 +199,12 @@ void EntryPreviewWidget::updateEntryGeneralTab()
} }
m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl()); m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl());
const QString url = m_currentEntry->webUrl(); const QString url = m_currentEntry->url();
if (!url.isEmpty()) { if (!url.isEmpty()) {
// URL is well formed and can be opened in a browser // URL is well formed and can be opened in a browser
m_ui->entryUrlLabel->setUrl(url); m_ui->entryUrlLabel->setUrl(url);
m_ui->entryUrlLabel->setCursor(Qt::PointingHandCursor); m_ui->entryUrlLabel->setCursor(Qt::PointingHandCursor);
m_ui->entryUrlLabel->setOpenExternalLinks(false);
} else { } else {
m_ui->entryUrlLabel->setUrl({}); m_ui->entryUrlLabel->setUrl({});
m_ui->entryUrlLabel->setCursor(Qt::ArrowCursor); m_ui->entryUrlLabel->setCursor(Qt::ArrowCursor);
@ -327,6 +330,13 @@ void EntryPreviewWidget::updateTabIndexes()
m_selectedTabGroup = m_ui->groupTabWidget->currentIndex(); 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) void EntryPreviewWidget::setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled)
{ {
const int tabIndex = tabWidget->indexOf(widget); const int tabIndex = tabWidget->indexOf(widget);

View File

@ -43,6 +43,7 @@ public slots:
signals: signals:
void errorOccurred(const QString& error); void errorOccurred(const QString& error);
void entryUrlActivated(Entry* entry);
private slots: private slots:
void updateEntryHeaderLine(); void updateEntryHeaderLine();
@ -63,6 +64,7 @@ private slots:
void updateTotpLabel(); void updateTotpLabel();
void updateTabIndexes(); void updateTabIndexes();
void openEntryUrl();
private: private:
void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled); void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled);

View File

@ -7,9 +7,11 @@
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QMimeData> #include <QMimeData>
#include <QProcessEnvironment>
#include <QTemporaryFile> #include <QTemporaryFile>
#include "EntryAttachmentsModel.h" #include "EntryAttachmentsModel.h"
#include "config-keepassx.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/EntryAttachments.h" #include "core/EntryAttachments.h"
#include "core/Tools.h" #include "core/Tools.h"
@ -324,7 +326,12 @@ bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& e
const QByteArray attachmentData = m_entryAttachments->value(filename); const QByteArray attachmentData = m_entryAttachments->value(filename);
// tmp file will be removed once the database (or the application) has been closed // 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)); const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename));
#endif
QScopedPointer<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this)); QScopedPointer<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this));

View File

@ -231,6 +231,11 @@ if(WITH_XC_FDOSECRETS)
LIBS testsupport ${TEST_LIBRARIES}) LIBS testsupport ${TEST_LIBRARIES})
endif() endif()
if(WITH_XC_BROWSER)
add_unit_test(NAME testbrowser SOURCES TestBrowser.cpp
LIBS ${TEST_LIBRARIES})
endif()
if(WITH_GUI_TESTS) if(WITH_GUI_TESTS)
# CLI clip tests need X environment on Linux # CLI clip tests need X environment on Linux

View File

@ -157,6 +157,7 @@ void TestAutoType::testGlobalAutoTypeWithNoMatch()
void TestAutoType::testGlobalAutoTypeWithOneMatch() void TestAutoType::testGlobalAutoTypeWithOneMatch()
{ {
m_test->setActiveWindowTitle("custom window"); m_test->setActiveWindowTitle("custom window");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password())); 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); config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("An Entry Title!"); m_test->setActiveWindowTitle("An Entry Title!");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); 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); config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>"); m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); 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); config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>"); m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); 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() void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
{ {
m_test->setActiveWindowTitle("An Entry Title!"); m_test->setActiveWindowTitle("An Entry Title!");
m_test->triggerGlobalAutoType();
MessageBox::setNextAnswer(MessageBox::Ok); MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
@ -205,58 +210,68 @@ void TestAutoType::testGlobalAutoTypeRegExp()
{ {
// substring matches are ok // substring matches are ok
m_test->setActiveWindowTitle("lorem REGEX1 ipsum"); m_test->setActiveWindowTitle("lorem REGEX1 ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex1")); QCOMPARE(m_test->actionChars(), QString("regex1"));
m_test->clearActions(); m_test->clearActions();
// should be case-insensitive // should be case-insensitive
m_test->setActiveWindowTitle("lorem regex1 ipsum"); m_test->setActiveWindowTitle("lorem regex1 ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex1")); QCOMPARE(m_test->actionChars(), QString("regex1"));
m_test->clearActions(); m_test->clearActions();
// exact match // exact match
m_test->setActiveWindowTitle("REGEX2"); m_test->setActiveWindowTitle("REGEX2");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex2")); QCOMPARE(m_test->actionChars(), QString("regex2"));
m_test->clearActions(); m_test->clearActions();
// a bit more complicated regex // a bit more complicated regex
m_test->setActiveWindowTitle("REGEX3-R2D2"); m_test->setActiveWindowTitle("REGEX3-R2D2");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex3")); QCOMPARE(m_test->actionChars(), QString("regex3"));
m_test->clearActions(); m_test->clearActions();
// with custom attributes // with custom attributes
m_test->setActiveWindowTitle("CustomAttr1"); m_test->setActiveWindowTitle("CustomAttr1");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute")); QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute"));
m_test->clearActions(); m_test->clearActions();
// with (non uppercase) undefined custom attributes // with (non uppercase) undefined custom attributes
m_test->setActiveWindowTitle("CustomAttr2"); m_test->setActiveWindowTitle("CustomAttr2");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("")); QCOMPARE(m_test->actionChars(), QString(""));
m_test->clearActions(); m_test->clearActions();
// with mixedcase default attributes // with mixedcase default attributes
m_test->setActiveWindowTitle("CustomAttr3"); m_test->setActiveWindowTitle("CustomAttr3");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr")); QCOMPARE(m_test->actionChars(), QString("custom_attr"));
m_test->clearActions(); m_test->clearActions();
// with resolve placeholders in window association title // with resolve placeholders in window association title
m_test->setActiveWindowTitle("AttrValueFirst"); m_test->setActiveWindowTitle("AttrValueFirst");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_first")); QCOMPARE(m_test->actionChars(), QString("custom_attr_first"));
m_test->clearActions(); m_test->clearActions();
m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum"); m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second")); QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second"));
m_test->clearActions(); m_test->clearActions();
m_test->setActiveWindowTitle("lorem AttrValueThird ipsum"); m_test->setActiveWindowTitle("lorem AttrValueThird ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_third")); QCOMPARE(m_test->actionChars(), QString("custom_attr_third"));
m_test->clearActions(); m_test->clearActions();

344
tests/TestBrowser.cpp Normal file
View File

@ -0,0 +1,344 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "TestBrowser.h"
#include "TestGlobal.h"
#include "crypto/Crypto.h"
#include "sodium/crypto_box.h"
#include "browser/BrowserSettings.h"
#include <QString>
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<Entry> entry1(new Entry());
QScopedPointer<Entry> entry2(new Entry());
QScopedPointer<Entry> entry3(new Entry());
QScopedPointer<Entry> entry4(new Entry());
QScopedPointer<Entry> entry5(new Entry());
QScopedPointer<Entry> entry6(new Entry());
QScopedPointer<Entry> entry7(new Entry());
QScopedPointer<Entry> entry8(new Entry());
QScopedPointer<Entry> entry9(new Entry());
QScopedPointer<Entry> 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<Database>::create();
auto* root = db->rootGroup();
QList<QString> 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<Database>::create();
auto* root = db->rootGroup();
QList<QString> 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<Database>::create();
auto* root = db->rootGroup();
QList<QString> 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<Entry*> 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<Database>::create();
auto* root = db->rootGroup();
QScopedPointer<Group> group1(new Group());
group1->setParent(root);
group1->setName("group1");
QScopedPointer<Group> group2(new Group());
group2->setParent(root);
group2->setName("group2");
QScopedPointer<Group> group3(new Group());
group3->setParent(root);
group3->setName("group3");
QScopedPointer<Group> group2_1(new Group());
group2_1->setParent(group2.data());
group2_1->setName("group2_1");
QScopedPointer<Group> group2_2(new Group());
group2_2->setParent(group2.data());
group2_2->setName("group2_2");
QScopedPointer<Group> 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"));
}

53
tests/TestBrowser.h Normal file
View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_TESTBROWSER_H
#define KEEPASSXC_TESTBROWSER_H
#include <QObject>
#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<BrowserAction> m_browserAction;
QScopedPointer<BrowserService> m_browserService;
};
#endif // KEEPASSXC_TESTBROWSER_H