mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-22 23:49:58 -05:00
Merge branch 'release/2.4.2' into develop
This commit is contained in:
commit
63aab99b9c
@ -80,7 +80,7 @@ parts:
|
||||
- libxtst6
|
||||
- libqt5x11extras5
|
||||
- libqt5svg5
|
||||
- libqrencode3
|
||||
- try: [libqrencode3, libqrencode4]
|
||||
- libqt5concurrent5
|
||||
- libquazip5-1
|
||||
- libusb-1.0-0
|
||||
|
@ -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<QSharedPointer<Database>>& 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<QSharedPointer<Database>>& dbLi
|
||||
for (const auto& db : dbList) {
|
||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||
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) {
|
||||
if (!sequence.isEmpty()) {
|
||||
matchList << AutoTypeMatch(entry, sequence);
|
||||
@ -304,8 +313,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& 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<QSharedPointer<Database>>& 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<QSharedPointer<Database>>& 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<QSharedPointer<Database>>& 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();
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
bool raiseOwnWindow() override;
|
||||
#endif
|
||||
|
||||
void triggerGlobalAutoType() override;
|
||||
void setActiveWindowTitle(const QString& title) override;
|
||||
|
||||
QString actionChars() override;
|
||||
|
@ -26,6 +26,7 @@ public:
|
||||
virtual ~AutoTypeTestInterface()
|
||||
{
|
||||
}
|
||||
virtual void triggerGlobalAutoType() = 0;
|
||||
virtual void setActiveWindowTitle(const QString& title) = 0;
|
||||
|
||||
virtual QString actionChars() = 0;
|
||||
|
@ -94,6 +94,8 @@ private:
|
||||
QString m_publicKey;
|
||||
QString m_secretKey;
|
||||
bool m_associated;
|
||||
|
||||
friend class TestBrowser;
|
||||
};
|
||||
|
||||
#endif // BROWSERACTION_H
|
||||
|
@ -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());
|
||||
|
@ -219,6 +219,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>&Allow returning expired credentials.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByTitle">
|
||||
<property name="text">
|
||||
|
@ -172,9 +172,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group)
|
||||
return groupList;
|
||||
}
|
||||
|
||||
QJsonObject BrowserService::getDatabaseGroups()
|
||||
QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& 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<Database>& selectedDb)
|
||||
Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb)
|
||||
{
|
||||
auto db = selectedDb ? selectedDb : getDatabase();
|
||||
if (!db) {
|
||||
@ -868,7 +870,7 @@ Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& 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()) {
|
||||
|
@ -44,7 +44,7 @@ public:
|
||||
bool openDatabase(bool triggerUnlock);
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
QJsonObject getDatabaseGroups();
|
||||
QJsonObject getDatabaseGroups(const QSharedPointer<Database>& 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<Database>& selectedDb = {});
|
||||
Group* getDefaultEntryGroup(const QSharedPointer<Database>& 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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "NativeMessagingBase.h"
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
@ -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);
|
||||
|
@ -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<QString, QByteArray> Kdbx4Reader::binaryPool() const
|
||||
{
|
||||
QHash<QString, QByteArray> 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;
|
||||
return m_binaryPool;
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ public:
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) override;
|
||||
QHash<QByteArray, QString> binaryPoolInverse() const;
|
||||
QHash<QString, QByteArray> binaryPool() const;
|
||||
|
||||
protected:
|
||||
@ -44,7 +43,7 @@ private:
|
||||
bool readInnerHeaderField(QIODevice* device);
|
||||
QVariantMap readVariantMap(QIODevice* device);
|
||||
|
||||
QHash<QByteArray, QString> m_binaryPoolInverse;
|
||||
QHash<QString, QByteArray> m_binaryPool;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX4READER_H
|
||||
|
@ -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
|
||||
|
||||
|
@ -178,7 +178,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> 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<Database> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -7,9 +7,11 @@
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#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<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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/ - <My Browser>");
|
||||
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/ - <My Browser>");
|
||||
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();
|
||||
|
344
tests/TestBrowser.cpp
Normal file
344
tests/TestBrowser.cpp
Normal 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
53
tests/TestBrowser.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user