diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index 535170939..3c8df5ffc 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -94,6 +94,8 @@ private: QString m_publicKey; QString m_secretKey; bool m_associated; + + friend class TestBrowser; }; #endif // BROWSERACTION_H diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 506e63395..3916b99ad 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -172,9 +172,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group) return groupList; } -QJsonObject BrowserService::getDatabaseGroups() +QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer& selectedDb) { - auto db = getDatabase(); + auto db = selectedDb ? selectedDb : getDatabase(); if (!db) { return {}; } diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index bbc6d187f..77d94b6bd 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -44,7 +44,7 @@ public: bool openDatabase(bool triggerUnlock); QString getDatabaseRootUuid(); QString getDatabaseRecycleBinUuid(); - QJsonObject getDatabaseGroups(); + QJsonObject getDatabaseGroups(const QSharedPointer& selectedDb = {}); QJsonObject createNewGroup(const QString& groupName); QString getKey(const QString& id); void addEntry(const QString& id, @@ -135,6 +135,8 @@ private: bool m_bringToFrontRequested; WindowState m_prevWindowState; QUuid m_keepassBrowserUUID; + + friend class TestBrowser; }; #endif // BROWSERSERVICE_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bab810cea..54670f2ac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -220,6 +220,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp add_unit_test(NAME testtools SOURCES TestTools.cpp LIBS ${TEST_LIBRARIES}) +if(WITH_XC_BROWSER) +add_unit_test(NAME testbrowser SOURCES TestBrowser.cpp + LIBS ${TEST_LIBRARIES}) +endif() + if(WITH_GUI_TESTS) # CLI clip tests need X environment on Linux diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp new file mode 100644 index 000000000..b2ec6d6dd --- /dev/null +++ b/tests/TestBrowser.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "TestBrowser.h" +#include "TestGlobal.h" +#include "crypto/Crypto.h" +#include "sodium/crypto_box.h" +#include "browser/BrowserSettings.h" +#include + +QTEST_GUILESS_MAIN(TestBrowser) + +const QString PUBLICKEY = "UIIPObeoya1G8g1M5omgyoPR/j1mR1HlYHu0wHCgMhA="; +const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI="; +const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ="; +const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q="; +const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ"; +const QString CLIENTID = "testClient"; + +void TestBrowser::initTestCase() +{ + QVERIFY(Crypto::init()); + m_browserService.reset(new BrowserService(nullptr)); + m_browserAction.reset(new BrowserAction(*m_browserService.data())); +} + +void TestBrowser::cleanupTestCase() +{ + +} + +/** + * Tests for BrowserAction + */ + +void TestBrowser::testChangePublicKeys() +{ + QJsonObject json; + json["action"] = "change-public-keys"; + json["publicKey"] = PUBLICKEY; + json["nonce"] = NONCE; + + auto response = m_browserAction->handleAction(json); + QCOMPARE(response["action"].toString(), QString("change-public-keys")); + QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false); + QCOMPARE(response["success"].toString(), QString("true")); +} + +void TestBrowser::testEncryptMessage() +{ + QJsonObject message; + message["action"] = "test-action"; + + m_browserAction->m_publicKey = SERVERPUBLICKEY; + m_browserAction->m_secretKey = SERVERSECRETKEY; + m_browserAction->m_clientPublicKey = PUBLICKEY; + auto encrypted = m_browserAction->encryptMessage(message, NONCE); + + QCOMPARE(encrypted, QString("+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP")); +} + +void TestBrowser::testDecryptMessage() +{ + QString message = "+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP"; + m_browserAction->m_publicKey = SERVERPUBLICKEY; + m_browserAction->m_secretKey = SERVERSECRETKEY; + m_browserAction->m_clientPublicKey = PUBLICKEY; + auto decrypted = m_browserAction->decryptMessage(message, NONCE); + + QCOMPARE(decrypted["action"].toString(), QString("test-action")); +} + +void TestBrowser::testGetBase64FromKey() +{ + unsigned char pk[crypto_box_PUBLICKEYBYTES]; + + for (unsigned int i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) { + pk[i] = i; + } + + auto response = m_browserAction->getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES); + QCOMPARE(response, QString("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=")); +} + +void TestBrowser::testIncrementNonce() +{ + auto result = m_browserAction->incrementNonce(NONCE); + QCOMPARE(result, QString("zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ")); +} + +/** + * Tests for BrowserService + */ +void TestBrowser::testBaseDomain() +{ + QString url1 = "https://another.example.co.uk"; + QString url2 = "https://www.example.com"; + QString url3 = "http://test.net"; + QString url4 = "http://so.many.subdomains.co.jp"; + + QString res1 = m_browserService->baseDomain(url1); + QString res2 = m_browserService->baseDomain(url2); + QString res3 = m_browserService->baseDomain(url3); + QString res4 = m_browserService->baseDomain(url4); + + QCOMPARE(res1, QString("example.co.uk")); + QCOMPARE(res2, QString("example.com")); + QCOMPARE(res3, QString("test.net")); + QCOMPARE(res4, QString("subdomains.co.jp")); +} + +void TestBrowser::testSortPriority() +{ + QString host = "github.com"; + QString submitUrl = "https://github.com/session"; + QString baseSubmitUrl = "https://github.com"; + + QScopedPointer entry1(new Entry()); + QScopedPointer entry2(new Entry()); + QScopedPointer entry3(new Entry()); + QScopedPointer entry4(new Entry()); + QScopedPointer entry5(new Entry()); + QScopedPointer entry6(new Entry()); + QScopedPointer entry7(new Entry()); + QScopedPointer entry8(new Entry()); + QScopedPointer entry9(new Entry()); + QScopedPointer entry10(new Entry()); + + entry1->setUrl("https://github.com/login"); + entry2->setUrl("https://github.com/login"); + entry3->setUrl("https://github.com/"); + entry4->setUrl("github.com/login"); + entry5->setUrl("http://github.com"); + entry6->setUrl("http://github.com/login"); + entry7->setUrl("github.com"); + entry8->setUrl("github.com/login"); + entry9->setUrl("https://github"); + entry10->setUrl("github.com"); + + // The extension uses the submitUrl as default for comparison + auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", baseSubmitUrl); + auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl); + auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl); + auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl); + auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl); + auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl); + auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl); + auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl); + auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl); + auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl); + + QCOMPARE(res1, 100); + QCOMPARE(res2, 40); + QCOMPARE(res3, 90); + QCOMPARE(res4, 0); + QCOMPARE(res5, 0); + QCOMPARE(res6, 0); + QCOMPARE(res7, 0); + QCOMPARE(res8, 0); + QCOMPARE(res9, 90); + QCOMPARE(res10, 0); +} + +void TestBrowser::testSearchEntries() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QList urls; + urls.push_back("https://github.com/login_page"); + urls.push_back("https://github.com/login"); + urls.push_back("https://github.com/"); + urls.push_back("github.com/login"); + urls.push_back("http://github.com"); + urls.push_back("http://github.com/login"); + urls.push_back("github.com"); + urls.push_back("github.com/login"); + urls.push_back("https://github"); + urls.push_back("github.com"); + + for (int i = 0; i < urls.length(); ++i) { + auto entry = new Entry(); + entry->setGroup(root); + entry->beginUpdate(); + entry->setUrl(urls[i]); + entry->setUsername(QString("User %1").arg(i)); + entry->endUpdate(); + } + + browserSettings()->setMatchUrlScheme(false); + auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url + QCOMPARE(result.length(), 7); + QCOMPARE(result[0]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[1]->url(), QString("https://github.com/login")); + QCOMPARE(result[2]->url(), QString("https://github.com/")); + QCOMPARE(result[3]->url(), QString("http://github.com")); + QCOMPARE(result[4]->url(), QString("http://github.com/login")); + QCOMPARE(result[5]->url(), QString("github.com")); + QCOMPARE(result[6]->url(), QString("github.com")) ; + + // With matching there should be only 5 results + browserSettings()->setMatchUrlScheme(true); + result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url + QCOMPARE(result.length(), 5); + QCOMPARE(result[0]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[1]->url(), QString("https://github.com/login")); + QCOMPARE(result[2]->url(), QString("https://github.com/")); + QCOMPARE(result[3]->url(), QString("github.com")); + QCOMPARE(result[4]->url(), QString("github.com")); +} + +void TestBrowser::testSearchEntriesWithPort() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QList urls; + urls.push_back("http://127.0.0.1:443"); + urls.push_back("http://127.0.0.1:80"); + + for (int i = 0; i < urls.length(); ++i) { + auto entry = new Entry(); + entry->setGroup(root); + entry->beginUpdate(); + entry->setUrl(urls[i]); + entry->setUsername(QString("User %1").arg(i)); + entry->endUpdate(); + } + + auto result = m_browserService->searchEntries(db, "127.0.0.1", "http://127.0.0.1:443"); // db, hostname, url + QCOMPARE(result.length(), 1); + QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443")); +} + +void TestBrowser::testSortEntries() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QList urls; + urls.push_back("https://github.com/login_page"); + urls.push_back("https://github.com/login"); + urls.push_back("https://github.com/"); + urls.push_back("github.com/login"); + urls.push_back("http://github.com"); + urls.push_back("http://github.com/login"); + urls.push_back("github.com"); + urls.push_back("github.com/login"); + urls.push_back("https://github"); + urls.push_back("github.com"); + + QList entries; + for (int i = 0; i < urls.length(); ++i) { + auto entry = new Entry(); + entry->setGroup(root); + entry->beginUpdate(); + entry->setUrl(urls[i]); + entry->setUsername(QString("User %1").arg(i)); + entry->endUpdate(); + entries.push_back(entry); + } + + browserSettings()->setBestMatchOnly(false); + auto result = m_browserService->sortEntries(entries, "github.com", "https://github.com/session"); // entries, host, submitUrl + QCOMPARE(result.size(), 10); + QCOMPARE(result[0]->username(), QString("User 2")); + QCOMPARE(result[0]->url(), QString("https://github.com/")); + QCOMPARE(result[1]->username(), QString("User 8")); + QCOMPARE(result[1]->url(), QString("https://github")); + QCOMPARE(result[2]->username(), QString("User 0")); + QCOMPARE(result[2]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[3]->username(), QString("User 1")); + QCOMPARE(result[3]->url(), QString("https://github.com/login")); +} + +void TestBrowser::testGetDatabaseGroups() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + QScopedPointer group1(new Group()); + group1->setParent(root); + group1->setName("group1"); + + QScopedPointer group2(new Group()); + group2->setParent(root); + group2->setName("group2"); + + QScopedPointer group3(new Group()); + group3->setParent(root); + group3->setName("group3"); + + QScopedPointer group2_1(new Group()); + group2_1->setParent(group2.data()); + group2_1->setName("group2_1"); + + QScopedPointer group2_2(new Group()); + group2_2->setParent(group2.data()); + group2_2->setName("group2_2"); + + QScopedPointer group2_1_1(new Group()); + group2_1_1->setParent(group2_1.data()); + group2_1_1->setName("group2_1_1"); + + auto result = m_browserService->getDatabaseGroups(db); + QCOMPARE(result.length(), 1); + + auto groups = result["groups"].toArray(); + auto first = groups.at(0); + auto children = first.toObject()["children"].toArray(); + QCOMPARE(first.toObject()["name"].toString(), QString("Root")); + QCOMPARE(children.size(), 3); + + auto firstChild = children.at(0); + auto secondChild = children.at(1); + auto thirdChild = children.at(2); + QCOMPARE(firstChild.toObject()["name"].toString(), QString("group1")); + QCOMPARE(secondChild.toObject()["name"].toString(), QString("group2")); + QCOMPARE(thirdChild.toObject()["name"].toString(), QString("group3")); + + auto childrenOfSecond = secondChild.toObject()["children"].toArray(); + auto firstOfCOS = childrenOfSecond.at(0); + auto secondOfCOS = childrenOfSecond.at(1); + QCOMPARE(firstOfCOS.toObject()["name"].toString(), QString("group2_1")); + QCOMPARE(secondOfCOS.toObject()["name"].toString(), QString("group2_2")); + + auto lastChildren = firstOfCOS.toObject()["children"].toArray(); + auto lastChild = lastChildren.at(0); + QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1")); + } diff --git a/tests/TestBrowser.h b/tests/TestBrowser.h new file mode 100644 index 000000000..0b939b077 --- /dev/null +++ b/tests/TestBrowser.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_TESTBROWSER_H +#define KEEPASSXC_TESTBROWSER_H + +#include + +#include "browser/BrowserAction.h" +#include "browser/BrowserService.h" +#include "core/Group.h" + +class TestBrowser : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void testChangePublicKeys(); + void testEncryptMessage(); + void testDecryptMessage(); + void testGetBase64FromKey(); + void testIncrementNonce(); + + void testBaseDomain(); + void testSortPriority(); + void testSearchEntries(); + void testSearchEntriesWithPort(); + void testSortEntries(); + void testGetDatabaseGroups(); + +private: + QScopedPointer m_browserAction; + QScopedPointer m_browserService; +}; + +#endif // KEEPASSXC_TESTBROWSER_H